diff --git a/packages/evm/src/Memory.ts b/packages/evm/src/Memory.ts index f02d545..d2c1a71 100644 --- a/packages/evm/src/Memory.ts +++ b/packages/evm/src/Memory.ts @@ -15,7 +15,7 @@ export class Memory { } getBytes (offset: number, length: number) { - this.onMemoryAccess(offset, length) + this.useGasForAccess(offset, length) if (length === 0) { return [] } @@ -24,7 +24,7 @@ export class Memory { } setBytes (offset: number, bytes: Byte[]) { - this.onMemoryAccess(offset, bytes.length) + this.useGasForAccess(offset, bytes.length) if (bytes.length === 0) { return } @@ -34,7 +34,7 @@ export class Memory { } } - private onMemoryAccess (offset: number, length: number) { + useGasForAccess (offset: number, length: number) { if (length === 0) { return } diff --git a/packages/evm/src/opcodes/code.ts b/packages/evm/src/opcodes/code.ts new file mode 100644 index 0000000..1423de3 --- /dev/null +++ b/packages/evm/src/opcodes/code.ts @@ -0,0 +1,25 @@ +import { ExecutionContext } from '../ExecutionContext' +import { GasCost } from './gasCosts' +import { Bytes32 } from '../Bytes32' +import { Byte } from '../Byte' + +export function opCODESIZE (ctx: ExecutionContext) { + ctx.useGas(GasCost.BASE) + ctx.stack.push(Bytes32.fromNumber(ctx.message.code.length)) +} + +export function opCODECOPY (ctx: ExecutionContext) { + const memoryOffset = ctx.stack.pop().toUnsignedNumber() + const codeOffset = ctx.stack.pop().toUnsignedNumber() + const memorySize = ctx.stack.pop().toUnsignedNumber() + + ctx.useGas(GasCost.VERYLOW + GasCost.COPY * Math.ceil(memorySize / 32)) + // we subtract the gas early in case of OutOfGas + ctx.memory.useGasForAccess(memoryOffset, memorySize) + + const code = ctx.message.code.slice(codeOffset, codeOffset + memorySize) + while (code.length < memorySize) { + code.push(0x00 as Byte) + } + ctx.memory.setBytes(memoryOffset, code) +} diff --git a/packages/evm/src/opcodes/gasCosts.ts b/packages/evm/src/opcodes/gasCosts.ts index 6c7d06b..edf61fb 100644 --- a/packages/evm/src/opcodes/gasCosts.ts +++ b/packages/evm/src/opcodes/gasCosts.ts @@ -2,6 +2,7 @@ export const GasCost = { ZERO: 0, BASE: 2, VERYLOW: 3, + COPY: 3, LOW: 5, MID: 8, HIGH: 10, diff --git a/packages/evm/src/opcodes/index.ts b/packages/evm/src/opcodes/index.ts index 95dce86..d6d3c06 100644 --- a/packages/evm/src/opcodes/index.ts +++ b/packages/evm/src/opcodes/index.ts @@ -39,6 +39,7 @@ import { makeOpDUP, makeOpSWAP, opPOP } from './stack' import { opMSIZE, opMLOAD, opMSTORE, opMSTORE8 } from './memory' import { opSSTORE, opSLOAD } from './storage' import { Byte } from '../Byte' +import { opCODESIZE, opCODECOPY } from './code' export { opUnreachable } from './invalid' export { makeOpPUSH } from './stack' @@ -76,6 +77,8 @@ const OP_CODES: Record = { 0x1b: opSHL, 0x1c: opSHR, 0x1d: opSAR, + 0x38: opCODESIZE, + 0x39: opCODECOPY, 0x50: opPOP, 0x51: opMLOAD, 0x52: opMSTORE, diff --git a/packages/evm/test/helpers/executeAssembly.ts b/packages/evm/test/helpers/executeAssembly.ts index 2b3108a..fc822a9 100644 --- a/packages/evm/test/helpers/executeAssembly.ts +++ b/packages/evm/test/helpers/executeAssembly.ts @@ -29,7 +29,7 @@ export function executeAssembly ( return executeCode({ ...DEFAULT_MESSAGE, ...params, code }, state) } -function assemblyToBytecode (code: string): Byte[] { +export function assemblyToBytecode (code: string): Byte[] { const instructions = code.trim().split(/\s+/) const result: Byte[] = [] for (const instruction of instructions) { diff --git a/packages/evm/test/opcodes/code.test.ts b/packages/evm/test/opcodes/code.test.ts new file mode 100644 index 0000000..450be5a --- /dev/null +++ b/packages/evm/test/opcodes/code.test.ts @@ -0,0 +1,57 @@ +import { + expectStorage, + Int256, + expectGas, + expectReturn, + assemblyToBytecode, + memoryGas, + expectUnderflow, +} from '../helpers' +import { GasCost } from '../../src/opcodes' + +describe('CODESIZE opcode', () => { + it('returns the code size of the current environment', () => { + expectStorage('CODESIZE PUSH1 00 SSTORE', { + [Int256.of(0)]: Int256.of(4), + }) + }) + + it(`costs ${GasCost.BASE} gas`, () => { + expectGas('CODESIZE', GasCost.BASE) + }) +}) + +describe('CODECOPY opcode', () => { + it('copies the code to the memory', () => { + const assembly = ` + PUSH1 0C + PUSH1 00 + PUSH1 42 + CODECOPY + PUSH1 0C + PUSH1 42 + RETURN + ` + expectReturn(assembly, assemblyToBytecode(assembly)) + }) + + it('uses a formula to calculate gas cost', () => { + const assembly = ` + PUSH1 42 + PUSH1 00 + PUSH1 69 + CODECOPY + ` + const gas = ( + GasCost.VERYLOW * 3 + + GasCost.VERYLOW + + GasCost.COPY * Math.ceil(0x42 / 32) + + memoryGas(0x69 + 0x42) + ) + expectGas(assembly, gas) + }) + + it('can cause stack underflow', () => { + expectUnderflow('CODECOPY', 3) + }) +})