Skip to content

Commit 2852dbf

Browse files
committed
cleanup
1 parent 4652f1c commit 2852dbf

1 file changed

Lines changed: 113 additions & 106 deletions

File tree

tests/benchmark/stateful/bloatnet/depth_benchmarks/test_deep_branch.py

Lines changed: 113 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
"""
2424

2525
from pathlib import Path
26-
from typing import Any, List, Self
26+
from typing import Any, Callable, List, Self
2727

2828
import pytest
2929
from execution_testing import (
@@ -38,11 +38,9 @@
3838
Bytes,
3939
Fork,
4040
Hash,
41-
Initcode,
4241
Op,
4342
Transaction,
4443
While,
45-
compute_deterministic_create2_address,
4644
)
4745
from pydantic import BaseModel, Field
4846

@@ -195,21 +193,34 @@ class Attack(BaseModel):
195193
value: int
196194
start: int
197195
end: int
198-
initcode_hash: Hash
196+
fork: Fork
197+
mined_contract_file: MinedContractFile
198+
attack_orchestrator_address: Address
199+
200+
def next(self) -> Self:
201+
"""Create a copy of the instance with the next salt as the new end."""
202+
return self.__class__(
203+
value=self.value,
204+
start=self.start,
205+
end=self.end + 1,
206+
fork=self.fork,
207+
mined_contract_file=self.mined_contract_file,
208+
attack_orchestrator_address=self.attack_orchestrator_address,
209+
)
199210

200211
def calldata(self) -> bytes:
201212
"""Return the calldata that needs to be passed to the orchestrator."""
202213
return Bytes(
203214
self.value.to_bytes(32, "big")
204215
+ self.start.to_bytes(32, "big")
205216
+ self.end.to_bytes(32, "big")
206-
+ self.initcode_hash
217+
+ self.mined_contract_file.initcode_hash
207218
)
208219

209-
def calculate_inner_call_cost(self, fork: Fork) -> int:
220+
def calculate_inner_call_cost(self) -> int:
210221
"""Calculate the exact gas this inner call would use."""
211-
gas_costs = fork.gas_costs()
212-
mem_expand_calc = fork.memory_expansion_gas_calculator()
222+
gas_costs = self.fork.gas_costs()
223+
mem_expand_calc = self.fork.memory_expansion_gas_calculator()
213224
inner_call_cost = (
214225
mem_expand_calc(new_bytes=96)
215226
+ 17 * gas_costs.G_VERY_LOW # PUSHN operations
@@ -238,11 +249,13 @@ def calculate_inner_call_cost(self, fork: Fork) -> int:
238249
)
239250
return inner_call_cost
240251

241-
def calculate_gas(self, fork: Fork) -> int:
252+
def calculate_gas(self) -> int:
242253
"""Calculate the exact gas this attack transaction will use."""
243-
tx_intrinsic_gas_calc = fork.transaction_intrinsic_cost_calculator()
244-
gas_costs = fork.gas_costs()
245-
mem_expand_calc = fork.memory_expansion_gas_calculator()
254+
tx_intrinsic_gas_calc = (
255+
self.fork.transaction_intrinsic_cost_calculator()
256+
)
257+
gas_costs = self.fork.gas_costs()
258+
mem_expand_calc = self.fork.memory_expansion_gas_calculator()
246259
tx_overhead = tx_intrinsic_gas_calc(
247260
calldata=self.calldata(),
248261
return_cost_deducted_prior_execution=True,
@@ -253,7 +266,7 @@ def calculate_gas(self, fork: Fork) -> int:
253266
+ 10 * gas_costs.G_VERY_LOW # PUSH operations
254267
+ 3 * gas_costs.G_VERY_LOW # CALLDATALOAD operations
255268
)
256-
inner_call_cost = self.calculate_inner_call_cost(fork)
269+
inner_call_cost = self.calculate_inner_call_cost()
257270
gas_per_attack = (
258271
1 * gas_costs.G_JUMPDEST # JUMPDEST operations
259272
+ 15 * gas_costs.G_VERY_LOW # PUSH operations
@@ -280,37 +293,83 @@ def calculate_gas(self, fork: Fork) -> int:
280293
)
281294
return call_count * gas_per_attack + setup_gas + tx_overhead
282295

283-
def calculate_tx_gas_limit(self, fork: Fork) -> int:
296+
def calculate_tx_gas_limit(self) -> int:
284297
"""Calculate the gas limit required for the transaction."""
285-
gas_cost = self.calculate_gas(fork)
298+
gas_cost = self.calculate_gas()
286299
# Add the 63/64 margin for the last inner call.
287-
inner_call_cost = self.calculate_inner_call_cost(fork)
300+
inner_call_cost = self.calculate_inner_call_cost()
288301
return gas_cost + ((inner_call_cost * 64 // 63) - inner_call_cost)
289302

290-
def generate_transaction(self, fork: Fork, sender: EOA) -> Transaction:
303+
def generate_transaction(self, *, sender: EOA) -> Transaction:
291304
"""Generate the transaction to perform the attack."""
292-
attack_orchestrator_address = compute_deterministic_create2_address(
293-
salt=Hash(0),
294-
initcode=Initcode(deploy_code=attack_orchestrator_bytecode(fork)),
295-
fork=fork,
296-
)
297305
return Transaction(
298-
to=attack_orchestrator_address,
299-
gas_limit=self.calculate_tx_gas_limit(fork),
306+
to=self.attack_orchestrator_address,
307+
gas_limit=self.calculate_tx_gas_limit(),
300308
sender=sender,
301309
data=self.calldata(),
302310
)
303311

304-
def add_post_verification(
305-
self, post: Alloc, mined_contract_file: MinedContractFile
306-
) -> None:
312+
def add_post_verification(self, *, post: Alloc) -> None:
307313
"""Add the post-verification transaction to the post-state."""
308-
contract = mined_contract_file.contracts[self.end]
309-
storage = dict.fromkeys(mined_contract_file.storage_keys, 1)
310-
storage[mined_contract_file.storage_keys[-1]] = self.value
314+
contract = self.mined_contract_file.contracts[self.end]
315+
storage = dict.fromkeys(self.mined_contract_file.storage_keys, 1)
316+
storage[self.mined_contract_file.storage_keys[-1]] = self.value
311317
post[contract.contract_address] = Account(storage=storage)
312318

313319

320+
@pytest.fixture
321+
def mined_contract_file(
322+
storage_depth: int,
323+
account_depth: int,
324+
) -> MinedContractFile:
325+
"""Return the correct file for the given test."""
326+
mined_contract_file = MinedContractFile.load(storage_depth, account_depth)
327+
# Verify we have contracts in the JSON
328+
available_contracts = len(mined_contract_file.contracts)
329+
if available_contracts == 0:
330+
json_name = f"s{storage_depth}_acc{account_depth}.json"
331+
raise ValueError(f"No contracts available in {json_name}")
332+
return mined_contract_file
333+
334+
335+
@pytest.fixture
336+
def mined_contract_deployer(
337+
pre: Alloc,
338+
mined_contract_file: MinedContractFile,
339+
) -> Callable[[int], None]:
340+
"""Return a helper to deploy a contract for a given salt when needed."""
341+
342+
def _mined_contract_deployer(salt: int) -> None:
343+
if salt >= len(mined_contract_file.contracts):
344+
raise RuntimeError(
345+
f"Requested salt {salt} but only "
346+
f"{len(mined_contract_file.contracts)} available"
347+
)
348+
salted_contract_info = mined_contract_file.contracts[salt]
349+
assert salted_contract_info.salt == salt, (
350+
f"Salt out of order: {salted_contract_info.salt} != {salt}"
351+
)
352+
deployed_contract_address = pre.deterministic_deploy_contract(
353+
deploy_code=mined_contract_file.deploy_code,
354+
salt=Hash(salt),
355+
initcode=mined_contract_file.initcode,
356+
storage=dict.fromkeys(mined_contract_file.storage_keys, 1),
357+
)
358+
assert (
359+
deployed_contract_address == salted_contract_info.contract_address
360+
), (
361+
f"Contract address mismatch: {deployed_contract_address} != "
362+
f"{salted_contract_info.contract_address}, salt: {salt}"
363+
)
364+
for auxiliary_account in salted_contract_info.auxiliary_accounts:
365+
# Ensure the account exists in the state trie
366+
pre.fund_address(
367+
address=auxiliary_account, amount=1, minimum_balance=True
368+
)
369+
370+
return _mined_contract_deployer
371+
372+
314373
@pytest.mark.valid_from("Prague")
315374
@pytest.mark.parametrize(
316375
"storage_depth,account_depth",
@@ -323,8 +382,8 @@ def test_worst_depth_stateroot_recomp(
323382
pre: Alloc,
324383
fork: Fork,
325384
gas_benchmark_value: int,
326-
storage_depth: int,
327-
account_depth: int,
385+
mined_contract_file: MinedContractFile,
386+
mined_contract_deployer: Callable[[int], None],
328387
) -> None:
329388
"""
330389
BloatNet worst-case SSTORE attack benchmark with pre-deployed contracts.
@@ -341,117 +400,65 @@ def test_worst_depth_stateroot_recomp(
341400
fork: The fork to test on
342401
env: Environment object that will be used to fill/execute
343402
gas_benchmark_value: Gas budget for benchmark
344-
storage_depth: Depth of storage slots in the contract
345-
account_depth: Account address prefix sharing depth
403+
mined_contract_file: The mined contract file
404+
mined_contract_deployer: A function to deploy a mined contract
346405
347406
"""
348-
# Dynamically calculate number of contracts based on gas budget
349-
print("\nTesting with pre-deployed contracts:")
350-
print(f" Storage depth: {storage_depth}")
351-
print(f" Account depth: {account_depth}")
352-
print(f" Gas benchmark value: {gas_benchmark_value:,}")
353-
354-
# Load the CREATE2 data to get the init code hash
355-
mined_contract_file = MinedContractFile.load(storage_depth, account_depth)
356-
initcode_hash = mined_contract_file.initcode_hash
357-
358-
# Verify we have enough contracts in the JSON
359-
available_contracts = len(mined_contract_file.contracts)
360-
if available_contracts == 0:
361-
json_name = f"s{storage_depth}_acc{account_depth}.json"
362-
raise ValueError(f"No contracts available in {json_name}")
363-
364-
# Create an EOA with funds for the deployer
365-
sender = pre.fund_eoa()
366-
367407
# Deploy orchestrator to deterministic address
368-
orchestrator_address = pre.deterministic_deploy_contract(
408+
attack_orchestrator_address = pre.deterministic_deploy_contract(
369409
deploy_code=attack_orchestrator_bytecode(fork)
370410
)
371-
print(f" Orchestrator will be deployed at: {orchestrator_address}")
411+
print(f" Orchestrator will be deployed at: {attack_orchestrator_address}")
412+
413+
# Create an EOA with funds for the deployer
414+
sender = pre.fund_eoa()
372415

373416
# Build attack transactions
374417
attack_txs: list[Transaction] = []
375-
attack_value = DEFAULT_ATTACK_VALUE
376418
accrued_tx_gas_usage = 0
377419
tx_gas_limit_cap = fork.transaction_gas_limit_cap()
378420
post = Alloc({})
379421

422+
# Create the starting attack
380423
current_attack_batch = Attack(
381-
value=attack_value,
424+
value=DEFAULT_ATTACK_VALUE,
382425
start=0,
383426
end=0,
384-
initcode_hash=initcode_hash,
427+
fork=fork,
428+
mined_contract_file=mined_contract_file,
429+
attack_orchestrator_address=attack_orchestrator_address,
385430
)
386431

387-
# Create a helper to deploy a contract for a given salt when needed.
388-
def deploy_mined_contract(salt: int) -> None:
389-
salted_contract_info = mined_contract_file.contracts[salt]
390-
assert salted_contract_info.salt == salt, (
391-
f"Salt out of order: {salted_contract_info.salt} != {salt}"
392-
)
393-
deployed_contract_address = pre.deterministic_deploy_contract(
394-
deploy_code=mined_contract_file.deploy_code,
395-
salt=Hash(salt),
396-
initcode=mined_contract_file.initcode,
397-
storage=dict.fromkeys(mined_contract_file.storage_keys, 1),
398-
)
399-
assert (
400-
deployed_contract_address == salted_contract_info.contract_address
401-
), (
402-
f"Contract address mismatch: {deployed_contract_address} != "
403-
f"{salted_contract_info.contract_address}, salt: {salt}"
404-
)
405-
for auxiliary_account in salted_contract_info.auxiliary_accounts:
406-
# Ensure the account exists in the state trie
407-
pre.fund_address(
408-
address=auxiliary_account, amount=1, minimum_balance=True
409-
)
410-
411432
# Deploy the starting contract
412-
deploy_mined_contract(current_attack_batch.start)
433+
mined_contract_deployer(current_attack_batch.start)
413434

414435
while True:
415-
next_attack_batch = Attack(
416-
value=attack_value,
417-
start=current_attack_batch.start,
418-
end=current_attack_batch.end + 1,
419-
initcode_hash=initcode_hash,
420-
)
421-
next_batch_cost = next_attack_batch.calculate_tx_gas_limit(fork=fork)
436+
next_attack_batch = current_attack_batch.next()
437+
next_batch_cost = next_attack_batch.calculate_tx_gas_limit()
422438

423439
if next_batch_cost + accrued_tx_gas_usage > gas_benchmark_value:
424440
# Next contract cost would go above benchmark limit, we are done.
425441
attack_txs.append(
426-
current_attack_batch.generate_transaction(fork, sender)
427-
)
428-
current_attack_batch.add_post_verification(
429-
post, mined_contract_file
442+
current_attack_batch.generate_transaction(sender=sender)
430443
)
431-
accrued_tx_gas_usage += current_attack_batch.calculate_gas(fork)
444+
current_attack_batch.add_post_verification(post=post)
445+
accrued_tx_gas_usage += current_attack_batch.calculate_gas()
432446
break
433447

434448
# Next contract would not go above limit, but we need to check
435449
# whether we have gone above the tx limit.
436450

437451
# We are going to use the next contract regardless
438-
if next_attack_batch.end > available_contracts:
439-
raise RuntimeError(
440-
f"Requested {next_attack_batch.end} contracts but only "
441-
f"{available_contracts} available, using {available_contracts}"
442-
)
443-
deploy_mined_contract(next_attack_batch.end)
452+
mined_contract_deployer(next_attack_batch.end)
444453

445454
if tx_gas_limit_cap is not None and next_batch_cost > tx_gas_limit_cap:
446455
# Adding a contract would go above the transaction gas limit cap,
447456
# make the cut here.
448457
attack_txs.append(
449-
current_attack_batch.generate_transaction(fork, sender)
450-
)
451-
current_attack_batch.add_post_verification(
452-
post, mined_contract_file
458+
current_attack_batch.generate_transaction(sender=sender)
453459
)
454-
accrued_tx_gas_usage += current_attack_batch.calculate_gas(fork)
460+
current_attack_batch.add_post_verification(post=post)
461+
accrued_tx_gas_usage += current_attack_batch.calculate_gas()
455462

456463
next_attack_batch.start = next_attack_batch.end
457464

0 commit comments

Comments
 (0)