From bbf676b70f1aef89d017945ec0431defba2e480d Mon Sep 17 00:00:00 2001 From: antazoey Date: Thu, 25 Jul 2024 19:03:12 -0500 Subject: [PATCH] feat: support `raise_on_revert` flag for calls and transactions (#107) approved from core, changes are really no different. --- .pre-commit-config.yaml | 4 ++-- ape_foundry/provider.py | 24 ++++++++++++++++-------- setup.py | 6 +++--- tests/test_provider.py | 15 +++++++++++++++ 4 files changed, 36 insertions(+), 13 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d7770d4..a921fc9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,12 +16,12 @@ repos: name: black - repo: https://github.com/pycqa/flake8 - rev: 7.0.0 + rev: 7.1.0 hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.10.0 + rev: v1.11.0 hooks: - id: mypy additional_dependencies: [types-PyYAML, types-requests, types-setuptools, pydantic] diff --git a/ape_foundry/provider.py b/ape_foundry/provider.py index 8910b60..a26f80c 100644 --- a/ape_foundry/provider.py +++ b/ape_foundry/provider.py @@ -504,6 +504,7 @@ def send_transaction(self, txn: TransactionAPI) -> ReceiptAPI: if sender: sender = self.conversion_manager.convert(txn.sender, AddressType) + vm_err = None if sender and sender in self.unlocked_accounts: # Allow for an unsigned transaction txn = self.prepare_transaction(txn) @@ -526,7 +527,7 @@ def send_transaction(self, txn: TransactionAPI) -> ReceiptAPI: if "nonce too low" in str(vm_err): # Add additional nonce information new_err_msg = f"Nonce '{txn.nonce}' is too low" - raise VirtualMachineError( + vm_err = VirtualMachineError( new_err_msg, base_err=vm_err.base_err, code=vm_err.code, @@ -536,7 +537,9 @@ def send_transaction(self, txn: TransactionAPI) -> ReceiptAPI: contract_address=vm_err.contract_address, ) - raise vm_err from err + txn_hash = txn.txn_hash + if txn.raise_on_revert: + raise vm_err from err receipt = self.get_receipt( txn_hash.hex(), @@ -546,6 +549,8 @@ def send_transaction(self, txn: TransactionAPI) -> ReceiptAPI: else self.network.required_confirmations ), ) + if vm_err: + receipt.error = vm_err if receipt.failed: txn_dict = receipt.transaction.model_dump(mode="json", by_alias=True) @@ -563,11 +568,14 @@ def send_transaction(self, txn: TransactionAPI) -> ReceiptAPI: self.web3.eth.call(txn_params) except Exception as err: vm_err = self.get_virtual_machine_error(err, txn=receipt) - raise vm_err from err + receipt.error = vm_err + if txn.raise_on_revert: + raise vm_err from err - # If we get here, for some reason the tx-replay did not produce - # a VM error. - receipt.raise_for_status() + if txn.raise_on_revert: + # If we get here, for some reason the tx-replay did not produce + # a VM error. + receipt.raise_for_status() self.chain_manager.history.append(receipt) return receipt @@ -709,7 +717,7 @@ def set_storage(self, address: AddressType, slot: int, value: HexBytes): ], ) - def _eth_call(self, arguments: list) -> HexBytes: + def _eth_call(self, arguments: list, raise_on_revert: bool = True) -> HexBytes: # Overridden to handle unique Foundry pickiness. txn_dict = copy(arguments[0]) if isinstance(txn_dict.get("type"), int): @@ -717,7 +725,7 @@ def _eth_call(self, arguments: list) -> HexBytes: txn_dict.pop("chainId", None) arguments[0] = txn_dict - return super()._eth_call(arguments) + return super()._eth_call(arguments, raise_on_revert=raise_on_revert) class FoundryForkProvider(FoundryProvider): diff --git a/setup.py b/setup.py index 2ef4cb1..164568b 100644 --- a/setup.py +++ b/setup.py @@ -14,11 +14,11 @@ ], "lint": [ "black>=24.4.2,<25", # Auto-formatter and linter - "mypy>=1.10.0,<2", # Static type analyzer + "mypy>=1.11.0,<2", # Static type analyzer "types-setuptools", # Needed for mypy type shed "types-requests", # Needed for mypy type shed "types-PyYAML", # Needed for mypy type shed - "flake8>=7.0.0,<8", # Style linter + "flake8>=7.1.0,<8", # Style linter "flake8-breakpoint>=1.1.0,<2", # Detect breakpoints left in code "flake8-print>=5.0.0,<6", # Detect print statements left in code "isort>=5.13.2,<6", # Import sorting linter @@ -72,7 +72,7 @@ url="https://github.com/ApeWorX/ape-foundry", include_package_data=True, install_requires=[ - "eth-ape>=0.8.9,<0.9", + "eth-ape>=0.8.10,<0.9", "ethpm-types", # Use same version as eth-ape "eth-pydantic-types", # Use same version as eth-ape "evm-trace", # Use same version as ape diff --git a/tests/test_provider.py b/tests/test_provider.py index c44eaa7..63c4322 100644 --- a/tests/test_provider.py +++ b/tests/test_provider.py @@ -268,6 +268,21 @@ def test_revert_error_using_impersonated_account(error_contract, accounts, conne with pytest.raises(error_contract.Unauthorized): error_contract.withdraw(sender=acct) + # Show we can "allow" reverts using impersonated accounts. + # NOTE: This is extra because of the lack of tx-hash available. + receipt = error_contract.withdraw(sender=acct, raise_on_revert=False) + assert receipt.failed + + +def test_revert_allow(error_contract, not_owner, contract_instance): + # 'sender' is not the owner so it will revert (with a message) + receipt = error_contract.withdraw(sender=not_owner, raise_on_revert=False) + assert receipt.error is not None + assert isinstance(receipt.error, error_contract.Unauthorized) + + # Ensure this also works for calls. + contract_instance.setNumber.call(5, raise_on_revert=False) + @pytest.mark.parametrize("host", ("https://example.com", "example.com")) def test_host(project, local_network, host):