Skip to content

Commit 13aadf8

Browse files
committed
Detect short-PUSH address refs and computed call targets
1 parent d5116eb commit 13aadf8

196 files changed

Lines changed: 3480 additions & 3011 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: 81 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -668,25 +668,37 @@ def _resolve_storage_values(
668668
def _find_address_refs_in_bytecode(
669669
code_bytes: bytes,
670670
known_addresses: set[Address],
671-
) -> set[Address]:
672-
"""Find which known addresses are referenced in bytecode via PUSH."""
673-
refs: set[Address] = set()
671+
) -> dict[Address, int]:
672+
"""Find which known addresses are referenced in bytecode via PUSH.
673+
674+
Return a mapping ``address -> minimum PUSH size observed``. A push size
675+
smaller than 20 means the baseline bytecode compiled the address down
676+
to fewer bytes (the original address had leading zero bytes); the
677+
referenced contract must stay hardcoded so that the compiler keeps
678+
emitting the same short PUSH opcode and the trace stays aligned.
679+
"""
680+
refs: dict[Address, int] = {}
674681
# Pre-compute int values for fast comparison
675682
known_ints = {int.from_bytes(a, "big") for a in known_addresses}
676683
i = 0
677684
while i < len(code_bytes):
678685
opcode = code_bytes[i]
679-
if 0x60 <= opcode <= 0x7F: # PUSHn
686+
if 0x60 <= opcode <= 0x7F: # PUSH1..PUSH32
680687
push_size = opcode - 0x5F
681688
push_data = code_bytes[i + 1 : i + 1 + push_size]
682-
if len(push_data) == push_size and push_size >= 4:
683-
# Addresses may be in PUSH19 or smaller if they have
684-
# leading zero bytes (compiler optimization). Use
685-
# min size 4 to avoid false positives on small values.
689+
if len(push_data) == push_size:
690+
# Addresses with leading zero bytes are compiled to
691+
# a PUSH smaller than PUSH20 (down to PUSH1 for 1-byte
692+
# addresses like 0x01). Match on int value against the
693+
# known-address set — false positives would require a
694+
# PUSHn that happens to push exactly a value already
695+
# registered as a pre-state address, which is rare in
696+
# practice.
686697
val = int.from_bytes(push_data, "big")
687698
if val in known_ints:
688699
addr = Address(val)
689-
refs.add(addr)
700+
if addr not in refs or push_size < refs[addr]:
701+
refs[addr] = push_size
690702
i += 1 + push_size
691703
else:
692704
i += 1
@@ -851,11 +863,27 @@ def _build_accounts(
851863
all_known_addrs.add(Address(int.from_bytes(addr_or_eoa, "big")))
852864

853865
deps: dict[Address, set[Address]] = {}
866+
# Addresses referenced via PUSH<20 anywhere: baseline bytecode
867+
# compiled them to a short PUSH because of leading zero bytes, so
868+
# they must stay hardcoded to keep the opcode sequence aligned.
869+
short_push_refs: set[Address] = set()
870+
# True when a short-PUSH ref targets an address that is not a
871+
# deployed contract in the pre-state (e.g. an external tag like
872+
# <contract:0x...dead> only referenced from bytecode). In that case
873+
# no contract can be pinned, so fall back to globally disabling
874+
# dynamic addresses for the whole test.
875+
short_push_unpinnable = False
854876
for addr, cb in code_bytes_map.items():
855877
refs = _find_address_refs_in_bytecode(cb, all_known_addrs)
856878
# Track deps on other contracts. Keep self-references — they
857879
# create self-loops detected as cycles, forcing hardcoded addr.
858-
deps[addr] = refs & known_contract_addrs
880+
deps[addr] = set(refs) & known_contract_addrs
881+
for ref_addr, push_size in refs.items():
882+
if push_size < 20:
883+
if ref_addr in known_contract_addrs:
884+
short_push_refs.add(ref_addr)
885+
else:
886+
short_push_unpinnable = True
859887

860888
contract_addrs_ordered = [
861889
acct.address
@@ -873,6 +901,9 @@ def _build_accounts(
873901
for acct in raw_accounts:
874902
if acct.oversized_code and acct.address is not None:
875903
non_dynamic_addrs.add(acct.address)
904+
# Short-PUSH refs: pin the referenced contract so its address keeps
905+
# the same leading-zero profile as baseline.
906+
non_dynamic_addrs.update(short_push_refs)
876907

877908
changed = True
878909
while changed:
@@ -936,9 +967,47 @@ def _build_accounts(
936967
if has_addr_arithmetic:
937968
break
938969

939-
if has_addr_arithmetic:
970+
# ------------------------------------------------------------------
971+
# Computed call targets: CALL/STATICCALL/DELEGATECALL/CALLCODE
972+
# receiving `address=` from arithmetic or memory reads. Tests that
973+
# do this usually rely on specific pre-state contract addresses
974+
# (dispatch-by-offset) and won't survive dynamic allocation.
975+
# ------------------------------------------------------------------
976+
_COMPUTED_ADDR_PATTERNS = (
977+
"address=Op.ADD(",
978+
"address=Op.SUB(",
979+
"address=Op.MUL(",
980+
"address=Op.DIV(",
981+
"address=Op.MOD(",
982+
"address=Op.MLOAD(",
983+
"address=Op.SLOAD(",
984+
"address=Op.CALLDATALOAD(",
985+
)
986+
has_computed_call_target = False
987+
for acct in raw_accounts:
988+
if not acct.code_expr:
989+
continue
990+
for pat in _COMPUTED_ADDR_PATTERNS:
991+
if pat in acct.code_expr:
992+
has_computed_call_target = True
993+
break
994+
if has_computed_call_target:
995+
break
996+
997+
if (
998+
has_addr_arithmetic
999+
or short_push_unpinnable
1000+
or has_computed_call_target
1001+
):
9401002
# Disable dynamic for all contracts and re-generate Op
941-
# expressions without addr_to_var
1003+
# expressions without addr_to_var. Triggers:
1004+
# * address arithmetic (Op.ADD(var, ...)) assumes sequential
1005+
# addresses that dynamic allocation can't preserve.
1006+
# * a short-PUSH ref that points outside the pre-state has no
1007+
# contract to pin, so the whole test must keep the filler's
1008+
# resolved addresses.
1009+
# * computed call targets (CALL with address=Op.ADD/MLOAD/
1010+
# CALLDATALOAD/...) dispatch by baseline-relative offsets.
9421011
for acct in raw_accounts:
9431012
if not acct.is_eoa:
9441013
acct.use_dynamic = False

tests/ported_static/stArgsZeroOneBalance/test_create_non_const.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
Environment,
1515
StateTestFiller,
1616
Transaction,
17+
compute_create_address,
1718
)
1819
from execution_testing.forks import Fork
1920
from execution_testing.specs.static_state.expect_section import (
@@ -97,7 +98,9 @@ def test_create_non_const(
9798
"network": [">=Cancun"],
9899
"result": {
99100
contract_0: Account(
100-
storage={0: 0xD2571607E241ECF590ED94B12D87C94BABE36DB6},
101+
storage={
102+
0: compute_create_address(address=contract_0, nonce=0),
103+
},
101104
),
102105
},
103106
},
@@ -106,7 +109,9 @@ def test_create_non_const(
106109
"network": [">=Cancun"],
107110
"result": {
108111
contract_0: Account(
109-
storage={0: 0xD2571607E241ECF590ED94B12D87C94BABE36DB6},
112+
storage={
113+
0: compute_create_address(address=contract_0, nonce=0),
114+
},
110115
),
111116
},
112117
},

tests/ported_static/stAttackTest/test_contract_creation_spam.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -621,6 +621,7 @@ def test_contract_creation_spam(
621621
+ Op.SSTORE,
622622
balance=0xDE0B6B3A7640000,
623623
nonce=0,
624+
address=Address(0x6A0A0FC761C612C340A0E98D33B37A75E5268472), # noqa: E501
624625
)
625626

626627
tx = Transaction(

tests/ported_static/stCallCodes/test_callcode_dynamic_code.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,9 @@ def test_callcode_dynamic_code(
264264
contract_1: Account(
265265
storage={
266266
0: 1,
267-
10: 0x13136008B64FF592819B2FA6D43F2835C452020E,
267+
10: compute_create_address(
268+
address=contract_1, nonce=0
269+
),
268270
11: 1,
269271
20: contract_1,
270272
21: sender,
@@ -298,9 +300,13 @@ def test_callcode_dynamic_code(
298300
0: 1,
299301
10: 0xBF1676BE6038AB86D66E00824C2E3577858040F6,
300302
11: 1,
301-
20: 0x4B86C4ED99B87F0F396BC0C76885453C343916ED,
303+
20: compute_create_address(
304+
address=contract_3, nonce=0
305+
),
302306
21: sender,
303-
22: 0x4B86C4ED99B87F0F396BC0C76885453C343916ED,
307+
22: compute_create_address(
308+
address=contract_3, nonce=0
309+
),
304310
},
305311
code=b"",
306312
balance=0,
@@ -317,9 +323,13 @@ def test_callcode_dynamic_code(
317323
0: 1,
318324
10: 0xF2D6BF688FAE45DA62AB2DD4F36945BC924CC61,
319325
11: 1,
320-
20: 0xA51C188504A60578914FCAE68F7A1F0DCBB856A9,
326+
20: compute_create_address(
327+
address=contract_4, nonce=0
328+
),
321329
21: sender,
322-
22: 0xA51C188504A60578914FCAE68F7A1F0DCBB856A9,
330+
22: compute_create_address(
331+
address=contract_4, nonce=0
332+
),
323333
},
324334
code=b"",
325335
balance=0,

tests/ported_static/stCallCodes/test_callcode_dynamic_code2_self_call.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,9 @@ def test_callcode_dynamic_code2_self_call(
168168
contract_2: Account(
169169
storage={
170170
0: 1,
171-
10: 0x13136008B64FF592819B2FA6D43F2835C452020E,
171+
10: compute_create_address(
172+
address=contract_2, nonce=0
173+
),
172174
11: 1,
173175
20: contract_2,
174176
21: sender,

tests/ported_static/stCallCodes/test_callcode_in_initcode_to_empty_contract.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ def test_callcode_in_initcode_to_empty_contract(
8686
)
8787
+ Op.STOP,
8888
nonce=0,
89+
address=Address(0x1100000000000000000000000000000000000000), # noqa: E501
8990
)
9091
# Source: lll
9192
# {(seq (CREATE 0 0 (lll (seq [[1]] (CALLCODE 500000 0x1000000000000000000000000000000000000001 1 0 0 0 0) [[2]] 1 ) 0) ) )} # noqa: E501
@@ -112,6 +113,7 @@ def test_callcode_in_initcode_to_empty_contract(
112113
+ Op.STOP,
113114
balance=10000,
114115
nonce=0,
116+
address=Address(0x1000000000000000000000000000000000000000), # noqa: E501
115117
)
116118
# Source: lll
117119
# {(seq (CREATE2 0 0 (lll (seq [[1]] (CALLCODE 500000 0x1000000000000000000000000000000000000001 1 0 0 0 0) [[2]] 1 ) 0) 0) )} # noqa: E501
@@ -139,6 +141,7 @@ def test_callcode_in_initcode_to_empty_contract(
139141
+ Op.STOP,
140142
balance=10000,
141143
nonce=0,
144+
address=Address(0x2000000000000000000000000000000000000000), # noqa: E501
142145
)
143146

144147
expect_entries_: list[dict] = [

tests/ported_static/stCallCodes/test_callcode_in_initcode_to_exis_contract_with_v_transfer_ne_money.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,12 +89,14 @@ def test_callcode_in_initcode_to_exis_contract_with_v_transfer_ne_money(
8989
)
9090
+ Op.STOP,
9191
nonce=0,
92+
address=Address(0x1100000000000000000000000000000000000000), # noqa: E501
9293
)
9394
# Source: lll
9495
# { (SSTORE 2 1) }
9596
contract_3 = pre.deploy_contract( # noqa: F841
9697
code=Op.SSTORE(key=0x2, value=0x1) + Op.STOP,
9798
nonce=0,
99+
address=Address(0x1000000000000000000000000000000000000001), # noqa: E501
98100
)
99101
# Source: lll
100102
# {(seq (CREATE2 0 0 (lll (seq [[1]] (CALLCODE 500000 0x1000000000000000000000000000000000000001 1 0 0 0 0)) 0) 0) )} # noqa: E501
@@ -121,6 +123,7 @@ def test_callcode_in_initcode_to_exis_contract_with_v_transfer_ne_money(
121123
+ Op.STOP,
122124
balance=10000,
123125
nonce=0,
126+
address=Address(0x2000000000000000000000000000000000000000), # noqa: E501
124127
)
125128
# Source: lll
126129
# {(seq (CREATE 0 0 (lll (seq [[1]] (CALLCODE 500000 0x1000000000000000000000000000000000000001 1 0 0 0 0)) 0) ) )} # noqa: E501
@@ -146,6 +149,7 @@ def test_callcode_in_initcode_to_exis_contract_with_v_transfer_ne_money(
146149
+ Op.STOP,
147150
balance=10000,
148151
nonce=0,
152+
address=Address(0x1000000000000000000000000000000000000000), # noqa: E501
149153
)
150154

151155
expect_entries_: list[dict] = [

tests/ported_static/stCallCodes/test_callcode_in_initcode_to_existing_contract.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,12 +89,14 @@ def test_callcode_in_initcode_to_existing_contract(
8989
)
9090
+ Op.STOP,
9191
nonce=0,
92+
address=Address(0x1100000000000000000000000000000000000000), # noqa: E501
9293
)
9394
# Source: lll
9495
# { (SSTORE 2 1) }
9596
contract_3 = pre.deploy_contract( # noqa: F841
9697
code=Op.SSTORE(key=0x2, value=0x1) + Op.STOP,
9798
nonce=0,
99+
address=Address(0x1000000000000000000000000000000000000001), # noqa: E501
98100
)
99101
# Source: lll
100102
# {(seq (CREATE2 1 0 (lll (seq [[1]] (CALLCODE 50000 0x1000000000000000000000000000000000000001 1 0 0 0 0)) 0) 0) )} # noqa: E501
@@ -122,6 +124,7 @@ def test_callcode_in_initcode_to_existing_contract(
122124
+ Op.STOP,
123125
balance=10000,
124126
nonce=0,
127+
address=Address(0x2000000000000000000000000000000000000000), # noqa: E501
125128
)
126129
# Source: lll
127130
# {(seq (CREATE 1 0 (lll (seq [[1]] (CALLCODE 50000 0x1000000000000000000000000000000000000001 1 0 0 0 0)) 0) ) )} # noqa: E501
@@ -148,6 +151,7 @@ def test_callcode_in_initcode_to_existing_contract(
148151
+ Op.STOP,
149152
balance=10000,
150153
nonce=0,
154+
address=Address(0x1000000000000000000000000000000000000000), # noqa: E501
151155
)
152156

153157
expect_entries_: list[dict] = [

tests/ported_static/stCallCreateCallCodeTest/test_create_fail_balance_too_low.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ def test_create_fail_balance_too_low(
8282
+ Op.STOP,
8383
balance=0xDE0B6B3A7640000,
8484
nonce=0,
85+
address=Address(0x095E7BAEA6A6C7C4C2DFEB977EFAC326AF552D87), # noqa: E501
8586
)
8687

8788
expect_entries_: list[dict] = [

tests/ported_static/stCallCreateCallCodeTest/test_create_init_fail_bad_jump_destination.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ def test_create_init_fail_bad_jump_destination(
5353
+ Op.STOP,
5454
balance=0xDE0B6B3A7640000,
5555
nonce=0,
56+
address=Address(0x866B704865D7D80842E1D7C2C1C8BF682A3A437C), # noqa: E501
5657
)
5758

5859
tx = Transaction(

0 commit comments

Comments
 (0)