|
10 | 10 | import pytest
|
11 | 11 |
|
12 | 12 | from ethereum_test_forks import Fork
|
| 13 | +from ethereum_test_specs import StateTestFiller |
13 | 14 | from ethereum_test_tools import (
|
14 | 15 | Account,
|
15 | 16 | Alloc,
|
@@ -204,3 +205,92 @@ def test_worst_bytecode_single_opcode(
|
204 | 205 | ],
|
205 | 206 | exclude_full_post_state_in_output=True,
|
206 | 207 | )
|
| 208 | + |
| 209 | + |
| 210 | +@pytest.mark.valid_from("Osaka") |
| 211 | +@pytest.mark.parametrize("initcode_size", [0xC000, 512 * 1024]) |
| 212 | +@pytest.mark.parametrize( |
| 213 | + "pattern", |
| 214 | + [ |
| 215 | + Op.STOP, |
| 216 | + Op.JUMPDEST, |
| 217 | + Op.PUSH1[Op.JUMPDEST], |
| 218 | + Op.PUSH2[Op.JUMPDEST + Op.JUMPDEST], |
| 219 | + Op.PUSH1[Op.JUMPDEST] + Op.JUMPDEST, |
| 220 | + Op.PUSH2[Op.JUMPDEST + Op.JUMPDEST] + Op.JUMPDEST, |
| 221 | + ], |
| 222 | + ids=lambda x: x.hex(), |
| 223 | +) |
| 224 | +def test_worst_initcode_jumpdest_analysis( |
| 225 | + state_test: StateTestFiller, |
| 226 | + fork: Fork, |
| 227 | + pre: Alloc, |
| 228 | + initcode_size: int, |
| 229 | + pattern: Bytecode, |
| 230 | +): |
| 231 | + """Test the jumpdest analysis performance of the initcode.""" |
| 232 | + # Expand the initcode pattern to the transaction data so it can be used in CALLDATACOPY |
| 233 | + # in the main contract. TODO: tune the tx_data_len param. |
| 234 | + tx_data_len = 1024 |
| 235 | + tx_data = pattern * (tx_data_len // len(pattern)) |
| 236 | + tx_data += (tx_data_len - len(tx_data)) * bytes(Op.JUMPDEST) |
| 237 | + assert len(tx_data) == tx_data_len |
| 238 | + assert initcode_size % len(tx_data) == 0 |
| 239 | + |
| 240 | + # Prepare the initcode in memory. |
| 241 | + code_prepare_initcode = sum( |
| 242 | + ( |
| 243 | + Op.CALLDATACOPY(dest_offset=i * len(tx_data), offset=0, size=Op.CALLDATASIZE) |
| 244 | + for i in range(initcode_size // len(tx_data)) |
| 245 | + ), |
| 246 | + Bytecode(), |
| 247 | + ) |
| 248 | + |
| 249 | + # At the start of the initcode execution, jump to the last opcode. |
| 250 | + # This forces EVM to do the full jumpdest analysis. |
| 251 | + initcode_prefix = Op.JUMP(initcode_size - 1) |
| 252 | + code_prepare_initcode += Op.MSTORE( |
| 253 | + 0, Op.PUSH32[initcode_prefix + Op.STOP * (32 - len(initcode_prefix))] |
| 254 | + ) |
| 255 | + |
| 256 | + # Make sure the last opcode in the initcode is JUMPDEST. |
| 257 | + code_prepare_initcode += Op.MSTORE(initcode_size - 32, Op.PUSH1[Op.JUMPDEST]) |
| 258 | + |
| 259 | + # We are using the Paris fork where there is no initcode size limit. |
| 260 | + # This allows us to test the increased initcode limits. |
| 261 | + # However, Paris doesn't have PUSH0 so simulate it with PUSH1. |
| 262 | + push0 = Op.PUSH0 if Op.PUSH0 in fork.valid_opcodes() else Op.PUSH1[0] |
| 263 | + |
| 264 | + code_invoke_create = ( |
| 265 | + Op.PUSH1[len(initcode_prefix)] |
| 266 | + + Op.MSTORE |
| 267 | + + Op.CREATE(value=push0, offset=push0, size=Op.MSIZE) |
| 268 | + ) |
| 269 | + |
| 270 | + initial_random = push0 |
| 271 | + code_prefix = code_prepare_initcode + initial_random |
| 272 | + code_loop_header = Op.JUMPDEST |
| 273 | + code_loop_footer = Op.JUMP(len(code_prefix)) |
| 274 | + code_loop_body_len = ( |
| 275 | + MAX_CONTRACT_SIZE - len(code_prefix) - len(code_loop_header) - len(code_loop_footer) |
| 276 | + ) |
| 277 | + |
| 278 | + code_loop_body = (code_loop_body_len // len(code_invoke_create)) * bytes(code_invoke_create) |
| 279 | + code = code_prefix + code_loop_header + code_loop_body + code_loop_footer |
| 280 | + assert (MAX_CONTRACT_SIZE - len(code_invoke_create)) < len(code) <= MAX_CONTRACT_SIZE |
| 281 | + |
| 282 | + env = Environment() |
| 283 | + |
| 284 | + tx = Transaction( |
| 285 | + to=pre.deploy_contract(code=code), |
| 286 | + data=tx_data, |
| 287 | + gas_limit=env.gas_limit, |
| 288 | + sender=pre.fund_eoa(), |
| 289 | + ) |
| 290 | + |
| 291 | + state_test( |
| 292 | + env=env, |
| 293 | + pre=pre, |
| 294 | + post={}, |
| 295 | + tx=tx, |
| 296 | + ) |
0 commit comments