Skip to content

Commit

Permalink
fix: issue deploying contract with default payable method (#18)
Browse files Browse the repository at this point in the history
  • Loading branch information
antazoey authored Feb 15, 2025
1 parent 5c03347 commit 6041d95
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 9 deletions.
26 changes: 18 additions & 8 deletions ape_titanoboa/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
)
from ape_ethereum.transactions import TransactionStatusEnum
from eth.constants import ZERO_ADDRESS
from eth.exceptions import Revert
from eth.exceptions import Revert, WriteProtection
from eth.vm.spoof import SpoofTransaction
from eth_abi import decode
from eth_pydantic_types import HexBytes, HexStr
Expand Down Expand Up @@ -278,9 +278,16 @@ def send_call(
computation = self._execute_code(txn.data, txn.gas_limit, receiver=txn.receiver)
try:
computation.raise_if_error()

except Revert as err:
raise self.get_virtual_machine_error(err) from err

except WriteProtection as err:
# This occurs when calling an ABI that does not exist
# on a contract with a default payable method. Regular
# nodes still seem to revert here, we pretend to do the same.
raise ContractLogicError() from err

return HexBytes(computation.output)

def get_receipt(self, txn_hash: str, **kwargs) -> "ReceiptAPI":
Expand Down Expand Up @@ -531,14 +538,17 @@ def _advance_chain(self, blocks: int = 1, transaction_hashes: Optional[list] = N

def get_virtual_machine_error(self, exception: Exception, **kwargs) -> VirtualMachineError:
if isinstance(exception, Revert):
raw_data = exception.args[0]
revert_data = raw_data[4:]
if raw_data := exception.args[0]:
revert_data = raw_data[4:]

try:
message = decode(("string",), revert_data, strict=False)[0]
except Exception:
# Likely a custom error.
message = to_hex(revert_data)
try:
message = decode(("string",), revert_data, strict=False)[0]
except Exception:
# Likely a custom error.
message = to_hex(revert_data)

else:
message = VirtualMachineError.DEFAULT_MESSAGE

contract_logic_error = ContractLogicError(
base_err=exception,
Expand Down
2 changes: 1 addition & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,6 @@ def not_owner(accounts) -> "AccountAPI":
return accounts[1]


@pytest.fixture
@pytest.fixture(scope="session")
def contract_instance(contract, owner) -> "ContractInstance":
return contract.deploy(123, sender=owner)
11 changes: 11 additions & 0 deletions tests/contracts/ContractWithDefaultPayable.vy
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# pragma version ^0.4.0

# Event for tracking donations
event Donation:
sender: address
amount: uint256

@external
@payable
def __default__():
log Donation(msg.sender, msg.value)
18 changes: 18 additions & 0 deletions tests/test_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,16 @@ def test_deploy_contract(contract, owner, networks):
assert instance.myNumber() == 123


def test_deploy_contract_with_default_payable_method(chain, project, owner):
"""
Tests against a bug where if you tried to deploy a contract with a default
payable method, Ape's proxy detection system would trigger an error
about mutating static-call state.
"""
contract = project.ContractWithDefaultPayable.deploy(sender=owner)
assert contract.contract_type.name == "ContractWithDefaultPayable"


def test_send_transaction(chain, contract_instance, contract, owner, networks, not_owner):
expected_block = chain.provider.get_block("pending")
tx = contract_instance.setNumber(321, sender=owner)
Expand Down Expand Up @@ -103,6 +113,14 @@ def test_send_call(contract_instance, owner, contract, networks, run_fork_tests)
assert result == 123


def test_send_call_method_not_exists(chain, contract_instance):
tx = chain.provider.network.ecosystem.create_transaction(
data="0x12345678000", receiver=contract_instance.address
)
with pytest.raises(ContractLogicError):
_ = chain.provider.send_call(tx)


def test_get_receipt(contract_instance, owner, chain, networks, run_fork_tests):
local_tx = contract_instance.setNumber(321, sender=owner)
actual = chain.provider.get_receipt(local_tx.txn_hash)
Expand Down

0 comments on commit 6041d95

Please sign in to comment.