diff --git a/src/ape/api/accounts.py b/src/ape/api/accounts.py index 4a6175b23f..fefe7d8278 100644 --- a/src/ape/api/accounts.py +++ b/src/ape/api/accounts.py @@ -7,7 +7,13 @@ from ape.api.address import BaseAddress from ape.api.transactions import ReceiptAPI, TransactionAPI -from ape.exceptions import AccountsError, AliasAlreadyInUseError, SignatureError, TransactionError +from ape.exceptions import ( + AccountsError, + AliasAlreadyInUseError, + MethodNonPayableError, + SignatureError, + TransactionError, +) from ape.logging import logger from ape.types import AddressType, MessageSignature, SignableMessage from ape.utils import BaseInterfaceModel, abstractmethod @@ -215,6 +221,9 @@ def deploy( :class:`~ape.contracts.ContractInstance`: An instance of the deployed contract. """ txn = contract(*args, **kwargs) + if kwargs.get("value") and not contract.contract_type.constructor.is_payable: + raise MethodNonPayableError("Sending funds to a non-payable constructor.") + txn.sender = self.address receipt = contract._cache_wrap(lambda: self.call(txn, **kwargs)) if not (address := receipt.contract_address): diff --git a/src/ape/contracts/base.py b/src/ape/contracts/base.py index c0c5001cc2..a377d7179e 100644 --- a/src/ape/contracts/base.py +++ b/src/ape/contracts/base.py @@ -19,6 +19,7 @@ ContractError, ContractLogicError, CustomError, + MethodNonPayableError, TransactionNotFoundError, ) from ape.logging import logger @@ -1301,6 +1302,9 @@ def deploy(self, *args, publish: bool = False, **kwargs) -> ContractInstance: txn = self(*args, **kwargs) private = kwargs.get("private", False) + if kwargs.get("value") and not self.contract_type.constructor.is_payable: + raise MethodNonPayableError("Sending funds to a non-payable constructor.") + if "sender" in kwargs and isinstance(kwargs["sender"], AccountAPI): # Handle account-related preparation if needed, such as signing receipt = self._cache_wrap(lambda: kwargs["sender"].call(txn, **kwargs)) diff --git a/src/ape/exceptions.py b/src/ape/exceptions.py index a900118882..b78d4ccf2e 100644 --- a/src/ape/exceptions.py +++ b/src/ape/exceptions.py @@ -131,6 +131,12 @@ def __init__(self, message: Optional[str] = None): super().__init__(message) +class MethodNonPayableError(ContractError): + """ + Raises when sending funds to a non-payable method + """ + + class TransactionError(ContractError): """ Raised when issues occur related to transactions. diff --git a/tests/functional/test_contract_instance.py b/tests/functional/test_contract_instance.py index d3082936b0..7a48b84a6e 100644 --- a/tests/functional/test_contract_instance.py +++ b/tests/functional/test_contract_instance.py @@ -16,6 +16,7 @@ ContractError, ContractLogicError, CustomError, + MethodNonPayableError, ) from ape.types import AddressType from ape_ethereum.transactions import TransactionStatusEnum @@ -850,3 +851,23 @@ def test_contract_declared_from_blueprint( # Ensure we can invoke a method on that contract. receipt = instance.setAddress(sender, sender=sender) assert not receipt.failed + + +def test_sending_funds_to_non_payable_constructor_by_contractContainerDeploy( + solidity_contract_container, owner +): + with pytest.raises( + MethodNonPayableError, + match="Sending funds to a non-payable constructor.", + ): + solidity_contract_container.deploy(1, sender=owner, value="1 ether") + + +def test_sending_funds_to_non_payable_constructor_by_accountDeploy( + solidity_contract_container, owner +): + with pytest.raises( + MethodNonPayableError, + match="Sending funds to a non-payable constructor.", + ): + owner.deploy(solidity_contract_container, 1, value="1 ether")