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 (Alloc , Block , BlockchainTestFiller , Bytecode ,
14
+ Environment , Transaction )
14
15
from ethereum_test_tools .vm .opcode import Opcodes as Op
15
16
16
17
REFERENCE_SPEC_GIT_PATH = "TODO"
17
18
REFERENCE_SPEC_VERSION = "TODO"
18
19
19
20
MAX_CODE_SIZE = 24 * 1024
20
21
KECCAK_RATE = 136
22
+ ECRECOVER_GAS_COST = 3_000
21
23
22
24
23
25
@pytest .mark .valid_from ("Cancun" )
24
26
@pytest .mark .parametrize (
25
27
"gas_limit" ,
26
28
[
27
- 36_000_000 ,
29
+ Environment (). gas_limit ,
28
30
],
29
31
)
30
32
def test_worst_keccak (
@@ -144,24 +146,60 @@ def test_worst_modexp(
144
146
iter_complexity = exp .bit_length () - 1
145
147
gas_cost = math .floor ((mul_complexity * iter_complexity ) / 3 )
146
148
attack_block = Op .POP (Op .STATICCALL (gas_cost , 0x5 , 0 , 32 * 6 , 0 , 0 ))
149
+ code = code_loop_precompile_call (calldata , attack_block )
147
150
148
- # The attack contract is: JUMPDEST + [attack_block]* + PUSH0 + JUMP
149
- jumpdest = Op .JUMPDEST
150
- jump_back = Op .JUMP (len (calldata ))
151
- max_iters_loop = (MAX_CODE_SIZE - len (calldata ) - len (jumpdest ) - len (jump_back )) // len (
152
- attack_block
151
+ code_address = pre .deploy_contract (code = code )
152
+
153
+ tx = Transaction (
154
+ to = code_address ,
155
+ gas_limit = gas_limit ,
156
+ sender = pre .fund_eoa (),
153
157
)
154
- code = calldata + jumpdest + sum ([attack_block ] * max_iters_loop ) + jump_back
155
- if len (code ) > MAX_CODE_SIZE :
156
- # Must never happen, but keep it as a sanity check.
157
- raise ValueError (f"Code size { len (code )} exceeds maximum code size { MAX_CODE_SIZE } " )
158
158
159
- code_address = pre .deploy_contract (code = code )
159
+ blockchain_test (
160
+ env = env ,
161
+ pre = pre ,
162
+ post = {},
163
+ blocks = [Block (txs = [tx ])],
164
+ )
165
+
166
+
167
+ @pytest .mark .zkevm
168
+ @pytest .mark .valid_from ("Cancun" )
169
+ @pytest .mark .parametrize (
170
+ "gas_limit" ,
171
+ [
172
+ Environment ().gas_limit ,
173
+ ],
174
+ )
175
+ def test_worst_ecrecover (
176
+ blockchain_test : BlockchainTestFiller ,
177
+ pre : Alloc ,
178
+ fork : Fork ,
179
+ gas_limit : int ,
180
+ ):
181
+ """Test running a block with as many ECRECOVER calls as possible."""
182
+ env = Environment (gas_limit = gas_limit )
183
+
184
+ # Calldata
185
+ calldata = (
186
+ Op .MSTORE (0 * 32 , 0x38D18ACB67D25C8BB9942764B62F18E17054F66A817BD4295423ADF9ED98873E )
187
+ + Op .MSTORE (1 * 32 , 27 )
188
+ + Op .MSTORE (2 * 32 , 0x38D18ACB67D25C8BB9942764B62F18E17054F66A817BD4295423ADF9ED98873E )
189
+ + Op .MSTORE (3 * 32 , 0x789D1DD423D25F0772D2748D60F7E4B81BB14D086EBA8E8E8EFB6DCFF8A4AE02 )
190
+ )
191
+
192
+ attack_block = Op .STATICCALL (ECRECOVER_GAS_COST , 0x1 , 0 , 32 * 4 , 0 , 0 ) + Op .POP
193
+ code = code_loop_precompile_call (calldata , attack_block )
194
+ code_address = pre .deploy_contract (code = bytes (code ))
160
195
161
196
tx = Transaction (
162
197
to = code_address ,
163
198
gas_limit = gas_limit ,
199
+ gas_price = 10 ,
164
200
sender = pre .fund_eoa (),
201
+ data = [],
202
+ value = 0 ,
165
203
)
166
204
167
205
blockchain_test (
@@ -170,3 +208,19 @@ def test_worst_modexp(
170
208
post = {},
171
209
blocks = [Block (txs = [tx ])],
172
210
)
211
+
212
+
213
+ def code_loop_precompile_call (calldata : Bytecode , attack_block : Bytecode ):
214
+ """Create a code loop that calls a precompile with the given calldata."""
215
+ # The attack contract is: JUMPDEST + [attack_block]* + PUSH0 + JUMP
216
+ jumpdest = Op .JUMPDEST
217
+ jump_back = Op .JUMP (len (calldata ))
218
+ max_iters_loop = (MAX_CODE_SIZE - len (calldata ) - len (jumpdest ) - len (jump_back )) // len (
219
+ attack_block
220
+ )
221
+ code = calldata + jumpdest + sum ([attack_block ] * max_iters_loop ) + jump_back
222
+ if len (code ) > MAX_CODE_SIZE :
223
+ # Must never happen, but keep it as a sanity check.
224
+ raise ValueError (f"Code size { len (code )} exceeds maximum code size { MAX_CODE_SIZE } " )
225
+
226
+ return code
0 commit comments