From 492a745b75cf458bafb8bdc5cfef7b95e4f97567 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Thu, 29 May 2025 12:18:44 -0300 Subject: [PATCH 01/12] zkevm: add selfdestruct existing contracts bench Signed-off-by: Ignacio Hagopian --- tests/zkevm/test_worst_stateful_opcodes.py | 135 +++++++++++++++++++++ 1 file changed, 135 insertions(+) diff --git a/tests/zkevm/test_worst_stateful_opcodes.py b/tests/zkevm/test_worst_stateful_opcodes.py index 67a2251f669..b6d8e8ea337 100644 --- a/tests/zkevm/test_worst_stateful_opcodes.py +++ b/tests/zkevm/test_worst_stateful_opcodes.py @@ -5,6 +5,8 @@ Tests running worst-case stateful opcodes for zkEVMs. """ +import math + import pytest from ethereum_test_forks import Fork @@ -16,9 +18,11 @@ BlockchainTestFiller, Bytecode, Environment, + Hash, StateTestFiller, Transaction, While, + compute_create2_address, compute_create_address, ) from ethereum_test_tools.vm.opcode import Opcodes as Op @@ -475,3 +479,134 @@ def test_worst_extcodecopy_warm( post={}, tx=tx, ) + + +@pytest.mark.valid_from("Cancun") +@pytest.mark.parametrize("value_bearing", [True, False]) +def test_worst_selfdestruct_existing( + blockchain_test: BlockchainTestFiller, + fork: Fork, + pre: Alloc, + value_bearing: bool, +): + """Test running a block with as SELFDESTRUCT calls as possible for existing contracts.""" + env = Environment(gas_limit=100_000_000_000) + attack_gas_limit = Environment().gas_limit + + # Create an account that will be used as the beneficiary of the SELFDESTRUCT calls, to avoid + # account creation costs. All SELFDESTRUCT calls will target the same account to avoid + # cold costs. + selfdestruct_beneficiary = pre.fund_eoa(amount=100) + + # Template code that will be used to deploy a large number of contracts. + selfdestructable_contract_addr = pre.deploy_contract( + code=Op.SELFDESTRUCT(selfdestruct_beneficiary) + ) + initcode = Op.EXTCODECOPY( + address=selfdestructable_contract_addr, + dest_offset=0, + offset=0, + size=Op.EXTCODESIZE(selfdestructable_contract_addr), + ) + Op.RETURN(0, Op.EXTCODESIZE(selfdestructable_contract_addr)) + initcode_address = pre.deploy_contract(code=initcode) + + # Create a factory that deployes a new SELFDESTRUCT contract instance pre-funded depending on + # the value_bearing parameter. We use CREATE2 so the caller contract can easily reproduce + # the addresses in a loop for CALLs. + factory_code = ( + Op.EXTCODECOPY( + address=initcode_address, + dest_offset=0, + offset=0, + size=Op.EXTCODESIZE(initcode_address), + ) + + Op.MSTORE( + 0, + Op.CREATE2( + value=1 if value_bearing else 0, + offset=0, + size=Op.EXTCODESIZE(initcode_address), + salt=Op.SLOAD(0), + ), + ) + + Op.SSTORE(0, Op.ADD(Op.SLOAD(0), 1)) + + Op.RETURN(0, 32) + ) + factory_address = pre.deploy_contract(code=factory_code, balance=10**18) + + factory_caller_code = Op.CALLDATALOAD(0) + While( + body=Op.POP(Op.CALL(address=factory_address)), + condition=Op.PUSH1(1) + Op.SWAP1 + Op.SUB + Op.DUP1 + Op.ISZERO + Op.ISZERO, + ) + 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 # CALL to self-destructing contract + + gas_costs.G_SELF_DESTRUCT + + 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, + data=Hash(num_contracts), + sender=pre.fund_eoa(), + ) + + post = {} + deployed_contract_addresses = [] + for i in range(num_contracts): + deployed_contract_address = compute_create2_address( + address=factory_address, + salt=i, + initcode=initcode, + ) + post[deployed_contract_address] = Account(nonce=1) + deployed_contract_addresses.append(deployed_contract_address) + + attack_call = Op.POP(Op.CALL(address=Op.SHA3(32 - 20 - 1, 85))) + attack_code = ( + # Setup memory for later CREATE2 address generation loop. + # 0xFF+[Address(20bytes)]+[seed(32bytes)]+[initcode keccak(32bytes)] + Op.MSTORE(0, factory_address) + + Op.MSTORE8(32 - 20 - 1, 0xFF) + + Op.MSTORE(32, 0) + + Op.MSTORE(64, initcode.keccak256()) + # Main loop + + While( + body=attack_call + Op.MSTORE(32, Op.ADD(Op.MLOAD(32), 1)), + # The condition is having enough gas for at least one more iteration. + # 2+2600+5000 is the `attack_call` gas cost, and 2* is a rough safety margin. + condition=Op.GT(Op.GAS, 2 * (2 + 2600 + 5000)), + ) + ) + assert len(attack_code) <= fork.max_code_size() + + attack_code_addr = pre.deploy_contract(code=attack_code) + opcode_tx = Transaction( + to=attack_code_addr, + gas_limit=attack_gas_limit, + gas_price=10**9, + sender=pre.fund_eoa(), + ) + + blockchain_test( + genesis_environment=env, + pre=pre, + post=post, + blocks=[ + Block(txs=[contracts_deployment_tx]), + Block(txs=[opcode_tx]), + ], + exclude_full_post_state_in_output=True, + ) From 0a19e7d1c0415c99265ca80b11fad331787477b7 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Thu, 29 May 2025 13:15:18 -0300 Subject: [PATCH 02/12] zkevm: add selfdestruct of contracts deployed in tx Signed-off-by: Ignacio Hagopian --- tests/zkevm/test_worst_stateful_opcodes.py | 116 +++++++++++++++++---- 1 file changed, 95 insertions(+), 21 deletions(-) diff --git a/tests/zkevm/test_worst_stateful_opcodes.py b/tests/zkevm/test_worst_stateful_opcodes.py index b6d8e8ea337..85eb26760ed 100644 --- a/tests/zkevm/test_worst_stateful_opcodes.py +++ b/tests/zkevm/test_worst_stateful_opcodes.py @@ -496,7 +496,7 @@ def test_worst_selfdestruct_existing( # Create an account that will be used as the beneficiary of the SELFDESTRUCT calls, to avoid # account creation costs. All SELFDESTRUCT calls will target the same account to avoid # cold costs. - selfdestruct_beneficiary = pre.fund_eoa(amount=100) + selfdestruct_beneficiary = pre.fund_eoa() # Template code that will be used to deploy a large number of contracts. selfdestructable_contract_addr = pre.deploy_contract( @@ -563,19 +563,7 @@ def test_worst_selfdestruct_existing( sender=pre.fund_eoa(), ) - post = {} - deployed_contract_addresses = [] - for i in range(num_contracts): - deployed_contract_address = compute_create2_address( - address=factory_address, - salt=i, - initcode=initcode, - ) - post[deployed_contract_address] = Account(nonce=1) - deployed_contract_addresses.append(deployed_contract_address) - - attack_call = Op.POP(Op.CALL(address=Op.SHA3(32 - 20 - 1, 85))) - attack_code = ( + code = ( # Setup memory for later CREATE2 address generation loop. # 0xFF+[Address(20bytes)]+[seed(32bytes)]+[initcode keccak(32bytes)] Op.MSTORE(0, factory_address) @@ -584,22 +572,37 @@ def test_worst_selfdestruct_existing( + Op.MSTORE(64, initcode.keccak256()) # Main loop + While( - body=attack_call + Op.MSTORE(32, Op.ADD(Op.MLOAD(32), 1)), - # The condition is having enough gas for at least one more iteration. - # 2+2600+5000 is the `attack_call` gas cost, and 2* is a rough safety margin. - condition=Op.GT(Op.GAS, 2 * (2 + 2600 + 5000)), + body=Op.POP(Op.CALL(address=Op.SHA3(32 - 20 - 1, 85))) + + Op.MSTORE(32, Op.ADD(Op.MLOAD(32), 1)), + # Stop before we run out of gas for the whole tx execution. + # The value was discovered practically rounded to the next 1000 multiple. + condition=Op.GT(Op.GAS, 28_000), ) + + Op.SSTORE(0, 42) # Done for successful tx execution assertion below. ) - assert len(attack_code) <= fork.max_code_size() + assert len(code) <= fork.max_code_size() - attack_code_addr = pre.deploy_contract(code=attack_code) + code_addr = pre.deploy_contract(code=code) opcode_tx = Transaction( - to=attack_code_addr, + to=code_addr, gas_limit=attack_gas_limit, gas_price=10**9, sender=pre.fund_eoa(), ) + post = { + code_addr: Account(storage={0: 42}) # Check for successful execution. + } + deployed_contract_addresses = [] + for i in range(num_contracts): + deployed_contract_address = compute_create2_address( + address=factory_address, + salt=i, + initcode=initcode, + ) + post[deployed_contract_address] = Account(nonce=1) + deployed_contract_addresses.append(deployed_contract_address) + blockchain_test( genesis_environment=env, pre=pre, @@ -610,3 +613,74 @@ def test_worst_selfdestruct_existing( ], exclude_full_post_state_in_output=True, ) + + +@pytest.mark.valid_from("Cancun") +@pytest.mark.parametrize("value_bearing", [True, False]) +def test_worst_selfdestruct_created( + state_test: StateTestFiller, + pre: Alloc, + value_bearing: bool, +): + """ + Test running a block with as SELFDESTRUCT calls as possible for deployed contracts in + the same transaction. + """ + env = Environment() + + # Create an account that will be used as the beneficiary of the SELFDESTRUCT calls, to avoid + # account creation costs. All SELFDESTRUCT calls will target the same account to avoid + # cold costs. + selfdestruct_beneficiary = pre.fund_eoa() + + # Template code that will be used to deploy a large number of contracts. + selfdestructable_contract_addr = pre.deploy_contract( + code=Op.SELFDESTRUCT(selfdestruct_beneficiary) + ) + initcode = Op.EXTCODECOPY( + address=selfdestructable_contract_addr, + dest_offset=0, + offset=0, + size=Op.EXTCODESIZE(selfdestructable_contract_addr), + ) + Op.RETURN(0, Op.EXTCODESIZE(selfdestructable_contract_addr)) + initcode_address = pre.deploy_contract(code=initcode) + + code = ( + Op.EXTCODECOPY( + address=initcode_address, + dest_offset=0, + offset=0, + size=Op.EXTCODESIZE(initcode_address), + ) + + While( + body=Op.POP( + Op.CALL( + address=Op.CREATE( + value=1 if value_bearing else 0, + offset=0, + size=Op.EXTCODESIZE(initcode_address), + ) + ) + ), + # Stop before we run out of gas for the whole tx execution. + # The value was discovered practically rounded to the next 1000 multiple. + condition=Op.GT(Op.GAS, 40_000), + ) + + Op.SSTORE(0, 42) # Done for successful tx execution assertion below. + ) + code_addr = pre.deploy_contract(code=code) + code_tx = Transaction( + to=code_addr, + gas_limit=env.gas_limit, + gas_price=10**9, + sender=pre.fund_eoa(), + ) + + post = {code_addr: Account(storage={0: 42})} # Check for successful execution. + + state_test( + env=env, + pre=pre, + post=post, + tx=code_tx, + ) From 1fa785c19e4e5e95c8cc4885d4c431dad95afadf Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Thu, 29 May 2025 13:27:44 -0300 Subject: [PATCH 03/12] improvements Signed-off-by: Ignacio Hagopian --- tests/zkevm/test_worst_stateful_opcodes.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/tests/zkevm/test_worst_stateful_opcodes.py b/tests/zkevm/test_worst_stateful_opcodes.py index 85eb26760ed..c8293d2ee48 100644 --- a/tests/zkevm/test_worst_stateful_opcodes.py +++ b/tests/zkevm/test_worst_stateful_opcodes.py @@ -575,14 +575,15 @@ def test_worst_selfdestruct_existing( body=Op.POP(Op.CALL(address=Op.SHA3(32 - 20 - 1, 85))) + Op.MSTORE(32, Op.ADD(Op.MLOAD(32), 1)), # Stop before we run out of gas for the whole tx execution. - # The value was discovered practically rounded to the next 1000 multiple. - condition=Op.GT(Op.GAS, 28_000), + # The value was found by trial-error rounded to the next 1000 multiple. + condition=Op.GT(Op.GAS, 12_000), ) + Op.SSTORE(0, 42) # Done for successful tx execution assertion below. ) assert len(code) <= fork.max_code_size() - code_addr = pre.deploy_contract(code=code) + # The 0 storage slot is initialize to avoid creation costs in SSTORE above. + code_addr = pre.deploy_contract(code=code, storage={0: 1}) opcode_tx = Transaction( to=code_addr, gas_limit=attack_gas_limit, @@ -663,12 +664,13 @@ def test_worst_selfdestruct_created( ) ), # Stop before we run out of gas for the whole tx execution. - # The value was discovered practically rounded to the next 1000 multiple. - condition=Op.GT(Op.GAS, 40_000), + # The value was found by trial-error rounded to the next 1000 multiple. + condition=Op.GT(Op.GAS, 33_000), ) + Op.SSTORE(0, 42) # Done for successful tx execution assertion below. ) - code_addr = pre.deploy_contract(code=code) + # The 0 storage slot is initialize to avoid creation costs in SSTORE above. + code_addr = pre.deploy_contract(code=code, storage={0: 1}) code_tx = Transaction( to=code_addr, gas_limit=env.gas_limit, From e877c7b6d8e71111e852c785cba91e7b84260aff Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Thu, 29 May 2025 15:02:11 -0300 Subject: [PATCH 04/12] Update tests/zkevm/test_worst_stateful_opcodes.py Co-authored-by: Jochem Brouwer --- tests/zkevm/test_worst_stateful_opcodes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/zkevm/test_worst_stateful_opcodes.py b/tests/zkevm/test_worst_stateful_opcodes.py index c8293d2ee48..02023674444 100644 --- a/tests/zkevm/test_worst_stateful_opcodes.py +++ b/tests/zkevm/test_worst_stateful_opcodes.py @@ -489,7 +489,7 @@ def test_worst_selfdestruct_existing( pre: Alloc, value_bearing: bool, ): - """Test running a block with as SELFDESTRUCT calls as possible for existing contracts.""" + """Test running a block with as many SELFDESTRUCTs as possible for existing contracts.""" env = Environment(gas_limit=100_000_000_000) attack_gas_limit = Environment().gas_limit From 9333b0d45fa3548d408a716241b379e0d6777afb Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Thu, 29 May 2025 15:44:22 -0300 Subject: [PATCH 05/12] improvements Signed-off-by: Ignacio Hagopian --- tests/zkevm/test_worst_stateful_opcodes.py | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/tests/zkevm/test_worst_stateful_opcodes.py b/tests/zkevm/test_worst_stateful_opcodes.py index 02023674444..c068563a260 100644 --- a/tests/zkevm/test_worst_stateful_opcodes.py +++ b/tests/zkevm/test_worst_stateful_opcodes.py @@ -493,15 +493,8 @@ def test_worst_selfdestruct_existing( env = Environment(gas_limit=100_000_000_000) attack_gas_limit = Environment().gas_limit - # Create an account that will be used as the beneficiary of the SELFDESTRUCT calls, to avoid - # account creation costs. All SELFDESTRUCT calls will target the same account to avoid - # cold costs. - selfdestruct_beneficiary = pre.fund_eoa() - # Template code that will be used to deploy a large number of contracts. - selfdestructable_contract_addr = pre.deploy_contract( - code=Op.SELFDESTRUCT(selfdestruct_beneficiary) - ) + selfdestructable_contract_addr = pre.deploy_contract(code=Op.SELFDESTRUCT(Op.COINBASE)) initcode = Op.EXTCODECOPY( address=selfdestructable_contract_addr, dest_offset=0, @@ -629,15 +622,8 @@ def test_worst_selfdestruct_created( """ env = Environment() - # Create an account that will be used as the beneficiary of the SELFDESTRUCT calls, to avoid - # account creation costs. All SELFDESTRUCT calls will target the same account to avoid - # cold costs. - selfdestruct_beneficiary = pre.fund_eoa() - # Template code that will be used to deploy a large number of contracts. - selfdestructable_contract_addr = pre.deploy_contract( - code=Op.SELFDESTRUCT(selfdestruct_beneficiary) - ) + selfdestructable_contract_addr = pre.deploy_contract(code=Op.SELFDESTRUCT(Op.COINBASE)) initcode = Op.EXTCODECOPY( address=selfdestructable_contract_addr, dest_offset=0, @@ -665,7 +651,7 @@ def test_worst_selfdestruct_created( ), # Stop before we run out of gas for the whole tx execution. # The value was found by trial-error rounded to the next 1000 multiple. - condition=Op.GT(Op.GAS, 33_000), + condition=Op.GT(Op.GAS, 42_000), ) + Op.SSTORE(0, 42) # Done for successful tx execution assertion below. ) @@ -679,7 +665,6 @@ def test_worst_selfdestruct_created( ) post = {code_addr: Account(storage={0: 42})} # Check for successful execution. - state_test( env=env, pre=pre, From 0602e63645244c0d6d95cd0021107513c4e577f5 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Thu, 29 May 2025 15:49:21 -0300 Subject: [PATCH 06/12] adjust gas prices Signed-off-by: Ignacio Hagopian --- tests/zkevm/test_worst_stateful_opcodes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/zkevm/test_worst_stateful_opcodes.py b/tests/zkevm/test_worst_stateful_opcodes.py index c068563a260..223789a75c4 100644 --- a/tests/zkevm/test_worst_stateful_opcodes.py +++ b/tests/zkevm/test_worst_stateful_opcodes.py @@ -580,7 +580,7 @@ def test_worst_selfdestruct_existing( opcode_tx = Transaction( to=code_addr, gas_limit=attack_gas_limit, - gas_price=10**9, + gas_price=10, sender=pre.fund_eoa(), ) @@ -660,7 +660,7 @@ def test_worst_selfdestruct_created( code_tx = Transaction( to=code_addr, gas_limit=env.gas_limit, - gas_price=10**9, + gas_price=10, sender=pre.fund_eoa(), ) From 09440f59fde6bc7ba9bf6f21dd48e76d0319938d Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Thu, 29 May 2025 16:09:57 -0300 Subject: [PATCH 07/12] Update tests/zkevm/test_worst_stateful_opcodes.py Co-authored-by: Jochem Brouwer --- tests/zkevm/test_worst_stateful_opcodes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/zkevm/test_worst_stateful_opcodes.py b/tests/zkevm/test_worst_stateful_opcodes.py index 223789a75c4..c4f317fe05f 100644 --- a/tests/zkevm/test_worst_stateful_opcodes.py +++ b/tests/zkevm/test_worst_stateful_opcodes.py @@ -617,7 +617,7 @@ def test_worst_selfdestruct_created( value_bearing: bool, ): """ - Test running a block with as SELFDESTRUCT calls as possible for deployed contracts in + Test running a block with as many SELFDESTRUCTs as possible for deployed contracts in the same transaction. """ env = Environment() From 17e7df9595a3ecea2f325dc30dc20acd92fa5870 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Thu, 29 May 2025 16:27:11 -0300 Subject: [PATCH 08/12] improvements Signed-off-by: Ignacio Hagopian --- tests/zkevm/test_worst_stateful_opcodes.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/tests/zkevm/test_worst_stateful_opcodes.py b/tests/zkevm/test_worst_stateful_opcodes.py index c4f317fe05f..c901e1cb057 100644 --- a/tests/zkevm/test_worst_stateful_opcodes.py +++ b/tests/zkevm/test_worst_stateful_opcodes.py @@ -623,13 +623,11 @@ def test_worst_selfdestruct_created( env = Environment() # Template code that will be used to deploy a large number of contracts. - selfdestructable_contract_addr = pre.deploy_contract(code=Op.SELFDESTRUCT(Op.COINBASE)) - initcode = Op.EXTCODECOPY( - address=selfdestructable_contract_addr, - dest_offset=0, - offset=0, - size=Op.EXTCODESIZE(selfdestructable_contract_addr), - ) + Op.RETURN(0, Op.EXTCODESIZE(selfdestructable_contract_addr)) + initcode = ( + Op.MSTORE8(0, 0x41) # COINBASE + + Op.MSTORE8(1, 0xFF) # SELFDESTRUCT + + Op.RETURN(0, 2) + ) initcode_address = pre.deploy_contract(code=initcode) code = ( @@ -651,7 +649,7 @@ def test_worst_selfdestruct_created( ), # Stop before we run out of gas for the whole tx execution. # The value was found by trial-error rounded to the next 1000 multiple. - condition=Op.GT(Op.GAS, 42_000), + condition=Op.GT(Op.GAS, 24_000), ) + Op.SSTORE(0, 42) # Done for successful tx execution assertion below. ) From 6ff68a80c9c8f4a0c113d08ca7273e9ba9010e47 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Thu, 29 May 2025 17:06:12 -0300 Subject: [PATCH 09/12] improvement Signed-off-by: Ignacio Hagopian --- tests/zkevm/test_worst_stateful_opcodes.py | 25 +++++++--------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/tests/zkevm/test_worst_stateful_opcodes.py b/tests/zkevm/test_worst_stateful_opcodes.py index c901e1cb057..5011daaa571 100644 --- a/tests/zkevm/test_worst_stateful_opcodes.py +++ b/tests/zkevm/test_worst_stateful_opcodes.py @@ -622,34 +622,25 @@ def test_worst_selfdestruct_created( """ env = Environment() - # Template code that will be used to deploy a large number of contracts. - initcode = ( - Op.MSTORE8(0, 0x41) # COINBASE - + Op.MSTORE8(1, 0xFF) # SELFDESTRUCT - + Op.RETURN(0, 2) - ) - initcode_address = pre.deploy_contract(code=initcode) - + # SELFDESTRUCT(COINBASE) contract deployment + # code = Op.MSTORE8(0, 0x41) + Op.MSTORE8(1, 0xFF) + Op.RETURN(0, 2) + initcode = 0x604160005360FF60015360026000F3 + initcode_length = (initcode.bit_length() + 7) // 8 code = ( - Op.EXTCODECOPY( - address=initcode_address, - dest_offset=0, - offset=0, - size=Op.EXTCODESIZE(initcode_address), - ) + Op.MSTORE(0, initcode) + While( body=Op.POP( Op.CALL( address=Op.CREATE( value=1 if value_bearing else 0, - offset=0, - size=Op.EXTCODESIZE(initcode_address), + offset=32 - initcode_length, + size=initcode_length, ) ) ), # Stop before we run out of gas for the whole tx execution. # The value was found by trial-error rounded to the next 1000 multiple. - condition=Op.GT(Op.GAS, 24_000), + condition=Op.GT(Op.GAS, 25_000), ) + Op.SSTORE(0, 42) # Done for successful tx execution assertion below. ) From d54e5ed4df21f37ebaac19f11427a3bc6cd48397 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Thu, 29 May 2025 17:18:30 -0300 Subject: [PATCH 10/12] fix bug Signed-off-by: Ignacio Hagopian --- tests/zkevm/test_worst_stateful_opcodes.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/zkevm/test_worst_stateful_opcodes.py b/tests/zkevm/test_worst_stateful_opcodes.py index 5011daaa571..f85d389653a 100644 --- a/tests/zkevm/test_worst_stateful_opcodes.py +++ b/tests/zkevm/test_worst_stateful_opcodes.py @@ -492,6 +492,7 @@ def test_worst_selfdestruct_existing( """Test running a block with as many SELFDESTRUCTs as possible for existing contracts.""" env = Environment(gas_limit=100_000_000_000) attack_gas_limit = Environment().gas_limit + pre.fund_address(env.fee_recipient, 1) # Template code that will be used to deploy a large number of contracts. selfdestructable_contract_addr = pre.deploy_contract(code=Op.SELFDESTRUCT(Op.COINBASE)) @@ -621,6 +622,7 @@ def test_worst_selfdestruct_created( the same transaction. """ env = Environment() + pre.fund_address(env.fee_recipient, 1) # SELFDESTRUCT(COINBASE) contract deployment # code = Op.MSTORE8(0, 0x41) + Op.MSTORE8(1, 0xFF) + Op.RETURN(0, 2) @@ -640,12 +642,12 @@ def test_worst_selfdestruct_created( ), # Stop before we run out of gas for the whole tx execution. # The value was found by trial-error rounded to the next 1000 multiple. - condition=Op.GT(Op.GAS, 25_000), + condition=Op.GT(Op.GAS, 10_000), ) + Op.SSTORE(0, 42) # Done for successful tx execution assertion below. ) # The 0 storage slot is initialize to avoid creation costs in SSTORE above. - code_addr = pre.deploy_contract(code=code, storage={0: 1}) + code_addr = pre.deploy_contract(code=code, balance=100_000, storage={0: 1}) code_tx = Transaction( to=code_addr, gas_limit=env.gas_limit, From 364aad1e20262c85d8a0aa21e6f4fb3c83eb8cdf Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Thu, 29 May 2025 17:28:40 -0300 Subject: [PATCH 11/12] selfdestruct in initcode Signed-off-by: Ignacio Hagopian --- tests/zkevm/test_worst_stateful_opcodes.py | 49 ++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/tests/zkevm/test_worst_stateful_opcodes.py b/tests/zkevm/test_worst_stateful_opcodes.py index f85d389653a..e633a73c506 100644 --- a/tests/zkevm/test_worst_stateful_opcodes.py +++ b/tests/zkevm/test_worst_stateful_opcodes.py @@ -662,3 +662,52 @@ def test_worst_selfdestruct_created( post=post, tx=code_tx, ) + + +@pytest.mark.valid_from("Cancun") +@pytest.mark.parametrize("value_bearing", [True, False]) +def test_worst_selfdestruct_initcode( + state_test: StateTestFiller, + pre: Alloc, + value_bearing: bool, +): + """Test running a block with as many SELFDESTRUCTs as possible executed in initcode.""" + env = Environment() + pre.fund_address(env.fee_recipient, 1) + + # code = Op.SELFDESTRUCT(Op.COINBASE) + initcode = 0x41FF + code = ( + Op.MSTORE(0, initcode) + + While( + body=Op.POP( + Op.CALL( + address=Op.CREATE( + value=1 if value_bearing else 0, + offset=30, + size=2, + ) + ) + ), + # Stop before we run out of gas for the whole tx execution. + # The value was found by trial-error rounded to the next 1000 multiple. + condition=Op.GT(Op.GAS, 10_000), + ) + + Op.SSTORE(0, 42) # Done for successful tx execution assertion below. + ) + # The 0 storage slot is initialize to avoid creation costs in SSTORE above. + code_addr = pre.deploy_contract(code=code, balance=100_000, storage={0: 1}) + code_tx = Transaction( + to=code_addr, + gas_limit=env.gas_limit, + gas_price=10, + sender=pre.fund_eoa(), + ) + + post = {code_addr: Account(storage={0: 42})} # Check for successful execution. + state_test( + env=env, + pre=pre, + post=post, + tx=code_tx, + ) From 3e2cf756e73bd2f6c9c5503ac9d8cb0e25efebb7 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Wed, 4 Jun 2025 15:42:44 -0300 Subject: [PATCH 12/12] feedback Signed-off-by: Ignacio Hagopian --- tests/zkevm/test_worst_stateful_opcodes.py | 27 +++++++++------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/tests/zkevm/test_worst_stateful_opcodes.py b/tests/zkevm/test_worst_stateful_opcodes.py index e633a73c506..17c89d9f3dd 100644 --- a/tests/zkevm/test_worst_stateful_opcodes.py +++ b/tests/zkevm/test_worst_stateful_opcodes.py @@ -625,18 +625,16 @@ def test_worst_selfdestruct_created( pre.fund_address(env.fee_recipient, 1) # SELFDESTRUCT(COINBASE) contract deployment - # code = Op.MSTORE8(0, 0x41) + Op.MSTORE8(1, 0xFF) + Op.RETURN(0, 2) - initcode = 0x604160005360FF60015360026000F3 - initcode_length = (initcode.bit_length() + 7) // 8 + initcode = Op.MSTORE8(0, 0x41) + Op.MSTORE8(1, 0xFF) + Op.RETURN(0, 2) code = ( - Op.MSTORE(0, initcode) + Op.MSTORE(0, initcode.hex()) + While( body=Op.POP( Op.CALL( address=Op.CREATE( value=1 if value_bearing else 0, - offset=32 - initcode_length, - size=initcode_length, + offset=32 - len(initcode), + size=len(initcode), ) ) ), @@ -675,23 +673,20 @@ def test_worst_selfdestruct_initcode( env = Environment() pre.fund_address(env.fee_recipient, 1) - # code = Op.SELFDESTRUCT(Op.COINBASE) - initcode = 0x41FF + initcode = Op.SELFDESTRUCT(Op.COINBASE) code = ( - Op.MSTORE(0, initcode) + Op.MSTORE(0, initcode.hex()) + While( body=Op.POP( - Op.CALL( - address=Op.CREATE( - value=1 if value_bearing else 0, - offset=30, - size=2, - ) + Op.CREATE( + value=1 if value_bearing else 0, + offset=32 - len(initcode), + size=len(initcode), ) ), # Stop before we run out of gas for the whole tx execution. # The value was found by trial-error rounded to the next 1000 multiple. - condition=Op.GT(Op.GAS, 10_000), + condition=Op.GT(Op.GAS, 12_000), ) + Op.SSTORE(0, 42) # Done for successful tx execution assertion below. )