1818 Bytecode ,
1919 Environment ,
2020 Hash ,
21+ StateTestFiller ,
2122 Transaction ,
2223 While ,
2324 compute_create2_address ,
2425)
2526from ethereum_test_tools .vm .opcode import Opcodes as Op
27+ from pytest_plugins .execute .pre_alloc import MAX_BYTECODE_SIZE , MAX_INITCODE_SIZE
2628
2729REFERENCE_SPEC_GIT_PATH = "TODO"
2830REFERENCE_SPEC_VERSION = "TODO"
2931
30- MAX_CONTRACT_SIZE = 24 * 1024 # TODO: This could be a fork property
31-
3232XOR_TABLE_SIZE = 256
3333XOR_TABLE = [Hash (i ).sha256 () for i in range (XOR_TABLE_SIZE )]
3434
@@ -86,13 +86,13 @@ def test_worst_bytecode_single_opcode(
8686 )
8787 + Op .POP
8888 ),
89- condition = Op .LT (Op .MSIZE , MAX_CONTRACT_SIZE ),
89+ condition = Op .LT (Op .MSIZE , MAX_BYTECODE_SIZE ),
9090 )
9191 # Despite the whole contract has random bytecode, we make the first opcode be a STOP
9292 # so CALL-like attacks return as soon as possible, while EXTCODE(HASH|SIZE) work as
9393 # intended.
9494 + Op .MSTORE8 (0 , 0x00 )
95- + Op .RETURN (0 , MAX_CONTRACT_SIZE )
95+ + Op .RETURN (0 , MAX_BYTECODE_SIZE )
9696 )
9797 initcode_address = pre .deploy_contract (code = initcode )
9898
@@ -180,11 +180,11 @@ def test_worst_bytecode_single_opcode(
180180 )
181181 )
182182
183- if len (attack_code ) > MAX_CONTRACT_SIZE :
183+ if len (attack_code ) > MAX_BYTECODE_SIZE :
184184 # TODO: A workaround could be to split the opcode code into multiple contracts
185185 # and call them in sequence.
186186 raise ValueError (
187- f"Code size { len (attack_code )} exceeds maximum code size { MAX_CONTRACT_SIZE } "
187+ f"Code size { len (attack_code )} exceeds maximum code size { MAX_BYTECODE_SIZE } "
188188 )
189189 opcode_address = pre .deploy_contract (code = attack_code )
190190 opcode_tx = Transaction (
@@ -204,3 +204,94 @@ def test_worst_bytecode_single_opcode(
204204 ],
205205 exclude_full_post_state_in_output = True ,
206206 )
207+
208+
209+ @pytest .mark .valid_from ("Cancun" )
210+ @pytest .mark .parametrize ("initcode_size" , [MAX_INITCODE_SIZE ])
211+ @pytest .mark .parametrize (
212+ "pattern" ,
213+ [
214+ Op .STOP ,
215+ Op .JUMPDEST ,
216+ Op .PUSH1 [bytes (Op .JUMPDEST )],
217+ Op .PUSH2 [bytes (Op .JUMPDEST + Op .JUMPDEST )],
218+ Op .PUSH1 [bytes (Op .JUMPDEST )] + Op .JUMPDEST ,
219+ Op .PUSH2 [bytes (Op .JUMPDEST + Op .JUMPDEST )] + Op .JUMPDEST ,
220+ ],
221+ ids = lambda x : x .hex (),
222+ )
223+ def test_worst_initcode_jumpdest_analysis (
224+ state_test : StateTestFiller ,
225+ pre : Alloc ,
226+ initcode_size : int ,
227+ pattern : Bytecode ,
228+ ):
229+ """
230+ Test the jumpdest analysis performance of the initcode.
231+
232+ This benchmark places a very long initcode in the memory and then invoke CREATE instructions
233+ with this initcode up to the block gas limit. The initcode itself has minimal execution time
234+ but forces the EVM to perform the full jumpdest analysis on the parametrized byte pattern.
235+ The initicode is modified by mixing-in the returned create address between CREATE invocations
236+ to prevent caching.
237+ """
238+ # Expand the initcode pattern to the transaction data so it can be used in CALLDATACOPY
239+ # in the main contract. TODO: tune the tx_data_len param.
240+ tx_data_len = 1024
241+ tx_data = pattern * (tx_data_len // len (pattern ))
242+ tx_data += (tx_data_len - len (tx_data )) * bytes (Op .JUMPDEST )
243+ assert len (tx_data ) == tx_data_len
244+ assert initcode_size % len (tx_data ) == 0
245+
246+ # Prepare the initcode in memory.
247+ code_prepare_initcode = sum (
248+ (
249+ Op .CALLDATACOPY (dest_offset = i * len (tx_data ), offset = 0 , size = Op .CALLDATASIZE )
250+ for i in range (initcode_size // len (tx_data ))
251+ ),
252+ Bytecode (),
253+ )
254+
255+ # At the start of the initcode execution, jump to the last opcode.
256+ # This forces EVM to do the full jumpdest analysis.
257+ initcode_prefix = Op .JUMP (initcode_size - 1 )
258+ code_prepare_initcode += Op .MSTORE (
259+ 0 , Op .PUSH32 [bytes (initcode_prefix ).ljust (32 , bytes (Op .JUMPDEST ))]
260+ )
261+
262+ # Make sure the last opcode in the initcode is JUMPDEST.
263+ code_prepare_initcode += Op .MSTORE (initcode_size - 32 , Op .PUSH32 [bytes (Op .JUMPDEST ) * 32 ])
264+
265+ code_invoke_create = (
266+ Op .PUSH1 [len (initcode_prefix )]
267+ + Op .MSTORE
268+ + Op .CREATE (value = Op .PUSH0 , offset = Op .PUSH0 , size = Op .MSIZE )
269+ )
270+
271+ initial_random = Op .PUSH0
272+ code_prefix = code_prepare_initcode + initial_random
273+ code_loop_header = Op .JUMPDEST
274+ code_loop_footer = Op .JUMP (len (code_prefix ))
275+ code_loop_body_len = (
276+ MAX_BYTECODE_SIZE - len (code_prefix ) - len (code_loop_header ) - len (code_loop_footer )
277+ )
278+
279+ code_loop_body = (code_loop_body_len // len (code_invoke_create )) * bytes (code_invoke_create )
280+ code = code_prefix + code_loop_header + code_loop_body + code_loop_footer
281+ assert (MAX_BYTECODE_SIZE - len (code_invoke_create )) < len (code ) <= MAX_BYTECODE_SIZE
282+
283+ env = Environment ()
284+
285+ tx = Transaction (
286+ to = pre .deploy_contract (code = code ),
287+ data = tx_data ,
288+ gas_limit = env .gas_limit ,
289+ sender = pre .fund_eoa (),
290+ )
291+
292+ state_test (
293+ env = env ,
294+ pre = pre ,
295+ post = {},
296+ tx = tx ,
297+ )
0 commit comments