10
10
import pytest
11
11
12
12
from ethereum_test_forks import Fork
13
- from ethereum_test_tools import Alloc , Block , BlockchainTestFiller , Environment , Transaction
13
+ from ethereum_test_tools import (
14
+ Alloc ,
15
+ Block ,
16
+ BlockchainTestFiller ,
17
+ Bytecode ,
18
+ Environment ,
19
+ Transaction ,
20
+ )
14
21
from ethereum_test_tools .vm .opcode import Opcodes as Op
15
22
16
23
REFERENCE_SPEC_GIT_PATH = "TODO"
17
24
REFERENCE_SPEC_VERSION = "TODO"
18
25
19
26
MAX_CODE_SIZE = 24 * 1024
20
27
KECCAK_RATE = 136
28
+ ECRECOVER_GAS_COST = 3_000
21
29
22
30
23
31
@pytest .mark .zkevm
@@ -60,17 +68,53 @@ def test_worst_modexp(
60
68
gas_cost = math .floor ((mul_complexity * iter_complexity ) / 3 )
61
69
attack_block = Op .STATICCALL (gas_cost , 0x5 , 0 , 32 * 6 , 0 , 0 ) + Op .POP
62
70
63
- # The attack contract is: JUMPDEST + [attack_block]* + PUSH0 + JUMP
64
- jumpdest = Op .JUMPDEST
65
- jump_back = Op .JUMP (len (calldata ))
66
- max_iters_loop = (MAX_CODE_SIZE - len (calldata ) - len (jumpdest ) - len (jump_back )) // len (
67
- attack_block
71
+ code = code_loop_precompile_call (calldata , attack_block )
72
+ code_address = pre .deploy_contract (code = bytes (code ))
73
+
74
+ tx = Transaction (
75
+ to = code_address ,
76
+ gas_limit = gas_limit ,
77
+ gas_price = 10 ,
78
+ sender = pre .fund_eoa (),
79
+ data = [],
80
+ value = 0 ,
68
81
)
69
- code = calldata + jumpdest + sum ([attack_block ] * max_iters_loop ) + jump_back
70
- if len (code ) > MAX_CODE_SIZE :
71
- # Must never happen, but keep it as a sanity check.
72
- raise ValueError (f"Code size { len (code )} exceeds maximum code size { MAX_CODE_SIZE } " )
73
82
83
+ blockchain_test (
84
+ env = env ,
85
+ pre = pre ,
86
+ post = {},
87
+ blocks = [Block (txs = [tx ])],
88
+ )
89
+
90
+
91
+ @pytest .mark .zkevm
92
+ @pytest .mark .valid_from ("Cancun" )
93
+ @pytest .mark .parametrize (
94
+ "gas_limit" ,
95
+ [
96
+ 36_000_000 ,
97
+ ],
98
+ )
99
+ def test_worst_ecrecover (
100
+ blockchain_test : BlockchainTestFiller ,
101
+ pre : Alloc ,
102
+ fork : Fork ,
103
+ gas_limit : int ,
104
+ ):
105
+ """Test running a block with as many ECRECOVER calls as possible."""
106
+ env = Environment (gas_limit = gas_limit )
107
+
108
+ # Calldata
109
+ calldata = (
110
+ Op .MSTORE (0 * 32 , 0x38D18ACB67D25C8BB9942764B62F18E17054F66A817BD4295423ADF9ED98873E )
111
+ + Op .MSTORE (1 * 32 , 27 )
112
+ + Op .MSTORE (2 * 32 , 0x38D18ACB67D25C8BB9942764B62F18E17054F66A817BD4295423ADF9ED98873E )
113
+ + Op .MSTORE (3 * 32 , 0x789D1DD423D25F0772D2748D60F7E4B81BB14D086EBA8E8E8EFB6DCFF8A4AE02 )
114
+ )
115
+
116
+ attack_block = Op .STATICCALL (ECRECOVER_GAS_COST , 0x1 , 0 , 32 * 4 , 0 , 0 ) + Op .POP
117
+ code = code_loop_precompile_call (calldata , attack_block )
74
118
code_address = pre .deploy_contract (code = bytes (code ))
75
119
76
120
tx = Transaction (
@@ -88,3 +132,18 @@ def test_worst_modexp(
88
132
post = {},
89
133
blocks = [Block (txs = [tx ])],
90
134
)
135
+
136
+
137
+ def code_loop_precompile_call (calldata : Bytecode , attack_block : Bytecode ):
138
+ # The attack contract is: JUMPDEST + [attack_block]* + PUSH0 + JUMP
139
+ jumpdest = Op .JUMPDEST
140
+ jump_back = Op .JUMP (len (calldata ))
141
+ max_iters_loop = (MAX_CODE_SIZE - len (calldata ) - len (jumpdest ) - len (jump_back )) // len (
142
+ attack_block
143
+ )
144
+ code = calldata + jumpdest + sum ([attack_block ] * max_iters_loop ) + jump_back
145
+ if len (code ) > MAX_CODE_SIZE :
146
+ # Must never happen, but keep it as a sanity check.
147
+ raise ValueError (f"Code size { len (code )} exceeds maximum code size { MAX_CODE_SIZE } " )
148
+
149
+ return code
0 commit comments