Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions packages/testing/src/execution_testing/test_types/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
FixedSizeBytesConvertible,
)
from execution_testing.forks import Fork
from execution_testing.vm import Op
from execution_testing.vm import Bytecode, Op

from .account_types import EOA
from .utils import int_to_bytes
Expand Down Expand Up @@ -88,14 +88,18 @@ def compute_create_address(
def compute_create2_address(
address: FixedSizeBytesConvertible,
salt: FixedSizeBytesConvertible,
initcode: BytesConvertible,
initcode: Bytecode | BytesConvertible,
) -> Address:
"""
Compute address of the resulting contract created using the `CREATE2`
opcode.
"""
if isinstance(initcode, Bytecode):
initcode_hash = initcode.keccak256()
else:
initcode_hash = Bytes(initcode).keccak256()
hash_bytes = Bytes(
b"\xff" + Address(address) + Hash(salt) + Bytes(initcode).keccak256()
b"\xff" + Address(address) + Hash(salt) + initcode_hash
).keccak256()
return Address(hash_bytes[-20:])

Expand Down
56 changes: 41 additions & 15 deletions packages/testing/src/execution_testing/vm/bytecode.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Ethereum Virtual Machine bytecode primitives and utilities."""

from functools import cache
from typing import Any, List, Self, SupportsBytes, Type

from pydantic import GetCoreSchemaHandler
Expand Down Expand Up @@ -33,6 +34,7 @@ class Bytecode:

_name_: str = ""
_bytes_: bytes
_keccak_256_: Hash | None = None

popped_stack_items: int
pushed_stack_items: int
Expand Down Expand Up @@ -261,7 +263,9 @@ def hex(self) -> str:

def keccak256(self) -> Hash:
"""Return the keccak256 hash of the opcode byte representation."""
return Bytes(self._bytes_).keccak256()
if self._keccak_256_ is None:
self._keccak_256_ = Bytes(self._bytes_).keccak256()
return self._keccak_256_

def gas_cost(
self,
Expand All @@ -271,13 +275,7 @@ def gas_cost(
timestamp: int = 0,
) -> int:
"""Use a fork object to calculate the gas used by this bytecode."""
opcode_gas_calculator = fork.opcode_gas_calculator(
block_number=block_number, timestamp=timestamp
)
total_gas = 0
for opcode in self.opcode_list:
total_gas += opcode_gas_calculator(opcode)
return total_gas
return _bytecode_gas_cost(self, fork, block_number, timestamp)

def refund(
self,
Expand All @@ -287,13 +285,7 @@ def refund(
timestamp: int = 0,
) -> int:
"""Use a fork object to calculate the gas refund from this bytecode."""
opcode_refund_calculator = fork.opcode_refund_calculator(
block_number=block_number, timestamp=timestamp
)
total_refund = 0
for opcode in self.opcode_list:
total_refund += opcode_refund_calculator(opcode)
return total_refund
return _bytecode_refund(self, fork, block_number, timestamp)

@classmethod
def __get_pydantic_core_schema__(
Expand All @@ -310,3 +302,37 @@ def __get_pydantic_core_schema__(
info_arg=False,
),
)


@cache
def _bytecode_gas_cost(
bytecode: Bytecode,
fork: Type[ForkOpcodeInterface],
block_number: int,
timestamp: int,
) -> int:
"""Return cached gas cost for the given bytecode and fork parameters."""
opcode_gas_calculator = fork.opcode_gas_calculator(
block_number=block_number, timestamp=timestamp
)
total_gas = 0
for opcode in bytecode.opcode_list:
total_gas += opcode_gas_calculator(opcode)
return total_gas


@cache
def _bytecode_refund(
bytecode: Bytecode,
fork: Type[ForkOpcodeInterface],
block_number: int,
timestamp: int,
) -> int:
"""Return cached gas refund for the given bytecode and fork parameters."""
opcode_refund_calculator = fork.opcode_refund_calculator(
block_number=block_number, timestamp=timestamp
)
total_refund = 0
for opcode in bytecode.opcode_list:
total_refund += opcode_refund_calculator(opcode)
return total_refund
17 changes: 11 additions & 6 deletions tests/benchmark/compute/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import math
from enum import Enum, auto
from typing import Generator, Self, Sequence, cast
from typing import Dict, Generator, Self, Sequence, cast

from execution_testing import (
EOA,
Expand Down Expand Up @@ -349,6 +349,8 @@ class CustomSizedContractFactory(IteratingBytecode):

_cached_address: Address
"""Cached address to avoid expensive recomputation."""
_cached_created_contracts: Dict[int, Address]
"""Cached created contract addresses to avoid expensive recomputation."""
contract_size: int
"""The size of the contracts to deploy."""

Expand Down Expand Up @@ -418,6 +420,7 @@ def __new__(
initcode=Initcode(deploy_code=instance),
fork=fork,
)
instance._cached_created_contracts = {}
instance.contract_size = initcode.contract_size
deployed_address = pre.deterministic_deploy_contract(
deploy_code=instance
Expand Down Expand Up @@ -494,8 +497,10 @@ def address(self) -> Address:

def created_contract_address(self, *, salt: int) -> Address:
"""Get the deterministic address of the created contract."""
return compute_create2_address(
address=self.address(),
salt=salt,
initcode=self.initcode,
)
if salt not in self._cached_created_contracts:
self._cached_created_contracts[salt] = compute_create2_address(
address=self.address(),
salt=salt,
initcode=self.initcode,
)
return self._cached_created_contracts[salt]
18 changes: 13 additions & 5 deletions tests/benchmark/compute/instruction/test_account_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"""

import math
from typing import Any
from typing import Any, Dict

import pytest
from execution_testing import (
Expand Down Expand Up @@ -539,17 +539,25 @@ def calldata(iteration_count: int, start_iteration: int) -> bytes:
# Access list generator for warm access tests.
# When access_warm=True, include all contract addresses that will be
# accessed in each transaction to warm them up via access list.
# Note: This access list generation is very expensive due to the binary
# search, which builds different access lists using the same elements
# over and over. Caching the elements helps a bit.
access_list_cache: Dict[int, AccessList] = {}

def access_list_generator(
iteration_count: int, start_iteration: int
) -> list[AccessList] | None:
if not access_warm:
return None
return [
AccessList(
address=custom_sized_contract_factory.created_contract_address(
salt=i
access_list_cache.setdefault(
i,
AccessList(
address=custom_sized_contract_factory.created_contract_address(
salt=i
),
storage_keys=[],
),
storage_keys=[],
)
for i in range(start_iteration, start_iteration + iteration_count)
]
Expand Down
Loading