From 41d4d3536865fbb52080ac0f561689b50dafbf44 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Sun, 27 Apr 2025 16:18:10 -0300 Subject: [PATCH 01/10] add modexp tests Signed-off-by: Ignacio Hagopian --- tests/zkevm/test_worst_compute.py | 77 +++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/tests/zkevm/test_worst_compute.py b/tests/zkevm/test_worst_compute.py index c3c0c5e5b9c..56b451f8a50 100644 --- a/tests/zkevm/test_worst_compute.py +++ b/tests/zkevm/test_worst_compute.py @@ -103,3 +103,80 @@ def test_worst_keccak( post={}, blocks=[Block(txs=[tx])], ) + + +@pytest.mark.zkevm +@pytest.mark.valid_from("Cancun") +@pytest.mark.parametrize( + "gas_limit", + [ + 36_000_000, + 60_000_000, + 100_000_000, + 300_000_000, + ], +) +@pytest.mark.parametrize( + "base_mod_length, exp_length", + [ + (32, 32), + ], +) +def test_worst_modexp( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, + gas_limit: int, + base_mod_length: int, + exp_length: int, +): + """Test running a block with as many MODEXP calls as possible.""" + env = Environment(gas_limit=gas_limit) + + base = 2 ** (8 * base_mod_length) - 1 + mod = 2 ** (8 * base_mod_length) - 1 + exp = 2 ** (8 * exp_length) - 1 + + # EIP-7883 (TODO: generalize) + mul_complexity = math.ceil(base_mod_length / 8) ** 2 + iter_complexity = exp.bit_length() - 1 + gas_cost = math.floor((mul_complexity * iter_complexity) / 3) + + mem_prep = ( + Op.MSTORE(0 * 32, 32) + + Op.MSTORE(1 * 32, 32) + + Op.MSTORE(2 * 32, 32) + + Op.MSTORE(3 * 32, base) + + Op.MSTORE(4 * 32, exp) + + Op.MSTORE(5 * 32, mod) + ) + + attack_block = Op.STATICCALL(gas_cost, 0x5, 0, 32 * 6, 0, 0) + Op.POP + + jumpdest = Op.JUMPDEST + jump_back = Op.JUMP(len(mem_prep)) + max_iters_loop = (MAX_CODE_SIZE - len(mem_prep) - len(jumpdest) - len(jump_back)) // len( + attack_block + ) + code = mem_prep + jumpdest + sum([attack_block] * max_iters_loop) + jump_back + 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}") + + code_address = pre.deploy_contract(code=bytes(code)) + + tx = Transaction( + to=code_address, + gas_limit=gas_limit, + gas_price=10, + sender=pre.fund_eoa(), + data=[], + value=0, + ) + + blockchain_test( + env=env, + pre=pre, + post={}, + blocks=[Block(txs=[tx])], + ) From 3ee68fcd847d796dd552a10c699e7b201665fe3a Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Thu, 1 May 2025 15:31:10 -0300 Subject: [PATCH 02/10] leave only 36M gas limit Signed-off-by: Ignacio Hagopian --- tests/zkevm/test_worst_compute.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/zkevm/test_worst_compute.py b/tests/zkevm/test_worst_compute.py index 56b451f8a50..6dbf527eb0c 100644 --- a/tests/zkevm/test_worst_compute.py +++ b/tests/zkevm/test_worst_compute.py @@ -10,7 +10,8 @@ import pytest from ethereum_test_forks import Fork -from ethereum_test_tools import Alloc, Block, BlockchainTestFiller, Environment, Transaction +from ethereum_test_tools import (Alloc, Block, BlockchainTestFiller, + Environment, Transaction) from ethereum_test_tools.vm.opcode import Opcodes as Op REFERENCE_SPEC_GIT_PATH = "TODO" @@ -111,9 +112,6 @@ def test_worst_keccak( "gas_limit", [ 36_000_000, - 60_000_000, - 100_000_000, - 300_000_000, ], ) @pytest.mark.parametrize( From ac4a22df2088dac5231c840d2607230f00753b36 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Thu, 1 May 2025 15:44:35 -0300 Subject: [PATCH 03/10] adjustments Signed-off-by: Ignacio Hagopian --- tests/zkevm/test_worst_compute.py | 33 ++++++++++++++----------------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/tests/zkevm/test_worst_compute.py b/tests/zkevm/test_worst_compute.py index 6dbf527eb0c..bc9d074946b 100644 --- a/tests/zkevm/test_worst_compute.py +++ b/tests/zkevm/test_worst_compute.py @@ -114,33 +114,25 @@ def test_worst_keccak( 36_000_000, ], ) -@pytest.mark.parametrize( - "base_mod_length, exp_length", - [ - (32, 32), - ], -) def test_worst_modexp( blockchain_test: BlockchainTestFiller, pre: Alloc, fork: Fork, gas_limit: int, - base_mod_length: int, - exp_length: int, + ): """Test running a block with as many MODEXP calls as possible.""" env = Environment(gas_limit=gas_limit) + base_mod_length = 32 + exp_length = 32 + base = 2 ** (8 * base_mod_length) - 1 - mod = 2 ** (8 * base_mod_length) - 1 + mod = 2 ** (8 * base_mod_length) - 2 # Prevnts base == mod exp = 2 ** (8 * exp_length) - 1 - # EIP-7883 (TODO: generalize) - mul_complexity = math.ceil(base_mod_length / 8) ** 2 - iter_complexity = exp.bit_length() - 1 - gas_cost = math.floor((mul_complexity * iter_complexity) / 3) - - mem_prep = ( + # MODEXP calldata + calldata = ( Op.MSTORE(0 * 32, 32) + Op.MSTORE(1 * 32, 32) + Op.MSTORE(2 * 32, 32) @@ -149,14 +141,19 @@ def test_worst_modexp( + Op.MSTORE(5 * 32, mod) ) + # EIP-2565 + mul_complexity = math.ceil(base_mod_length / 8) ** 2 + iter_complexity = exp.bit_length() - 1 + gas_cost = math.floor((mul_complexity * iter_complexity) / 3) attack_block = Op.STATICCALL(gas_cost, 0x5, 0, 32 * 6, 0, 0) + Op.POP + # The attack contract is: JUMPDEST + [attack_block]* + PUSH0 + JUMP jumpdest = Op.JUMPDEST - jump_back = Op.JUMP(len(mem_prep)) - max_iters_loop = (MAX_CODE_SIZE - len(mem_prep) - len(jumpdest) - len(jump_back)) // len( + jump_back = Op.JUMP(len(calldata)) + max_iters_loop = (MAX_CODE_SIZE - len(calldata) - len(jumpdest) - len(jump_back)) // len( attack_block ) - code = mem_prep + jumpdest + sum([attack_block] * max_iters_loop) + jump_back + code = calldata + jumpdest + sum([attack_block] * max_iters_loop) + jump_back 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}") From d8d2965415246ad9e9513c53af190218fd9a5566 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Thu, 1 May 2025 15:45:33 -0300 Subject: [PATCH 04/10] lints Signed-off-by: Ignacio Hagopian --- tests/zkevm/test_worst_compute.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/zkevm/test_worst_compute.py b/tests/zkevm/test_worst_compute.py index bc9d074946b..8db259c605e 100644 --- a/tests/zkevm/test_worst_compute.py +++ b/tests/zkevm/test_worst_compute.py @@ -10,8 +10,7 @@ import pytest from ethereum_test_forks import Fork -from ethereum_test_tools import (Alloc, Block, BlockchainTestFiller, - Environment, Transaction) +from ethereum_test_tools import Alloc, Block, BlockchainTestFiller, Environment, Transaction from ethereum_test_tools.vm.opcode import Opcodes as Op REFERENCE_SPEC_GIT_PATH = "TODO" @@ -119,7 +118,6 @@ def test_worst_modexp( pre: Alloc, fork: Fork, gas_limit: int, - ): """Test running a block with as many MODEXP calls as possible.""" env = Environment(gas_limit=gas_limit) @@ -128,7 +126,7 @@ def test_worst_modexp( exp_length = 32 base = 2 ** (8 * base_mod_length) - 1 - mod = 2 ** (8 * base_mod_length) - 2 # Prevnts base == mod + mod = 2 ** (8 * base_mod_length) - 2 # Prevnts base == mod exp = 2 ** (8 * exp_length) - 1 # MODEXP calldata From 1bfe9cbb88a774617321d21dcdf806fdd75bdb8d Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Thu, 1 May 2025 16:01:12 -0300 Subject: [PATCH 05/10] lints and cleanup Signed-off-by: Ignacio Hagopian --- tests/zkevm/test_worst_compute.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/zkevm/test_worst_compute.py b/tests/zkevm/test_worst_compute.py index 8db259c605e..1c9ec10fd82 100644 --- a/tests/zkevm/test_worst_compute.py +++ b/tests/zkevm/test_worst_compute.py @@ -10,7 +10,8 @@ import pytest from ethereum_test_forks import Fork -from ethereum_test_tools import Alloc, Block, BlockchainTestFiller, Environment, Transaction +from ethereum_test_tools import (Alloc, Block, BlockchainTestFiller, + Environment, Transaction) from ethereum_test_tools.vm.opcode import Opcodes as Op REFERENCE_SPEC_GIT_PATH = "TODO" @@ -126,7 +127,7 @@ def test_worst_modexp( exp_length = 32 base = 2 ** (8 * base_mod_length) - 1 - mod = 2 ** (8 * base_mod_length) - 2 # Prevnts base == mod + mod = 2 ** (8 * base_mod_length) - 2 # Prevents base == mod exp = 2 ** (8 * exp_length) - 1 # MODEXP calldata From b9ceb3514baab54f504a3fa3dae385e9882020e8 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Fri, 2 May 2025 16:08:48 -0300 Subject: [PATCH 06/10] lints Signed-off-by: Ignacio Hagopian --- tests/zkevm/test_worst_compute.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/zkevm/test_worst_compute.py b/tests/zkevm/test_worst_compute.py index 1c9ec10fd82..f53dd1992c6 100644 --- a/tests/zkevm/test_worst_compute.py +++ b/tests/zkevm/test_worst_compute.py @@ -10,8 +10,7 @@ import pytest from ethereum_test_forks import Fork -from ethereum_test_tools import (Alloc, Block, BlockchainTestFiller, - Environment, Transaction) +from ethereum_test_tools import Alloc, Block, BlockchainTestFiller, Environment, Transaction from ethereum_test_tools.vm.opcode import Opcodes as Op REFERENCE_SPEC_GIT_PATH = "TODO" From df8f2adbdcce1581938ea75a4e773db49c2da7a9 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Fri, 2 May 2025 18:12:58 -0300 Subject: [PATCH 07/10] Update tests/zkevm/test_worst_compute.py Co-authored-by: Mario Vega --- tests/zkevm/test_worst_compute.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/zkevm/test_worst_compute.py b/tests/zkevm/test_worst_compute.py index f53dd1992c6..36a61bdc358 100644 --- a/tests/zkevm/test_worst_compute.py +++ b/tests/zkevm/test_worst_compute.py @@ -143,7 +143,7 @@ def test_worst_modexp( mul_complexity = math.ceil(base_mod_length / 8) ** 2 iter_complexity = exp.bit_length() - 1 gas_cost = math.floor((mul_complexity * iter_complexity) / 3) - attack_block = Op.STATICCALL(gas_cost, 0x5, 0, 32 * 6, 0, 0) + Op.POP + attack_block = Op.POP(Op.STATICCALL(gas_cost, 0x5, 0, 32 * 6, 0, 0)) # The attack contract is: JUMPDEST + [attack_block]* + PUSH0 + JUMP jumpdest = Op.JUMPDEST From a4794a2de941b3da00cea9d72f39ba87c85a754e Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Fri, 2 May 2025 18:13:10 -0300 Subject: [PATCH 08/10] Update tests/zkevm/test_worst_compute.py Co-authored-by: Mario Vega --- tests/zkevm/test_worst_compute.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/zkevm/test_worst_compute.py b/tests/zkevm/test_worst_compute.py index 36a61bdc358..10a77c4f51a 100644 --- a/tests/zkevm/test_worst_compute.py +++ b/tests/zkevm/test_worst_compute.py @@ -131,9 +131,9 @@ def test_worst_modexp( # MODEXP calldata calldata = ( - Op.MSTORE(0 * 32, 32) - + Op.MSTORE(1 * 32, 32) - + Op.MSTORE(2 * 32, 32) + Op.MSTORE(0 * 32, base_mod_length) + + Op.MSTORE(1 * 32, exp_length) + + Op.MSTORE(2 * 32, base_mod_length) + Op.MSTORE(3 * 32, base) + Op.MSTORE(4 * 32, exp) + Op.MSTORE(5 * 32, mod) From 66f10dcbfd7a13f75d5ffb8ee55a41a79982fd8c Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Fri, 2 May 2025 18:13:22 -0300 Subject: [PATCH 09/10] Update tests/zkevm/test_worst_compute.py Co-authored-by: Mario Vega --- tests/zkevm/test_worst_compute.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/zkevm/test_worst_compute.py b/tests/zkevm/test_worst_compute.py index 10a77c4f51a..9a166a67e00 100644 --- a/tests/zkevm/test_worst_compute.py +++ b/tests/zkevm/test_worst_compute.py @@ -161,10 +161,7 @@ def test_worst_modexp( tx = Transaction( to=code_address, gas_limit=gas_limit, - gas_price=10, sender=pre.fund_eoa(), - data=[], - value=0, ) blockchain_test( From 99f249d957d3a2e91548032fa8a69fc16ee4452f Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Mon, 5 May 2025 19:27:00 -0300 Subject: [PATCH 10/10] feedback Signed-off-by: Ignacio Hagopian --- tests/zkevm/test_worst_compute.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/zkevm/test_worst_compute.py b/tests/zkevm/test_worst_compute.py index 9a166a67e00..107cf428124 100644 --- a/tests/zkevm/test_worst_compute.py +++ b/tests/zkevm/test_worst_compute.py @@ -110,7 +110,7 @@ def test_worst_keccak( @pytest.mark.parametrize( "gas_limit", [ - 36_000_000, + Environment().gas_limit, ], ) def test_worst_modexp( @@ -156,7 +156,7 @@ def test_worst_modexp( # Must never happen, but keep it as a sanity check. raise ValueError(f"Code size {len(code)} exceeds maximum code size {MAX_CODE_SIZE}") - code_address = pre.deploy_contract(code=bytes(code)) + code_address = pre.deploy_contract(code=code) tx = Transaction( to=code_address,