Skip to content

Commit 301f2e6

Browse files
committed
Partially implement CREATE
1 parent 2f33a01 commit 301f2e6

File tree

6 files changed

+169
-1
lines changed

6 files changed

+169
-1
lines changed

packages/evm/src/Bytes32.ts

+10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import BN from 'bn.js'
22
import { Byte } from './Byte'
3+
import { Address } from './Address'
34

45
const TWO_POW256 = new BN('1' + '0'.repeat(64), 16)
56
const MAX_256 = new BN('f'.repeat(64), 16)
@@ -23,6 +24,10 @@ export class Bytes32 {
2324
return new Bytes32(new BN(value).toTwos(256))
2425
}
2526

27+
static fromAddress (value: Address) {
28+
return Bytes32.fromHex(value)
29+
}
30+
2631
static fromHex (value: string) {
2732
return new Bytes32(new BN(value, 16))
2833
}
@@ -43,6 +48,11 @@ export class Bytes32 {
4348
return this.value.toString(16, 64)
4449
}
4550

51+
toAddress () {
52+
const hex = this.toHex()
53+
return hex.substring(24) as Address
54+
}
55+
4656
toBytes () {
4757
return this.value.toArray(undefined, 32) as Byte[]
4858
}

packages/evm/src/opcodes/create.ts

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { ExecutionContext } from '../ExecutionContext'
2+
import { Bytes32 } from '../Bytes32'
3+
import { getContractAddress } from '../getContractAddress'
4+
import { executeCode } from '../executeCode'
5+
6+
export function opCREATE (ctx: ExecutionContext) {
7+
const value = ctx.stack.pop()
8+
const memoryOffset = ctx.stack.pop().toUnsignedNumber()
9+
const memoryBytes = ctx.stack.pop().toUnsignedNumber()
10+
11+
const gasLeft = ctx.message.gasLimit - ctx.gasUsed
12+
const gasLimit = allButOne64th(gasLeft)
13+
const initCode = ctx.memory.getBytes(memoryOffset, memoryBytes)
14+
15+
const nonce = ctx.state.getNonce(ctx.message.account)
16+
const balance = ctx.state.getBalance(ctx.message.account)
17+
18+
const contract = getContractAddress(ctx.message.account, nonce)
19+
20+
if (balance.lt(value)) {
21+
ctx.stack.push(Bytes32.ZERO)
22+
return
23+
}
24+
25+
ctx.state.setNonce(ctx.message.account, nonce + 1)
26+
ctx.state.setBalance(ctx.message.account, balance.sub(value))
27+
28+
const result = executeCode({
29+
account: contract,
30+
callDepth: ctx.message.callDepth + 1,
31+
sender: ctx.message.account,
32+
origin: ctx.message.origin,
33+
gasLimit,
34+
gasPrice: ctx.message.gasPrice,
35+
code: initCode,
36+
data: [],
37+
enableStateModifications: ctx.message.enableStateModifications,
38+
value,
39+
}, ctx.state.clone())
40+
41+
if (result.type === 'ExecutionSuccess') {
42+
ctx.stack.push(Bytes32.fromAddress(contract))
43+
ctx.state = result.state
44+
45+
ctx.useGas(result.gasUsed)
46+
ctx.refund(result.gasRefund)
47+
} else {
48+
ctx.stack.push(Bytes32.ZERO)
49+
}
50+
}
51+
52+
function allButOne64th (value: number) {
53+
return value - Math.floor(value / 64)
54+
}

packages/evm/src/opcodes/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import { opMSIZE, opMLOAD, opMSTORE, opMSTORE8 } from './memory'
4040
import { opSSTORE, opSLOAD } from './storage'
4141
import { Byte } from '../Byte'
4242
import { opCODESIZE, opCODECOPY } from './code'
43+
import { opCREATE } from './create'
4344

4445
export { opUnreachable } from './invalid'
4546
export { makeOpPUSH } from './stack'
@@ -122,6 +123,7 @@ const OP_CODES: Record<number, Opcode | undefined> = {
122123
0x9d: makeOpSWAP(14),
123124
0x9e: makeOpSWAP(15),
124125
0x9f: makeOpSWAP(16),
126+
0xf0: opCREATE,
125127
0xf3: opRETURN,
126128
0xfd: opREVERT,
127129
}

packages/evm/test/Bytes32.test.ts

+16
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Bytes32 } from '../src/Bytes32'
33
import { TestCases } from './opcodes/bytes32/cases'
44
import { TestCase } from './opcodes/bytes32/cases/helpers'
55
import { Byte } from '../src/Byte'
6+
import { Address } from '../src/Address'
67

78
describe('Bytes32', () => {
89
runTestCases('add', TestCases.ADD)
@@ -29,6 +30,21 @@ describe('Bytes32', () => {
2930
runTestCases('shr', invert(TestCases.SHR))
3031
runTestCases('sar', invert(TestCases.SAR))
3132

33+
describe('to and from Address', () => {
34+
it('toAddress ignores first bytes', () => {
35+
const hex = 'ab'.repeat(16) + 'cd'.repeat(16)
36+
const value = Bytes32.fromHex(hex)
37+
expect(value.toAddress()).to.equal('ab'.repeat(4) + 'cd'.repeat(16))
38+
})
39+
40+
it('fromAddress works like from hex', () => {
41+
const address = 'ab'.repeat(20) as Address
42+
const a = Bytes32.fromAddress(address)
43+
const b = Bytes32.fromHex(address)
44+
expect(a.eq(b)).to.equal(true)
45+
})
46+
})
47+
3248
describe('to and from number', () => {
3349
it('fromNumber works for positive numbers', () => {
3450
const a = Bytes32.fromNumber(42)

packages/evm/test/helpers/executeAssembly.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@ export function executeAssembly (
3030
}
3131

3232
export function assemblyToBytecode (code: string): Byte[] {
33-
const instructions = code.trim().split(/\s+/)
33+
const instructions = code
34+
.replace(/\/\/.*/g, ' ') // remove comments
35+
.trim()
36+
.split(/\s+/)
3437
const result: Byte[] = []
3538
for (const instruction of instructions) {
3639
const opcode = OPCODES[instruction] as Byte
+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { expect } from 'chai'
2+
import { executeAssembly } from "../helpers"
3+
import { Address } from "../../src/Address"
4+
import { State } from "../../src/State"
5+
import { getContractAddress } from '../../src/getContractAddress'
6+
import { Bytes32 } from '../../src/Bytes32'
7+
8+
describe('CREATE opcode', () => {
9+
const assembly = `
10+
PUSH1 05 // size of the code
11+
PUSH1 12 // code offset
12+
PUSH1 00 // memory offset of the code
13+
CODECOPY
14+
15+
PUSH1 05 // size of the code
16+
PUSH1 00 // memory offset of the code
17+
PUSH1 69 // value passed
18+
CREATE
19+
20+
PUSH1 00
21+
SSTORE // save the address of the created contract
22+
STOP
23+
24+
// code of the contract
25+
PUSH1 01
26+
PUSH1 00
27+
SSTORE // save 1 at address 0 in the storage of the new contract
28+
`
29+
const account = 'abcd'.repeat(10) as Address
30+
31+
it('results in the creation of a new contract', () => {
32+
const state = new State()
33+
state.setNonce(account, 42)
34+
state.setBalance(account, Bytes32.fromNumber(0x100))
35+
36+
const result = executeAssembly(assembly, { account }, state)
37+
38+
if (result.type !== 'ExecutionSuccess') {
39+
expect(result.type).to.equal('ExecutionSuccess')
40+
} else {
41+
// increments nonce
42+
expect(result.state.getNonce(account)).to.equal(43)
43+
44+
// subtracts balance
45+
const balance = result.state.getBalance(account)
46+
expect(balance.eq(Bytes32.fromNumber(0x100 - 0x69))).to.equal(true)
47+
48+
// returns correct address
49+
const expectedAddress = getContractAddress(account, 42)
50+
const actualAddress = result.state
51+
.getStorage(account, Bytes32.ZERO)
52+
.toAddress()
53+
expect(actualAddress).to.equal(expectedAddress)
54+
55+
// actually runs the contract code
56+
const stored = result.state.getStorage(actualAddress, Bytes32.ZERO)
57+
expect(stored.eq(Bytes32.ONE)).to.equal(true)
58+
}
59+
})
60+
61+
it('account does not have the balance', () => {
62+
const state = new State()
63+
state.setNonce(account, 42)
64+
state.setBalance(account, Bytes32.fromNumber(0x68))
65+
66+
const result = executeAssembly(assembly, { account }, state)
67+
68+
if (result.type !== 'ExecutionSuccess') {
69+
expect(result.type).to.equal('ExecutionSuccess')
70+
} else {
71+
// does not increment the nonce
72+
expect(result.state.getNonce(account)).to.equal(42)
73+
74+
// does not subtract the balance
75+
const balance = result.state.getBalance(account)
76+
expect(balance.eq(Bytes32.fromNumber(0x68))).to.equal(true)
77+
78+
// returns zero
79+
const returnValue = result.state.getStorage(account, Bytes32.ZERO)
80+
expect(returnValue).to.equal(Bytes32.ZERO)
81+
}
82+
})
83+
})

0 commit comments

Comments
 (0)