Skip to content

Commit cd551ec

Browse files
evm: fix 3860 implementation + tests (#2397)
* evm: fix 3860 implementation + tests * Adapt original EIP-3860 tests from vm * add test for Create2 * Add test for CREATE Co-authored-by: acolytec3 <[email protected]>
1 parent 5776107 commit cd551ec

File tree

2 files changed

+146
-0
lines changed

2 files changed

+146
-0
lines changed

packages/evm/src/opcodes/gas.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,11 @@ export const dynamicGasHandlers: Map<number, AsyncDynamicGasHandler | SyncDynami
295295
gas += accessAddressEIP2929(runState, runState.interpreter.getAddress(), common, false)
296296
}
297297

298+
if (common.isActivatedEIP(3860) === true) {
299+
gas +=
300+
((length + BigInt(31)) / BigInt(32)) * common.param('gasPrices', 'initCodeWordCost')
301+
}
302+
298303
gas += subMemUsage(runState, offset, length, common)
299304

300305
let gasLimit = BigInt(runState.interpreter.getGasLeft()) - gas
@@ -462,6 +467,11 @@ export const dynamicGasHandlers: Map<number, AsyncDynamicGasHandler | SyncDynami
462467
gas += accessAddressEIP2929(runState, runState.interpreter.getAddress(), common, false)
463468
}
464469

470+
if (common.isActivatedEIP(3860) === true) {
471+
gas +=
472+
((length + BigInt(31)) / BigInt(32)) * common.param('gasPrices', 'initCodeWordCost')
473+
}
474+
465475
gas += common.param('gasPrices', 'sha3Word') * divCeil(length, BigInt(32))
466476
let gasLimit = runState.interpreter.getGasLeft() - gas
467477
gasLimit = maxCallGas(gasLimit, gasLimit, runState, common) // CREATE2 is only available after TangerineWhistle (Constantinople introduced this opcode)
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import { Chain, Common, Hardfork } from '@ethereumjs/common'
2+
import { Address, privateToAddress } from '@ethereumjs/util'
3+
import * as tape from 'tape'
4+
5+
import { EVM } from '../../src'
6+
import { getEEI } from '../utils'
7+
8+
const pkey = Buffer.from('20'.repeat(32), 'hex')
9+
const sender = new Address(privateToAddress(pkey))
10+
11+
tape('EIP 3860 tests', (t) => {
12+
t.test('code exceeds max initcode size', async (st) => {
13+
const common = new Common({
14+
chain: Chain.Mainnet,
15+
hardfork: Hardfork.London,
16+
eips: [3860],
17+
})
18+
const eei = await getEEI()
19+
const evm = await EVM.create({ common, eei })
20+
21+
const buffer = Buffer.allocUnsafe(1000000).fill(0x60)
22+
23+
// setup the call arguments
24+
const runCallArgs = {
25+
sender, // call address
26+
gasLimit: BigInt(0xffffffffff), // ensure we pass a lot of gas, so we do not run out of gas
27+
// Simple test, PUSH <big number> PUSH 0 RETURN
28+
// It tries to deploy a contract too large, where the code is all zeros
29+
// (since memory which is not allocated/resized to yet is always defaulted to 0)
30+
data: Buffer.concat([
31+
Buffer.from(
32+
'0x7F6000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060005260206000F3',
33+
'hex'
34+
),
35+
buffer,
36+
]),
37+
}
38+
const result = await evm.runCall(runCallArgs)
39+
st.ok(
40+
(result.execResult.exceptionError?.error as string) === 'initcode exceeds max initcode size',
41+
'initcode exceeds max size'
42+
)
43+
})
44+
45+
t.test('ensure EIP-3860 gas is applied on CREATE calls', async (st) => {
46+
// Transaction/Contract data taken from https://github.com/ethereum/tests/pull/990
47+
const commonWith3860 = new Common({
48+
chain: Chain.Mainnet,
49+
hardfork: Hardfork.London,
50+
eips: [3860],
51+
})
52+
const commonWithout3860 = new Common({
53+
chain: Chain.Mainnet,
54+
hardfork: Hardfork.London,
55+
eips: [],
56+
})
57+
const caller = Address.fromString('0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b')
58+
const eei = await getEEI()
59+
const evm = await EVM.create({ common: commonWith3860, eei })
60+
const evmWithout3860 = await EVM.create({ common: commonWithout3860, eei: eei.copy() })
61+
const contractFactory = Address.fromString('0xb94f5374fce5edbc8e2a8697c15331677e6ebf0b')
62+
const contractAccount = await evm.eei.getAccount(contractFactory)
63+
await evm.eei.putAccount(contractFactory, contractAccount)
64+
await evmWithout3860.eei.putAccount(contractFactory, contractAccount)
65+
const factoryCode = Buffer.from(
66+
'7f600a80600080396000f3000000000000000000000000000000000000000000006000526000355a8160006000f05a8203600a55806000556001600155505050',
67+
'hex'
68+
)
69+
70+
await evm.eei.putContractCode(contractFactory, factoryCode)
71+
await evmWithout3860.eei.putContractCode(contractFactory, factoryCode)
72+
const data = Buffer.from(
73+
'000000000000000000000000000000000000000000000000000000000000c000',
74+
'hex'
75+
)
76+
const runCallArgs = {
77+
from: caller,
78+
to: contractFactory,
79+
data,
80+
gasLimit: BigInt(0xfffffffff),
81+
}
82+
const res = await evm.runCall(runCallArgs)
83+
const res2 = await evmWithout3860.runCall(runCallArgs)
84+
st.ok(
85+
res.execResult.executionGasUsed > res2.execResult.executionGasUsed,
86+
'execution gas used is higher with EIP 3860 active'
87+
)
88+
st.end()
89+
})
90+
91+
t.test('ensure EIP-3860 gas is applied on CREATE2 calls', async (st) => {
92+
// Transaction/Contract data taken from https://github.com/ethereum/tests/pull/990
93+
const commonWith3860 = new Common({
94+
chain: Chain.Mainnet,
95+
hardfork: Hardfork.London,
96+
eips: [3860],
97+
})
98+
const commonWithout3860 = new Common({
99+
chain: Chain.Mainnet,
100+
hardfork: Hardfork.London,
101+
eips: [],
102+
})
103+
const caller = Address.fromString('0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b')
104+
const eei = await getEEI()
105+
const evm = await EVM.create({ common: commonWith3860, eei })
106+
const evmWithout3860 = await EVM.create({ common: commonWithout3860, eei: eei.copy() })
107+
const contractFactory = Address.fromString('0xb94f5374fce5edbc8e2a8697c15331677e6ebf0b')
108+
const contractAccount = await evm.eei.getAccount(contractFactory)
109+
await evm.eei.putAccount(contractFactory, contractAccount)
110+
await evmWithout3860.eei.putAccount(contractFactory, contractAccount)
111+
const factoryCode = Buffer.from(
112+
'7f600a80600080396000f3000000000000000000000000000000000000000000006000526000355a60008260006000f55a8203600a55806000556001600155505050',
113+
'hex'
114+
)
115+
116+
await evm.eei.putContractCode(contractFactory, factoryCode)
117+
await evmWithout3860.eei.putContractCode(contractFactory, factoryCode)
118+
const data = Buffer.from(
119+
'000000000000000000000000000000000000000000000000000000000000c000',
120+
'hex'
121+
)
122+
const runCallArgs = {
123+
from: caller,
124+
to: contractFactory,
125+
data,
126+
gasLimit: BigInt(0xfffffffff),
127+
}
128+
const res = await evm.runCall(runCallArgs)
129+
const res2 = await evmWithout3860.runCall(runCallArgs)
130+
st.ok(
131+
res.execResult.executionGasUsed > res2.execResult.executionGasUsed,
132+
'execution gas used is higher with EIP 3860 active'
133+
)
134+
st.end()
135+
})
136+
})

0 commit comments

Comments
 (0)