Skip to content

Commit e7043cc

Browse files
authored
feat(scripts): use dynamic addresses in ported static tests (#2695)
Convert all ported static tests under `tests/ported_static/` from hard-coded addresses (literal `Address(0x…)` and `EOA(key=…)`) to dynamic allocation (`pre.fund_eoa()`, `pre.deploy_contract()` without `address=`). Tests now behave like hand-written EEST tests, where the framework picks addresses at fill time. 68 of 2151 tests become runnable under the `execute` plugin against live networks (no `pre_alloc_mutable` marker). Achieved without changing test semantics — verified by 39,002 / 39,002 trace comparisons equivalent under `exact-no-stack` against pre-conversion baseline traces. ### What changed **`scripts/filler_to_python/analyzer.py`** — bulk of the work. New analyzer logic: - Topological sort of contracts by bytecode address references; substitute known addresses with variable names (`addr_to_var`) in emitted Op expressions. - Resolve CREATE-derived addresses in post-state storage and account references via `compute_create_address(...)` (depth 2, nonce 0–15). - Coinbase pinning: any account whose address equals `env.current_coinbase` is kept hardcoded so the env's `fee_recipient` and the pre-state entry agree. - Sender pinning when post-state has unresolvable addresses or address-like storage values (>2³² ints) — keeps CREATE addresses derivable from sender consistent with baseline. - Short-PUSH detection: contracts referenced via `PUSH<20` (i.e. addresses with leading-zero bytes) are pinned to baseline addresses. - Same for EOAs (precompile-touch case): EOAs referenced via short PUSH or hint-overridden into the precompile range (`<eoa:0x...01>`) are pinned literally at `0x01–0x10`. - Computed call targets (`address=Op.ADD/MLOAD/CALLDATALOAD/...`) and address arithmetic trigger a global hardcoded fallback. - `FORCE_HARDCODED_TESTS` allowlist (144 entries) for tests that fundamentally don't survive dynamic addresses (gas-precise tests, keccak-derived storage keys, cryptographic signatures in tx-data, CREATE2 collisions, structural pre-execution rejections). - `needs_mutable_pre` flag — emitted only when `assert_mutable()` would actually fire (sender or any account hardcoded, sender funded with non-default nonce, or any contract emitted with `nonce=0`). **`scripts/filler_to_python/ir.py`** — `use_dynamic` on `AccountIR`/`SenderIR`/`AccessListEntryIR`, `nonce` on `SenderIR`, `needs_mutable_pre` on `IntermediateTestModel`. **`scripts/filler_to_python/templates/state_test.py.j2`** — conditional rendering: `pre.fund_eoa(amount=...)` / `pre.deploy_contract(...)` for dynamic tests, `EOA(key=...)` / `pre[var] = Account(...)` / `pre.deploy_contract(..., address=Address(...))` for hardcoded ones. `@pytest.mark.pre_alloc_mutable` is only emitted when `needs_mutable_pre`. **`scripts/filler_to_python/render.py`** — `format_storage` handles string variable references; new context fields piped to the template. **`tests/ported_static/`** — all 2151 test files regenerated. 14 `@manually-enhanced` files preserved as-is via the existing skip mechanism. ### Verification | Check | Result | |---|---| | Regeneration | 2137 / 2151 OK + 14 skipped (manually-enhanced) | | `just static` | clean | | Full fill (CI mode) | 60,476 / 60,476 passed | | Full fill `--traces` | 60,476 / 60,476 passed (depends on PR #2709 in `forks/amsterdam`) | | `--verify-traces` `exact-no-stack` | **39,002 / 39,002 equivalent** | > **Note:** The trace report shows 78 additional "divergences" on 3 STRUCTURAL tests (CREATE-collision / EIP-3607). These are spurious and tracked in #2758 — an asymmetry between in-memory and disk-loaded traces in the verify-traces machinery (extends #2709). The tests themselves pass, fixtures are byte-identical to baseline, and the comparison artifact is independent of this PR.
1 parent 5a2cba1 commit e7043cc

2,037 files changed

Lines changed: 26845 additions & 33972 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

scripts/filler_to_python/analyzer.py

Lines changed: 765 additions & 42 deletions
Large diffs are not rendered by default.

scripts/filler_to_python/ir.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ class AccountIR:
3636
code_expr: str = ""
3737
storage: dict = field(default_factory=dict)
3838
oversized_code: bool = False
39+
use_dynamic: bool = True
3940

4041

4142
@dataclass
@@ -80,6 +81,7 @@ class AccessListEntryIR:
8081

8182
address: str = ""
8283
storage_keys: list = field(default_factory=list)
84+
use_dynamic: bool = False
8385

8486

8587
@dataclass
@@ -108,7 +110,9 @@ class SenderIR:
108110
is_tagged: bool = False
109111
key: int | None = None
110112
balance: int = 0
113+
nonce: int | None = None
111114
not_in_pre: bool = False
115+
use_dynamic: bool = True
112116

113117

114118
@dataclass
@@ -136,6 +140,7 @@ class IntermediateTestModel:
136140
is_slow: bool = False
137141
is_multi_case: bool = False
138142
is_fork_dependent: bool = False
143+
needs_mutable_pre: bool = False
139144
environment: EnvironmentIR = field(
140145
default_factory=lambda: EnvironmentIR(
141146
coinbase_var="coinbase", number=0, timestamp=0

scripts/filler_to_python/render.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,11 @@ def format_storage(d: dict) -> str:
4242
return "{}"
4343
items = []
4444
for k in sorted(d.keys()):
45-
items.append(f"{format_int(k)}: {format_int(d[k])}")
45+
v = d[k]
46+
if isinstance(v, str):
47+
items.append(f"{format_int(k)}: {v}")
48+
else:
49+
items.append(f"{format_int(k)}: {format_int(v)}")
4650
single = "{" + ", ".join(items) + "}"
4751
if len(single) <= 50:
4852
return single
@@ -271,6 +275,7 @@ def render_test(ir: IntermediateTestModel) -> str:
271275
"is_slow": ir.is_slow,
272276
"is_multi_case": ir.is_multi_case,
273277
"is_fork_dependent": ir.is_fork_dependent,
278+
"needs_mutable_pre": ir.needs_mutable_pre,
274279
"has_exceptions": has_exceptions,
275280
"env": ir.environment,
276281
"accounts": ir.accounts,

scripts/filler_to_python/templates/state_test.py.j2

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,9 @@ def _storage_with_any(base: dict, any_keys: list) -> Storage:
159159
{% if has_exceptions and not is_multi_case %}
160160
@pytest.mark.exception_test
161161
{% endif %}
162+
{% if needs_mutable_pre %}
162163
@pytest.mark.pre_alloc_mutable
164+
{% endif %}
163165
def {{ test_name }}(
164166
state_test: StateTestFiller,
165167
pre: Alloc,
@@ -176,7 +178,13 @@ def {{ test_name }}(
176178
{% for addr in address_constants %}
177179
{{ addr.var_name }} = Address({{ addr.hex }})
178180
{% endfor %}
179-
{% if sender.not_in_pre %}
181+
{% if sender.use_dynamic %}
182+
{% if sender.nonce %}
183+
sender = pre.fund_eoa(amount={{ sender.balance | format_int }}, nonce={{ sender.nonce }})
184+
{% else %}
185+
sender = pre.fund_eoa(amount={{ sender.balance | format_int }})
186+
{% endif %}
187+
{% elif sender.not_in_pre %}
180188
sender = pre.fund_eoa(amount=0)
181189
{% else %}
182190
sender = EOA(
@@ -205,15 +213,19 @@ def {{ test_name }}(
205213

206214
{# Pre-state account setup #}
207215
{% for account in accounts %}
208-
{% if account.is_sender %}
216+
{% if account.is_sender and sender.use_dynamic %}
217+
{# Sender balance already set via pre.fund_eoa() above #}
218+
{% elif account.is_sender %}
209219
pre[sender] = Account(balance={{ account.balance | format_int }}{{ ", nonce=%d" | format(account.nonce) if account.nonce }}{{ ", storage=%s" | format(account.storage | format_storage) if account.storage }}{{ ", code=%s" | format(account.code_expr | wrap_op_chain(indent=8)) if account.code_expr }})
220+
{% elif account.is_eoa and account.use_dynamic %}
221+
{{ account.var_name }} = pre.fund_eoa(amount={{ account.balance | format_int }}) # noqa: F841
210222
{% elif account.is_eoa %}
211223
pre[{{ account.var_name }}] = Account(balance={{ account.balance | format_int }}{{ ", nonce=%d" | format(account.nonce) if account.nonce }}{{ ", storage=%s" | format(account.storage | format_storage) if account.storage }}{{ ", code=%s" | format(account.code_expr | wrap_op_chain(indent=8)) if account.code_expr }})
212224
{% else %}
213225
{# Contract: source comment + deploy #}
214226
{{ account.source_comment }}
215227
{% if account.oversized_code %}
216-
{{ account.var_name }} = Address({{ account.address }})
228+
{{ account.var_name }} = Address({{ account.address }}) # oversized contract
217229
pre[{{ account.var_name }}] = Account(
218230
code={{ account.code_expr | wrap_op_chain(indent=8) }},
219231
{% if account.storage %}
@@ -238,7 +250,9 @@ def {{ test_name }}(
238250
{% if account.nonce is not none %}
239251
nonce={{ account.nonce }},
240252
{% endif %}
253+
{% if not account.use_dynamic %}
241254
address=Address({{ account.address }}), # noqa: E501
255+
{% endif %}
242256
)
243257
{% endif %}
244258
{% endif %}
@@ -287,7 +301,11 @@ def {{ test_name }}(
287301
{{ d_idx }}: [
288302
{% for al in al_entries %}
289303
AccessList(
304+
{% if al.use_dynamic %}
305+
address={{ al.address }},
306+
{% else %}
290307
address=Address({{ al.address }}),
308+
{% endif %}
291309
storage_keys=[
292310
{% for sk in al.storage_keys %}
293311
Hash("{{ sk }}"), # noqa: E501
@@ -357,7 +375,11 @@ def {{ test_name }}(
357375
access_list=[
358376
{% for al in tx.access_list %}
359377
AccessList(
378+
{% if al.use_dynamic %}
379+
address={{ al.address }},
380+
{% else %}
360381
address=Address({{ al.address }}),
382+
{% endif %}
361383
storage_keys=[
362384
{% for sk in al.storage_keys %}
363385
Hash(
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#!/bin/bash
2+
# Verify that filler_to_python with dynamic addresses produces
3+
# trace-equivalent tests. Assumes output/traces_baseline/ already
4+
# exists (generated once before any code changes).
5+
set -euo pipefail
6+
7+
export TMPDIR=./.tmp
8+
mkdir -p "$TMPDIR" output/traces_new
9+
10+
if [ ! -d "output/traces_baseline" ]; then
11+
echo "ERROR: output/traces_baseline/ not found."
12+
echo "Generate baseline first (before code changes):"
13+
echo " TMPDIR=./.tmp uv run fill tests/ported_static/ --evm-dump-dir output/traces_baseline -n 10 -m 'not slow'"
14+
exit 1
15+
fi
16+
17+
# Step 1: Run filler_to_python (overwrites tests/ported_static/)
18+
echo "=== Step 1: Running filler_to_python ==="
19+
uv run python -m scripts.filler_to_python \
20+
--fillers tests/static/static/state_tests/ \
21+
--output tests/ported_static/
22+
23+
# Step 2: Fill new tests + verify against baseline
24+
echo "=== Step 2: Filling new tests and verifying traces ==="
25+
uv run fill \
26+
tests/ported_static/ \
27+
--evm-dump-dir output/traces_new \
28+
--verify-traces output/traces_baseline \
29+
--verify-traces-comparator exact-no-stack \
30+
-n 10 \
31+
-m "not slow"
32+
33+
echo "=== Done. Check output above for trace mismatches ==="
34+
echo "=== Use 'git diff tests/ported_static/' to see code changes ==="

tests/ported_static/stArgsZeroOneBalance/test_add_non_const.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77

88
import pytest
99
from execution_testing import (
10-
EOA,
1110
Account,
1211
Address,
1312
Alloc,
@@ -58,9 +57,7 @@ def test_add_non_const(
5857
) -> None:
5958
"""Test_add_non_const."""
6059
coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
61-
sender = EOA(
62-
key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005
63-
)
60+
sender = pre.fund_eoa(amount=0xDE0B6B3A7640000)
6461

6562
env = Environment(
6663
fee_recipient=coinbase,
@@ -85,7 +82,6 @@ def test_add_non_const(
8582
nonce=0,
8683
address=Address(0xF1722FE346FA35E045DE07E47CF6AF9BAE8ADE0A), # noqa: E501
8784
)
88-
pre[sender] = Account(balance=0xDE0B6B3A7640000)
8985

9086
expect_entries_: list[dict] = [
9187
{

tests/ported_static/stArgsZeroOneBalance/test_addmod_non_const.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77

88
import pytest
99
from execution_testing import (
10-
EOA,
1110
Account,
1211
Address,
1312
Alloc,
@@ -58,9 +57,7 @@ def test_addmod_non_const(
5857
) -> None:
5958
"""Test_addmod_non_const."""
6059
coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
61-
sender = EOA(
62-
key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005
63-
)
60+
sender = pre.fund_eoa(amount=0xDE0B6B3A7640000)
6461

6562
env = Environment(
6663
fee_recipient=coinbase,
@@ -86,7 +83,6 @@ def test_addmod_non_const(
8683
nonce=0,
8784
address=Address(0x92D2FC80312ACD8C37857696D2224AF18CE6F966), # noqa: E501
8885
)
89-
pre[sender] = Account(balance=0xDE0B6B3A7640000)
9086

9187
expect_entries_: list[dict] = [
9288
{

tests/ported_static/stArgsZeroOneBalance/test_and_non_const.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77

88
import pytest
99
from execution_testing import (
10-
EOA,
1110
Account,
1211
Address,
1312
Alloc,
@@ -58,9 +57,7 @@ def test_and_non_const(
5857
) -> None:
5958
"""Test_and_non_const."""
6059
coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
61-
sender = EOA(
62-
key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005
63-
)
60+
sender = pre.fund_eoa(amount=0xDE0B6B3A7640000)
6461

6562
env = Environment(
6663
fee_recipient=coinbase,
@@ -85,7 +82,6 @@ def test_and_non_const(
8582
nonce=0,
8683
address=Address(0x4C26357E0D164B702BCEB18690FC742EE1D36913), # noqa: E501
8784
)
88-
pre[sender] = Account(balance=0xDE0B6B3A7640000)
8985

9086
expect_entries_: list[dict] = [
9187
{

tests/ported_static/stArgsZeroOneBalance/test_balance_non_const.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77

88
import pytest
99
from execution_testing import (
10-
EOA,
1110
Account,
1211
Address,
1312
Alloc,
@@ -58,9 +57,7 @@ def test_balance_non_const(
5857
) -> None:
5958
"""Test_balance_non_const."""
6059
coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
61-
sender = EOA(
62-
key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005
63-
)
60+
sender = pre.fund_eoa(amount=0xDE0B6B3A7640000)
6461

6562
env = Environment(
6663
fee_recipient=coinbase,
@@ -86,7 +83,6 @@ def test_balance_non_const(
8683
nonce=0,
8784
address=Address(0xEE6A324B2ECE5EACDF881ABFDCC62B5361D0FB50), # noqa: E501
8885
)
89-
pre[sender] = Account(balance=0xDE0B6B3A7640000)
9086

9187
expect_entries_: list[dict] = [
9288
{

tests/ported_static/stArgsZeroOneBalance/test_byte_non_const.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77

88
import pytest
99
from execution_testing import (
10-
EOA,
1110
Account,
1211
Address,
1312
Alloc,
@@ -58,9 +57,7 @@ def test_byte_non_const(
5857
) -> None:
5958
"""Test_byte_non_const."""
6059
coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA)
61-
sender = EOA(
62-
key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005
63-
)
60+
sender = pre.fund_eoa(amount=0xDE0B6B3A7640000)
6461

6562
env = Environment(
6663
fee_recipient=coinbase,
@@ -85,7 +82,6 @@ def test_byte_non_const(
8582
nonce=0,
8683
address=Address(0x86D606901085BA78C64D2E0B16831E6AFD89DE2D), # noqa: E501
8784
)
88-
pre[sender] = Account(balance=0xDE0B6B3A7640000)
8985

9086
expect_entries_: list[dict] = [
9187
{

0 commit comments

Comments
 (0)