Skip to content

Commit

Permalink
Partially implement CREATE
Browse files Browse the repository at this point in the history
  • Loading branch information
sz-piotr committed Mar 5, 2020
1 parent f4b3f0b commit edc113e
Show file tree
Hide file tree
Showing 6 changed files with 169 additions and 1 deletion.
10 changes: 10 additions & 0 deletions packages/evm/src/Bytes32.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import BN from 'bn.js'
import { Byte } from './Byte'
import { Address } from './Address'

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

static fromAddress (value: Address) {
return Bytes32.fromHex(value)
}

static fromHex (value: string) {
return new Bytes32(new BN(value, 16))
}
Expand All @@ -43,6 +48,11 @@ export class Bytes32 {
return this.value.toString(16, 64)
}

toAddress () {
const hex = this.toHex()
return hex.substring(24) as Address
}

toBytes () {
return this.value.toArray(undefined, 32) as Byte[]
}
Expand Down
54 changes: 54 additions & 0 deletions packages/evm/src/opcodes/create.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { ExecutionContext } from '../ExecutionContext'
import { Bytes32 } from '../Bytes32'
import { getContractAddress } from '../getContractAddress'
import { executeCode } from '../executeCode'

export function opCREATE (ctx: ExecutionContext) {
const value = ctx.stack.pop()
const memoryOffset = ctx.stack.pop().toUnsignedNumber()
const memoryBytes = ctx.stack.pop().toUnsignedNumber()

const gasLeft = ctx.message.gasLimit - ctx.gasUsed
const gasLimit = allButOne64th(gasLeft)
const initCode = ctx.memory.getBytes(memoryOffset, memoryBytes)

const nonce = ctx.state.getNonce(ctx.message.account)
const balance = ctx.state.getBalance(ctx.message.account)

const contract = getContractAddress(ctx.message.account, nonce)

if (balance.lt(value)) {
ctx.stack.push(Bytes32.ZERO)
return
}

ctx.state.setNonce(ctx.message.account, nonce + 1)
ctx.state.setBalance(ctx.message.account, balance.sub(value))

const result = executeCode({
account: contract,
callDepth: ctx.message.callDepth + 1,
sender: ctx.message.account,
origin: ctx.message.origin,
gasLimit,
gasPrice: ctx.message.gasPrice,
code: initCode,
data: [],
enableStateModifications: ctx.message.enableStateModifications,
value,
}, ctx.state.clone())

if (result.type === 'ExecutionSuccess') {
ctx.stack.push(Bytes32.fromAddress(contract))
ctx.state = result.state

ctx.useGas(result.gasUsed)
ctx.refund(result.gasRefund)
} else {
ctx.stack.push(Bytes32.ZERO)
}
}

function allButOne64th (value: number) {
return value - Math.floor(value / 64)
}
2 changes: 2 additions & 0 deletions packages/evm/src/opcodes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import { opMSIZE, opMLOAD, opMSTORE, opMSTORE8 } from './memory'
import { opSSTORE, opSLOAD } from './storage'
import { Byte } from '../Byte'
import { opCODESIZE, opCODECOPY } from './code'
import { opCREATE } from './create'

export { opUnreachable } from './invalid'
export { makeOpPUSH } from './stack'
Expand Down Expand Up @@ -122,6 +123,7 @@ const OP_CODES: Record<number, Opcode | undefined> = {
0x9d: makeOpSWAP(14),
0x9e: makeOpSWAP(15),
0x9f: makeOpSWAP(16),
0xf0: opCREATE,
0xf3: opRETURN,
0xfd: opREVERT,
}
16 changes: 16 additions & 0 deletions packages/evm/test/Bytes32.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Bytes32 } from '../src/Bytes32'
import { TestCases } from './opcodes/bytes32/cases'
import { TestCase } from './opcodes/bytes32/cases/helpers'
import { Byte } from '../src/Byte'
import { Address } from '../src/Address'

describe('Bytes32', () => {
runTestCases('add', TestCases.ADD)
Expand All @@ -29,6 +30,21 @@ describe('Bytes32', () => {
runTestCases('shr', invert(TestCases.SHR))
runTestCases('sar', invert(TestCases.SAR))

describe('to and from Address', () => {
it('toAddress ignores first bytes', () => {
const hex = 'ab'.repeat(16) + 'cd'.repeat(16)
const value = Bytes32.fromHex(hex)
expect(value.toAddress()).to.equal('ab'.repeat(4) + 'cd'.repeat(16))
})

it('fromAddress works like from hex', () => {
const address = 'ab'.repeat(20) as Address
const a = Bytes32.fromAddress(address)
const b = Bytes32.fromHex(address)
expect(a.eq(b)).to.equal(true)
})
})

describe('to and from number', () => {
it('fromNumber works for positive numbers', () => {
const a = Bytes32.fromNumber(42)
Expand Down
5 changes: 4 additions & 1 deletion packages/evm/test/helpers/executeAssembly.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ export function executeAssembly (
}

export function assemblyToBytecode (code: string): Byte[] {
const instructions = code.trim().split(/\s+/)
const instructions = code
.replace(/\/\/.*/g, ' ') // remove comments
.trim()
.split(/\s+/)
const result: Byte[] = []
for (const instruction of instructions) {
const opcode = OPCODES[instruction] as Byte
Expand Down
83 changes: 83 additions & 0 deletions packages/evm/test/opcodes/create.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { expect } from 'chai'
import { executeAssembly } from "../helpers"
import { Address } from "../../src/Address"
import { State } from "../../src/State"
import { getContractAddress } from '../../src/getContractAddress'
import { Bytes32 } from '../../src/Bytes32'

describe('CREATE opcode', () => {
const assembly = `
PUSH1 05 // size of the code
PUSH1 12 // code offset
PUSH1 00 // memory offset of the code
CODECOPY
PUSH1 05 // size of the code
PUSH1 00 // memory offset of the code
PUSH1 69 // value passed
CREATE
PUSH1 00
SSTORE // save the address of the created contract
STOP
// code of the contract
PUSH1 01
PUSH1 00
SSTORE // save 1 at address 0 in the storage of the new contract
`
const account = 'abcd'.repeat(10) as Address

it('results in the creation of a new contract', () => {
const state = new State()
state.setNonce(account, 42)
state.setBalance(account, Bytes32.fromNumber(0x100))

const result = executeAssembly(assembly, { account }, state)

if (result.type !== 'ExecutionSuccess') {
expect(result.type).to.equal('ExecutionSuccess')
} else {
// increments nonce
expect(result.state.getNonce(account)).to.equal(43)

// subtracts balance
const balance = result.state.getBalance(account)
expect(balance.eq(Bytes32.fromNumber(0x100 - 0x69))).to.equal(true)

// returns correct address
const expectedAddress = getContractAddress(account, 42)
const actualAddress = result.state
.getStorage(account, Bytes32.ZERO)
.toAddress()
expect(actualAddress).to.equal(expectedAddress)

// actually runs the contract code
const stored = result.state.getStorage(actualAddress, Bytes32.ZERO)
expect(stored.eq(Bytes32.ONE)).to.equal(true)
}
})

it('account does not have the balance', () => {
const state = new State()
state.setNonce(account, 42)
state.setBalance(account, Bytes32.fromNumber(0x68))

const result = executeAssembly(assembly, { account }, state)

if (result.type !== 'ExecutionSuccess') {
expect(result.type).to.equal('ExecutionSuccess')
} else {
// does not increment the nonce
expect(result.state.getNonce(account)).to.equal(42)

// does not subtract the balance
const balance = result.state.getBalance(account)
expect(balance.eq(Bytes32.fromNumber(0x68))).to.equal(true)

// returns zero
const returnValue = result.state.getStorage(account, Bytes32.ZERO)
expect(returnValue).to.equal(Bytes32.ZERO)
}
})
})

0 comments on commit edc113e

Please sign in to comment.