From 491a2106191bc6ab4a8780b5bf7aa930d15592e1 Mon Sep 17 00:00:00 2001 From: Jochem Brouwer Date: Sun, 25 May 2025 20:21:25 +0200 Subject: [PATCH 01/15] feat(forks): introduce max code size / max initcode size --- src/ethereum_test_forks/base_fork.py | 12 ++++++++++ src/ethereum_test_forks/forks/forks.py | 31 ++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/src/ethereum_test_forks/base_fork.py b/src/ethereum_test_forks/base_fork.py index 7883b5921a2..531da919578 100644 --- a/src/ethereum_test_forks/base_fork.py +++ b/src/ethereum_test_forks/base_fork.py @@ -471,6 +471,18 @@ def evm_code_types(cls, block_number: int = 0, timestamp: int = 0) -> List[EVMCo """Return list of EVM code types supported by the fork.""" pass + @classmethod + @abstractmethod + def max_code_size(cls) -> int | None: + """Return the maximum code size to be deployed in a CREATE-frame.""" + pass + + @classmethod + @abstractmethod + def max_initcode_size(cls) -> int | None: + """Return the maximum initcode size when using a CREATE frame.""" + pass + @classmethod @abstractmethod def call_opcodes( diff --git a/src/ethereum_test_forks/forks/forks.py b/src/ethereum_test_forks/forks/forks.py index 514bdd8eaea..e811a1d491d 100644 --- a/src/ethereum_test_forks/forks/forks.py +++ b/src/ethereum_test_forks/forks/forks.py @@ -369,6 +369,16 @@ def evm_code_types(cls, block_number: int = 0, timestamp: int = 0) -> List[EVMCo """At Genesis, only legacy EVM code is supported.""" return [EVMCodeType.LEGACY] + @classmethod + def max_code_size(cls) -> None: + """At genesis, there is no upper bound for code size (bounded by block gas limit).""" + return None + + @classmethod + def max_initcode_size(cls) -> None: + """At genesis, there is no upper bound for initcode size (bounded by block gas limit).""" + return None + @classmethod def call_opcodes( cls, block_number: int = 0, timestamp: int = 0 @@ -634,6 +644,12 @@ def precompiles(cls, block_number: int = 0, timestamp: int = 0) -> List[Address] block_number, timestamp ) + @classmethod + def max_code_size(cls) -> int: + # NOTE: Move this to Spurious Dragon once this fork is introduced. See EIP-170. + """At Spurious Dragon, an upper bound was introduced for max contract code size.""" + return int(0x6000) + @classmethod def call_opcodes( cls, block_number: int = 0, timestamp: int = 0 @@ -860,6 +876,11 @@ def engine_new_payload_version( """From Shanghai, new payload calls must use version 2.""" return 2 + @classmethod + def max_initcode_size(cls) -> int: + """From Shanghai, the initcode size is now limited. See EIP-3860.""" + return int(0xC000) + @classmethod def valid_opcodes( cls, @@ -1317,6 +1338,16 @@ def engine_get_blobs_version(cls, block_number: int = 0, timestamp: int = 0) -> """At Osaka, the engine get blobs version is 2.""" return 2 + @classmethod + def max_code_size(cls) -> int: + """From Osaka, the max code size is lifted. See EIP-7907.""" + return int(0x40000) + + @classmethod + def max_initcode_size(cls) -> int: + """At genesis, there is no upper bound for initcode size (bounded by block gas limit).""" + return int(0x80000) + @classmethod def is_deployed(cls) -> bool: """ From f50f9a481df0d6e21ee2939f4a0f3a8fd6bc65aa Mon Sep 17 00:00:00 2001 From: Jochem Brouwer Date: Sun, 25 May 2025 21:35:32 +0200 Subject: [PATCH 02/15] feat(tests): read max code size from fork / scale gas limit as appropriate for test --- tests/zkevm/test_worst_bytecode.py | 69 +++++++++++++++++++----------- 1 file changed, 44 insertions(+), 25 deletions(-) diff --git a/tests/zkevm/test_worst_bytecode.py b/tests/zkevm/test_worst_bytecode.py index d59982ec5d7..d145787e5c1 100644 --- a/tests/zkevm/test_worst_bytecode.py +++ b/tests/zkevm/test_worst_bytecode.py @@ -27,8 +27,6 @@ REFERENCE_SPEC_GIT_PATH = "TODO" REFERENCE_SPEC_VERSION = "TODO" -MAX_CONTRACT_SIZE = 24 * 1024 # TODO: This could be a fork property - XOR_TABLE_SIZE = 256 XOR_TABLE = [Hash(i).sha256() for i in range(XOR_TABLE_SIZE)] @@ -65,10 +63,45 @@ def test_worst_bytecode_single_opcode( The test is performed in the last block of the test, and the entire block gas limit is consumed by repeated opcode executions. """ - # We use 100G gas limit to be able to deploy a large number of contracts in a single block, - # avoiding bloating the number of preparing blocks in the test. - env = Environment(gas_limit=100_000_000_000) - attack_gas_limit = Environment().gas_limit + # The attack gas limit is the gas limit which the target tx will use + # The test will scale the block gas limit to setup the contracts accordingly to be + # able to pay for the contract deposit. This has to take into account the 200 gas per byte, + # but also the quadratic memory expansion costs which have to be paid each time the + # memory is being setup + attack_gas_limit = 100_000_000 + max_contract_size = fork.max_code_size() or 24 * 1024 # Uses Spurious Dragon limit by default + + gas_costs = fork.gas_costs() + + # Calculate the absolute minimum gas costs to deploy the contract + # This does not take into account setting up the actual memory (using KECCAK256 and XOR) + # so the actual costs of deploying the contract is higher + memory_expansion_gas_calculator = fork.memory_expansion_gas_calculator() + memory_gas_minimum = memory_expansion_gas_calculator(new_bytes=len(bytes(max_contract_size))) + code_deposit_gas_minimum = ( + fork.gas_costs().G_CODE_DEPOSIT_BYTE * max_contract_size + memory_gas_minimum + ) + + intrinsic_gas_cost_calc = fork.transaction_intrinsic_cost_calculator() + # Calculate the loop cost of the attacker to query one address + loop_cost = ( + gas_costs.G_KECCAK_256 # KECCAK static cost + + math.ceil(85 / 32) * gas_costs.G_KECCAK_256_WORD # KECCAK dynamic cost for CREATE2 + + gas_costs.G_VERY_LOW * 3 # ~MSTOREs+ADDs + + gas_costs.G_COLD_ACCOUNT_ACCESS # Opcode cost + + 30 # ~Gluing opcodes + ) + # Calculate the number of contracts to be targeted + num_contracts = ( + # Base available gas = GAS_LIMIT - intrinsic - (out of loop MSTOREs) + attack_gas_limit - intrinsic_gas_cost_calc() - gas_costs.G_VERY_LOW * 4 + ) // loop_cost + + # Set the block gas limit to a relative high value to ensure the code deposit tx + # fits in the block (there is enough gas available in the block to execute this) + # TODO: verify this, I think we can just do `code_deposit_gas_minimum * 2 * num_contracts`? + # and then take the max of this and the attack gas limit + env = Environment(gas_limit=attack_gas_limit * 2 * (code_deposit_gas_minimum // loop_cost + 1)) # The initcode will take its address as a starting point to the input to the keccak # hash function. @@ -86,13 +119,13 @@ def test_worst_bytecode_single_opcode( ) + Op.POP ), - condition=Op.LT(Op.MSIZE, MAX_CONTRACT_SIZE), + condition=Op.LT(Op.MSIZE, max_contract_size), ) # Despite the whole contract has random bytecode, we make the first opcode be a STOP # so CALL-like attacks return as soon as possible, while EXTCODE(HASH|SIZE) work as # intended. + Op.MSTORE8(0, 0x00) - + Op.RETURN(0, MAX_CONTRACT_SIZE) + + Op.RETURN(0, max_contract_size) ) initcode_address = pre.deploy_contract(code=initcode) @@ -127,24 +160,10 @@ def test_worst_bytecode_single_opcode( ) factory_caller_address = pre.deploy_contract(code=factory_caller_code) - gas_costs = fork.gas_costs() - intrinsic_gas_cost_calc = fork.transaction_intrinsic_cost_calculator() - loop_cost = ( - gas_costs.G_KECCAK_256 # KECCAK static cost - + math.ceil(85 / 32) * gas_costs.G_KECCAK_256_WORD # KECCAK dynamic cost for CREATE2 - + gas_costs.G_VERY_LOW * 3 # ~MSTOREs+ADDs - + gas_costs.G_COLD_ACCOUNT_ACCESS # Opcode cost - + 30 # ~Gluing opcodes - ) - num_contracts = ( - # Base available gas = GAS_LIMIT - intrinsic - (out of loop MSTOREs) - attack_gas_limit - intrinsic_gas_cost_calc() - gas_costs.G_VERY_LOW * 4 - ) // loop_cost - contracts_deployment_tx = Transaction( to=factory_caller_address, gas_limit=env.gas_limit, - gas_price=10**9, + gas_price=10**6, data=Hash(num_contracts), sender=pre.fund_eoa(), ) @@ -180,11 +199,11 @@ def test_worst_bytecode_single_opcode( ) ) - if len(attack_code) > MAX_CONTRACT_SIZE: + if len(attack_code) > max_contract_size: # TODO: A workaround could be to split the opcode code into multiple contracts # and call them in sequence. raise ValueError( - f"Code size {len(attack_code)} exceeds maximum code size {MAX_CONTRACT_SIZE}" + f"Code size {len(attack_code)} exceeds maximum code size {max_contract_size}" ) opcode_address = pre.deploy_contract(code=attack_code) opcode_tx = Transaction( From caf996edb18b715fb5d476ad29a0a48b05d10543 Mon Sep 17 00:00:00 2001 From: Jochem Brouwer Date: Wed, 28 May 2025 22:37:48 +0200 Subject: [PATCH 03/15] Update src/ethereum_test_forks/base_fork.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Paweł Bylica --- src/ethereum_test_forks/base_fork.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ethereum_test_forks/base_fork.py b/src/ethereum_test_forks/base_fork.py index 531da919578..c5d6b38b5a4 100644 --- a/src/ethereum_test_forks/base_fork.py +++ b/src/ethereum_test_forks/base_fork.py @@ -474,7 +474,7 @@ def evm_code_types(cls, block_number: int = 0, timestamp: int = 0) -> List[EVMCo @classmethod @abstractmethod def max_code_size(cls) -> int | None: - """Return the maximum code size to be deployed in a CREATE-frame.""" + """Return the maximum code size allowed to be deployed in a contract creation.""" pass @classmethod From e6218c0fde500a6c5deb7c6f7c16bcd516730cce Mon Sep 17 00:00:00 2001 From: Jochem Brouwer Date: Wed, 28 May 2025 22:37:55 +0200 Subject: [PATCH 04/15] Update src/ethereum_test_forks/base_fork.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Paweł Bylica --- src/ethereum_test_forks/base_fork.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ethereum_test_forks/base_fork.py b/src/ethereum_test_forks/base_fork.py index c5d6b38b5a4..cc77f48b991 100644 --- a/src/ethereum_test_forks/base_fork.py +++ b/src/ethereum_test_forks/base_fork.py @@ -480,7 +480,7 @@ def max_code_size(cls) -> int | None: @classmethod @abstractmethod def max_initcode_size(cls) -> int | None: - """Return the maximum initcode size when using a CREATE frame.""" + """Return the maximum initcode size allowed to be used in a contract creation.""" pass @classmethod From 06e310cc9ebd007dfc62a0e2daa217df8c126e37 Mon Sep 17 00:00:00 2001 From: Jochem Brouwer Date: Wed, 28 May 2025 22:38:00 +0200 Subject: [PATCH 05/15] Update src/ethereum_test_forks/forks/forks.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Paweł Bylica --- src/ethereum_test_forks/forks/forks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ethereum_test_forks/forks/forks.py b/src/ethereum_test_forks/forks/forks.py index e811a1d491d..642abecaacc 100644 --- a/src/ethereum_test_forks/forks/forks.py +++ b/src/ethereum_test_forks/forks/forks.py @@ -648,7 +648,7 @@ def precompiles(cls, block_number: int = 0, timestamp: int = 0) -> List[Address] def max_code_size(cls) -> int: # NOTE: Move this to Spurious Dragon once this fork is introduced. See EIP-170. """At Spurious Dragon, an upper bound was introduced for max contract code size.""" - return int(0x6000) + return 0x6000 @classmethod def call_opcodes( From b20320e786069cdcae4f8811cf1255213366e707 Mon Sep 17 00:00:00 2001 From: Jochem Brouwer Date: Sat, 31 May 2025 00:32:55 +0200 Subject: [PATCH 06/15] feat(tests): fix type error complaints --- src/ethereum_test_forks/forks/forks.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/ethereum_test_forks/forks/forks.py b/src/ethereum_test_forks/forks/forks.py index 642abecaacc..26ab0a8d8bd 100644 --- a/src/ethereum_test_forks/forks/forks.py +++ b/src/ethereum_test_forks/forks/forks.py @@ -370,12 +370,12 @@ def evm_code_types(cls, block_number: int = 0, timestamp: int = 0) -> List[EVMCo return [EVMCodeType.LEGACY] @classmethod - def max_code_size(cls) -> None: + def max_code_size(cls) -> int | None: """At genesis, there is no upper bound for code size (bounded by block gas limit).""" return None @classmethod - def max_initcode_size(cls) -> None: + def max_initcode_size(cls) -> int | None: """At genesis, there is no upper bound for initcode size (bounded by block gas limit).""" return None @@ -645,7 +645,7 @@ def precompiles(cls, block_number: int = 0, timestamp: int = 0) -> List[Address] ) @classmethod - def max_code_size(cls) -> int: + def max_code_size(cls) -> int | None: # NOTE: Move this to Spurious Dragon once this fork is introduced. See EIP-170. """At Spurious Dragon, an upper bound was introduced for max contract code size.""" return 0x6000 @@ -877,7 +877,7 @@ def engine_new_payload_version( return 2 @classmethod - def max_initcode_size(cls) -> int: + def max_initcode_size(cls) -> int | None: """From Shanghai, the initcode size is now limited. See EIP-3860.""" return int(0xC000) @@ -1339,12 +1339,12 @@ def engine_get_blobs_version(cls, block_number: int = 0, timestamp: int = 0) -> return 2 @classmethod - def max_code_size(cls) -> int: + def max_code_size(cls) -> int | None: """From Osaka, the max code size is lifted. See EIP-7907.""" return int(0x40000) @classmethod - def max_initcode_size(cls) -> int: + def max_initcode_size(cls) -> int | None: """At genesis, there is no upper bound for initcode size (bounded by block gas limit).""" return int(0x80000) From 5e823ffc99ef52eed0e880ac93a97bbf6b892470 Mon Sep 17 00:00:00 2001 From: Jochem Brouwer Date: Sat, 31 May 2025 00:35:28 +0200 Subject: [PATCH 07/15] feat(tests): make tests dependent on MAX_CODE_SIZE now depend on the fork-based max code size --- tests/zkevm/test_worst_compute.py | 87 +++++++++++----------- tests/zkevm/test_worst_stateful_opcodes.py | 10 +-- 2 files changed, 50 insertions(+), 47 deletions(-) diff --git a/tests/zkevm/test_worst_compute.py b/tests/zkevm/test_worst_compute.py index 41f56335cd1..dbfe9c5afb3 100644 --- a/tests/zkevm/test_worst_compute.py +++ b/tests/zkevm/test_worst_compute.py @@ -40,7 +40,6 @@ REFERENCE_SPEC_GIT_PATH = "TODO" REFERENCE_SPEC_VERSION = "TODO" -MAX_CODE_SIZE = 24 * 1024 KECCAK_RATE = 136 @@ -343,6 +342,8 @@ def test_worst_keccak( gsc = fork.gas_costs() mem_exp_gas_calculator = fork.memory_expansion_gas_calculator() + max_code_size = fork.max_code_size() + # Discover the optimal input size to maximize keccak-permutations, not keccak calls. # The complication of the discovery arises from the non-linear gas cost of memory expansion. max_keccak_perm_per_block = 0 @@ -373,18 +374,18 @@ def test_worst_keccak( # The loop structure is: JUMPDEST + [attack iteration] + PUSH0 + JUMP # # Now calculate available gas for [attack iteration]: - # Numerator = MAX_CODE_SIZE-3. The -3 is for the JUMPDEST, PUSH0 and JUMP. + # Numerator = max_code_size-3. The -3 is for the JUMPDEST, PUSH0 and JUMP. # Denominator = (PUSHN + PUSH1 + KECCAK256 + POP) + PUSH1_DATA + PUSHN_DATA # TODO: the testing framework uses PUSH1(0) instead of PUSH0 which is suboptimal for the # attack, whenever this is fixed adjust accordingly. start_code = Op.JUMPDEST + Op.PUSH20[optimal_input_length] loop_code = Op.POP(Op.SHA3(Op.PUSH0, Op.DUP1)) end_code = Op.POP + Op.JUMP(Op.PUSH0) - max_iters_loop = (MAX_CODE_SIZE - (len(start_code) + len(end_code))) // len(loop_code) + max_iters_loop = (max_code_size - (len(start_code) + len(end_code))) // len(loop_code) code = start_code + (loop_code * max_iters_loop) + end_code - if len(code) > MAX_CODE_SIZE: + if len(code) > max_code_size: # Must never happen, but keep it as a sanity check. - raise ValueError(f"Code size {len(code)} exceeds maximum code size {MAX_CODE_SIZE}") + raise ValueError(f"Code size {len(code)} exceeds maximum code size {max_code_size}") code_address = pre.deploy_contract(code=bytes(code)) @@ -731,18 +732,20 @@ def test_worst_precompile_fixed_cost( ) -def code_loop_precompile_call(calldata: Bytecode, attack_block: Bytecode): +def code_loop_precompile_call(calldata: Bytecode, attack_block: Bytecode, fork: Fork): """Create a code loop that calls a precompile with the given calldata.""" + max_code_size = fork.max_code_size() + # The attack contract is: CALLDATA_PREP + #JUMPDEST + [attack_block]* + JUMP(#) jumpdest = Op.JUMPDEST jump_back = Op.JUMP(len(calldata)) - max_iters_loop = (MAX_CODE_SIZE - len(calldata) - len(jumpdest) - len(jump_back)) // len( + max_iters_loop = (max_code_size - len(calldata) - len(jumpdest) - len(jump_back)) // len( attack_block ) code = calldata + jumpdest + sum([attack_block] * max_iters_loop) + jump_back - if len(code) > MAX_CODE_SIZE: + if len(code) > max_code_size: # Must never happen, but keep it as a sanity check. - raise ValueError(f"Code size {len(code)} exceeds maximum code size {MAX_CODE_SIZE}") + raise ValueError(f"Code size {len(code)} exceeds maximum code size {max_code_size}") return code @@ -750,18 +753,16 @@ def code_loop_precompile_call(calldata: Bytecode, attack_block: Bytecode): @pytest.mark.zkevm @pytest.mark.valid_from("Cancun") @pytest.mark.slow -def test_worst_jumps( - state_test: StateTestFiller, - pre: Alloc, -): +def test_worst_jumps(state_test: StateTestFiller, pre: Alloc, fork: Fork): """Test running a JUMP-intensive contract.""" env = Environment() + max_code_size = fork.max_code_size() def jump_seq(): return Op.JUMP(Op.ADD(Op.PC, 1)) + Op.JUMPDEST bytes_per_seq = len(jump_seq()) - seqs_per_call = MAX_CODE_SIZE // bytes_per_seq + seqs_per_call = max_code_size // bytes_per_seq # Create and deploy the jump-intensive contract jumps_code = sum([jump_seq() for _ in range(seqs_per_call)]) @@ -788,15 +789,13 @@ def jump_seq(): @pytest.mark.zkevm @pytest.mark.valid_from("Cancun") @pytest.mark.slow -def test_worst_jumpdests( - state_test: StateTestFiller, - pre: Alloc, -): +def test_worst_jumpdests(state_test: StateTestFiller, pre: Alloc, fork: Fork): """Test running a JUMPDEST-intensive contract.""" env = Environment() + max_code_size = fork.max_code_size() # Create and deploy a contract with many JUMPDESTs - jumpdests_code = sum([Op.JUMPDEST] * MAX_CODE_SIZE) + jumpdests_code = sum([Op.JUMPDEST] * max_code_size) jumpdests_address = pre.deploy_contract(code=bytes(jumpdests_code)) # Call the contract repeatedly until gas runs out. @@ -961,10 +960,7 @@ def test_worst_jumpdests( ids=lambda param: "" if isinstance(param, tuple) else param, ) def test_worst_binop_simple( - state_test: StateTestFiller, - pre: Alloc, - opcode: Op, - opcode_args: tuple[int, int], + state_test: StateTestFiller, pre: Alloc, opcode: Op, fork: Fork, opcode_args: tuple[int, int] ): """ Test running a block with as many binary instructions (takes two args, produces one value) @@ -972,15 +968,16 @@ def test_worst_binop_simple( balanced by the DUP2 instruction. """ env = Environment() + max_code_size = fork.max_code_size() tx_data = b"".join(arg.to_bytes(32, byteorder="big") for arg in opcode_args) code_prefix = Op.JUMPDEST + Op.CALLDATALOAD(0) + Op.CALLDATALOAD(32) code_suffix = Op.POP + Op.POP + Op.PUSH0 + Op.JUMP - code_body_len = MAX_CODE_SIZE - len(code_prefix) - len(code_suffix) + code_body_len = max_code_size - len(code_prefix) - len(code_suffix) code_body = (Op.DUP2 + opcode) * (code_body_len // 2) code = code_prefix + code_body + code_suffix - assert len(code) == MAX_CODE_SIZE - 1 + assert len(code) == max_code_size - 1 tx = Transaction( to=pre.deploy_contract(code=code), @@ -999,23 +996,20 @@ def test_worst_binop_simple( @pytest.mark.valid_from("Cancun") @pytest.mark.parametrize("opcode", [Op.ISZERO, Op.NOT]) -def test_worst_unop( - state_test: StateTestFiller, - pre: Alloc, - opcode: Op, -): +def test_worst_unop(state_test: StateTestFiller, pre: Alloc, opcode: Op, fork: Fork): """ Test running a block with as many unary instructions (takes one arg, produces one value) as possible. """ env = Environment() + max_code_size = fork.max_code_size() code_prefix = Op.JUMPDEST + Op.PUSH0 # Start with the arg 0. code_suffix = Op.POP + Op.PUSH0 + Op.JUMP - code_body_len = MAX_CODE_SIZE - len(code_prefix) - len(code_suffix) + code_body_len = max_code_size - len(code_prefix) - len(code_suffix) code_body = opcode * code_body_len code = code_prefix + code_body + code_suffix - assert len(code) == MAX_CODE_SIZE + assert len(code) == max_code_size tx = Transaction( to=pre.deploy_contract(code=code), @@ -1036,6 +1030,7 @@ def test_worst_unop( def test_worst_shifts( state_test: StateTestFiller, pre: Alloc, + fork: Fork, shift_right: Op, ): """ @@ -1043,6 +1038,7 @@ def test_worst_shifts( This test generates left-right pairs of shifts to avoid zeroing the argument. The shift amounts are randomly pre-selected from the constant pool of 15 values on the stack. """ + max_code_size = fork.max_code_size() def to_signed(x): return x if x < 2**255 else x - 2**256 @@ -1076,7 +1072,7 @@ def sar(x, s): code_prefix = sum(Op.PUSH1[sh] for sh in shift_amounts) + Op.JUMPDEST + Op.CALLDATALOAD(0) code_suffix = Op.POP + Op.JUMP(len(shift_amounts) * 2) - code_body_len = MAX_CODE_SIZE - len(code_prefix) - len(code_suffix) + code_body_len = max_code_size - len(code_prefix) - len(code_suffix) def select_shift_amount(shift_fn, v): """Select a shift amount that will produce a non-zero result.""" @@ -1096,7 +1092,7 @@ def select_shift_amount(shift_fn, v): code_body += make_dup(len(shift_amounts) - i) + shift_right code = code_prefix + code_body + code_suffix - assert len(code) == MAX_CODE_SIZE - 2 + assert len(code) == max_code_size - 2 env = Environment() @@ -1134,14 +1130,15 @@ def test_worst_blobhash( ): """Test running a block with as many BLOBHASH instructions as possible.""" env = Environment() + max_code_size = fork.max_code_size() code_prefix = Op.PUSH1(blob_index) + Op.JUMPDEST code_suffix = Op.JUMP(len(code_prefix) - 1) loop_iter = Op.POP(Op.BLOBHASH(Op.DUP1)) - code_body_len = (MAX_CODE_SIZE - len(code_prefix) - len(code_suffix)) // len(loop_iter) + code_body_len = (max_code_size - len(code_prefix) - len(code_suffix)) // len(loop_iter) code_body = loop_iter * code_body_len code = code_prefix + code_body + code_suffix - assert len(code) <= MAX_CODE_SIZE + assert len(code) <= max_code_size tx_type = TransactionType.LEGACY blob_versioned_hashes = None @@ -1177,6 +1174,7 @@ def test_worst_blobhash( def test_worst_mod( state_test: StateTestFiller, pre: Alloc, + fork: Fork, mod_bits: int, op: Op, ): @@ -1190,6 +1188,8 @@ def test_worst_mod( The order of accessing the numerators is selected in a way the mod value remains in the range as long as possible. """ + max_code_size = fork.max_code_size() + # For SMOD we negate both numerator and modulus. The underlying computation is the same, # just the SMOD implementation will have to additionally handle the sign bits. # The result stays negative. @@ -1263,7 +1263,7 @@ def test_worst_mod( code_constant_pool = sum((Op.PUSH32[n] for n in numerators), Bytecode()) code_prefix = code_constant_pool + Op.JUMPDEST code_suffix = Op.JUMP(len(code_constant_pool)) - code_body_len = MAX_CODE_SIZE - len(code_prefix) - len(code_suffix) + code_body_len = max_code_size - len(code_prefix) - len(code_suffix) code_segment = ( Op.CALLDATALOAD(0) + sum(make_dup(len(numerators) - i) + op for i in indexes) + Op.POP ) @@ -1273,7 +1273,7 @@ def test_worst_mod( + sum(code_segment for _ in range(code_body_len // len(code_segment))) + code_suffix ) - assert (MAX_CODE_SIZE - len(code_segment)) < len(code) <= MAX_CODE_SIZE + assert (max_code_size - len(code_segment)) < len(code) <= max_code_size env = Environment() @@ -1301,6 +1301,7 @@ def test_worst_mod( def test_worst_memory_access( state_test: StateTestFiller, pre: Alloc, + fork: Fork, opcode: Op, offset: int, offset_initialized: bool, @@ -1308,6 +1309,7 @@ def test_worst_memory_access( ): """Test running a block with as many memory access instructions as possible.""" env = Environment() + max_code_size = fork.max_code_size() mem_exp_code = Op.MSTORE8(10 * 1024, 1) if big_memory_expansion else Bytecode() offset_set_code = Op.MSTORE(offset, 43) if offset_initialized else Bytecode() @@ -1317,10 +1319,10 @@ def test_worst_memory_access( loop_iter = Op.POP(Op.MLOAD(Op.DUP1)) if opcode == Op.MLOAD else opcode(Op.DUP2, Op.DUP2) - code_body_len = (MAX_CODE_SIZE - len(code_prefix) - len(code_suffix)) // len(loop_iter) + code_body_len = (max_code_size - len(code_prefix) - len(code_suffix)) // len(loop_iter) code_body = loop_iter * code_body_len code = code_prefix + code_body + code_suffix - assert len(code) <= MAX_CODE_SIZE + assert len(code) <= max_code_size tx = Transaction( to=pre.deploy_contract(code=code), @@ -1570,14 +1572,15 @@ def test_worst_calldataload( ): """Test running a block with as many CALLDATALOAD as possible.""" env = Environment() + max_code_size = fork.max_code_size() code_prefix = Op.PUSH0 + Op.JUMPDEST code_suffix = Op.PUSH1(1) + Op.JUMP - code_body_len = MAX_CODE_SIZE - len(code_prefix) - len(code_suffix) + code_body_len = max_code_size - len(code_prefix) - len(code_suffix) code_loop_iter = Op.CALLDATALOAD code_body = code_loop_iter * (code_body_len // len(code_loop_iter)) code = code_prefix + code_body + code_suffix - assert len(code) <= MAX_CODE_SIZE + assert len(code) <= max_code_size tx = Transaction( to=pre.deploy_contract(code=code), diff --git a/tests/zkevm/test_worst_stateful_opcodes.py b/tests/zkevm/test_worst_stateful_opcodes.py index 501ecd47e75..67a2251f669 100644 --- a/tests/zkevm/test_worst_stateful_opcodes.py +++ b/tests/zkevm/test_worst_stateful_opcodes.py @@ -26,8 +26,6 @@ REFERENCE_SPEC_GIT_PATH = "TODO" REFERENCE_SPEC_VERSION = "TODO" -MAX_CODE_SIZE = 24 * 1024 - @pytest.mark.valid_from("Cancun") @pytest.mark.parametrize( @@ -137,11 +135,13 @@ def test_worst_address_state_cold( def test_worst_address_state_warm( state_test: StateTestFiller, pre: Alloc, + fork: Fork, opcode: Op, absent_target: bool, ): """Test running a block with as many stateful opcodes doing warm access for an account.""" env = Environment(gas_limit=100_000_000_000) + max_code_size = fork.max_code_size() attack_gas_limit = Environment().gas_limit # Setup @@ -157,13 +157,13 @@ def test_worst_address_state_warm( jumpdest = Op.JUMPDEST jump_back = Op.JUMP(len(prep)) iter_block = Op.POP(opcode(address=Op.MLOAD(0))) - max_iters_loop = (MAX_CODE_SIZE - len(prep) - len(jumpdest) - len(jump_back)) // len( + max_iters_loop = (max_code_size - len(prep) - len(jumpdest) - len(jump_back)) // len( iter_block ) op_code = prep + jumpdest + sum([iter_block] * max_iters_loop) + jump_back - if len(op_code) > MAX_CODE_SIZE: + if len(op_code) > max_code_size: # Must never happen, but keep it as a sanity check. - raise ValueError(f"Code size {len(op_code)} exceeds maximum code size {MAX_CODE_SIZE}") + raise ValueError(f"Code size {len(op_code)} exceeds maximum code size {max_code_size}") op_address = pre.deploy_contract(code=op_code) tx = Transaction( to=op_address, From 4ac1b68186fe851ce95f7e454fce7aaf27f3653f Mon Sep 17 00:00:00 2001 From: Jochem Brouwer Date: Mon, 2 Jun 2025 18:00:28 +0200 Subject: [PATCH 08/15] feat(tests): use a more logical gas limit formula for contract deployment --- tests/zkevm/test_worst_bytecode.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/zkevm/test_worst_bytecode.py b/tests/zkevm/test_worst_bytecode.py index d145787e5c1..61f432b3751 100644 --- a/tests/zkevm/test_worst_bytecode.py +++ b/tests/zkevm/test_worst_bytecode.py @@ -99,9 +99,7 @@ def test_worst_bytecode_single_opcode( # Set the block gas limit to a relative high value to ensure the code deposit tx # fits in the block (there is enough gas available in the block to execute this) - # TODO: verify this, I think we can just do `code_deposit_gas_minimum * 2 * num_contracts`? - # and then take the max of this and the attack gas limit - env = Environment(gas_limit=attack_gas_limit * 2 * (code_deposit_gas_minimum // loop_cost + 1)) + env = Environment(gas_limit=code_deposit_gas_minimum * 2 * num_contracts) # The initcode will take its address as a starting point to the input to the keccak # hash function. From 4b515b81a871f416e63257f5c13d81d670144f78 Mon Sep 17 00:00:00 2001 From: Jochem Brouwer Date: Tue, 3 Jun 2025 03:20:47 +0200 Subject: [PATCH 09/15] feat(tests): default (init)code size limit to first limit set --- src/ethereum_test_forks/base_fork.py | 4 ++-- src/ethereum_test_forks/forks/forks.py | 24 +++++++++++++----------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/ethereum_test_forks/base_fork.py b/src/ethereum_test_forks/base_fork.py index cc77f48b991..cd3c6bcca0a 100644 --- a/src/ethereum_test_forks/base_fork.py +++ b/src/ethereum_test_forks/base_fork.py @@ -473,13 +473,13 @@ def evm_code_types(cls, block_number: int = 0, timestamp: int = 0) -> List[EVMCo @classmethod @abstractmethod - def max_code_size(cls) -> int | None: + def max_code_size(cls) -> int: """Return the maximum code size allowed to be deployed in a contract creation.""" pass @classmethod @abstractmethod - def max_initcode_size(cls) -> int | None: + def max_initcode_size(cls) -> int: """Return the maximum initcode size allowed to be used in a contract creation.""" pass diff --git a/src/ethereum_test_forks/forks/forks.py b/src/ethereum_test_forks/forks/forks.py index 26ab0a8d8bd..640a53a04f7 100644 --- a/src/ethereum_test_forks/forks/forks.py +++ b/src/ethereum_test_forks/forks/forks.py @@ -370,14 +370,16 @@ def evm_code_types(cls, block_number: int = 0, timestamp: int = 0) -> List[EVMCo return [EVMCodeType.LEGACY] @classmethod - def max_code_size(cls) -> int | None: + def max_code_size(cls) -> int: """At genesis, there is no upper bound for code size (bounded by block gas limit).""" - return None + """However, the default is set to the limit of EIP-170 (Spurious Dragon)""" + return 0x6000 @classmethod - def max_initcode_size(cls) -> int | None: - """At genesis, there is no upper bound for initcode size (bounded by block gas limit).""" - return None + def max_initcode_size(cls) -> int: + """At genesis, there is no upper bound for initcode size.""" + """However, the default is set to the limit of EIP-3860 (Shanghai)""" + return 0xC000 @classmethod def call_opcodes( @@ -645,7 +647,7 @@ def precompiles(cls, block_number: int = 0, timestamp: int = 0) -> List[Address] ) @classmethod - def max_code_size(cls) -> int | None: + def max_code_size(cls) -> int: # NOTE: Move this to Spurious Dragon once this fork is introduced. See EIP-170. """At Spurious Dragon, an upper bound was introduced for max contract code size.""" return 0x6000 @@ -877,9 +879,9 @@ def engine_new_payload_version( return 2 @classmethod - def max_initcode_size(cls) -> int | None: + def max_initcode_size(cls) -> int: """From Shanghai, the initcode size is now limited. See EIP-3860.""" - return int(0xC000) + return 0xC000 @classmethod def valid_opcodes( @@ -1339,13 +1341,13 @@ def engine_get_blobs_version(cls, block_number: int = 0, timestamp: int = 0) -> return 2 @classmethod - def max_code_size(cls) -> int | None: + def max_code_size(cls) -> int: """From Osaka, the max code size is lifted. See EIP-7907.""" return int(0x40000) @classmethod - def max_initcode_size(cls) -> int | None: - """At genesis, there is no upper bound for initcode size (bounded by block gas limit).""" + def max_initcode_size(cls) -> int: + """From Osaka, the max initcode size is lifted. See EIP-7907.""" return int(0x80000) @classmethod From 8980cf7754df35e0e894d3ed9e702c3a839d4ea1 Mon Sep 17 00:00:00 2001 From: Jochem Brouwer Date: Tue, 3 Jun 2025 03:21:27 +0200 Subject: [PATCH 10/15] feat(tests): remove EIP 7907 from Osaka --- src/ethereum_test_forks/forks/forks.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/ethereum_test_forks/forks/forks.py b/src/ethereum_test_forks/forks/forks.py index 640a53a04f7..7bf9463ac90 100644 --- a/src/ethereum_test_forks/forks/forks.py +++ b/src/ethereum_test_forks/forks/forks.py @@ -1340,16 +1340,6 @@ def engine_get_blobs_version(cls, block_number: int = 0, timestamp: int = 0) -> """At Osaka, the engine get blobs version is 2.""" return 2 - @classmethod - def max_code_size(cls) -> int: - """From Osaka, the max code size is lifted. See EIP-7907.""" - return int(0x40000) - - @classmethod - def max_initcode_size(cls) -> int: - """From Osaka, the max initcode size is lifted. See EIP-7907.""" - return int(0x80000) - @classmethod def is_deployed(cls) -> bool: """ From 676d87f0850c9fde0afde5002ae6e0ba8ea76910 Mon Sep 17 00:00:00 2001 From: Jochem Brouwer Date: Tue, 3 Jun 2025 03:24:16 +0200 Subject: [PATCH 11/15] feat(tests): make typecheck happy --- tests/zkevm/test_worst_compute.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/tests/zkevm/test_worst_compute.py b/tests/zkevm/test_worst_compute.py index dbfe9c5afb3..856308eba31 100644 --- a/tests/zkevm/test_worst_compute.py +++ b/tests/zkevm/test_worst_compute.py @@ -466,7 +466,7 @@ def test_worst_precompile_only_data_input( calldata = Op.CODECOPY(0, 0, optimal_input_length) attack_block = Op.POP(Op.STATICCALL(Op.GAS, address, 0, optimal_input_length, 0, 0)) - code = code_loop_precompile_call(calldata, attack_block) + code = code_loop_precompile_call(calldata, attack_block, fork) code_address = pre.deploy_contract(code=code) @@ -485,10 +485,7 @@ def test_worst_precompile_only_data_input( @pytest.mark.valid_from("Cancun") -def test_worst_modexp( - state_test: StateTestFiller, - pre: Alloc, -): +def test_worst_modexp(state_test: StateTestFiller, pre: Alloc, fork: Fork): """Test running a block with as many MODEXP calls as possible.""" env = Environment() @@ -514,7 +511,7 @@ def test_worst_modexp( iter_complexity = exp.bit_length() - 1 gas_cost = math.floor((mul_complexity * iter_complexity) / 3) attack_block = Op.POP(Op.STATICCALL(gas_cost, 0x5, 0, 32 * 6, 0, 0)) - code = code_loop_precompile_call(calldata, attack_block) + code = code_loop_precompile_call(calldata, attack_block, fork) code_address = pre.deploy_contract(code=code) @@ -681,6 +678,7 @@ def test_worst_modexp( def test_worst_precompile_fixed_cost( state_test: StateTestFiller, pre: Alloc, + fork: Fork, precompile_address: Address, parameters: list[str] | list[BytesConcatenation] | list[bytes], ): @@ -715,7 +713,7 @@ def test_worst_precompile_fixed_cost( attack_block = Op.POP( Op.STATICCALL(Op.GAS, precompile_address, 0, len(concatenated_bytes), 0, 0) ) - code = code_loop_precompile_call(calldata, attack_block) + code = code_loop_precompile_call(calldata, attack_block, fork) code_address = pre.deploy_contract(code=bytes(code)) tx = Transaction( @@ -1507,7 +1505,7 @@ def test_amortized_bn128_pairings( calldata = Op.CALLDATACOPY(size=Op.CALLDATASIZE) attack_block = Op.POP(Op.STATICCALL(Op.GAS, 0x08, 0, Op.CALLDATASIZE, 0, 0)) - code = code_loop_precompile_call(calldata, attack_block) + code = code_loop_precompile_call(calldata, attack_block, fork) code_address = pre.deploy_contract(code=code) From 08c421c567cf57f9ea6d2f09caa405906c216ba7 Mon Sep 17 00:00:00 2001 From: Jochem Brouwer Date: Tue, 3 Jun 2025 03:45:03 +0200 Subject: [PATCH 12/15] feat(tests): do not use fallback for code size being returned `None` anymore --- tests/zkevm/test_worst_bytecode.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/zkevm/test_worst_bytecode.py b/tests/zkevm/test_worst_bytecode.py index 61f432b3751..5d83a123c42 100644 --- a/tests/zkevm/test_worst_bytecode.py +++ b/tests/zkevm/test_worst_bytecode.py @@ -69,7 +69,7 @@ def test_worst_bytecode_single_opcode( # but also the quadratic memory expansion costs which have to be paid each time the # memory is being setup attack_gas_limit = 100_000_000 - max_contract_size = fork.max_code_size() or 24 * 1024 # Uses Spurious Dragon limit by default + max_contract_size = fork.max_code_size() gas_costs = fork.gas_costs() From 9961f11c146055b0bb5d3a4062233ef7d2683b54 Mon Sep 17 00:00:00 2001 From: Jochem Brouwer Date: Tue, 3 Jun 2025 03:45:25 +0200 Subject: [PATCH 13/15] feat(tests): read code size from forks from newly added tests after rebase --- tests/zkevm/test_worst_compute.py | 40 ++++++++++++++++++------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/tests/zkevm/test_worst_compute.py b/tests/zkevm/test_worst_compute.py index 856308eba31..b466c4c9167 100644 --- a/tests/zkevm/test_worst_compute.py +++ b/tests/zkevm/test_worst_compute.py @@ -82,17 +82,19 @@ def make_dup(index: int) -> Opcode: def test_worst_zero_param( state_test: StateTestFiller, pre: Alloc, + fork: Fork, opcode: Op, ): """Test running a block with as many zero-parameter opcodes as possible.""" env = Environment() + max_code_size = fork.max_code_size() code_prefix = Op.JUMPDEST iter_loop = Op.POP(opcode) code_suffix = Op.PUSH0 + Op.JUMP - code_iter_len = (MAX_CODE_SIZE - len(code_prefix) - len(code_suffix)) // len(iter_loop) + code_iter_len = (max_code_size - len(code_prefix) - len(code_suffix)) // len(iter_loop) code = code_prefix + iter_loop * code_iter_len + code_suffix - assert len(code) <= MAX_CODE_SIZE + assert len(code) <= max_code_size tx = Transaction( to=pre.deploy_contract(code=bytes(code)), @@ -113,17 +115,19 @@ def test_worst_zero_param( def test_worst_calldatasize( state_test: StateTestFiller, pre: Alloc, + fork: Fork, calldata_length: int, ): """Test running a block with as many CALLDATASIZE as possible.""" env = Environment() + max_code_size = fork.max_code_size() code_prefix = Op.JUMPDEST iter_loop = Op.POP(Op.CALLDATASIZE) code_suffix = Op.PUSH0 + Op.JUMP - code_iter_len = (MAX_CODE_SIZE - len(code_prefix) - len(code_suffix)) // len(iter_loop) + code_iter_len = (max_code_size - len(code_prefix) - len(code_suffix)) // len(iter_loop) code = code_prefix + iter_loop * code_iter_len + code_suffix - assert len(code) <= MAX_CODE_SIZE + assert len(code) <= max_code_size tx = Transaction( to=pre.deploy_contract(code=bytes(code)), @@ -146,6 +150,7 @@ def test_worst_calldatasize( def test_worst_callvalue( state_test: StateTestFiller, pre: Alloc, + fork: Fork, non_zero_value: bool, from_origin: bool, ): @@ -157,13 +162,14 @@ def test_worst_callvalue( transaction or a previous CALL. """ env = Environment() + max_code_size = fork.max_code_size() code_prefix = Op.JUMPDEST iter_loop = Op.POP(Op.CALLVALUE) code_suffix = Op.PUSH0 + Op.JUMP - code_iter_len = (MAX_CODE_SIZE - len(code_prefix) - len(code_suffix)) // len(iter_loop) + code_iter_len = (max_code_size - len(code_prefix) - len(code_suffix)) // len(iter_loop) code = code_prefix + iter_loop * code_iter_len + code_suffix - assert len(code) <= MAX_CODE_SIZE + assert len(code) <= max_code_size code_address = pre.deploy_contract(code=bytes(code)) tx_to = ( @@ -210,6 +216,7 @@ class ReturnDataStyle(Enum): def test_worst_returndatasize_nonzero( state_test: StateTestFiller, pre: Alloc, + fork: Fork, returned_size: int, return_data_style: ReturnDataStyle, ): @@ -221,6 +228,7 @@ def test_worst_returndatasize_nonzero( The `return_data_style` indicates how returned data is produced for the opcode caller. """ env = Environment() + max_code_size = fork.max_code_size() dummy_contract_call = Bytecode() if return_data_style != ReturnDataStyle.IDENTITY: @@ -240,9 +248,9 @@ def test_worst_returndatasize_nonzero( code_prefix = dummy_contract_call + Op.JUMPDEST iter_loop = Op.POP(Op.RETURNDATASIZE) code_suffix = Op.JUMP(len(code_prefix) - 1) - code_iter_len = (MAX_CODE_SIZE - len(code_prefix) - len(code_suffix)) // len(iter_loop) + code_iter_len = (max_code_size - len(code_prefix) - len(code_suffix)) // len(iter_loop) code = code_prefix + iter_loop * code_iter_len + code_suffix - assert len(code) <= MAX_CODE_SIZE + assert len(code) <= max_code_size tx = Transaction( to=pre.deploy_contract(code=bytes(code)), @@ -259,21 +267,19 @@ def test_worst_returndatasize_nonzero( @pytest.mark.valid_from("Cancun") -def test_worst_returndatasize_zero( - state_test: StateTestFiller, - pre: Alloc, -): +def test_worst_returndatasize_zero(state_test: StateTestFiller, pre: Alloc, fork: Fork): """Test running a block with as many RETURNDATASIZE opcodes as possible with a zero buffer.""" env = Environment() + max_code_size = fork.max_code_size() dummy_contract_call = Bytecode() code_prefix = dummy_contract_call + Op.JUMPDEST iter_loop = Op.POP(Op.RETURNDATASIZE) code_suffix = Op.JUMP(len(code_prefix) - 1) - code_iter_len = (MAX_CODE_SIZE - len(code_prefix) - len(code_suffix)) // len(iter_loop) + code_iter_len = (max_code_size - len(code_prefix) - len(code_suffix)) // len(iter_loop) code = code_prefix + iter_loop * code_iter_len + code_suffix - assert len(code) <= MAX_CODE_SIZE + assert len(code) <= max_code_size tx = Transaction( to=pre.deploy_contract(code=bytes(code)), @@ -294,6 +300,7 @@ def test_worst_returndatasize_zero( def test_worst_msize( state_test: StateTestFiller, pre: Alloc, + fork: Fork, mem_size: int, ): """ @@ -302,14 +309,15 @@ def test_worst_msize( The `mem_size` parameter indicates by how much the memory is expanded. """ env = Environment() + max_code_size = fork.max_code_size() # We use CALLVALUE for the parameter since is 1 gas cheaper than PUSHX. code_prefix = Op.MLOAD(Op.CALLVALUE) + Op.JUMPDEST iter_loop = Op.POP(Op.MSIZE) code_suffix = Op.JUMP(len(code_prefix) - 1) - code_iter_len = (MAX_CODE_SIZE - len(code_prefix) - len(code_suffix)) // len(iter_loop) + code_iter_len = (max_code_size - len(code_prefix) - len(code_suffix)) // len(iter_loop) code = code_prefix + iter_loop * code_iter_len + code_suffix - assert len(code) <= MAX_CODE_SIZE + assert len(code) <= max_code_size tx = Transaction( to=pre.deploy_contract(code=bytes(code)), From 3734fc8ec6e7878a0718c05a4786446fbca5f52f Mon Sep 17 00:00:00 2001 From: Jochem Brouwer Date: Wed, 4 Jun 2025 07:15:51 +0200 Subject: [PATCH 14/15] Update tests/zkevm/test_worst_bytecode.py Co-authored-by: Mario Vega --- tests/zkevm/test_worst_bytecode.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/zkevm/test_worst_bytecode.py b/tests/zkevm/test_worst_bytecode.py index 5d83a123c42..4343da0a0e4 100644 --- a/tests/zkevm/test_worst_bytecode.py +++ b/tests/zkevm/test_worst_bytecode.py @@ -68,7 +68,7 @@ def test_worst_bytecode_single_opcode( # able to pay for the contract deposit. This has to take into account the 200 gas per byte, # but also the quadratic memory expansion costs which have to be paid each time the # memory is being setup - attack_gas_limit = 100_000_000 + attack_gas_limit = Environment().gas_limit max_contract_size = fork.max_code_size() gas_costs = fork.gas_costs() From 0ed1dc16bbd3386a0e8c83e4df33dec22d42911e Mon Sep 17 00:00:00 2001 From: Jochem Brouwer Date: Wed, 4 Jun 2025 07:26:30 +0200 Subject: [PATCH 15/15] feat(tests): insert max_code_size for modarith zkevm test --- tests/zkevm/test_worst_compute.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/zkevm/test_worst_compute.py b/tests/zkevm/test_worst_compute.py index b466c4c9167..7a6b3bb2360 100644 --- a/tests/zkevm/test_worst_compute.py +++ b/tests/zkevm/test_worst_compute.py @@ -1350,6 +1350,7 @@ def test_worst_memory_access( def test_worst_modarith( state_test: StateTestFiller, pre: Alloc, + fork: Fork, mod_bits: int, op: Op, ): @@ -1368,6 +1369,8 @@ def test_worst_modarith( fixed_arg = 2**256 - 1 num_args = 15 + max_code_size = fork.max_code_size() + # Pick the modulus min value so that it is _unlikely_ to drop to the lower word count. assert mod_bits >= 63 mod_min = 2 ** (mod_bits - 63) @@ -1428,7 +1431,7 @@ def test_worst_modarith( # Construct the final code. Because of the usage of PUSH32 the code segment is very long, # so don't try to include multiple of these. code = code_constant_pool + Op.JUMPDEST + code_segment + Op.JUMP(len(code_constant_pool)) - assert (MAX_CODE_SIZE - len(code_segment)) < len(code) <= MAX_CODE_SIZE + assert (max_code_size - len(code_segment)) < len(code) <= max_code_size env = Environment()