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 4be3f8aa36c..d13a09f9ab6 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 @@ -20,6 +20,13 @@ logger = get_logger(__name__) +class DeterministicFactoryNotDeployableError(Exception): + """ + Raised when the deterministic proxy cannot deploy. + Example: fixed gas limit insufficient for network creation cost. + """ + + def check_deterministic_factory_deployment( *, eth_rpc: EthRPC, @@ -96,6 +103,28 @@ def deploy_deterministic_factory_contract( ).with_signature_and_sender() deploy_tx_sender = deploy_tx.sender assert deploy_tx_sender is not None + + # Pre-flight: skip deploy if network gas > fixed limit. + # Gas limit is fixed as changing it alters sender/factory address. + # If network requires more gas, transaction can never be included. + try: + required_gas = eth_rpc.estimate_gas( + transaction={ + "from": f"{deploy_tx_sender}", + "input": f"{deploy_tx.data}", + } + ) + except Exception: + # If the estimate itself is unavailable, fall through and attempt the + # deploy as before (failures are still handled by the caller). + required_gas = None + if required_gas is not None and required_gas > deploy_tx_gas_limit: + raise DeterministicFactoryNotDeployableError( + f"network requires {required_gas} gas to create the deterministic " + f"deployment proxy, exceeding the keyless transaction's fixed gas " + f"limit of {deploy_tx_gas_limit}" + ) + 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: 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 5cb131b3ba9..995a8e387b1 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 @@ -152,6 +152,10 @@ def execute_required_contracts( Deploy required contracts for the execute command. - Deterministic deployment proxy + + Proxy deploy failure doesn't abort the session. + Tests skip deterministic deploys on use. + Details check `(see Alloc._resolve_deterministic_deploys)`. """ base_lock_file = session_temp_folder / "execute_required_contracts.lock" with FileLock(base_lock_file): @@ -171,12 +175,13 @@ def execute_required_contracts( 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 + logger.warning( + "Could not deploy the deterministic deployment proxy; " + "tests that require it will be skipped. To deploy it " + "manually against a different RPC endpoint run " + "`uv run execute deploy-required-contracts`. " + f"Reason: {e}" + ) class PendingTransaction(Transaction): @@ -752,12 +757,17 @@ def _resolve_deterministic_deploys(self) -> None: ) else: if not factory_checked: - assert ( + if ( check_deterministic_factory_deployment( eth_rpc=self._eth_rpc, fork=fork ) - is not None - ), "Deployment contract code is not found" + is None + ): + pytest.skip( + "deterministic deployment proxy is not available " + "on this network; skipping test that requires a " + "deterministic contract deployment" + ) factory_checked = True logger.info( diff --git a/packages/testing/src/execution_testing/rpc/rpc.py b/packages/testing/src/execution_testing/rpc/rpc.py index 2c8cdcb50b5..3f9c64c332f 100644 --- a/packages/testing/src/execution_testing/rpc/rpc.py +++ b/packages/testing/src/execution_testing/rpc/rpc.py @@ -634,6 +634,22 @@ def get_balances( responses = self.post_batch_request(calls=calls) return [int(r.result_or_raise(), 16) for r in responses] + def estimate_gas( + self, + transaction: Dict[str, Any], + block_number: BlockNumberType = "latest", + ) -> int: + """`eth_estimateGas`: Return the gas required to execute a tx.""" + block = ( + hex(block_number) + if isinstance(block_number, int) + else block_number + ) + response = self.post_request( + request=RPCCall(method="estimateGas", params=[transaction, block]) + ).result_or_raise() + return int(response, 16) + def get_code( self, address: Address, block_number: BlockNumberType = "latest" ) -> Bytes: