From 864bd94e08695b3a6159b53e3697bc200a96199c Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Tue, 16 Dec 2025 22:06:00 +0000 Subject: [PATCH 01/20] feat(test-all): Implement `deterministic_deploy_contract` --- .../testing/src/execution_testing/__init__.py | 2 + .../plugins/execute/execute.py | 6 +- .../plugins/execute/pre_alloc.py | 338 +++++++++++++----- .../plugins/filler/pre_alloc.py | 64 +++- .../testing/src/execution_testing/rpc/rpc.py | 2 +- .../execution_testing/test_types/__init__.py | 2 + .../test_types/account_types.py | 21 +- .../execution_testing/test_types/helpers.py | 19 + 8 files changed, 366 insertions(+), 88 deletions(-) diff --git a/packages/testing/src/execution_testing/__init__.py b/packages/testing/src/execution_testing/__init__.py index c5dab81a4ab..53215e8b3cb 100644 --- a/packages/testing/src/execution_testing/__init__.py +++ b/packages/testing/src/execution_testing/__init__.py @@ -83,6 +83,7 @@ ceiling_division, compute_create2_address, compute_create_address, + compute_deterministic_create2_address, compute_eofcreate_address, keccak256, ) @@ -210,6 +211,7 @@ "ceiling_division", "compute_create_address", "compute_create2_address", + "compute_deterministic_create2_address", "compute_eofcreate_address", "extend_with_defaults", "gas_test", diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/execute.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/execute.py index 93b84b80653..82a7cb0fd55 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/execute.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/execute.py @@ -658,20 +658,20 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: # send the funds to the required sender accounts pre.send_pending_transactions() - # wait for pre-requisite transactions to be included in blocks - pre.wait_for_transactions() for ( deployed_contract, expected_code, ) in pre._deployed_contracts: actual_code = eth_rpc.get_code(deployed_contract) if actual_code != expected_code: - raise Exception( + msg = ( f"Deployed test contract didn't match expected code at address " f"{deployed_contract} (not enough gas_limit?).\n" f"Expected: {expected_code}\n" f"Actual: {actual_code}" ) + logger.error(msg) + raise Exception(msg) request.node.config.funded_accounts = ", ".join( [str(eoa) for eoa in pre._funded_eoa] ) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py index 6127453c86f..24a8fcbb8f0 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py @@ -36,6 +36,7 @@ ChainConfig, Transaction, TransactionTestMetadata, + compute_deterministic_create2_address, ) from execution_testing.test_types import Alloc as BaseAlloc from execution_testing.test_types.eof.v1 import Container @@ -45,6 +46,16 @@ MAX_BYTECODE_SIZE = 24576 MAX_INITCODE_SIZE = MAX_BYTECODE_SIZE * 2 +DETERMINISTIC_DEPLOYMENT_CONTRACT_ADDRESS = Address( + 0x4E59B44847B379578588920CA78FBF26C0B4956C +) +DETERMINISTIC_DEPLOYMENT_SIGNER_ADDRESS = Address( + 0x3FAB184622DC19B6109349B94811493BF2A45362 +) +DETERMINISTIC_DEPLOYMENT_TRANSACTION = Bytes( + "0xf8a58085174876e800830186a08080b853604580600e600039806000f350fe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf31ba02222222222222222222222222222222222222222222222222222222222222222a02222222222222222222222222222222222222222222222222222222222222222" +) + logger = get_logger(__name__) @@ -187,6 +198,7 @@ class PendingTransaction(Transaction): """ value: HexNumber | None = None # type: ignore + requires_predecessor: bool = False class Alloc(BaseAlloc): @@ -255,6 +267,176 @@ def code_pre_processor( return Container.Code(code) return code + def _add_pending_tx( + self, + *, + action: str | None, + target: str | None, + **kwargs: Any, + ) -> PendingTransaction: + """ + Prepares a transaction to be sent to the network with the appropriate + metadata and adds it to the queue. + """ + if "sender" not in kwargs and "v" not in kwargs: + kwargs["sender"] = self._sender + pending_tx = PendingTransaction( + **kwargs, + ) + pending_tx.metadata = TransactionTestMetadata( + test_id=self._node_id, + phase="setup", + action=action, + target=target, + tx_index=len(self._pending_txs), + ) + self._pending_txs.append(pending_tx) + return pending_tx + + def deterministic_deploy_contract( + self, + *, + deploy_code: BytesConvertible, + salt: Hash | int = 0, + initcode: BytesConvertible | None = None, + minimum_balance: NumberConvertible = 0, + setup_calldata: BytesConvertible | None = None, + label: str | None = None, + ) -> Address: + """ + Deploy a contract to the allocation at a deterministic location + using a deterministic deployment proxy. + """ + gas_costs = self._fork.gas_costs() + memory_expansion_gas_calculator = ( + self._fork.memory_expansion_gas_calculator() + ) + calldata_gas_calculator = self._fork.calldata_gas_calculator( + block_number=0, timestamp=0 + ) + if not isinstance(deploy_code, Bytes): + deploy_code = Bytes(deploy_code) + if initcode is None: + initcode = Initcode(deploy_code=deploy_code) + elif not isinstance(initcode, Bytes): + initcode = Bytes(initcode) + minimum_balance = Number(minimum_balance) + salt = Hash(salt) + contract_address = compute_deterministic_create2_address( + salt=salt, initcode=initcode + ) + # 1) Determine if this contract already exists + chain_code = self._eth_rpc.get_code(contract_address) + if chain_code != b"": + assert chain_code == deploy_code, ( + "Deterministic deployed contract's code on chain does not " + "match the expected code: " + f"Expected: {deploy_code}, " + f"Current: {chain_code}" + ) + else: + # 2) Determine if the deployment contract is already on chain + deployment_contract_code = self._eth_rpc.get_code( + DETERMINISTIC_DEPLOYMENT_CONTRACT_ADDRESS + ) + if deployment_contract_code == b"": + # 2b) Add transaction to fund the deployer. + self._add_pending_tx( + action="deterministic_deploy_contract", + target=f"{DETERMINISTIC_DEPLOYMENT_SIGNER_ADDRESS}", + to=DETERMINISTIC_DEPLOYMENT_SIGNER_ADDRESS, + value=10**16, + ) + # 2c) Add deployment transaction. + self._add_pending_tx( + action="deterministic_deploy_contract", + target=f"{DETERMINISTIC_DEPLOYMENT_CONTRACT_ADDRESS}", + ty=0, + protected=False, + nonce=0, + gas_price=0x174876E800, + gas_limit=0x0186A0, + to=None, + value=0, + data=Bytes( + "0x604580600e600039806000f350fe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3" + ), + v=0x1B, + r=0x2222222222222222222222222222222222222222222222222222222222222222, + s=0x2222222222222222222222222222222222222222222222222222222222222222, + requires_predecessor=True, + ) + + # 3) Deploy the actual contract. + deploy_gas_limit = ( + gas_costs.G_TRANSACTION + gas_costs.G_TRANSACTION_CREATE + ) + deploy_gas_limit += ( + len(deploy_code) * gas_costs.G_CODE_DEPOSIT_BYTE + ) + deploy_gas_limit += memory_expansion_gas_calculator( + new_bytes=len(initcode) + ) + deploy_gas_limit += calldata_gas_calculator(data=initcode) + deploy_gas_limit = min(deploy_gas_limit * 2, 30_000_000) + deploy_tx = self._add_pending_tx( + action="deterministic_deploy_contract", + target=label, + to=DETERMINISTIC_DEPLOYMENT_CONTRACT_ADDRESS, + data=Bytes(salt) + Bytes(initcode), + value=minimum_balance, + gas_limit=deploy_gas_limit, + ) + logger.info( + f"Contract deployment tx created (label={label}): " + f"tx_nonce={deploy_tx.nonce}, gas_limit={deploy_gas_limit}, " + f"code_size={len(deploy_code)} bytes, initcode_size={len(initcode)} bytes, " + f"balance={Number(minimum_balance) / 10**18:.18f} ETH" + ) + + logger.debug( + f"Contract will be deployed at {contract_address} " + f"(label={label}, tx_index={len(self._pending_txs) - 1})" + ) + + self._deployed_contracts.append((contract_address, deploy_code)) + + balance = self._eth_rpc.get_balance(contract_address) + nonce = self._eth_rpc.get_transaction_count(contract_address) + super().__setitem__( + contract_address, + Account( + nonce=nonce, + balance=balance, + code=deploy_code, + storage={}, + ), + ) + + if setup_calldata is not None: + value = 0 + if balance < minimum_balance: + value = minimum_balance - balance + self._add_pending_tx( + action="deterministic_deploy_contract", + target=label, + value=value, + to=contract_address, + data=setup_calldata, + gas_limit=100_000, + ) + elif balance < minimum_balance: + self._add_pending_tx( + action="deterministic_deploy_contract", + target=label, + value=minimum_balance - balance, + to=contract_address, + gas_limit=100_000, + ) + + contract_address.label = label + return contract_address + def deploy_contract( self, code: BytesConvertible, @@ -272,6 +454,16 @@ def deploy_contract( storage = {} assert address is None, "address parameter is not supported" + gas_costs = self._fork.gas_costs() + memory_expansion_gas_calculator = ( + self._fork.memory_expansion_gas_calculator() + ) + calldata_gas_calculator = self._fork.calldata_gas_calculator( + block_number=0, timestamp=0 + ) + if not isinstance(code, Bytes): + code = Bytes(code) + if not isinstance(storage, Storage): storage = Storage(storage) # type: ignore @@ -309,7 +501,9 @@ def deploy_contract( initcode_prefix = Bytecode() - deploy_gas_limit = 21_000 + 32_000 + deploy_gas_limit = ( + gas_costs.G_TRANSACTION + gas_costs.G_TRANSACTION_CREATE + ) if len(storage.root) > 0: initcode_prefix += sum( @@ -326,57 +520,44 @@ def deploy_contract( f"code too large: {len(code)} > {MAX_BYTECODE_SIZE}" ) - deploy_gas_limit += len(bytes(code)) * 200 + deploy_gas_limit += len(code) * gas_costs.G_CODE_DEPOSIT_BYTE - initcode: Bytecode | Container + prepared_initcode: Bytecode | Container if evm_code_type == EVMCodeType.EOF_V1: assert isinstance(code, Container) - initcode = Container.Init( + prepared_initcode = Container.Init( deploy_container=code, initcode_prefix=initcode_prefix ) else: - initcode = Initcode( + prepared_initcode = Initcode( deploy_code=code, initcode_prefix=initcode_prefix ) - memory_expansion_gas_calculator = ( - self._fork.memory_expansion_gas_calculator() - ) deploy_gas_limit += memory_expansion_gas_calculator( - new_bytes=len(bytes(initcode)) + new_bytes=len(bytes(prepared_initcode)) ) - assert len(initcode) <= MAX_INITCODE_SIZE, ( - f"initcode too large {len(initcode)} > {MAX_INITCODE_SIZE}" + assert len(prepared_initcode) <= MAX_INITCODE_SIZE, ( + f"initcode too large {len(prepared_initcode)} > {MAX_INITCODE_SIZE}" ) - calldata_gas_calculator = self._fork.calldata_gas_calculator( - block_number=0, timestamp=0 - ) - deploy_gas_limit += calldata_gas_calculator(data=initcode) + deploy_gas_limit += calldata_gas_calculator(data=prepared_initcode) # Limit the gas limit deploy_gas_limit = min(deploy_gas_limit * 2, 30_000_000) - deploy_tx = PendingTransaction( - sender=self._sender, + deploy_tx = self._add_pending_tx( + action="deploy_contract", + target=label, to=None, - data=initcode, + data=prepared_initcode, value=balance, gas_limit=deploy_gas_limit, ) - deploy_tx.metadata = TransactionTestMetadata( - test_id=self._node_id, - phase="setup", - action="deploy_contract", - target=label, - tx_index=len(self._pending_txs), - ) - self._pending_txs.append(deploy_tx) logger.info( f"Contract deployment tx created (label={label}): " f"tx_nonce={deploy_tx.nonce}, gas_limit={deploy_gas_limit}, " - f"code_size={len(code)} bytes, initcode_size={len(initcode)} bytes, " + f"code_size={len(code)} bytes, initcode_size={len(prepared_initcode)} bytes, " f"balance={Number(balance) / 10**18:.18f} ETH, storage_slots={len(storage.root)}" ) @@ -385,7 +566,7 @@ def deploy_contract( f"Contract will be deployed at {contract_address} " f"(label={label}, tx_index={len(self._pending_txs) - 1})" ) - self._deployed_contracts.append((contract_address, Bytes(code))) + self._deployed_contracts.append((contract_address, code)) assert Number(nonce) >= 1, ( "impossible to deploy contract with nonce lower than one" @@ -450,8 +631,9 @@ def fund_eoa( f"Storage contract deployed at {sstore_address} for EOA {eoa}" ) - set_storage_tx = PendingTransaction( - sender=self._sender, + self._add_pending_tx( + action="eoa_storage_set", + target=label, to=eoa, value=0, authorization_list=[ @@ -465,14 +647,6 @@ def fund_eoa( gas_limit=100_000, ) eoa.nonce = Number(eoa.nonce + 1) - set_storage_tx.metadata = TransactionTestMetadata( - test_id=self._node_id, - phase="setup", - action="eoa_storage_set", - target=label, - tx_index=len(self._pending_txs), - ) - self._pending_txs.append(set_storage_tx) if delegation is not None: if ( @@ -482,8 +656,9 @@ def fund_eoa( delegation = eoa # TODO: This tx has side-effects on the EOA state because of # the delegation - fund_tx = PendingTransaction( - sender=self._sender, + fund_tx = self._add_pending_tx( + action="fund_eoa", + target=label, to=eoa, value=amount, authorization_list=[ @@ -498,8 +673,9 @@ def fund_eoa( ) eoa.nonce = Number(eoa.nonce + 1) else: - fund_tx = PendingTransaction( - sender=self._sender, + fund_tx = self._add_pending_tx( + action="fund_eoa", + target=label, to=eoa, value=amount if amount is not None else 0, authorization_list=[ @@ -517,21 +693,14 @@ def fund_eoa( else: if amount is None or Number(amount) > 0: - fund_tx = PendingTransaction( - sender=self._sender, + fund_tx = self._add_pending_tx( + action="fund_eoa", + target=label, to=eoa, value=amount, ) if fund_tx is not None: - fund_tx.metadata = TransactionTestMetadata( - test_id=self._node_id, - phase="setup", - action="fund_eoa", - target=label, - tx_index=len(self._pending_txs), - ) - self._pending_txs.append(fund_tx) logger.info( f"Added funding transaction for EOA {eoa} (label={label}): " f"tx_nonce={fund_tx.nonce}, " @@ -569,19 +738,12 @@ def fund_address( f"Funding address {address} (label={address.label}): " f"{Number(amount) / 10**18:.18f} ETH" ) - fund_tx = PendingTransaction( - sender=self._sender, - to=address, - value=amount, - ) - fund_tx.metadata = TransactionTestMetadata( - test_id=self._node_id, - phase="setup", + self._add_pending_tx( action="fund_address", target=address.label, - tx_index=len(self._pending_txs), + to=address, + value=amount, ) - self._pending_txs.append(fund_tx) if address in self: account = self[address] if account is not None: @@ -651,6 +813,7 @@ def minimum_balance_for_pending_transactions( # never sends a transaction during the test. assert tx.to in sender_balances, ( "Sender balance must be set before sending" + f"{tx.model_dump_json()}" ) sender_balance = sender_balances[tx.to] logger.info( @@ -667,32 +830,47 @@ def minimum_balance_for_pending_transactions( minimum_balance += tx.signer_minimum_balance(fork=self._fork) return minimum_balance + gas_consumption * gas_price, gas_consumption - def send_pending_transactions(self) -> List[Hash]: - """Send all pending transactions.""" + def send_pending_transactions(self) -> List[TransactionByHashResponse]: + """Send all pending transactions and wait for them to be included.""" logger.info( f"Sending {len(self._pending_txs)} pending transactions " f"(deployed_contracts={len(self._deployed_contracts)}, " f"funded_eoas={len(self._funded_eoa)})" ) - txs = [tx.with_signature_and_sender() for tx in self._pending_txs] - tx_hashes = self._eth_rpc.send_transactions(txs) - logger.info( - f"Sent {len(tx_hashes)} transactions: {[str(h) for h in tx_hashes[:5]]}" - + (f" and {len(tx_hashes) - 5} more" if len(tx_hashes) > 5 else "") - ) - return tx_hashes - - def wait_for_transactions(self) -> List[TransactionByHashResponse]: - """Wait for all transactions to be included in blocks.""" - logger.info( - f"Waiting for {len(self._pending_txs)} transactions to be included in blocks" - ) + transaction_batches: List[List[PendingTransaction]] = [] + last_tx_batch: List[PendingTransaction] = [] for tx in self._pending_txs: assert tx.value is not None, ( - "Transaction value must be set before waiting for it to be included in a block" + "Transaction value must be set before sending them to the RPC." + ) + if tx.requires_predecessor and last_tx_batch: + transaction_batches.append(last_tx_batch) + last_tx_batch = [] + last_tx_batch.append(tx) + if last_tx_batch: + transaction_batches.append(last_tx_batch) + + responses: List[TransactionByHashResponse] = [] + for tx_batch in transaction_batches: + txs = [tx.with_signature_and_sender() for tx in tx_batch] + tx_hashes = self._eth_rpc.send_transactions(txs) + logger.info( + f"Sent {len(tx_hashes)} transactions: {[str(h) for h in tx_hashes[:5]]}" + + ( + f" and {len(tx_hashes) - 5} more" + if len(tx_hashes) > 5 + else "" + ) + ) + logger.info( + f"Waiting for {len(tx_batch)} transactions to be included in blocks" + ) + responses += self._eth_rpc.wait_for_transactions(tx_batch) + logger.info( + f"All {len(responses)} transactions confirmed in blocks" ) - responses = self._eth_rpc.wait_for_transactions(self._pending_txs) - logger.info(f"All {len(responses)} transactions confirmed in blocks") + for response in responses: + logger.debug(f"Transaction response: {response.model_dump_json()}") return responses diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py index fb81dfcfaa9..06a388694c8 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py @@ -13,6 +13,8 @@ from execution_testing.base_types import ( Account, Address, + Bytes, + Hash, Number, Storage, StorageRootType, @@ -28,9 +30,13 @@ from execution_testing.fixtures import LabeledFixtureFormat from execution_testing.forks import Fork from execution_testing.specs import BaseTest -from execution_testing.test_types import EOA +from execution_testing.test_types import ( + EOA, + compute_deterministic_create2_address, +) from execution_testing.test_types import Alloc as BaseAlloc from execution_testing.test_types.eof.v1 import Container +from execution_testing.tools import Initcode from execution_testing.vm import Bytecode, EVMCodeType, Opcodes CONTRACT_START_ADDRESS_DEFAULT = 0x1000000000000000000000000000000000001000 @@ -95,6 +101,7 @@ class AllocMode(IntEnum): class Alloc(BaseAlloc): """Allocation of accounts in the state, pre and post test execution.""" + _eoa_fund_amount_default: int = PrivateAttr(10**21) _alloc_mode: AllocMode = PrivateAttr() _contract_address_iterator: Iterator[Address] = PrivateAttr() _eoa_iterator: Iterator[EOA] = PrivateAttr() @@ -142,6 +149,61 @@ def code_pre_processor( return Container.Code(code) return code + def deterministic_deploy_contract( + self, + *, + deploy_code: BytesConvertible, + salt: Hash | int = 0, + initcode: BytesConvertible | None = None, + minimum_balance: NumberConvertible = 0, + setup_calldata: BytesConvertible | None = None, + label: str | None = None, + ) -> Address: + """ + Deploy a contract to the allocation at a deterministic location + using a deterministic deployment proxy. + """ + if not isinstance(deploy_code, Bytes): + deploy_code = Bytes(deploy_code) + if initcode is None: + initcode = Initcode(deploy_code=deploy_code) + elif not isinstance(initcode, Bytes): + initcode = Bytes(initcode) + salt = Hash(salt) + contract_address = compute_deterministic_create2_address( + salt=salt, initcode=initcode + ) + max_code_size = self._fork.max_code_size() + assert len(deploy_code) <= max_code_size, ( + f"code too large: {len(deploy_code)} > {max_code_size}" + ) + + super().__setitem__( + contract_address, + Account( + nonce=1, + balance=minimum_balance, + code=deploy_code, + storage={}, + ), + ) + if label is None: + # Try to deduce the label from the code + frame = inspect.currentframe() + if frame is not None: + caller_frame = frame.f_back + if caller_frame is not None: + code_context = inspect.getframeinfo( + caller_frame + ).code_context + if code_context is not None: + line = code_context[0].strip() + if "=" in line: + label = line.split("=")[0].strip() + + contract_address.label = label + return contract_address + def deploy_contract( self, code: BytesConvertible, diff --git a/packages/testing/src/execution_testing/rpc/rpc.py b/packages/testing/src/execution_testing/rpc/rpc.py index 75a08266a0f..54a74182a4e 100644 --- a/packages/testing/src/execution_testing/rpc/rpc.py +++ b/packages/testing/src/execution_testing/rpc/rpc.py @@ -677,7 +677,7 @@ def wait_for_transactions( if tx is not None and tx.block_number is not None: responses.append(tx) logger.info( - f"Tx {tx} was included in block {tx.block_number}" + f"Tx {tx.hash} was included in block {tx.block_number}" ) tx_hashes.pop(i) else: diff --git a/packages/testing/src/execution_testing/test_types/__init__.py b/packages/testing/src/execution_testing/test_types/__init__.py index a8009b3b36a..27fc9e5867a 100644 --- a/packages/testing/src/execution_testing/test_types/__init__.py +++ b/packages/testing/src/execution_testing/test_types/__init__.py @@ -25,6 +25,7 @@ ceiling_division, compute_create2_address, compute_create_address, + compute_deterministic_create2_address, compute_eofcreate_address, ) from .phase_manager import TestPhase, TestPhaseManager @@ -82,6 +83,7 @@ "ceiling_division", "compute_create_address", "compute_create2_address", + "compute_deterministic_create2_address", "compute_eofcreate_address", "keccak256", ) diff --git a/packages/testing/src/execution_testing/test_types/account_types.py b/packages/testing/src/execution_testing/test_types/account_types.py index a221e1d756c..89be70cccdf 100644 --- a/packages/testing/src/execution_testing/test_types/account_types.py +++ b/packages/testing/src/execution_testing/test_types/account_types.py @@ -18,7 +18,6 @@ from coincurve.keys import PrivateKey from ethereum_types.bytes import Bytes20 from ethereum_types.numeric import U256, Bytes32, Uint -from pydantic import PrivateAttr from execution_testing.base_types import ( Account, @@ -165,8 +164,6 @@ def copy(self) -> Self: class Alloc(BaseAlloc): """Allocation of accounts in the state, pre and post test execution.""" - _eoa_fund_amount_default: int = PrivateAttr(10**21) - @dataclass(kw_only=True) class UnexpectedAccountError(Exception): """Unexpected account found in the allocation.""" @@ -388,6 +385,24 @@ def verify_post_alloc(self, got_alloc: "Alloc") -> None: else: raise Alloc.MissingAccountError(address=address) + def deterministic_deploy_contract( + self, + *, + deploy_code: BytesConvertible, + salt: Hash | int = 0, + initcode: BytesConvertible | None = None, + minimum_balance: NumberConvertible = 0, + setup_calldata: BytesConvertible | None = None, + label: str | None = None, + ) -> Address: + """ + Deploy a contract to the allocation at a deterministic location + using a deterministic deployment proxy. + """ + raise NotImplementedError( + "deterministic_deploy_contract is not implemented in the base class" + ) + def deploy_contract( self, code: BytesConvertible, diff --git a/packages/testing/src/execution_testing/test_types/helpers.py b/packages/testing/src/execution_testing/test_types/helpers.py index 7e97e611a28..dd2001c180b 100644 --- a/packages/testing/src/execution_testing/test_types/helpers.py +++ b/packages/testing/src/execution_testing/test_types/helpers.py @@ -19,6 +19,10 @@ Helper functions """ +DETERMINISTIC_DEPLOYMENT_CONTRACT_ADDRESS = Address( + 0x4E59B44847B379578588920CA78FBF26C0B4956C +) + def ceiling_division(a: int, b: int) -> int: """ @@ -72,6 +76,21 @@ def compute_create2_address( return Address(hash_bytes[-20:]) +def compute_deterministic_create2_address( + salt: FixedSizeBytesConvertible, + initcode: BytesConvertible, +) -> Address: + """ + Compute address of the resulting contract created using the `CREATE2` + opcode using the `DETERMINISTIC_DEPLOYMENT_CONTRACT_ADDRESS`. + """ + return compute_create2_address( + address=DETERMINISTIC_DEPLOYMENT_CONTRACT_ADDRESS, + salt=salt, + initcode=initcode, + ) + + def compute_eofcreate_address( address: FixedSizeBytesConvertible, salt: FixedSizeBytesConvertible ) -> Address: From 6f5845d9c36dcc25365fec9bdd377827b5b98a4f Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Tue, 16 Dec 2025 22:10:04 +0000 Subject: [PATCH 02/20] feat(tests): `deterministic_deploy_contract` example --- .../test_deterministic_deployment.py | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 tests/constantinople/eip1014_create2/test_deterministic_deployment.py diff --git a/tests/constantinople/eip1014_create2/test_deterministic_deployment.py b/tests/constantinople/eip1014_create2/test_deterministic_deployment.py new file mode 100644 index 00000000000..faa19dc6c84 --- /dev/null +++ b/tests/constantinople/eip1014_create2/test_deterministic_deployment.py @@ -0,0 +1,58 @@ +""" +Test deterministic deployment of contracts through +`pre.deterministic_deploy_contract`. +""" + +import pytest +from execution_testing import ( + Account, + Alloc, + Hash, + Op, + StateTestFiller, + Transaction, +) + +from .spec import ref_spec_1014 + +REFERENCE_SPEC_GIT_PATH = ref_spec_1014.git_path +REFERENCE_SPEC_VERSION = ref_spec_1014.version + + +@pytest.mark.valid_from("Constantinople") +def test_deterministic_deployment( + state_test: StateTestFiller, + pre: Alloc, +) -> None: + """ + Test deterministic deployments for contracts using + `pre.deterministic_deploy_contract`. + """ + deploy_code = Op.SSTORE(1, Op.CALLDATALOAD(0)) + + contract_address = pre.deterministic_deploy_contract( + deploy_code=deploy_code, + # A setup transaction is sent after deployment with `Hash(0)` as + # calldata to restore the contract's storage. + setup_calldata=Hash(0), + ) + + sender = pre.fund_eoa() + + tx = Transaction( + sender=sender, + to=contract_address, + data=Hash(1), + gas_limit=100_000, + ) + + post = { + contract_address: Account( + deploy_code=deploy_code, + storage={ + 1: 1, + }, + ), + } + + state_test(pre=pre, post=post, tx=tx) From beb755449b972318d14db71c5bbaac2a76366ba6 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Tue, 16 Dec 2025 23:12:46 +0000 Subject: [PATCH 03/20] docs: update --- docs/running_tests/execute/remote.md | 2 ++ docs/running_tests/execute/transaction_metadata.md | 1 + 2 files changed, 3 insertions(+) diff --git a/docs/running_tests/execute/remote.md b/docs/running_tests/execute/remote.md index e382de0b90a..8328b9d0692 100644 --- a/docs/running_tests/execute/remote.md +++ b/docs/running_tests/execute/remote.md @@ -32,6 +32,8 @@ These transactions are created from the seed or worker accounts provided via the When the test is executed, all `pre.fund_eoa` and `pre.deploy_contract` calls generate transactions that create the accounts on chain, instead of placing them directly in the `pre` object like `fill` does. +If a test uses `pre.deterministic_deploy_contract`, the `execute` command first checks if the contract is already present on chain before attempting to deploy it. If the proxy used for deterministic deployment is not present on chain either, the command will automatically deploy it too. The address of these contracts is calculated using `keccak256( 0xff ++ 0x4E59B44847B379578588920CA78FBF26C0B4956C ++ salt ++ keccak256(init_code))[12:]`. + The transactions are collected and only sent after the test function finishes execution. This is done in order to perform optimizations based on the transactions that the test requires to perform its verifications. One optimization is the deferred calculation of the funding amount for the EOA, which is calculated on the fly depending the test transactions that use the account as sender, and this amount is the minimum balance that the account would need in order for the transactions to be included given the current network gas prices. diff --git a/docs/running_tests/execute/transaction_metadata.md b/docs/running_tests/execute/transaction_metadata.md index 2340080c283..71b7a63dbeb 100644 --- a/docs/running_tests/execute/transaction_metadata.md +++ b/docs/running_tests/execute/transaction_metadata.md @@ -30,6 +30,7 @@ Each transaction includes a `TransactionTestMetadata` object with the following Transactions that prepare the test environment: - **`deploy_contract`**: Contract deployment transactions +- **`deterministic_deploy_contract`**: Deterministic contract deployments using `CREATE2` - **`fund_eoa`**: Funding EOAs with initial balances - **`eoa_storage_set`**: Setting storage values for EOAs - **`fund_address`**: Funding specific addresses From 70039792e6fcc2d29e7a972a99006b9574327a08 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Mon, 5 Jan 2026 18:27:14 +0000 Subject: [PATCH 04/20] fix(testing): Remove `minimum_balance`, `setup_calldata` --- .../plugins/execute/pre_alloc.py | 28 +------------------ .../plugins/filler/pre_alloc.py | 14 ++++++---- .../test_types/account_types.py | 9 ++++-- .../test_deterministic_deployment.py | 20 +++++++------ 4 files changed, 28 insertions(+), 43 deletions(-) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py index 24a8fcbb8f0..d7973800002 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py @@ -299,8 +299,6 @@ def deterministic_deploy_contract( deploy_code: BytesConvertible, salt: Hash | int = 0, initcode: BytesConvertible | None = None, - minimum_balance: NumberConvertible = 0, - setup_calldata: BytesConvertible | None = None, label: str | None = None, ) -> Address: """ @@ -320,7 +318,6 @@ def deterministic_deploy_contract( initcode = Initcode(deploy_code=deploy_code) elif not isinstance(initcode, Bytes): initcode = Bytes(initcode) - minimum_balance = Number(minimum_balance) salt = Hash(salt) contract_address = compute_deterministic_create2_address( salt=salt, initcode=initcode @@ -384,14 +381,12 @@ def deterministic_deploy_contract( target=label, to=DETERMINISTIC_DEPLOYMENT_CONTRACT_ADDRESS, data=Bytes(salt) + Bytes(initcode), - value=minimum_balance, gas_limit=deploy_gas_limit, ) logger.info( f"Contract deployment tx created (label={label}): " f"tx_nonce={deploy_tx.nonce}, gas_limit={deploy_gas_limit}, " - f"code_size={len(deploy_code)} bytes, initcode_size={len(initcode)} bytes, " - f"balance={Number(minimum_balance) / 10**18:.18f} ETH" + f"code_size={len(deploy_code)} bytes, initcode_size={len(initcode)} bytes" ) logger.debug( @@ -413,27 +408,6 @@ def deterministic_deploy_contract( ), ) - if setup_calldata is not None: - value = 0 - if balance < minimum_balance: - value = minimum_balance - balance - self._add_pending_tx( - action="deterministic_deploy_contract", - target=label, - value=value, - to=contract_address, - data=setup_calldata, - gas_limit=100_000, - ) - elif balance < minimum_balance: - self._add_pending_tx( - action="deterministic_deploy_contract", - target=label, - value=minimum_balance - balance, - to=contract_address, - gas_limit=100_000, - ) - contract_address.label = label return contract_address diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py index 06a388694c8..b052623b297 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py @@ -155,8 +155,6 @@ def deterministic_deploy_contract( deploy_code: BytesConvertible, salt: Hash | int = 0, initcode: BytesConvertible | None = None, - minimum_balance: NumberConvertible = 0, - setup_calldata: BytesConvertible | None = None, label: str | None = None, ) -> Address: """ @@ -173,16 +171,20 @@ def deterministic_deploy_contract( contract_address = compute_deterministic_create2_address( salt=salt, initcode=initcode ) + if contract_address in self: + raise ValueError( + f"contract address already in pre-alloc: {contract_address}" + ) max_code_size = self._fork.max_code_size() - assert len(deploy_code) <= max_code_size, ( - f"code too large: {len(deploy_code)} > {max_code_size}" - ) + if len(deploy_code) > max_code_size: + raise ValueError( + f"code too large: {len(deploy_code)} > {max_code_size}" + ) super().__setitem__( contract_address, Account( nonce=1, - balance=minimum_balance, code=deploy_code, storage={}, ), diff --git a/packages/testing/src/execution_testing/test_types/account_types.py b/packages/testing/src/execution_testing/test_types/account_types.py index 89be70cccdf..3dd16961e5b 100644 --- a/packages/testing/src/execution_testing/test_types/account_types.py +++ b/packages/testing/src/execution_testing/test_types/account_types.py @@ -391,13 +391,18 @@ def deterministic_deploy_contract( deploy_code: BytesConvertible, salt: Hash | int = 0, initcode: BytesConvertible | None = None, - minimum_balance: NumberConvertible = 0, - setup_calldata: BytesConvertible | None = None, label: str | None = None, ) -> Address: """ Deploy a contract to the allocation at a deterministic location using a deterministic deployment proxy. + + Args: + deploy_code: Contract code to deploy + salt: Salt to use for deterministic deployment + initcode: Initcode to use for deterministic deployment. + Can be `None` and `Initcode` will be used to derive it. + label: Label to use for the contract """ raise NotImplementedError( "deterministic_deploy_contract is not implemented in the base class" diff --git a/tests/constantinople/eip1014_create2/test_deterministic_deployment.py b/tests/constantinople/eip1014_create2/test_deterministic_deployment.py index faa19dc6c84..a0fe5f7ac22 100644 --- a/tests/constantinople/eip1014_create2/test_deterministic_deployment.py +++ b/tests/constantinople/eip1014_create2/test_deterministic_deployment.py @@ -7,9 +7,10 @@ from execution_testing import ( Account, Alloc, + Block, + BlockchainTestFiller, Hash, Op, - StateTestFiller, Transaction, ) @@ -21,7 +22,7 @@ @pytest.mark.valid_from("Constantinople") def test_deterministic_deployment( - state_test: StateTestFiller, + blockchain_test: BlockchainTestFiller, pre: Alloc, ) -> None: """ @@ -31,15 +32,18 @@ def test_deterministic_deployment( deploy_code = Op.SSTORE(1, Op.CALLDATALOAD(0)) contract_address = pre.deterministic_deploy_contract( - deploy_code=deploy_code, - # A setup transaction is sent after deployment with `Hash(0)` as - # calldata to restore the contract's storage. - setup_calldata=Hash(0), + deploy_code=deploy_code ) sender = pre.fund_eoa() - tx = Transaction( + reset_tx = Transaction( + sender=sender, + to=contract_address, + data=Hash(0), + gas_limit=100_000, + ) + set_tx = Transaction( sender=sender, to=contract_address, data=Hash(1), @@ -55,4 +59,4 @@ def test_deterministic_deployment( ), } - state_test(pre=pre, post=post, tx=tx) + blockchain_test(pre=pre, post=post, blocks=[Block(txs=[reset_tx, set_tx])]) From b488fe8dd8cc511bda3e2fcf1e2aa0e7a1d9a4a9 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Mon, 5 Jan 2026 18:30:51 +0000 Subject: [PATCH 05/20] fix(execute): Use `fork.transaction_gas_limit_cap` --- .../pytest_commands/plugins/execute/pre_alloc.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py index d7973800002..756f06e3c3a 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py @@ -375,7 +375,12 @@ def deterministic_deploy_contract( new_bytes=len(initcode) ) deploy_gas_limit += calldata_gas_calculator(data=initcode) - deploy_gas_limit = min(deploy_gas_limit * 2, 30_000_000) + deploy_gas_limit = deploy_gas_limit * 2 + tx_gas_limit_cap = self._fork.transaction_gas_limit_cap() + if tx_gas_limit_cap and deploy_gas_limit > tx_gas_limit_cap: + raise ValueError( + f"deterministic deploy gas limit exceeds the transaction gas limit cap: {deploy_gas_limit} > {tx_gas_limit_cap}" + ) deploy_tx = self._add_pending_tx( action="deterministic_deploy_contract", target=label, @@ -517,8 +522,12 @@ def deploy_contract( deploy_gas_limit += calldata_gas_calculator(data=prepared_initcode) - # Limit the gas limit - deploy_gas_limit = min(deploy_gas_limit * 2, 30_000_000) + deploy_gas_limit = deploy_gas_limit * 2 + tx_gas_limit_cap = self._fork.transaction_gas_limit_cap() + if tx_gas_limit_cap and deploy_gas_limit > tx_gas_limit_cap: + raise ValueError( + f"deploy gas limit exceeds the transaction gas limit cap: {deploy_gas_limit} > {tx_gas_limit_cap}" + ) deploy_tx = self._add_pending_tx( action="deploy_contract", From 71dbfb735883c129156e0b813ab1eb68fdd72cce Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Tue, 6 Jan 2026 04:09:06 +0000 Subject: [PATCH 06/20] fix: Allow storage in deterministic deploy contract --- .../testing/src/execution_testing/__init__.py | 2 ++ .../plugins/execute/pre_alloc.py | 5 ++--- .../pytest_commands/plugins/filler/pre_alloc.py | 17 ++++++++++++++++- .../execution_testing/test_types/__init__.py | 4 ++++ .../test_types/account_types.py | 6 ++++-- .../src/execution_testing/test_types/helpers.py | 3 +++ 6 files changed, 31 insertions(+), 6 deletions(-) diff --git a/packages/testing/src/execution_testing/__init__.py b/packages/testing/src/execution_testing/__init__.py index 53215e8b3cb..7a8bf5a66f7 100644 --- a/packages/testing/src/execution_testing/__init__.py +++ b/packages/testing/src/execution_testing/__init__.py @@ -52,6 +52,7 @@ TransactionTestFiller, ) from .test_types import ( + DETERMINISTIC_DEPLOYMENT_CONTRACT_ADDRESS, EOA, Alloc, AuthorizationTuple, @@ -117,6 +118,7 @@ ) __all__ = ( + "DETERMINISTIC_DEPLOYMENT_CONTRACT_ADDRESS", "AccessList", "Account", "Address", diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py index 756f06e3c3a..0134ce00a67 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py @@ -31,6 +31,7 @@ from execution_testing.rpc import EthRPC from execution_testing.rpc.rpc_types import TransactionByHashResponse from execution_testing.test_types import ( + DETERMINISTIC_DEPLOYMENT_CONTRACT_ADDRESS, EOA, AuthorizationTuple, ChainConfig, @@ -46,9 +47,6 @@ MAX_BYTECODE_SIZE = 24576 MAX_INITCODE_SIZE = MAX_BYTECODE_SIZE * 2 -DETERMINISTIC_DEPLOYMENT_CONTRACT_ADDRESS = Address( - 0x4E59B44847B379578588920CA78FBF26C0B4956C -) DETERMINISTIC_DEPLOYMENT_SIGNER_ADDRESS = Address( 0x3FAB184622DC19B6109349B94811493BF2A45362 ) @@ -299,6 +297,7 @@ def deterministic_deploy_contract( deploy_code: BytesConvertible, salt: Hash | int = 0, initcode: BytesConvertible | None = None, + storage: Storage | StorageRootType | None = None, label: str | None = None, ) -> Address: """ diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py index b052623b297..be2400052e0 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py @@ -31,6 +31,8 @@ from execution_testing.forks import Fork from execution_testing.specs import BaseTest from execution_testing.test_types import ( + DETERMINISTIC_DEPLOYMENT_BYTECODE, + DETERMINISTIC_DEPLOYMENT_CONTRACT_ADDRESS, EOA, compute_deterministic_create2_address, ) @@ -155,6 +157,7 @@ def deterministic_deploy_contract( deploy_code: BytesConvertible, salt: Hash | int = 0, initcode: BytesConvertible | None = None, + storage: Storage | StorageRootType | None = None, label: str | None = None, ) -> Address: """ @@ -167,6 +170,8 @@ def deterministic_deploy_contract( initcode = Initcode(deploy_code=deploy_code) elif not isinstance(initcode, Bytes): initcode = Bytes(initcode) + if storage is None: + storage = {} salt = Hash(salt) contract_address = compute_deterministic_create2_address( salt=salt, initcode=initcode @@ -181,12 +186,22 @@ def deterministic_deploy_contract( f"code too large: {len(deploy_code)} > {max_code_size}" ) + if DETERMINISTIC_DEPLOYMENT_CONTRACT_ADDRESS not in self: + super().__setitem__( + DETERMINISTIC_DEPLOYMENT_CONTRACT_ADDRESS, + Account( + nonce=1, + code=DETERMINISTIC_DEPLOYMENT_BYTECODE, + storage={}, + ), + ) + super().__setitem__( contract_address, Account( nonce=1, code=deploy_code, - storage={}, + storage=storage, ), ) if label is None: diff --git a/packages/testing/src/execution_testing/test_types/__init__.py b/packages/testing/src/execution_testing/test_types/__init__.py index 27fc9e5867a..5f16085e81e 100644 --- a/packages/testing/src/execution_testing/test_types/__init__.py +++ b/packages/testing/src/execution_testing/test_types/__init__.py @@ -20,6 +20,8 @@ ) from .chain_config_types import ChainConfig, ChainConfigDefaults from .helpers import ( + DETERMINISTIC_DEPLOYMENT_BYTECODE, + DETERMINISTIC_DEPLOYMENT_CONTRACT_ADDRESS, TestParameterGroup, add_kzg_version, ceiling_division, @@ -47,6 +49,8 @@ from .utils import Removable, keccak256 __all__ = ( + "DETERMINISTIC_DEPLOYMENT_BYTECODE", + "DETERMINISTIC_DEPLOYMENT_CONTRACT_ADDRESS", "Alloc", "AuthorizationTuple", "BalAccountChange", diff --git a/packages/testing/src/execution_testing/test_types/account_types.py b/packages/testing/src/execution_testing/test_types/account_types.py index 3dd16961e5b..0ffe95b28e5 100644 --- a/packages/testing/src/execution_testing/test_types/account_types.py +++ b/packages/testing/src/execution_testing/test_types/account_types.py @@ -391,6 +391,7 @@ def deterministic_deploy_contract( deploy_code: BytesConvertible, salt: Hash | int = 0, initcode: BytesConvertible | None = None, + storage: Storage | StorageRootType | None = None, label: str | None = None, ) -> Address: """ @@ -400,8 +401,9 @@ def deterministic_deploy_contract( Args: deploy_code: Contract code to deploy salt: Salt to use for deterministic deployment - initcode: Initcode to use for deterministic deployment. - Can be `None` and `Initcode` will be used to derive it. + initcode: Initcode to use for deterministic deployment + Can be `None` and `Initcode` will be used to derive it + storage: Set the storage during test filling label: Label to use for the contract """ raise NotImplementedError( diff --git a/packages/testing/src/execution_testing/test_types/helpers.py b/packages/testing/src/execution_testing/test_types/helpers.py index dd2001c180b..3ffb0896b6f 100644 --- a/packages/testing/src/execution_testing/test_types/helpers.py +++ b/packages/testing/src/execution_testing/test_types/helpers.py @@ -22,6 +22,9 @@ DETERMINISTIC_DEPLOYMENT_CONTRACT_ADDRESS = Address( 0x4E59B44847B379578588920CA78FBF26C0B4956C ) +DETERMINISTIC_DEPLOYMENT_BYTECODE = Bytes( + "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3" +) def ceiling_division(a: int, b: int) -> int: From 7a520ae400359cabf0d324ea9d881672faa337c4 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Tue, 6 Jan 2026 22:09:17 +0000 Subject: [PATCH 07/20] feat(testing): Add `minimum_balance` to `pre.fund_address` --- .../plugins/execute/pre_alloc.py | 84 ++++++++++++++----- .../plugins/filler/pre_alloc.py | 18 +++- .../test_types/account_types.py | 13 ++- 3 files changed, 91 insertions(+), 24 deletions(-) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py index 0134ce00a67..9c36ef8a127 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py @@ -330,6 +330,9 @@ def deterministic_deploy_contract( f"Expected: {deploy_code}, " f"Current: {chain_code}" ) + logger.info( + f"Contract already deployed at {contract_address} (label={label})" + ) else: # 2) Determine if the deployment contract is already on chain deployment_contract_code = self._eth_rpc.get_code( @@ -386,6 +389,7 @@ def deterministic_deploy_contract( to=DETERMINISTIC_DEPLOYMENT_CONTRACT_ADDRESS, data=Bytes(salt) + Bytes(initcode), gas_limit=deploy_gas_limit, + value=0, ) logger.info( f"Contract deployment tx created (label={label}): " @@ -708,7 +712,11 @@ def fund_eoa( return eoa def fund_address( - self, address: Address, amount: NumberConvertible + self, + address: Address, + amount: NumberConvertible, + *, + minimum_balance: bool = False, ) -> None: """ Fund an address with a given amount. @@ -716,29 +724,62 @@ def fund_address( If the address is already present in the pre-alloc the amount will be added to its existing balance. """ - logger.debug( - f"Funding address {address} (label={address.label}): " - f"{Number(amount) / 10**18:.18f} ETH" - ) - self._add_pending_tx( - action="fund_address", - target=address.label, - to=address, - value=amount, - ) + current_balance = self._eth_rpc.get_balance(address) + fund_amount = Number(amount) + + if minimum_balance: + if current_balance >= fund_amount: + logger.info( + f"Skipping funding for address {address} (label={address.label}): " + f"current balance {current_balance / 10**18:.18f} ETH >= " + f"minimum {fund_amount / 10**18:.18f} ETH" + ) + if address in self: + account = self[address] + if account is not None: + account.balance = ZeroPaddedHexNumber(current_balance) + else: + super().__setitem__( + address, Account(balance=current_balance) + ) + return + logger.debug( + f"Funding address to minimum balance {address} (label={address.label}): " + f"{fund_amount / 10**18:.18f} ETH" + ) + self._add_pending_tx( + action="fund_address", + target=address.label, + to=address, + value=fund_amount - current_balance, + ) + new_balance = fund_amount + else: + logger.debug( + f"Funding address {address} (label={address.label}): " + f"{fund_amount / 10**18:.18f} ETH" + ) + self._add_pending_tx( + action="fund_address", + target=address.label, + to=address, + value=amount, + ) + new_balance = current_balance + fund_amount + if address in self: account = self[address] if account is not None: - current_balance = account.balance or 0 - new_balance = current_balance + Number(amount) account.balance = ZeroPaddedHexNumber(new_balance) logger.debug( f"Updated balance for existing address {address}: " f"{current_balance / 10**18:.18f} ETH -> {new_balance / 10**18:.18f} ETH" ) - return + else: + super().__setitem__(address, Account(balance=new_balance)) + else: + super().__setitem__(address, Account(balance=new_balance)) - super().__setitem__(address, Account(balance=amount)) logger.info( f"Address {address} funding tx created (label={address.label}): " f"{Number(amount) / 10**18:.18f} ETH" @@ -793,10 +834,15 @@ def minimum_balance_for_pending_transactions( if tx.value is None: # WARN: This currently fails if there's an account with `pre.fund_eoa()` that # never sends a transaction during the test. - assert tx.to in sender_balances, ( - "Sender balance must be set before sending" - f"{tx.model_dump_json()}" - ) + if tx.to not in sender_balances: + error_message = ( + "Sender balance must be set before sending:" + f"\nTransaction: {tx.model_dump_json(indent=2)}" + ) + if tx.metadata is not None: + error_message += f"\nMetadata: {tx.metadata.model_dump_json(indent=2)}" + logger.error(error_message) + raise ValueError(error_message) sender_balance = sender_balances[tx.to] logger.info( f"Deferred EOA balance for {tx.to} set to {sender_balance / 10**18:.18f} ETH" diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py index be2400052e0..f029c37fe62 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py @@ -357,7 +357,11 @@ def fund_eoa( return eoa def fund_address( - self, address: Address, amount: NumberConvertible + self, + address: Address, + amount: NumberConvertible, + *, + minimum_balance: bool = False, ) -> None: """ Fund an address with a given amount. @@ -369,9 +373,15 @@ def fund_address( account = self[address] if account is not None: current_balance = account.balance or 0 - account.balance = ZeroPaddedHexNumber( - current_balance + Number(amount) - ) + fund_amount = Number(amount) + if minimum_balance: + if current_balance >= fund_amount: + return + account.balance = ZeroPaddedHexNumber(fund_amount) + else: + account.balance = ZeroPaddedHexNumber( + current_balance + fund_amount + ) return super().__setitem__(address, Account(balance=amount)) diff --git a/packages/testing/src/execution_testing/test_types/account_types.py b/packages/testing/src/execution_testing/test_types/account_types.py index 0ffe95b28e5..c6b39982835 100644 --- a/packages/testing/src/execution_testing/test_types/account_types.py +++ b/packages/testing/src/execution_testing/test_types/account_types.py @@ -444,13 +444,24 @@ def fund_eoa( ) def fund_address( - self, address: Address, amount: NumberConvertible + self, + address: Address, + amount: NumberConvertible, + *, + minimum_balance: bool = False, ) -> None: """ Fund an address with a given amount. If the address is already present in the pre-alloc the amount will be added to its existing balance. + + Args: + address: Address to fund + amount: Amount to fund in Wei + minimum_balance: If set to True, account will be checked to have a + minimum balance of `amount` and only fund if the balance is + insufficient """ raise NotImplementedError( "fund_address is not implemented in the base class" From 0630b49a650a331e95b5857e5532dedfbee63ce5 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Tue, 6 Jan 2026 22:24:30 +0000 Subject: [PATCH 08/20] fix(testing): tox --- .../cli/pytest_commands/plugins/execute/pre_alloc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py index 9c36ef8a127..97a464d65d9 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py @@ -725,7 +725,7 @@ def fund_address( added to its existing balance. """ current_balance = self._eth_rpc.get_balance(address) - fund_amount = Number(amount) + fund_amount = int(Number(amount)) if minimum_balance: if current_balance >= fund_amount: From 76cd47658c70b68a71d3174cef8819eaa03583da Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Tue, 6 Jan 2026 23:07:11 +0000 Subject: [PATCH 09/20] fix: Review comments --- .../plugins/execute/pre_alloc.py | 3 --- .../execution_testing/test_types/helpers.py | 20 +++++++++++++++++-- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py index 97a464d65d9..3993100f05f 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py @@ -50,9 +50,6 @@ DETERMINISTIC_DEPLOYMENT_SIGNER_ADDRESS = Address( 0x3FAB184622DC19B6109349B94811493BF2A45362 ) -DETERMINISTIC_DEPLOYMENT_TRANSACTION = Bytes( - "0xf8a58085174876e800830186a08080b853604580600e600039806000f350fe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf31ba02222222222222222222222222222222222222222222222222222222222222222a02222222222222222222222222222222222222222222222222222222222222222" -) logger = get_logger(__name__) diff --git a/packages/testing/src/execution_testing/test_types/helpers.py b/packages/testing/src/execution_testing/test_types/helpers.py index 3ffb0896b6f..934ee7e6a08 100644 --- a/packages/testing/src/execution_testing/test_types/helpers.py +++ b/packages/testing/src/execution_testing/test_types/helpers.py @@ -22,8 +22,24 @@ DETERMINISTIC_DEPLOYMENT_CONTRACT_ADDRESS = Address( 0x4E59B44847B379578588920CA78FBF26C0B4956C ) -DETERMINISTIC_DEPLOYMENT_BYTECODE = Bytes( - "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3" +DETERMINISTIC_DEPLOYMENT_BYTECODE = ( + Op.ADD(Op.CALLDATASIZE, 2**256 - 32) + + Op.PUSH1[0x0] + + Op.CALLDATACOPY(dest_offset=Op.DUP3, offset=0x20, size=Op.DUP2) + + Op.CREATE2( + value=Op.CALLVALUE, + offset=Op.DUP3, + size=Op.DUP3, + salt=Op.CALLDATALOAD(Op.DUP1), + ) + + Op.JUMPI(pc=0x39, condition=Op.ISZERO(Op.ISZERO(Op.DUP1))) + + Op.REVERT(offset=Op.DUP3, size=Op.DUP2) + + Op.JUMPDEST + + Op.MSTORE(offset=Op.DUP3, value=Op.DUP1) + + Op.POP + + Op.POP + + Op.POP + + Op.RETURN(offset=0xC, size=0x14) ) From 717e80fdf31de6fb5e0275f5a34d96e4c16176f0 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Wed, 7 Jan 2026 21:25:56 +0000 Subject: [PATCH 10/20] feat(testing/forks): Determinisitc predeploy fork method --- .../testing/src/execution_testing/forks/base_fork.py | 12 ++++++++++++ .../src/execution_testing/forks/forks/forks.py | 8 ++++++++ 2 files changed, 20 insertions(+) diff --git a/packages/testing/src/execution_testing/forks/base_fork.py b/packages/testing/src/execution_testing/forks/base_fork.py index 34979e8c492..4232ef1e5f9 100644 --- a/packages/testing/src/execution_testing/forks/base_fork.py +++ b/packages/testing/src/execution_testing/forks/base_fork.py @@ -624,6 +624,18 @@ def system_contracts( """Return list system-contracts supported by the fork.""" pass + @classmethod + @abstractmethod + def deterministic_factory_predeploy_address( + cls, *, block_number: int = 0, timestamp: int = 0 + ) -> Address | None: + """ + Return the address of the deterministic factory predeploy at a + given fork. Return `None` if the fork does not support deterministic + deployment. + """ + pass + @classmethod @prefer_transition_to_method @abstractmethod diff --git a/packages/testing/src/execution_testing/forks/forks/forks.py b/packages/testing/src/execution_testing/forks/forks/forks.py index 89e62df6f9e..0f5725f27a9 100644 --- a/packages/testing/src/execution_testing/forks/forks/forks.py +++ b/packages/testing/src/execution_testing/forks/forks/forks.py @@ -600,6 +600,14 @@ def system_contracts( del block_number, timestamp return [] + @classmethod + def deterministic_factory_predeploy_address( + cls, *, block_number: int = 0, timestamp: int = 0 + ) -> Address | None: + """At Genesis, no deterministic factory predeploy is present.""" + del block_number, timestamp + return None + @classmethod def evm_code_types( cls, *, block_number: int = 0, timestamp: int = 0 From 724a7e469047172ba4a35272e00cf314166aed37 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Wed, 7 Jan 2026 21:47:26 +0000 Subject: [PATCH 11/20] feat(testing/cli): add `execute deploy-required-contracts` --- .../testing/src/execution_testing/__init__.py | 4 +- .../cli/pytest_commands/execute.py | 13 +++ .../plugins/execute/contracts.py | 88 +++++++++++++++++ .../execute/deploy_required_contracts.py | 70 +++++++++++++ .../execute_deploy_required_contracts.py | 79 +++++++++++++++ .../execute/execute_flags/execute_flags.py | 24 ++++- .../plugins/execute/pre_alloc.py | 98 +++++++++++-------- .../plugins/execute/rpc/remote.py | 16 ++- .../plugins/execute/rpc/remote_seed_sender.py | 17 +++- .../plugins/filler/pre_alloc.py | 18 ++-- ...test-execute-deploy-required-contracts.ini | 14 +++ .../execution_testing/test_types/__init__.py | 8 +- .../execution_testing/test_types/helpers.py | 15 ++- 13 files changed, 396 insertions(+), 68 deletions(-) create mode 100644 packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/contracts.py create mode 100644 packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/deploy_required_contracts.py create mode 100644 packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/execute_deploy_required_contracts.py create mode 100644 packages/testing/src/execution_testing/cli/pytest_commands/pytest_ini_files/pytest-execute-deploy-required-contracts.ini diff --git a/packages/testing/src/execution_testing/__init__.py b/packages/testing/src/execution_testing/__init__.py index 7a8bf5a66f7..8f092e6b485 100644 --- a/packages/testing/src/execution_testing/__init__.py +++ b/packages/testing/src/execution_testing/__init__.py @@ -52,7 +52,7 @@ TransactionTestFiller, ) from .test_types import ( - DETERMINISTIC_DEPLOYMENT_CONTRACT_ADDRESS, + DETERMINISTIC_FACTORY_ADDRESS, EOA, Alloc, AuthorizationTuple, @@ -118,7 +118,7 @@ ) __all__ = ( - "DETERMINISTIC_DEPLOYMENT_CONTRACT_ADDRESS", + "DETERMINISTIC_FACTORY_ADDRESS", "AccessList", "Account", "Address", diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/execute.py b/packages/testing/src/execution_testing/cli/pytest_commands/execute.py index 303ff5fcdd0..11c7d80cdb5 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/execute.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/execute.py @@ -95,3 +95,16 @@ def command(pytest_args: List[str], **_kwargs: Any) -> None: Path("cli/pytest_commands/plugins/execute/execute_recover.py") ], ) + +deploy_required_contracts = _create_execute_subcommand( + "deploy-required-contracts", + "pytest-execute-deploy-required-contracts.ini", + "Deploy required contracts (deterministic deployment proxy) to a network.", + required_args=[ + "--rpc-endpoint=http://localhost:8545", + "--chain-id=1", + ], + command_logic_test_paths=[ + Path("cli/pytest_commands/plugins/execute/execute_deploy_required_contracts.py") + ], +) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/contracts.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/contracts.py new file mode 100644 index 00000000000..809edf862c1 --- /dev/null +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/contracts.py @@ -0,0 +1,88 @@ +"""Methods to deploy required contracts for execute command.""" + +from execution_testing.base_types import Address +from execution_testing.forks import Fork +from execution_testing.logging import get_logger +from execution_testing.rpc import EthRPC +from execution_testing.test_types import ( + DETERMINISTIC_FACTORY_ADDRESS, + DETERMINISTIC_FACTORY_BYTECODE, + EOA, + Transaction, +) + +logger = get_logger(__name__) + + +def check_deterministic_factory_deployment( + *, + eth_rpc: EthRPC, + fork: Fork, +) -> Address | None: + """Check if the deterministic deployment contract is deployed.""" + fork_deterministic_factory_predeploy_address = ( + fork.deterministic_factory_predeploy_address() + ) + if fork_deterministic_factory_predeploy_address is not None: + return fork_deterministic_factory_predeploy_address + # Check the manually deployed contract. + deployment_contract_code = eth_rpc.get_code(DETERMINISTIC_FACTORY_ADDRESS) + if deployment_contract_code == DETERMINISTIC_FACTORY_BYTECODE: + return DETERMINISTIC_FACTORY_ADDRESS + + return None + + +def deploy_deterministic_factory_contract( + *, + eth_rpc: EthRPC, + seed_key: EOA, + gas_price: int, +) -> None: + """Deploy the deterministic deployment contract.""" + deploy_tx = Transaction( + ty=0, + protected=False, + nonce=0, + gas_price=0x174876E800, + gas_limit=0x0186A0, + to=None, + value=0, + data=( + "0x604580600e600039806000f350fe7fffffffffffffffffffffffffffffffffff" + "ffffffffffffffffffffffffffffe03601600081602082378035828234f5801515" + "6039578182fd5b8082525050506014600cf3" + ), + v=0x1B, + r=0x2222222222222222222222222222222222222222222222222222222222222222, + s=0x2222222222222222222222222222222222222222222222222222222222222222, + ).with_signature_and_sender() + + required_deployer_balance = deploy_tx.gas_price * deploy_tx.gas_limit + current_balance = eth_rpc.get_balance(deploy_tx.sender) + if current_balance < required_deployer_balance: + # Add transaction to fund the deployer. + fund_amount = required_deployer_balance - current_balance + logger.info( + "Funding deterministic factory deployer address " + f"{deploy_tx.sender} with " + f"{fund_amount / 10**18:.18f} ETH" + ) + fund_tx = Transaction( + to=deploy_tx.sender, + value=fund_amount, + gas_price=gas_price, + sender=seed_key, + ) + eth_rpc.send_wait_transaction(fund_tx) + logger.info(f"Funding transaction mined: {fund_tx.hash}") + + # Add deployment transaction. + logger.info("Sending deployment transaction...") + eth_rpc.send_wait_transaction(deploy_tx) + logger.info(f"Deployment transaction mined: {deploy_tx.hash}") + deployment_contract_code = eth_rpc.get_code(DETERMINISTIC_FACTORY_ADDRESS) + logger.info(f"Deployment contract code: {deployment_contract_code}") + assert deployment_contract_code == DETERMINISTIC_FACTORY_BYTECODE, ( + f"Deployment contract code is not the expected code: {deployment_contract_code} != {DETERMINISTIC_FACTORY_BYTECODE}" + ) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/deploy_required_contracts.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/deploy_required_contracts.py new file mode 100644 index 00000000000..480afd8f716 --- /dev/null +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/deploy_required_contracts.py @@ -0,0 +1,70 @@ +"""Pytest plugin to deploy required contracts for execute command.""" + +from typing import Literal + +import pytest + +from execution_testing.forks import Fork, ForkAdapter +from execution_testing.rpc import EthRPC + + +def pytest_addoption(parser: pytest.Parser) -> None: + """Add command-line options to pytest.""" + deploy_group = parser.getgroup( + "execute", "Arguments for deploying required contracts." + ) + deploy_group.addoption( + "--gas-price", + action="store", + dest="deploy_gas_price", + type=int, + default=None, + help=( + "Gas price to use for deployment transactions in wei. " + "Default: 1.5x the current network gas price." + ), + ) + deploy_group.addoption( + "--check-only", + action="store_true", + dest="check_only", + default=False, + help="Only check if contracts are deployed without deploying them.", + ) + deploy_group.addoption( + "--fork", + action="store", + dest="fork", + type=ForkAdapter.validate_python, + default=None, + help="Currently active fork of the network.", + ) + + +@pytest.fixture(scope="session") +def gas_price(request: pytest.FixtureRequest, eth_rpc: EthRPC) -> int: + """Get the gas price for deployment transactions.""" + gas_price_option = request.config.option.deploy_gas_price + if gas_price_option is not None: + return gas_price_option + + # Use network gas price + return int(eth_rpc.gas_price() * 1.5) + + +@pytest.fixture(scope="session") +def check_only(request: pytest.FixtureRequest) -> bool: + """Get the check-only flag.""" + return request.config.option.check_only + + +@pytest.fixture(scope="session") +def session_fork(request: pytest.FixtureRequest) -> Fork: + """Return a default fork for the deploy command.""" + return request.config.option.fork + + +@pytest.fixture(scope="session") +def transactions_per_block() -> Literal[1]: + """Return the number of transactions per block for the deploy command.""" + return 1 diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/execute_deploy_required_contracts.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/execute_deploy_required_contracts.py new file mode 100644 index 00000000000..94d5c2bad8b --- /dev/null +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/execute_deploy_required_contracts.py @@ -0,0 +1,79 @@ +"""Pytest test to deploy required contracts for execute command.""" + +import pytest + +from execution_testing.base_types import Bytes +from execution_testing.forks import Fork +from execution_testing.logging import get_logger +from execution_testing.rpc import EthRPC +from execution_testing.test_types import ( + DETERMINISTIC_FACTORY_ADDRESS, + DETERMINISTIC_FACTORY_BYTECODE, + EOA, +) + +from .contracts import ( + check_deterministic_factory_deployment, + deploy_deterministic_factory_contract, +) + +logger = get_logger(__name__) + + +def test_deploy_deterministic_deployment_contract( + seed_key: EOA, + gas_price: int, + eth_rpc: EthRPC, + check_only: bool, + session_fork: Fork, +) -> None: + """Deploy the deterministic deployment contract to the network.""" + # Check if contract already deployed + current_deterministic_deployment_contract_address = ( + check_deterministic_factory_deployment( + eth_rpc=eth_rpc, fork=session_fork + ) + ) + if current_deterministic_deployment_contract_address is not None: + logger.info( + f"✓ Deterministic deployment contract already deployed at " + f"{current_deterministic_deployment_contract_address}" + ) + if check_only: + print( + f"✓ Contract is already deployed at {current_deterministic_deployment_contract_address}" + ) + else: + print( + f"Contract already exists at {current_deterministic_deployment_contract_address}, skipping deployment" + ) + return + + if check_only: + logger.info( + f"✗ Deterministic deployment contract NOT deployed at {DETERMINISTIC_FACTORY_ADDRESS}" + ) + print(f"✗ Contract is NOT deployed at {DETERMINISTIC_FACTORY_ADDRESS}") + pytest.fail("Contract not deployed (check-only mode)") + + try: + deploy_deterministic_factory_contract( + eth_rpc=eth_rpc, seed_key=seed_key, gas_price=gas_price + ) + except Exception as e: + pytest.fail(f"Failed to deploy contract: {e}") + + # Verify deployment + deployed_code = eth_rpc.get_code(DETERMINISTIC_FACTORY_ADDRESS) + if deployed_code != Bytes(DETERMINISTIC_FACTORY_BYTECODE): + pytest.fail( + f"Verification failed: Contract code mismatch at {DETERMINISTIC_FACTORY_ADDRESS}. " + f"Expected: {DETERMINISTIC_FACTORY_BYTECODE}, " + f"Deployed: {deployed_code}" + ) + + logger.info("✓ Successfully deployed deterministic deployment contract!") + print( + f"✓ Successfully deployed contract at {DETERMINISTIC_FACTORY_ADDRESS}" + ) + print(f" Contract size: {len(deployed_code)} bytes") diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/execute_flags/execute_flags.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/execute_flags/execute_flags.py index 7e7696f3e63..80699428f2b 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/execute_flags/execute_flags.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/execute_flags/execute_flags.py @@ -1,5 +1,7 @@ """Plugin that handles correct setting of chain-id and rpc-chain-id.""" +import os + import pytest from execution_testing.test_types.chain_config_types import ChainConfigDefaults @@ -47,17 +49,31 @@ def pytest_configure(config: pytest.Config) -> None: chain_id = config.getoption("chain_id") rpc_chain_id = config.getoption("rpc_chain_id") - if not ((chain_id is None) ^ (rpc_chain_id is None)): # XOR + if chain_id is None and rpc_chain_id is None: + # Try to get the chain ID from the environment variable + chain_id = os.environ.get("CHAIN_ID") + if chain_id is None: + chain_id = os.environ.get("RPC_CHAIN_ID") + if chain_id is not None: + chain_id = int(chain_id) + + elif not ((chain_id is None) ^ (rpc_chain_id is None)): # XOR pytest.exit( "ERROR: you must either pass --chain-id or --rpc-chain-id, " "but not both!\n" f"You passed: chain-id={chain_id}, rpc-chain-id={rpc_chain_id}", returncode=4, ) + else: + # Use rpc_chain_id if chain_id is not provided (for backwards compatibility) + if not chain_id: + chain_id = rpc_chain_id - # Use rpc_chain_id if chain_id is not provided (for backwards compatibility) - if not chain_id: - chain_id = rpc_chain_id + if chain_id is None: + pytest.exit( + "Chain ID must be provided with the --chain-id/--rpc-chain-id flags or " + "the CHAIN_ID/RPC_CHAIN_ID environment variables." + ) # write to config ChainConfigDefaults.chain_id = chain_id diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py index 3993100f05f..5e58a8a38ba 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py @@ -7,6 +7,7 @@ import pytest import yaml +from filelock import FileLock from pydantic import PrivateAttr from execution_testing.base_types import ( @@ -31,7 +32,7 @@ from execution_testing.rpc import EthRPC from execution_testing.rpc.rpc_types import TransactionByHashResponse from execution_testing.test_types import ( - DETERMINISTIC_DEPLOYMENT_CONTRACT_ADDRESS, + DETERMINISTIC_FACTORY_ADDRESS, EOA, AuthorizationTuple, ChainConfig, @@ -44,12 +45,14 @@ from execution_testing.tools import Initcode from execution_testing.vm import Bytecode, EVMCodeType, Op +from .contracts import ( + check_deterministic_factory_deployment, + deploy_deterministic_factory_contract, +) + MAX_BYTECODE_SIZE = 24576 MAX_INITCODE_SIZE = MAX_BYTECODE_SIZE * 2 -DETERMINISTIC_DEPLOYMENT_SIGNER_ADDRESS = Address( - 0x3FAB184622DC19B6109349B94811493BF2A45362 -) logger = get_logger(__name__) @@ -185,6 +188,45 @@ def eoa_iterator(request: pytest.FixtureRequest) -> Iterator[EOA]: return iter(EOA(key=i, nonce=0) for i in count(start=eoa_start)) +@pytest.fixture(scope="session", autouse=True) +def execute_required_contracts( + session_fork: Fork, + session_worker_key: EOA, + eth_rpc: EthRPC, + sender_funding_transactions_gas_price: int, + session_temp_folder: Path, +) -> None: + """ + Deploy required contracts for the execute command: + + - Deterministic deployment proxy + """ + base_lock_file = session_temp_folder / "execute_required_contracts.lock" + with FileLock(base_lock_file): + logger.info( + "Checking if deterministic factory contract is already deployed" + ) + if ( + check_deterministic_factory_deployment( + eth_rpc=eth_rpc, fork=session_fork + ) + is None + ): + try: + deploy_deterministic_factory_contract( + eth_rpc=eth_rpc, + seed_key=session_worker_key, + gas_price=sender_funding_transactions_gas_price, + ) + except Exception as e: + raise RuntimeError( + f"Error deploying deterministic deployment contract:\n{e}" + "\nTry deploying the contract manually using a different " + "RPC endpoint with the following command:\n" + "uv run execute deploy-required-contracts" + ) from e + + class PendingTransaction(Transaction): """ Custom transaction class that defines a transaction that is yet to be sent. @@ -193,7 +235,6 @@ class PendingTransaction(Transaction): """ value: HexNumber | None = None # type: ignore - requires_predecessor: bool = False class Alloc(BaseAlloc): @@ -316,7 +357,7 @@ def deterministic_deploy_contract( initcode = Bytes(initcode) salt = Hash(salt) contract_address = compute_deterministic_create2_address( - salt=salt, initcode=initcode + salt=salt, initcode=initcode, fork=self._fork ) # 1) Determine if this contract already exists chain_code = self._eth_rpc.get_code(contract_address) @@ -331,39 +372,15 @@ def deterministic_deploy_contract( f"Contract already deployed at {contract_address} (label={label})" ) else: - # 2) Determine if the deployment contract is already on chain - deployment_contract_code = self._eth_rpc.get_code( - DETERMINISTIC_DEPLOYMENT_CONTRACT_ADDRESS - ) - if deployment_contract_code == b"": - # 2b) Add transaction to fund the deployer. - self._add_pending_tx( - action="deterministic_deploy_contract", - target=f"{DETERMINISTIC_DEPLOYMENT_SIGNER_ADDRESS}", - to=DETERMINISTIC_DEPLOYMENT_SIGNER_ADDRESS, - value=10**16, - ) - # 2c) Add deployment transaction. - self._add_pending_tx( - action="deterministic_deploy_contract", - target=f"{DETERMINISTIC_DEPLOYMENT_CONTRACT_ADDRESS}", - ty=0, - protected=False, - nonce=0, - gas_price=0x174876E800, - gas_limit=0x0186A0, - to=None, - value=0, - data=Bytes( - "0x604580600e600039806000f350fe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3" - ), - v=0x1B, - r=0x2222222222222222222222222222222222222222222222222222222222222222, - s=0x2222222222222222222222222222222222222222222222222222222222222222, - requires_predecessor=True, + # Assert the deployment contract is already on chain + assert ( + check_deterministic_factory_deployment( + eth_rpc=self._eth_rpc, fork=self._fork ) + is not None + ), "Deployment contract code is not found" - # 3) Deploy the actual contract. + # Deploy the actual contract. deploy_gas_limit = ( gas_costs.G_TRANSACTION + gas_costs.G_TRANSACTION_CREATE ) @@ -383,7 +400,7 @@ def deterministic_deploy_contract( deploy_tx = self._add_pending_tx( action="deterministic_deploy_contract", target=label, - to=DETERMINISTIC_DEPLOYMENT_CONTRACT_ADDRESS, + to=DETERMINISTIC_FACTORY_ADDRESS, data=Bytes(salt) + Bytes(initcode), gas_limit=deploy_gas_limit, value=0, @@ -440,8 +457,6 @@ def deploy_contract( calldata_gas_calculator = self._fork.calldata_gas_calculator( block_number=0, timestamp=0 ) - if not isinstance(code, Bytes): - code = Bytes(code) if not isinstance(storage, Storage): storage = Storage(storage) # type: ignore @@ -864,11 +879,12 @@ def send_pending_transactions(self) -> List[TransactionByHashResponse]: ) transaction_batches: List[List[PendingTransaction]] = [] last_tx_batch: List[PendingTransaction] = [] + MAX_TXS_PER_BATCH = 100 for tx in self._pending_txs: assert tx.value is not None, ( "Transaction value must be set before sending them to the RPC." ) - if tx.requires_predecessor and last_tx_batch: + if len(last_tx_batch) >= MAX_TXS_PER_BATCH: transaction_batches.append(last_tx_batch) last_tx_batch = [] last_tx_batch.append(tx) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/rpc/remote.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/rpc/remote.py index 423697aa652..14424c21d0c 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/rpc/remote.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/rpc/remote.py @@ -1,5 +1,6 @@ """Pytest plugin to run the execute in remote-rpc-mode.""" +import os from pathlib import Path import pytest @@ -21,7 +22,7 @@ def pytest_addoption(parser: pytest.Parser) -> None: ) remote_rpc_group.addoption( "--rpc-endpoint", - required=True, + required=False, action="store", dest="rpc_endpoint", help="RPC endpoint to an execution client", @@ -81,7 +82,14 @@ def pytest_addoption(parser: pytest.Parser) -> None: def pytest_configure(config: pytest.Config) -> None: """Check if a chain ID configuration is provided.""" # Verify the chain ID configuration is consistent with the remote RPC endpoint - rpc_endpoint = config.getoption("rpc_endpoint") + rpc_endpoint = config.getoption("rpc_endpoint") or os.environ.get( + "RPC_ENDPOINT" + ) + if rpc_endpoint is None: + pytest.fail( + "RPC endpoint must be provided with the --rpc-endpoint flag or " + "the RPC_ENDPOINT environment variable." + ) eth_rpc = EthRPC(rpc_endpoint) remote_chain_id = eth_rpc.chain_id() if remote_chain_id != ChainConfigDefaults.chain_id: @@ -138,7 +146,9 @@ def rpc_endpoint(request: pytest.FixtureRequest) -> str: Return remote RPC endpoint to be used to make requests to the execution client. """ - return request.config.getoption("rpc_endpoint") + return request.config.getoption("rpc_endpoint") or os.environ.get( + "RPC_ENDPOINT" + ) @pytest.fixture(autouse=True, scope="session") diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/rpc/remote_seed_sender.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/rpc/remote_seed_sender.py index 9f26bcfa4cb..8c3569b3007 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/rpc/remote_seed_sender.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/rpc/remote_seed_sender.py @@ -1,10 +1,11 @@ """Seed sender on a remote execution client.""" +import os from typing import Generator import pytest -from execution_testing.base_types import Hash, Number +from execution_testing.base_types import Number from execution_testing.logging import get_logger from execution_testing.rpc import EthRPC from execution_testing.test_types import EOA @@ -22,13 +23,14 @@ def pytest_addoption(parser: pytest.Parser) -> None: remote_seed_sender_group.addoption( "--rpc-seed-key", action="store", - required=True, + required=False, dest="rpc_seed_key", help=( "Seed key used to fund all sender keys. This account must have a balance of at least " "`sender_key_initial_balance` * `workers` + gas fees. It should also be " "exclusively used by this command because the nonce is only checked once and if " - "it's externally increased, the seed transactions might fail." + "it's externally increased, the seed transactions might fail. " + "Can also be set via RPC_SEED_KEY environment variable." ), ) @@ -41,7 +43,14 @@ def seed_key( Get the seed key from the command flags and create the EOA account object with the updated nonce value from the network. """ - rpc_seed_key = Hash(request.config.getoption("rpc_seed_key")) + rpc_seed_key = request.config.getoption("rpc_seed_key") or os.environ.get( + "RPC_SEED_KEY" + ) + if rpc_seed_key is None: + pytest.fail( + "Seed key must be provided via --rpc-seed-key or RPC_SEED_KEY " + "environment variable" + ) # check the nonce through the rpc client seed_key = EOA(key=rpc_seed_key) seed_key.nonce = Number(eth_rpc.get_transaction_count(seed_key)) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py index f029c37fe62..dc91b686b07 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py @@ -31,8 +31,8 @@ from execution_testing.forks import Fork from execution_testing.specs import BaseTest from execution_testing.test_types import ( - DETERMINISTIC_DEPLOYMENT_BYTECODE, - DETERMINISTIC_DEPLOYMENT_CONTRACT_ADDRESS, + DETERMINISTIC_FACTORY_ADDRESS, + DETERMINISTIC_FACTORY_BYTECODE, EOA, compute_deterministic_create2_address, ) @@ -174,7 +174,7 @@ def deterministic_deploy_contract( storage = {} salt = Hash(salt) contract_address = compute_deterministic_create2_address( - salt=salt, initcode=initcode + salt=salt, initcode=initcode, fork=self._fork ) if contract_address in self: raise ValueError( @@ -186,12 +186,18 @@ def deterministic_deploy_contract( f"code too large: {len(deploy_code)} > {max_code_size}" ) - if DETERMINISTIC_DEPLOYMENT_CONTRACT_ADDRESS not in self: + fork_deterministic_factory_address = ( + self._fork.deterministic_factory_predeploy_address() + ) + if ( + fork_deterministic_factory_address is None + and DETERMINISTIC_FACTORY_ADDRESS not in self + ): super().__setitem__( - DETERMINISTIC_DEPLOYMENT_CONTRACT_ADDRESS, + DETERMINISTIC_FACTORY_ADDRESS, Account( nonce=1, - code=DETERMINISTIC_DEPLOYMENT_BYTECODE, + code=DETERMINISTIC_FACTORY_BYTECODE, storage={}, ), ) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/pytest_ini_files/pytest-execute-deploy-required-contracts.ini b/packages/testing/src/execution_testing/cli/pytest_commands/pytest_ini_files/pytest-execute-deploy-required-contracts.ini new file mode 100644 index 00000000000..4ef1350726e --- /dev/null +++ b/packages/testing/src/execution_testing/cli/pytest_commands/pytest_ini_files/pytest-execute-deploy-required-contracts.ini @@ -0,0 +1,14 @@ +[pytest] +console_output_style = count +minversion = 7.0 +python_files = *.py +addopts = + -p execution_testing.cli.pytest_commands.plugins.execute.execute_flags.execute_flags + -p execution_testing.cli.pytest_commands.plugins.execute.deploy_required_contracts + -p execution_testing.cli.pytest_commands.plugins.concurrency + -p execution_testing.cli.pytest_commands.plugins.execute.rpc.remote + -p execution_testing.cli.pytest_commands.plugins.execute.rpc.remote_seed_sender + -p execution_testing.cli.pytest_commands.plugins.help.help + -m "not eip_version_check" + --tb short + --dist loadscope \ No newline at end of file diff --git a/packages/testing/src/execution_testing/test_types/__init__.py b/packages/testing/src/execution_testing/test_types/__init__.py index 5f16085e81e..fd2cfcf1009 100644 --- a/packages/testing/src/execution_testing/test_types/__init__.py +++ b/packages/testing/src/execution_testing/test_types/__init__.py @@ -20,8 +20,8 @@ ) from .chain_config_types import ChainConfig, ChainConfigDefaults from .helpers import ( - DETERMINISTIC_DEPLOYMENT_BYTECODE, - DETERMINISTIC_DEPLOYMENT_CONTRACT_ADDRESS, + DETERMINISTIC_FACTORY_ADDRESS, + DETERMINISTIC_FACTORY_BYTECODE, TestParameterGroup, add_kzg_version, ceiling_division, @@ -49,8 +49,8 @@ from .utils import Removable, keccak256 __all__ = ( - "DETERMINISTIC_DEPLOYMENT_BYTECODE", - "DETERMINISTIC_DEPLOYMENT_CONTRACT_ADDRESS", + "DETERMINISTIC_FACTORY_BYTECODE", + "DETERMINISTIC_FACTORY_ADDRESS", "Alloc", "AuthorizationTuple", "BalAccountChange", diff --git a/packages/testing/src/execution_testing/test_types/helpers.py b/packages/testing/src/execution_testing/test_types/helpers.py index 934ee7e6a08..bd13d6c635d 100644 --- a/packages/testing/src/execution_testing/test_types/helpers.py +++ b/packages/testing/src/execution_testing/test_types/helpers.py @@ -10,6 +10,7 @@ BytesConvertible, FixedSizeBytesConvertible, ) +from execution_testing.forks import Fork from execution_testing.vm import Op from .account_types import EOA @@ -19,10 +20,10 @@ Helper functions """ -DETERMINISTIC_DEPLOYMENT_CONTRACT_ADDRESS = Address( +DETERMINISTIC_FACTORY_ADDRESS = Address( 0x4E59B44847B379578588920CA78FBF26C0B4956C ) -DETERMINISTIC_DEPLOYMENT_BYTECODE = ( +DETERMINISTIC_FACTORY_BYTECODE = ( Op.ADD(Op.CALLDATASIZE, 2**256 - 32) + Op.PUSH1[0x0] + Op.CALLDATACOPY(dest_offset=Op.DUP3, offset=0x20, size=Op.DUP2) @@ -96,15 +97,21 @@ def compute_create2_address( def compute_deterministic_create2_address( + *, salt: FixedSizeBytesConvertible, initcode: BytesConvertible, + fork: Fork, ) -> Address: """ Compute address of the resulting contract created using the `CREATE2` - opcode using the `DETERMINISTIC_DEPLOYMENT_CONTRACT_ADDRESS`. + opcode using the `DETERMINISTIC_FACTORY_ADDRESS`. """ + factory_address = ( + fork.deterministic_factory_predeploy_address() + or DETERMINISTIC_FACTORY_ADDRESS + ) return compute_create2_address( - address=DETERMINISTIC_DEPLOYMENT_CONTRACT_ADDRESS, + address=factory_address, salt=salt, initcode=initcode, ) From e88dd7630e23439e6af0056d5a78c14d918e2a2d Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Wed, 7 Jan 2026 22:17:24 +0000 Subject: [PATCH 12/20] fix: tox --- .../execution_testing/cli/pytest_commands/execute.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/execute.py b/packages/testing/src/execution_testing/cli/pytest_commands/execute.py index 11c7d80cdb5..1ac30a01b81 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/execute.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/execute.py @@ -66,6 +66,8 @@ def command(pytest_args: List[str], **_kwargs: Any) -> None: ], ) +EXECUTE_PATH = Path("cli/pytest_commands/plugins/execute") + eth_config = _create_execute_subcommand( "eth-config", "pytest-execute-eth-config.ini", @@ -75,9 +77,7 @@ def command(pytest_args: List[str], **_kwargs: Any) -> None: "--rpc-endpoint=http://localhost:8545", ], command_logic_test_paths=[ - Path( - "cli/pytest_commands/plugins/execute/eth_config/execute_eth_config.py" - ) + EXECUTE_PATH / "eth_config" / "execute_eth_config.py" ], ) @@ -92,7 +92,7 @@ def command(pytest_args: List[str], **_kwargs: Any) -> None: "--destination=0x0000000000000000000000000000000000000000", ], command_logic_test_paths=[ - Path("cli/pytest_commands/plugins/execute/execute_recover.py") + EXECUTE_PATH / "execute_recover.py" ], ) @@ -105,6 +105,6 @@ def command(pytest_args: List[str], **_kwargs: Any) -> None: "--chain-id=1", ], command_logic_test_paths=[ - Path("cli/pytest_commands/plugins/execute/execute_deploy_required_contracts.py") + EXECUTE_PATH / "execute_deploy_required_contracts.py" ], ) From bdd8f8b3c3e1e0cdf5378e75f0222a84456c0dd1 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Wed, 7 Jan 2026 22:24:50 +0000 Subject: [PATCH 13/20] fix: tox 2 --- .../cli/pytest_commands/execute.py | 4 +--- .../plugins/execute/contracts.py | 17 ++++++++++------- .../plugins/execute/pre_alloc.py | 4 ++-- .../plugins/execute/rpc/remote.py | 4 +++- 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/execute.py b/packages/testing/src/execution_testing/cli/pytest_commands/execute.py index 1ac30a01b81..4c8b0ae5a93 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/execute.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/execute.py @@ -91,9 +91,7 @@ def command(pytest_args: List[str], **_kwargs: Any) -> None: "--start-eoa-index=1", "--destination=0x0000000000000000000000000000000000000000", ], - command_logic_test_paths=[ - EXECUTE_PATH / "execute_recover.py" - ], + command_logic_test_paths=[EXECUTE_PATH / "execute_recover.py"], ) deploy_required_contracts = _create_execute_subcommand( diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/contracts.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/contracts.py index 809edf862c1..4be44834961 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/contracts.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/contracts.py @@ -40,12 +40,14 @@ def deploy_deterministic_factory_contract( gas_price: int, ) -> None: """Deploy the deterministic deployment contract.""" + deploy_tx_gas_price = 0x174876E800 + deploy_tx_gas_limit = 0x0186A0 deploy_tx = Transaction( ty=0, protected=False, nonce=0, - gas_price=0x174876E800, - gas_limit=0x0186A0, + gas_price=deploy_tx_gas_price, + gas_limit=deploy_tx_gas_limit, to=None, value=0, data=( @@ -57,19 +59,20 @@ def deploy_deterministic_factory_contract( r=0x2222222222222222222222222222222222222222222222222222222222222222, s=0x2222222222222222222222222222222222222222222222222222222222222222, ).with_signature_and_sender() - - required_deployer_balance = deploy_tx.gas_price * deploy_tx.gas_limit - current_balance = eth_rpc.get_balance(deploy_tx.sender) + deploy_tx_sender = deploy_tx.sender + assert deploy_tx_sender is not None + required_deployer_balance = deploy_tx_gas_price * deploy_tx_gas_limit + current_balance = eth_rpc.get_balance(deploy_tx_sender) if current_balance < required_deployer_balance: # Add transaction to fund the deployer. fund_amount = required_deployer_balance - current_balance logger.info( "Funding deterministic factory deployer address " - f"{deploy_tx.sender} with " + f"{deploy_tx_sender} with " f"{fund_amount / 10**18:.18f} ETH" ) fund_tx = Transaction( - to=deploy_tx.sender, + to=deploy_tx_sender, value=fund_amount, gas_price=gas_price, sender=seed_key, diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py index 5e58a8a38ba..9a3c3f064ba 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py @@ -244,8 +244,8 @@ class Alloc(BaseAlloc): _sender: EOA = PrivateAttr() _eth_rpc: EthRPC = PrivateAttr() _pending_txs: List[PendingTransaction] = PrivateAttr(default_factory=list) - _deployed_contracts: List[Tuple[Address, Bytes]] = PrivateAttr( - default_factory=list + _deployed_contracts: List[Tuple[Address, Bytes | Bytecode | Container]] = ( + PrivateAttr(default_factory=list) ) _funded_eoa: List[EOA] = PrivateAttr(default_factory=list) _evm_code_type: EVMCodeType | None = PrivateAttr(None) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/rpc/remote.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/rpc/remote.py index 14424c21d0c..68d2a35a7bd 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/rpc/remote.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/rpc/remote.py @@ -146,9 +146,11 @@ def rpc_endpoint(request: pytest.FixtureRequest) -> str: Return remote RPC endpoint to be used to make requests to the execution client. """ - return request.config.getoption("rpc_endpoint") or os.environ.get( + rpc_endpoint = request.config.getoption("rpc_endpoint") or os.environ.get( "RPC_ENDPOINT" ) + assert rpc_endpoint is not None + return rpc_endpoint @pytest.fixture(autouse=True, scope="session") From a5d5052b48cdbe2da057a176f432d532f5151365 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Wed, 7 Jan 2026 23:48:14 +0000 Subject: [PATCH 14/20] better docstrings --- .../test_types/account_types.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/packages/testing/src/execution_testing/test_types/account_types.py b/packages/testing/src/execution_testing/test_types/account_types.py index c6b39982835..148c729782c 100644 --- a/packages/testing/src/execution_testing/test_types/account_types.py +++ b/packages/testing/src/execution_testing/test_types/account_types.py @@ -398,13 +398,19 @@ def deterministic_deploy_contract( Deploy a contract to the allocation at a deterministic location using a deterministic deployment proxy. + The initcode is not executed during test filling; it is executed only + when the tests run on live networks. Therefore, if the initcode + performs modifications to the storage, these must be specified using + the `storage` parameter. + Args: - deploy_code: Contract code to deploy - salt: Salt to use for deterministic deployment - initcode: Initcode to use for deterministic deployment - Can be `None` and `Initcode` will be used to derive it - storage: Set the storage during test filling - label: Label to use for the contract + deploy_code: Contract code to deploy. + salt: Salt to use for deterministic deployment. + initcode: Initcode to use for deterministic deployment. + If `None`, the initcode is derived from `deploy_code`. + storage: The expected storage state of the deployed contract after + initcode execution. + label: Label to use for the contract. """ raise NotImplementedError( "deterministic_deploy_contract is not implemented in the base class" From 235ecd8360682689e1c0638c20c41269519d998b Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Thu, 8 Jan 2026 14:09:46 +0000 Subject: [PATCH 15/20] Add max code size checks to execute --- .../plugins/execute/pre_alloc.py | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py index 9a3c3f064ba..ddf372146e1 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py @@ -50,10 +50,6 @@ deploy_deterministic_factory_contract, ) -MAX_BYTECODE_SIZE = 24576 -MAX_INITCODE_SIZE = MAX_BYTECODE_SIZE * 2 - - logger = get_logger(__name__) @@ -381,6 +377,16 @@ def deterministic_deploy_contract( ), "Deployment contract code is not found" # Deploy the actual contract. + max_code_size = self._fork.max_code_size() + if len(deploy_code) > max_code_size: + raise ValueError( + f"code too large: {len(deploy_code)} > {max_code_size}" + ) + max_initcode_size = self._fork.max_initcode_size() + if len(initcode) > max_initcode_size: + raise ValueError( + f"initcode too large {len(initcode)} > {max_initcode_size}" + ) deploy_gas_limit = ( gas_costs.G_TRANSACTION + gas_costs.G_TRANSACTION_CREATE ) @@ -510,9 +516,9 @@ def deploy_contract( ) code = self.code_pre_processor(code, evm_code_type=evm_code_type) - assert len(code) <= MAX_BYTECODE_SIZE, ( - f"code too large: {len(code)} > {MAX_BYTECODE_SIZE}" - ) + max_code_size = self._fork.max_code_size() + if len(code) > max_code_size: + raise ValueError(f"code too large: {len(code)} > {max_code_size}") deploy_gas_limit += len(code) * gas_costs.G_CODE_DEPOSIT_BYTE @@ -531,9 +537,11 @@ def deploy_contract( new_bytes=len(bytes(prepared_initcode)) ) - assert len(prepared_initcode) <= MAX_INITCODE_SIZE, ( - f"initcode too large {len(prepared_initcode)} > {MAX_INITCODE_SIZE}" - ) + max_initcode_size = self._fork.max_initcode_size() + if len(prepared_initcode) > max_initcode_size: + raise ValueError( + f"initcode too large {len(prepared_initcode)} > {max_initcode_size}" + ) deploy_gas_limit += calldata_gas_calculator(data=prepared_initcode) From 3a7fd43a16248c90ab5100f7cf4b8d9492264016 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Thu, 8 Jan 2026 14:33:33 +0000 Subject: [PATCH 16/20] fix(docs): Add more details --- docs/running_tests/execute/remote.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/running_tests/execute/remote.md b/docs/running_tests/execute/remote.md index 8328b9d0692..c2521a7793d 100644 --- a/docs/running_tests/execute/remote.md +++ b/docs/running_tests/execute/remote.md @@ -32,7 +32,12 @@ These transactions are created from the seed or worker accounts provided via the When the test is executed, all `pre.fund_eoa` and `pre.deploy_contract` calls generate transactions that create the accounts on chain, instead of placing them directly in the `pre` object like `fill` does. -If a test uses `pre.deterministic_deploy_contract`, the `execute` command first checks if the contract is already present on chain before attempting to deploy it. If the proxy used for deterministic deployment is not present on chain either, the command will automatically deploy it too. The address of these contracts is calculated using `keccak256( 0xff ++ 0x4E59B44847B379578588920CA78FBF26C0B4956C ++ salt ++ keccak256(init_code))[12:]`. +If a test uses `pre.deterministic_deploy_contract`, the `execute` command first checks if the contract is already present on chain before attempting to deploy it. If the proxy used for deterministic deployment is not present on chain either, the command will automatically deploy it too. The address of these contracts is calculated using `keccak256( 0xff ++ DETERMINISTIC_FACTORY_ADDRESS ++ salt ++ keccak256(init_code))[12:]`. + +The address of `DETERMINISTIC_FACTORY_ADDRESS` depends on the currently active fork: + +- If EIP-7997 active in the current fork, the address of the predeploy will be used. +- Otherwise, address `0x4E59B44847B379578588920CA78FBF26C0B4956C` will be used (see https://github.com/Arachnid/deterministic-deployment-proxy for details on how this address is computed). The transactions are collected and only sent after the test function finishes execution. This is done in order to perform optimizations based on the transactions that the test requires to perform its verifications. From 8f6c314453240e4d5a688f519bb90181a7d37367 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Thu, 8 Jan 2026 14:34:12 +0000 Subject: [PATCH 17/20] nit --- docs/running_tests/execute/remote.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/running_tests/execute/remote.md b/docs/running_tests/execute/remote.md index c2521a7793d..d22821b53f2 100644 --- a/docs/running_tests/execute/remote.md +++ b/docs/running_tests/execute/remote.md @@ -36,8 +36,8 @@ If a test uses `pre.deterministic_deploy_contract`, the `execute` command first The address of `DETERMINISTIC_FACTORY_ADDRESS` depends on the currently active fork: -- If EIP-7997 active in the current fork, the address of the predeploy will be used. -- Otherwise, address `0x4E59B44847B379578588920CA78FBF26C0B4956C` will be used (see https://github.com/Arachnid/deterministic-deployment-proxy for details on how this address is computed). +- If EIP-7997 active in the current fork, the address of the predeploy will be used +- Otherwise, address `0x4E59B44847B379578588920CA78FBF26C0B4956C` will be used (see https://github.com/Arachnid/deterministic-deployment-proxy for details on how this address is computed) The transactions are collected and only sent after the test function finishes execution. This is done in order to perform optimizations based on the transactions that the test requires to perform its verifications. From 44bb187aa4d014ce241298f26e108a6ec7fd2e56 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Thu, 8 Jan 2026 14:36:47 +0000 Subject: [PATCH 18/20] words --- docs/running_tests/execute/remote.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/running_tests/execute/remote.md b/docs/running_tests/execute/remote.md index d22821b53f2..1a9b7547f2d 100644 --- a/docs/running_tests/execute/remote.md +++ b/docs/running_tests/execute/remote.md @@ -32,12 +32,12 @@ These transactions are created from the seed or worker accounts provided via the When the test is executed, all `pre.fund_eoa` and `pre.deploy_contract` calls generate transactions that create the accounts on chain, instead of placing them directly in the `pre` object like `fill` does. -If a test uses `pre.deterministic_deploy_contract`, the `execute` command first checks if the contract is already present on chain before attempting to deploy it. If the proxy used for deterministic deployment is not present on chain either, the command will automatically deploy it too. The address of these contracts is calculated using `keccak256( 0xff ++ DETERMINISTIC_FACTORY_ADDRESS ++ salt ++ keccak256(init_code))[12:]`. +If a test uses `pre.deterministic_deploy_contract`, the `execute` command first checks whether the contract is already present on chain before attempting to deploy it. If the proxy used for deterministic deployment is also not present on chain, the command will automatically deploy it as well. The address of these contracts is computed as `keccak256(0xff ++ DETERMINISTIC_FACTORY_ADDRESS ++ salt ++ keccak256(init_code))[12:]`. -The address of `DETERMINISTIC_FACTORY_ADDRESS` depends on the currently active fork: +The value of `DETERMINISTIC_FACTORY_ADDRESS` depends on the currently active fork: -- If EIP-7997 active in the current fork, the address of the predeploy will be used -- Otherwise, address `0x4E59B44847B379578588920CA78FBF26C0B4956C` will be used (see https://github.com/Arachnid/deterministic-deployment-proxy for details on how this address is computed) +- If EIP-7997 is active in the current fork, the address of the pre-deploy is used. +- Otherwise, the address `0x4E59B44847B379578588920CA78FBF26C0B4956C` is used (see https://github.com/Arachnid/deterministic-deployment-proxy for details on how this address is computed) The transactions are collected and only sent after the test function finishes execution. This is done in order to perform optimizations based on the transactions that the test requires to perform its verifications. From dca93737664851ac6ac53095b7f0e01ad1034c0f Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Thu, 8 Jan 2026 14:39:49 +0000 Subject: [PATCH 19/20] Add comment. --- packages/testing/src/execution_testing/test_types/helpers.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/testing/src/execution_testing/test_types/helpers.py b/packages/testing/src/execution_testing/test_types/helpers.py index bd13d6c635d..24bed7203ed 100644 --- a/packages/testing/src/execution_testing/test_types/helpers.py +++ b/packages/testing/src/execution_testing/test_types/helpers.py @@ -20,6 +20,10 @@ Helper functions """ +# Default deterministic factory address and bytecode for forks that do not +# support EIP-7997. +# See https://github.com/Arachnid/deterministic-deployment-proxy for more +# details. DETERMINISTIC_FACTORY_ADDRESS = Address( 0x4E59B44847B379578588920CA78FBF26C0B4956C ) From 5c86d49a65549da60d3d3a82ec0568518d22b131 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Thu, 8 Jan 2026 14:43:50 +0000 Subject: [PATCH 20/20] Add comment. --- .../cli/pytest_commands/plugins/execute/contracts.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/contracts.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/contracts.py index 4be44834961..6f143a8053b 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/contracts.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/contracts.py @@ -43,6 +43,8 @@ def deploy_deterministic_factory_contract( deploy_tx_gas_price = 0x174876E800 deploy_tx_gas_limit = 0x0186A0 deploy_tx = Transaction( + # See https://github.com/Arachnid/deterministic-deployment-proxy for + # more details on these values. ty=0, protected=False, nonce=0,