62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166 | @EIPChecklist.GasCostChanges.Test.ForkTransition.Before()
@EIPChecklist.GasCostChanges.Test.ForkTransition.After()
@pytest.mark.parametrize(
"addresses,keys_per_address",
[
pytest.param(1, 0, id="single_address_no_keys"),
pytest.param(1, 2, id="single_address_two_keys"),
pytest.param(2, 3, id="two_addresses_three_keys_each"),
],
)
def test_access_list_intrinsic_across_amsterdam_transition(
blockchain_test: BlockchainTestFiller,
pre: Alloc,
fork: TransitionFork,
addresses: int,
keys_per_address: int,
) -> None:
"""
Pin the access list intrinsic change across the Amsterdam boundary.
The same access-list transaction shape is sent in a pre-fork block
(flat base plus the EIP-2930 per-entry charges, no data cost) and a
post-fork block (decomposed base, repriced entries, plus the
EIP-7981 byte surcharge). Each block uses a distinct sender so its
post-tx balance pins the fork-appropriate intrinsic; the recipient
is an existing EOA, so no EVM bytecode runs and `gas_used` equals
the intrinsic exactly.
The per-fork intrinsic returned by the calculator is also checked
against a hand-derived per-EIP decomposition, so a calculator
regression fails here with a clear message rather than only as a
downstream balance mismatch.
"""
gas_price = 1_000_000_000
access_list = access_list_shape(addresses, keys_per_address)
total_keys = addresses * keys_per_address
pre_fork = fork.fork_at(timestamp=PRE_FORK_TIMESTAMP)
post_fork = fork.fork_at(timestamp=POST_FORK_TIMESTAMP)
pre_costs = pre_fork.gas_costs()
post_costs = post_fork.gas_costs()
# Pre-fork: flat base plus the EIP-2930 per-entry charges; access
# list bytes carry no data cost.
expected_pre = (
pre_costs.TX_BASE
+ addresses * pre_costs.TX_ACCESS_LIST_ADDRESS
+ total_keys * pre_costs.TX_ACCESS_LIST_STORAGE_KEY
)
# Post-fork: EIP-2780 decomposed base and recipient charge, EIP-8038
# repriced entry charges, and the EIP-7981 byte surcharge.
surcharge = (
calculate_access_list_floor_tokens(access_list)
* post_costs.TX_DATA_TOKEN_FLOOR
)
expected_post = (
post_costs.TX_BASE
+ post_costs.COLD_ACCOUNT_ACCESS
+ addresses * post_costs.TX_ACCESS_LIST_ADDRESS
+ total_keys * post_costs.TX_ACCESS_LIST_STORAGE_KEY
+ surcharge
)
timestamps = [PRE_FORK_TIMESTAMP, POST_FORK_TIMESTAMP]
expected_intrinsics = [expected_pre, expected_post]
blocks = []
post: dict[Address, Account] = {}
for timestamp, expected_intrinsic in zip(
timestamps, expected_intrinsics, strict=True
):
sub_fork = fork.fork_at(timestamp=timestamp)
intrinsic_gas = sub_fork.transaction_intrinsic_cost_calculator()(
access_list=access_list,
return_cost_deducted_prior_execution=True,
)
assert intrinsic_gas == expected_intrinsic, (
f"intrinsic at timestamp {timestamp} ({sub_fork}) is "
f"{intrinsic_gas}, expected {expected_intrinsic}"
)
# The intrinsic side must bind so gas_used equals the intrinsic.
floor_gas = sub_fork.transaction_data_floor_cost_calculator()(
data=b"", access_list=access_list
)
assert floor_gas <= intrinsic_gas
sender_initial_balance = 10**18
sender = pre.fund_eoa(sender_initial_balance)
target = pre.fund_eoa(amount=0)
tx = Transaction(
sender=sender,
to=target,
gas_limit=intrinsic_gas,
gas_price=gas_price,
access_list=access_list,
)
blocks.append(Block(timestamp=timestamp, txs=[tx]))
post[sender] = Account(
nonce=1,
balance=sender_initial_balance - intrinsic_gas * gas_price,
)
blockchain_test(pre=pre, blocks=blocks, post=post)
|