@@ -71,6 +71,88 @@ class NoIntResolver(_yaml.SafeLoader): # type: ignore[no-redef]
7171 "stTimeConsuming" ,
7272}
7373
74+ # Ported tests (relative to ``tests/ported_static/``) that must keep
75+ # hardcoded addresses. These do not converge under ``exact-no-stack``
76+ # with dynamic addresses because of patterns the analyzer's heuristics
77+ # cannot cover:
78+ #
79+ # - EIP-2929 warm/cold gas accounting that depends on which addresses
80+ # are warm at call time (baseline-specific layout).
81+ # - CREATE2 collision semantics that depend on specific pre-state
82+ # addresses colliding with computed CREATE2 targets.
83+ # - Keccak-derived storage keys (Solidity mappings) baked into the
84+ # pre-state on specific sender / contract addresses.
85+ # - Structural transaction rejections sensitive to exact pre-state
86+ # collisions (empty-but-code, init-colliding-with-non-empty).
87+ # - Edge cases where dynamic allocation randomly picks an address
88+ # with a leading zero byte, changing PUSH size.
89+ # - Tag resolution mismatches (analyzer resolves <contract:0x…hint>
90+ # to a fresh deterministic address, but baseline used the hint).
91+ #
92+ # Treat this list as an allowlist of "we've accepted the divergence
93+ # here; don't try to make it dynamic". See trace-divergences.md for
94+ # the per-file rationale.
95+ FORCE_HARDCODED_TESTS : set [str ] = {
96+ # GAS_ONLY (29) — EIP-2929 warm/cold access cost differences
97+ "stCallCodes/test_callcode_dynamic_code.py" ,
98+ "stCallCodes/test_callcode_dynamic_code2_self_call.py" ,
99+ "stCallCreateCallCodeTest/test_call1024_pre_calls.py" ,
100+ "stCallCreateCallCodeTest/test_contract_creation_make_call_that_ask_more_gas_then_transaction_provided.py" , # noqa: E501
101+ "stCreate2/test_returndatacopy_following_create.py" ,
102+ "stCreateTest/test_create_collision_to_empty2.py" ,
103+ "stCreateTest/test_create_transaction_refund_ef.py" ,
104+ "stDelegatecallTestHomestead/test_call1024_pre_calls.py" ,
105+ "stDelegatecallTestHomestead/test_delegatecode_dynamic_code2_self_call.py" , # noqa: E501
106+ "stEIP150singleCodeGasPrices/test_eip2929_oog.py" ,
107+ "stEIP2930/test_manual_create.py" ,
108+ "stEIP3651_warmcoinbase/test_coinbase_warm_account_call_gas_fail.py" ,
109+ "stEIP3855_push0/test_push0.py" ,
110+ "stEIP3855_push0/test_push0_gas2.py" ,
111+ "stHomesteadSpecific/test_contract_creation_oo_gdont_leave_empty_contract_via_transaction.py" , # noqa: E501
112+ "stRandom/test_random_statetest282.py" ,
113+ "stRandom/test_random_statetest287.py" ,
114+ "stRandom/test_random_statetest384.py" ,
115+ "stRandom2/test_random_statetest401.py" ,
116+ "stRandom2/test_random_statetest508.py" ,
117+ "stRevertTest/test_cost_revert.py" ,
118+ "stRevertTest/test_revert_opcode_in_calls_on_non_empty_return_data.py" ,
119+ "stRevertTest/test_revert_opcode_multiple_sub_calls.py" ,
120+ "stRevertTest/test_revert_precompiled_touch_paris.py" ,
121+ "stStackTests/test_underflow_test.py" ,
122+ "stSystemOperationsTest/test_suicide_caller_addres_too_big_left.py" ,
123+ "vmBitwiseLogicOperation/test_byte.py" ,
124+ "vmIOandFlowOperations/test_jump_to_push.py" ,
125+ "vmIOandFlowOperations/test_jumpi.py" ,
126+ # EXECUTION_PATH_DIVERGED — remaining 8 (Categories B, C, D, E)
127+ "stCreate2/test_create2_suicide.py" ,
128+ "stCreate2/test_create2collision_code2.py" ,
129+ "stCreate2/test_create2collision_selfdestructed2.py" ,
130+ "stDelegatecallTestHomestead/test_delegatecall_in_initcode_to_existing_contract_oog.py" , # noqa: E501
131+ "stLogTests/test_log1_non_empty_mem.py" ,
132+ "stSystemOperationsTest/test_double_selfdestruct_touch_paris.py" ,
133+ "stWalletTest/test_multi_owned_change_requirement_to1.py" ,
134+ "stWalletTest/test_multi_owned_revoke_nothing.py" ,
135+ # EXECUTION_PATH_DIVERGED + GAS (5)
136+ "stCreate2/test_create2collision_code.py" ,
137+ "stCreate2/test_create2collision_nonce.py" ,
138+ "stCreate2/test_create2collision_selfdestructed.py" ,
139+ "stCreate2/test_create2collision_selfdestructed_revert.py" ,
140+ "stSStoreTest/test_sstore_gas_left.py" ,
141+ # OUTPUT_DIFFERS — remaining 2 (Categories F, H)
142+ "stEIP3651_warmcoinbase/test_coinbase_warm_account_call_gas.py" ,
143+ "stWalletTest/test_multi_owned_is_owner_true.py" ,
144+ # STRUCTURAL (2) — CREATE collision behaviour
145+ "stCreateTest/test_transaction_collision_to_empty_but_code.py" ,
146+ "stEIP3607/test_init_colliding_with_non_empty_account.py" ,
147+ }
148+
149+
150+ def _ported_rel_path (filler_path : Path ) -> str :
151+ """Return the ``<category>/test_<snake>.py`` path for a filler."""
152+ category = filler_path .parent .name if filler_path .parent .name else ""
153+ py_test_name = _filler_name_to_test_name (filler_path .stem )
154+ return f"{ category } /{ py_test_name } .py"
155+
74156
75157class _AnalyzerAlloc (Alloc ):
76158 """Alloc subclass that supports fund_eoa for analysis."""
@@ -163,8 +245,14 @@ def analyze(
163245 is_fork_dependent = not is_multi_case and len (model .expect ) > 1
164246
165247 # 9. Build accounts
248+ force_hardcoded = _ported_rel_path (filler_path ) in FORCE_HARDCODED_TESTS
166249 accounts = _build_accounts (
167- model , tags , addr_to_var , sender_tag_name , imports
250+ model ,
251+ tags ,
252+ addr_to_var ,
253+ sender_tag_name ,
254+ imports ,
255+ force_hardcoded = force_hardcoded ,
168256 )
169257
170258 # Track if sender is not in the pre-state (for fund_eoa handling).
@@ -173,8 +261,8 @@ def analyze(
173261 if sender_tag_name and not any (a .is_sender for a in accounts ):
174262 sender_ir .not_in_pre = True
175263
176- # Always use dynamic sender (pre.fund_eoa) for non- hardcoded tests
177- sender_ir .use_dynamic = True
264+ # Dynamic sender unless this test is on the hardcoded allowlist.
265+ sender_ir .use_dynamic = not force_hardcoded
178266
179267 # 10. Build environment
180268 environment_ir = _build_environment (model , tags , addr_to_var )
@@ -196,6 +284,13 @@ def analyze(
196284 if not acct .is_eoa :
197285 acct .use_dynamic = False
198286
287+ # 11c. Forced hardcoded (allowlist) also pins every EOA so coinbase
288+ # rebinds and fund_eoa-generated EOAs don't leak into an otherwise
289+ # hardcoded test.
290+ if force_hardcoded :
291+ for acct in accounts :
292+ acct .use_dynamic = False
293+
199294 # 12. Build transaction IR
200295 transaction_ir , access_list_entries = _build_transaction_ir (
201296 model ,
@@ -747,6 +842,8 @@ def _build_accounts(
747842 addr_to_var : dict [Address | EOA , str ],
748843 sender_tag_name : str | None ,
749844 imports : ImportsIR ,
845+ * ,
846+ force_hardcoded : bool = False ,
750847) -> list [AccountIR ]:
751848 """Build AccountIR list with dependency-ordered contracts."""
752849 # ------------------------------------------------------------------
@@ -998,6 +1095,7 @@ def _build_accounts(
9981095 has_addr_arithmetic
9991096 or short_push_unpinnable
10001097 or has_computed_call_target
1098+ or force_hardcoded
10011099 ):
10021100 # Disable dynamic for all contracts and re-generate Op
10031101 # expressions without addr_to_var. Triggers:
@@ -1008,6 +1106,9 @@ def _build_accounts(
10081106 # resolved addresses.
10091107 # * computed call targets (CALL with address=Op.ADD/MLOAD/
10101108 # CALLDATALOAD/...) dispatch by baseline-relative offsets.
1109+ # * the test is on the FORCE_HARDCODED_TESTS allowlist — we've
1110+ # accepted that it can't converge under exact-no-stack with
1111+ # dynamic addresses (see module docstring on that set).
10111112 for acct in raw_accounts :
10121113 if not acct .is_eoa :
10131114 acct .use_dynamic = False
0 commit comments