Skip to content

Commit 35f83db

Browse files
marioevzfselmo
andauthored
fix(tests/benchmark): Max size contract deployment execution (#2149)
* fix(tests/benchmark): Max size contract deployment execution * fix * fix(test-fill): cache address, don't re-calculate every time; very expensive --------- Co-authored-by: fselmo <fselmo2@gmail.com>
1 parent e7d8ccf commit 35f83db

3 files changed

Lines changed: 94 additions & 36 deletions

File tree

packages/testing/src/execution_testing/tools/tools_code/generators.py

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Code generating classes and functions."""
22

33
from dataclasses import dataclass
4-
from typing import Any, Generator, List, Self, SupportsBytes, Tuple, Type
4+
from typing import Any, Dict, Generator, List, Self, SupportsBytes, Tuple, Type
55

66
from pydantic import Field
77

@@ -807,6 +807,16 @@ def tx_iterations_by_gas_limit(
807807
remaining_gas -= best_iterations_gas
808808
start_iteration += best_iterations
809809

810+
def _intrinsic_cost_is_constant(
811+
self,
812+
intrinsic_cost_kwargs: Dict[str, Any],
813+
) -> bool:
814+
"""If none of the kwarg values is callable, return True."""
815+
for _, value in intrinsic_cost_kwargs.items():
816+
if callable(value):
817+
return False
818+
return True
819+
810820
def tx_iterations_by_total_iteration_count(
811821
self,
812822
*,
@@ -828,14 +838,19 @@ def tx_iterations_by_total_iteration_count(
828838
yield total_iterations
829839
return
830840
remaining_iterations = total_iterations
841+
best_iterations: int | None = None
842+
constant_intrinsic_gas_cost = self._intrinsic_cost_is_constant(
843+
intrinsic_cost_kwargs
844+
)
831845

832846
while remaining_iterations > 0:
833-
best_iterations, _ = self._binary_search_iterations(
834-
fork=fork,
835-
gas_limit=gas_limit_cap,
836-
start_iteration=start_iteration,
837-
**intrinsic_cost_kwargs,
838-
)
847+
if best_iterations is None or not constant_intrinsic_gas_cost:
848+
best_iterations, _ = self._binary_search_iterations(
849+
fork=fork,
850+
gas_limit=gas_limit_cap,
851+
start_iteration=start_iteration,
852+
**intrinsic_cost_kwargs,
853+
)
839854
if best_iterations >= remaining_iterations:
840855
yield remaining_iterations
841856
return

tests/benchmark/compute/helpers.py

Lines changed: 70 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,9 @@ class MaxSizedContractInitcode(FixedIterationsBytecode):
227227
fork's limits.
228228
"""
229229

230+
_cached_address: Address
231+
"""Cached address to avoid expensive recomputation."""
232+
230233
def __new__(cls, *, pre: Alloc, fork: Fork) -> Self:
231234
"""
232235
Create a new MaxSizedContractInitcode instance.
@@ -292,19 +295,21 @@ def __new__(cls, *, pre: Alloc, fork: Fork) -> Self:
292295
cleanup=cleanup,
293296
iteration_count=iteration_count,
294297
)
298+
# Cache the address to avoid expensive recomputation
299+
instance._cached_address = compute_deterministic_create2_address(
300+
salt=0,
301+
initcode=Initcode(deploy_code=instance),
302+
fork=fork,
303+
)
295304
deployed_address = pre.deterministic_deploy_contract(
296305
deploy_code=instance
297306
)
298-
assert deployed_address == instance.address(fork=fork)
307+
assert deployed_address == instance._cached_address
299308
return instance
300309

301-
def address(self, *, fork: Fork) -> Address:
310+
def address(self) -> Address:
302311
"""Get the deterministic address of the initcode."""
303-
return compute_deterministic_create2_address(
304-
salt=0,
305-
initcode=Initcode(deploy_code=self),
306-
fork=fork,
307-
)
312+
return self._cached_address
308313

309314

310315
class MaxSizedContractFactory(IteratingBytecode):
@@ -322,6 +327,9 @@ class MaxSizedContractFactory(IteratingBytecode):
322327
initcode: MaxSizedContractInitcode
323328
"""The initcode used to deploy maximum-sized contracts via CREATE2."""
324329

330+
_cached_address: Address
331+
"""Cached address to avoid expensive recomputation."""
332+
325333
def __new__(cls, *, pre: Alloc, fork: Fork) -> Self:
326334
"""
327335
Create a new MaxSizedContractFactory instance.
@@ -337,7 +345,7 @@ def __new__(cls, *, pre: Alloc, fork: Fork) -> Self:
337345
338346
"""
339347
initcode = MaxSizedContractInitcode(pre=pre, fork=fork)
340-
initcode_address = initcode.address(fork=fork)
348+
initcode_address = initcode.address()
341349
setup = (
342350
Op.EXTCODECOPY(
343351
address=initcode_address,
@@ -381,10 +389,16 @@ def __new__(cls, *, pre: Alloc, fork: Fork) -> Self:
381389
cleanup=cleanup,
382390
)
383391
instance.initcode = initcode
392+
# Cache the address to avoid expensive recomputation
393+
instance._cached_address = compute_deterministic_create2_address(
394+
salt=0,
395+
initcode=Initcode(deploy_code=instance),
396+
fork=fork,
397+
)
384398
deployed_address = pre.deterministic_deploy_contract(
385399
deploy_code=instance
386400
)
387-
assert deployed_address == instance.address(fork=fork)
401+
assert deployed_address == instance._cached_address
388402
return instance
389403

390404
def transactions_by_total_contract_count(
@@ -400,33 +414,64 @@ def transactions_by_total_contract_count(
400414
given number of contracts, each capped tx properly capped by the
401415
gas limit cap of the fork.
402416
"""
403-
to = self.address(fork=fork)
417+
to = self.address()
418+
419+
# Use a sensible hardcoded maximum for the calldata, to avoid
420+
# binary searching.
421+
max_number = (2 ** (contract_count.bit_length() + 1)) - 1
422+
calldata_max = Hash(max_number) + Hash(max_number)
404423

405424
def calldata(iteration_count: int, start_iteration: int) -> bytes:
406425
index_end = iteration_count + start_iteration - 1
407426
return Hash(start_iteration) + Hash(index_end)
408427

409-
yield from self.transactions_by_total_iteration_count(
428+
start_iteration: int = contract_start_index
429+
430+
tx_gas_limit: int | None = None
431+
tx_gas_cost: int | None = None
432+
last_iteration_count: int = 0
433+
434+
for iteration_count in self.tx_iterations_by_total_iteration_count(
410435
fork=fork,
411436
total_iterations=contract_count,
412-
start_iteration=contract_start_index,
413-
sender=sender,
414-
to=to,
415-
calldata=calldata,
416-
)
437+
start_iteration=start_iteration,
438+
calldata=calldata_max,
439+
):
440+
if (
441+
tx_gas_limit is None
442+
or tx_gas_cost is None
443+
or iteration_count != last_iteration_count
444+
):
445+
tx_gas_limit = self.tx_gas_limit_by_iteration_count(
446+
fork=fork,
447+
iteration_count=iteration_count,
448+
start_iteration=start_iteration,
449+
calldata=calldata_max,
450+
)
451+
tx_gas_cost = self.tx_gas_cost_by_iteration_count(
452+
fork=fork,
453+
iteration_count=iteration_count,
454+
start_iteration=start_iteration,
455+
calldata=calldata_max,
456+
)
457+
yield TransactionWithCost(
458+
to=to,
459+
gas_limit=tx_gas_limit,
460+
sender=sender,
461+
gas_cost=tx_gas_cost,
462+
data=calldata(iteration_count, start_iteration),
463+
)
464+
start_iteration += iteration_count
465+
last_iteration_count = iteration_count
417466

418-
def address(self, *, fork: Fork) -> Address:
419-
"""Get the deterministic address of the initcode."""
420-
return compute_deterministic_create2_address(
421-
salt=0,
422-
initcode=Initcode(deploy_code=self),
423-
fork=fork,
424-
)
467+
def address(self) -> Address:
468+
"""Get the deterministic address of the factory contract."""
469+
return self._cached_address
425470

426-
def created_contract_address(self, *, fork: Fork, salt: int) -> Address:
471+
def created_contract_address(self, *, salt: int) -> Address:
427472
"""Get the deterministic address of the created contract."""
428473
return compute_create2_address(
429-
address=self.address(fork=fork),
474+
address=self.address(),
430475
salt=salt,
431476
initcode=self.initcode,
432477
)

tests/benchmark/compute/scenario/test_unchunkified_bytecode.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def test_unchunkified_bytecode(
5151

5252
# Create the max-sized fork-dependent contract factory.
5353
max_sized_contract_factory = MaxSizedContractFactory(pre=pre, fork=fork)
54-
factory_address = max_sized_contract_factory.address(fork=fork)
54+
factory_address = max_sized_contract_factory.address()
5555
initcode = max_sized_contract_factory.initcode
5656

5757
# Prepare the attack iterating bytecode.
@@ -148,9 +148,7 @@ def calldata(iteration_count: int, start_iteration: int) -> bytes:
148148
post = {}
149149
for i in range(num_contracts):
150150
deployed_contract_address = (
151-
max_sized_contract_factory.created_contract_address(
152-
fork=fork, salt=i
153-
)
151+
max_sized_contract_factory.created_contract_address(salt=i)
154152
)
155153
post[deployed_contract_address] = Account(nonce=1)
156154

0 commit comments

Comments
 (0)