Skip to content

Commit 624f60b

Browse files
jsignmarioevz
andauthored
new(tests/zkEVM): fix max contract size limitation in bytecode attack (#1521)
* use CREATE2 for creating contracts Signed-off-by: Ignacio Hagopian <[email protected]> * redo loop using CREATE2 address calculation for the attack Signed-off-by: Ignacio Hagopian <[email protected]> * adjust calculations Signed-off-by: Ignacio Hagopian <[email protected]> * lints Signed-off-by: Ignacio Hagopian <[email protected]> * fix(tools): Allow `None` condition in `While` * fix(tests): fix --------- Signed-off-by: Ignacio Hagopian <[email protected]> Co-authored-by: Mario Vega <[email protected]>
1 parent fce88dd commit 624f60b

File tree

2 files changed

+38
-12
lines changed

2 files changed

+38
-12
lines changed

src/ethereum_test_tools/code/generators.py

+7-4
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ def __new__(
242242
cls,
243243
*,
244244
body: Bytecode | Op,
245-
condition: Bytecode | Op,
245+
condition: Bytecode | Op | None = None,
246246
evm_code_type: EVMCodeType = EVMCodeType.LEGACY,
247247
):
248248
"""
@@ -254,9 +254,12 @@ def __new__(
254254
if evm_code_type == EVMCodeType.LEGACY:
255255
bytecode += Op.JUMPDEST
256256
bytecode += body
257-
bytecode += Op.JUMPI(
258-
Op.SUB(Op.PC, Op.PUSH4[len(body) + len(condition) + 6]), condition
259-
)
257+
if condition is not None:
258+
bytecode += Op.JUMPI(
259+
Op.SUB(Op.PC, Op.PUSH4[len(body) + len(condition) + 6]), condition
260+
)
261+
else:
262+
bytecode += Op.JUMP(Op.SUB(Op.PC, Op.PUSH4[len(body) + 6]))
260263
elif evm_code_type == EVMCodeType.EOF_V1:
261264
raise NotImplementedError("EOF while loops are not implemented")
262265
return super().__new__(cls, bytecode)

tests/zkevm/test_worst_bytecode.py

+31-8
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
Tests for zkEVMs worst-cases scenarios.
66
"""
77

8+
import math
9+
810
import pytest
911

1012
from ethereum_test_forks import Fork
@@ -17,7 +19,7 @@
1719
Hash,
1820
Transaction,
1921
While,
20-
compute_create_address,
22+
compute_create2_address,
2123
)
2224
from ethereum_test_tools.vm.opcode import Opcodes as Op
2325

@@ -33,7 +35,6 @@
3335
XOR_TABLE = [Hash(i).sha256() for i in range(XOR_TABLE_SIZE)]
3436

3537

36-
# TODO: Parametrize for EOF
3738
@pytest.mark.zkevm
3839
@pytest.mark.parametrize(
3940
"opcode",
@@ -95,12 +96,14 @@ def test_worst_bytecode_single_opcode(
9596
)
9697
+ Op.MSTORE(
9798
0,
98-
Op.CREATE(
99+
Op.CREATE2(
99100
value=0,
100101
offset=0,
101102
size=Op.MSIZE,
103+
salt=Op.SLOAD(0),
102104
),
103105
)
106+
+ Op.SSTORE(0, Op.ADD(Op.SLOAD(0), 1))
104107
+ Op.RETURN(0, 32)
105108
)
106109
factory_address = pre.deploy_contract(code=factory_code)
@@ -115,9 +118,18 @@ def test_worst_bytecode_single_opcode(
115118

116119
gas_costs = fork.gas_costs()
117120
intrinsic_gas_cost_calc = fork.transaction_intrinsic_cost_calculator()
118-
max_number_of_contract_calls = (OPCODE_GAS_LIMIT - intrinsic_gas_cost_calc()) // (
119-
gas_costs.G_VERY_LOW + gas_costs.G_BASE + gas_costs.G_COLD_ACCOUNT_ACCESS
121+
loop_cost = (
122+
gas_costs.G_KECCAK_256 # KECCAK static cost
123+
+ math.ceil(85 / 32) * gas_costs.G_KECCAK_256_WORD # KECCAK dynamic cost for CREATE2
124+
+ gas_costs.G_VERY_LOW * 3 # ~MSTOREs+ADDs
125+
+ gas_costs.G_COLD_ACCOUNT_ACCESS # EXTCODESIZE
126+
+ 30 # ~Gluing opcodes
120127
)
128+
max_number_of_contract_calls = (
129+
# Base available gas = GAS_LIMIT - intrinsic - (out of loop MSTOREs)
130+
OPCODE_GAS_LIMIT - intrinsic_gas_cost_calc() - gas_costs.G_VERY_LOW * 4
131+
) // loop_cost
132+
121133
total_contracts_to_deploy = max_number_of_contract_calls
122134
approximate_gas_per_deployment = 4_970_000 # Obtained from evm tracing
123135
contracts_deployed_per_tx = BLOCK_GAS_LIMIT // approximate_gas_per_deployment
@@ -144,16 +156,27 @@ def generate_deploy_tx(contracts_to_deploy: int):
144156
post = {}
145157
deployed_contract_addresses = []
146158
for i in range(total_contracts_to_deploy):
147-
deployed_contract_address = compute_create_address(
159+
deployed_contract_address = compute_create2_address(
148160
address=factory_address,
149-
nonce=i + 1,
161+
salt=i,
162+
initcode=initcode,
150163
)
151164
post[deployed_contract_address] = Account(nonce=1)
152165
deployed_contract_addresses.append(deployed_contract_address)
153166

154167
opcode_code = (
155-
sum(Op.POP(opcode(address=address)) for address in deployed_contract_addresses) + Op.STOP
168+
# Setup memory for later CREATE2 address generation loop.
169+
Op.MSTORE(0, factory_address)
170+
+ Op.MSTORE8(32 - 20 - 1, 0xFF) # 0xFF prefix byte
171+
+ Op.MSTORE(32, 0)
172+
+ Op.MSTORE(64, initcode.keccak256())
173+
# Main loop
174+
+ While(
175+
body=Op.POP(Op.EXTCODESIZE(Op.SHA3(32 - 20 - 1, 85)))
176+
+ Op.MSTORE(32, Op.ADD(Op.MLOAD(32), 1)),
177+
)
156178
)
179+
157180
if len(opcode_code) > MAX_CONTRACT_SIZE:
158181
# TODO: A workaround could be to split the opcode code into multiple contracts
159182
# and call them in sequence.

0 commit comments

Comments
 (0)