Skip to content

Commit

Permalink
Implement EIP5656 MCOPY (ethereumjs#2808)
Browse files Browse the repository at this point in the history
* common/evm: implement EIP5656 MCOPY

* evm: add tests 5656

* evm: add comment 5656 tests

* evm: update correct 5656 gas

* evm: lint

* evm: fix test
  • Loading branch information
jochem-brouwer authored Jun 25, 2023
1 parent c51d81e commit d3055ae
Show file tree
Hide file tree
Showing 7 changed files with 167 additions and 14 deletions.
17 changes: 17 additions & 0 deletions packages/common/src/eips/5656.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "EIP-5656",
"number": 5656,
"comment": "MCOPY - Memory copying instruction",
"url": "https://eips.ethereum.org/EIPS/eip-5656",
"status": "Draft",
"minimumHardfork": "shanghai",
"gasConfig": {},
"gasPrices": {
"mcopy": {
"v": 3,
"d": "Base fee of the MCOPY opcode"
}
},
"vm": {},
"pow": {}
}
2 changes: 2 additions & 0 deletions packages/common/src/eips/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import * as eip4399 from './4399.json'
import * as eip4844 from './4844.json'
import * as eip4895 from './4895.json'
import * as eip5133 from './5133.json'
import * as eip5656 from './5656.json'
import * as eip6780 from './6780.json'

export const EIPs: { [key: number]: any } = {
Expand Down Expand Up @@ -51,5 +52,6 @@ export const EIPs: { [key: number]: any } = {
4844: eip4844,
4895: eip4895,
5133: eip5133,
5656: eip5656,
6780: eip6780,
}
4 changes: 2 additions & 2 deletions packages/evm/src/evm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export interface EVMOpts {
* - [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844) - Shard Blob Transactions (`experimental`)
* - [EIP-4895](https://eips.ethereum.org/EIPS/eip-4895) - Beacon chain push withdrawals as operations
* - [EIP-5133](https://eips.ethereum.org/EIPS/eip-5133) - Delaying Difficulty Bomb to mid-September 2022
*
* - [EIP-5656](https://eips.ethereum.org/EIPS/eip-5656) - MCOPY - Memory copying instruction (`experimental`)
* *Annotations:*
*
* - `experimental`: behaviour can change on patch versions
Expand Down Expand Up @@ -286,7 +286,7 @@ export class EVM implements EVMInterface {
// Supported EIPs
const supportedEIPs = [
1153, 1559, 2315, 2537, 2565, 2718, 2929, 2930, 3074, 3198, 3529, 3540, 3541, 3607, 3651,
3670, 3855, 3860, 4399, 4895, 4844, 5133, 6780,
3670, 3855, 3860, 4399, 4895, 4844, 5133, 5656, 6780,
]

for (const eip of this._common.eips()) {
Expand Down
6 changes: 6 additions & 0 deletions packages/evm/src/opcodes/codes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,12 @@ const eipOpcodes: { eip: number; opcodes: OpcodeEntry }[] = [
0x49: { name: 'BLOBHASH', isAsync: false, dynamicGas: false },
},
},
{
eip: 5656,
opcodes: {
0x5e: { name: 'MCOPY', isAsync: false, dynamicGas: true },
},
},
]

/**
Expand Down
32 changes: 20 additions & 12 deletions packages/evm/src/opcodes/functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -796,24 +796,32 @@ export const handlers: Map<number, OpHandler> = new Map([
runState.programCounter = Number(dest)
},
],
// 0x5e: JUMPSUB
// 0x5e: JUMPSUB (2315) / MCOPY (5656)
[
0x5e,
function (runState) {
const dest = runState.stack.pop()
function (runState, common) {
if (common.isActivatedEIP(2315)) {
// JUMPSUB
const dest = runState.stack.pop()

if (dest > runState.interpreter.getCodeSize()) {
trap(ERROR.INVALID_JUMPSUB + ' at ' + describeLocation(runState))
}
if (dest > runState.interpreter.getCodeSize()) {
trap(ERROR.INVALID_JUMPSUB + ' at ' + describeLocation(runState))
}

const destNum = Number(dest)
const destNum = Number(dest)

if (!jumpSubIsValid(runState, destNum)) {
trap(ERROR.INVALID_JUMPSUB + ' at ' + describeLocation(runState))
}
if (!jumpSubIsValid(runState, destNum)) {
trap(ERROR.INVALID_JUMPSUB + ' at ' + describeLocation(runState))
}

runState.returnStack.push(BigInt(runState.programCounter))
runState.programCounter = destNum + 1
runState.returnStack.push(BigInt(runState.programCounter))
runState.programCounter = destNum + 1
} else if (common.isActivatedEIP(5656)) {
// MCOPY
const [dst, src, length] = runState.stack.popN(3)
const data = runState.memory.read(Number(src), Number(length), true)
runState.memory.write(Number(dst), Number(length), data)
}
},
],
// 0x5f: PUSH0
Expand Down
12 changes: 12 additions & 0 deletions packages/evm/src/opcodes/gas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,18 @@ export const dynamicGasHandlers: Map<number, AsyncDynamicGasHandler | SyncDynami
return gas
},
],
[
/* MCOPY */
0x5e,
async function (runState, gas, common): Promise<bigint> {
const [dst, src, length] = runState.stack.peek(3)
const wordsCopied = (length + BigInt(31)) / BigInt(32)
gas += BigInt(3) * wordsCopied
gas += subMemUsage(runState, src, length, common)
gas += subMemUsage(runState, dst, length, common)
return gas
},
],
[
/* LOG */
0xa0,
Expand Down
108 changes: 108 additions & 0 deletions packages/evm/test/eips/eip-5656.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { Chain, Common, Hardfork } from '@ethereumjs/common'
import { DefaultStateManager } from '@ethereumjs/statemanager'
import { bytesToHex, hexStringToBytes } from '@ethereumjs/util'
import { assert, describe, it } from 'vitest'

import { EVM } from '../../src'

type Situation = {
pre: string
post: string
dst: number
src: number
length: number
}

// Taken from EIP

const situations: Situation[] = [
{
pre: '0000000000000000000000000000000000000000000000000000000000000000000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f',
post: '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f',
dst: 0,
src: 32,
length: 32,
},
{
pre: '0101010101010101010101010101010101010101010101010101010101010101',
post: '0101010101010101010101010101010101010101010101010101010101010101',
dst: 0,
src: 0,
length: 32,
},
// For the situation below, pre/post have 1 byte less than in the current state of the EIP
{
pre: '0001020304050607080000000000000000000000000000000000000000000000',
post: '0102030405060708080000000000000000000000000000000000000000000000',
dst: 0,
src: 1,
length: 8,
},
// For the situation below, pre/post have 1 byte less than in the current state of the EIP
{
pre: '0001020304050607080000000000000000000000000000000000000000000000',
post: '0000010203040506070000000000000000000000000000000000000000000000',
dst: 1,
src: 0,
length: 8,
},
]

function numToOpcode(num: number) {
return num.toString(16).padStart(2, '0')
}

const PUSH1 = '60'
const MSTORE8 = '53'
const MCOPY = '5E'
const STOP = '00'

describe('should test mcopy', () => {
for (const situation of situations) {
it('should produce correct output', async () => {
// create bytecode
let bytecode = ''
// prepare the memory
for (let i = 0; i < situation.pre.length / 2; i++) {
const start = i * 2
const hexNum = situation.pre.slice(start, start + 2)
bytecode += PUSH1 + hexNum + PUSH1 + numToOpcode(i) + MSTORE8
}
// mcopy
bytecode +=
PUSH1 +
numToOpcode(situation.length) +
PUSH1 +
numToOpcode(situation.src) +
PUSH1 +
numToOpcode(situation.dst)
bytecode += MCOPY + STOP

const common = new Common({
chain: Chain.Mainnet,
hardfork: Hardfork.Shanghai,
eips: [5656],
})

const evm = await EVM.create({
common,
stateManager: new DefaultStateManager(),
})

let currentMem = ''

evm.events.on('step', (e) => {
if (e.opcode.name === 'STOP') {
currentMem = bytesToHex(e.memory)
}
})

await evm.runCall({
data: hexStringToBytes(bytecode),
gasLimit: BigInt(0xffffff),
})

assert.equal(currentMem, situation.post, 'post-memory correct')
})
}
})

0 comments on commit d3055ae

Please sign in to comment.