Execute EXTCODESIZE benchmark against pre-deployed contracts.
Uses a gas-based loop exit strategy:
1. Attack contract reads/writes salt from storage slot 0
2. Loop exits when gas < 50K, saves salt for next TX
3. Each TX automatically resumes from where previous left off
Post-state verifies that the attack contract's slot 1 contains the
expected bytecode size (last EXTCODESIZE result).
Source code in tests/benchmark/stateful/bloatnet/test_extcodesize_bytecode_sizes.py
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243 | @pytest.mark.parametrize(
"bytecode_size_kb",
[0.5, 1.0, 2.0, 5.0, 10.0, 24.0],
ids=lambda size: f"{size}KB",
)
@pytest.mark.valid_from("Prague")
def test_extcodesize_bytecode_sizes(
blockchain_test: BlockchainTestFiller,
pre: Alloc,
bytecode_size_kb: float,
gas_benchmark_value: int,
tx_gas_limit: int,
) -> None:
"""
Execute EXTCODESIZE benchmark against pre-deployed contracts.
Uses a gas-based loop exit strategy:
1. Attack contract reads/writes salt from storage slot 0
2. Loop exits when gas < 50K, saves salt for next TX
3. Each TX automatically resumes from where previous left off
Post-state verifies that the attack contract's slot 1 contains the
expected bytecode size (last EXTCODESIZE result).
"""
expected_size_bytes = int(bytecode_size_kb * 1024)
# Get factory stub name for this size
factory_stub = get_factory_stub_name(bytecode_size_kb)
# Deploy factory stub (address comes from stub file)
factory_address = pre.deploy_contract(
code=Bytecode(), # Empty bytecode - address from stub
stub=factory_stub,
)
# Build and deploy the attack contract
attack_code = build_attack_contract(factory_address)
attack_address = pre.deploy_contract(code=attack_code)
# Calculate how many transactions we need to fill the block
num_attack_txs = gas_benchmark_value // tx_gas_limit
if num_attack_txs == 0:
num_attack_txs = 1
# Fund the sender
sender = pre.fund_eoa()
# Build transactions
txs = []
# Attack transactions: all identical, no calldata needed
for _ in range(num_attack_txs):
attack_tx = Transaction(
gas_limit=tx_gas_limit,
to=attack_address,
sender=sender,
)
txs.append(attack_tx)
# Create block with all transactions
block = Block(txs=txs)
# Post-state verification:
# Attack contract slot 1 = expected size (last EXTCODESIZE result)
# Slot 0 can be any value (final salt depends on gas used)
attack_storage = Storage({1: expected_size_bytes}) # type: ignore[dict-item]
attack_storage.set_expect_any(0)
post = {
attack_address: Account(storage=attack_storage),
}
blockchain_test(
pre=pre,
post=post,
blocks=[block],
)
|