Skip to content

Commit

Permalink
fix: raise error when attempting to deploy a contract without init-co…
Browse files Browse the repository at this point in the history
…de (#1909)
  • Loading branch information
antazoey authored Feb 2, 2024
1 parent 2240d14 commit 8b53652
Show file tree
Hide file tree
Showing 7 changed files with 99 additions and 28 deletions.
5 changes: 5 additions & 0 deletions src/ape/api/accounts.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
AccountsError,
AliasAlreadyInUseError,
MethodNonPayableError,
MissingDeploymentBytecodeError,
SignatureError,
TransactionError,
)
Expand Down Expand Up @@ -239,6 +240,10 @@ def deploy(
"a contract in your project."
)

bytecode = contract.contract_type.deployment_bytecode
if not bytecode or bytecode.bytecode in (None, "", "0x"):
raise MissingDeploymentBytecodeError(contract.contract_type)

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.")
Expand Down
5 changes: 5 additions & 0 deletions src/ape/contracts/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
ContractNotFoundError,
CustomError,
MethodNonPayableError,
MissingDeploymentBytecodeError,
TransactionNotFoundError,
)
from ape.logging import logger
Expand Down Expand Up @@ -1344,6 +1345,10 @@ def deploy(self, *args, publish: bool = False, **kwargs) -> ContractInstance:
:class:`~ape.contracts.base.ContractInstance`
"""

bytecode = self.contract_type.deployment_bytecode
if not bytecode or bytecode.bytecode in (None, "", "0x"):
raise MissingDeploymentBytecodeError(self.contract_type)

txn = self(*args, **kwargs)
private = kwargs.get("private", False)

Expand Down
17 changes: 17 additions & 0 deletions src/ape/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import click
from eth_typing import Hash32
from eth_utils import humanize_hash
from ethpm_types import ContractType
from ethpm_types.abi import ConstructorABI, ErrorABI, MethodABI
from rich import print as rich_print

Expand Down Expand Up @@ -77,6 +78,22 @@ class ContractDataError(ApeException):
"""


class MissingDeploymentBytecodeError(ContractDataError):
"""
Raised when trying to deploy an interface or empty data.
"""

def __init__(self, contract_type: ContractType):
message = "Cannot deploy: contract"
if name := contract_type.name:
message = f"{message} '{name}'"

message = (
f"{message} has no deployment-bytecode. Are you attempting to deploy an interface?"
)
super().__init__(message)


class ArgumentsLengthError(ContractDataError):
"""
Raised when calling a contract method with the wrong number of arguments.
Expand Down
22 changes: 21 additions & 1 deletion tests/functional/test_accounts.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@
from eip712.messages import EIP712Message
from eth_account.messages import encode_defunct
from eth_pydantic_types import HexBytes
from ethpm_types import ContractType

import ape
from ape.api import ImpersonatedAccount
from ape.contracts import ContractContainer
from ape.exceptions import (
AccountsError,
AliasAlreadyInUseError,
MissingDeploymentBytecodeError,
NetworkError,
ProjectError,
SignatureError,
Expand Down Expand Up @@ -281,7 +284,7 @@ def test_deploy_proxy(owner, vyper_contract_instance, proxy_contract_container,
assert implementation.contract_type == vyper_contract_instance.contract_type


def test_deploy_instance(owner, vyper_contract_instance, chain, clean_contracts_cache):
def test_deploy_instance(owner, vyper_contract_instance):
"""
Tests against a confusing scenario where you would get a SignatureError when
trying to deploy a ContractInstance because Ape would attempt to create a tx
Expand All @@ -297,6 +300,23 @@ def test_deploy_instance(owner, vyper_contract_instance, chain, clean_contracts_
owner.deploy(vyper_contract_instance)


@pytest.mark.parametrize("bytecode", (None, {}, {"bytecode": "0x"}))
def test_deploy_no_deployment_bytecode(owner, bytecode):
"""
https://github.com/ApeWorX/ape/issues/1904
"""
expected = (
r"Cannot deploy: contract 'Apes' has no deployment-bytecode\. "
r"Are you attempting to deploy an interface\?"
)
contract_type = ContractType.model_validate(
{"abi": [], "contractName": "Apes", "deploymentBytecode": bytecode}
)
contract = ContractContainer(contract_type)
with pytest.raises(MissingDeploymentBytecodeError, match=expected):
owner.deploy(contract)


def test_send_transaction_with_bad_nonce(sender, receiver):
# Bump the nonce so we can set one that is too low.
sender.transfer(receiver, "1 gwei", type=0)
Expand Down
6 changes: 3 additions & 3 deletions tests/functional/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@
from tests.functional.conftest import PROJECT_WITH_LONG_CONTRACTS_FOLDER


def test_deployments(networks, owner, project_with_contract):
def test_deployments(networks, owner, vyper_contract_container):
# First, obtain a "previously-deployed" contract.
instance = project_with_contract.ApeContract0.deploy(sender=owner)
instance = vyper_contract_container.deploy(1000200000, sender=owner)

# Create a config using this new contract for a "later time".
data = {
Expand All @@ -33,7 +33,7 @@ def test_deployments(networks, owner, project_with_contract):
assert config.root["ethereum"]["local"][0]["address"] == instance.address

# Ensure we can reference the deployment on the project.
deployment = project_with_contract.ApeContract0.deployments[0]
deployment = vyper_contract_container.deployments[0]
assert deployment.address == instance.address


Expand Down
33 changes: 28 additions & 5 deletions tests/functional/test_contract_container.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import pytest
from ethpm_types import ContractType

from ape import Contract
from ape.contracts import ContractInstance
from ape.exceptions import ArgumentsLengthError, NetworkError, ProjectError
from ape.contracts import ContractContainer, ContractInstance
from ape.exceptions import (
ArgumentsLengthError,
MissingDeploymentBytecodeError,
NetworkError,
ProjectError,
)
from ape_ethereum.ecosystem import ProxyType


Expand Down Expand Up @@ -73,9 +79,26 @@ def test_deploy_privately(owner, contract_container):
assert isinstance(deploy_1, ContractInstance)


def test_deployment_property(chain, owner, project_with_contract, eth_tester_provider):
initial_deployed_contract = project_with_contract.ApeContract0.deploy(sender=owner)
actual = project_with_contract.ApeContract0.deployments[-1].address
@pytest.mark.parametrize("bytecode", (None, {}, {"bytecode": "0x"}))
def test_deploy_no_deployment_bytecode(owner, bytecode):
"""
https://github.com/ApeWorX/ape/issues/1904
"""
expected = (
r"Cannot deploy: contract 'Apes' has no deployment-bytecode\. "
r"Are you attempting to deploy an interface\?"
)
contract_type = ContractType.model_validate(
{"abi": [], "contractName": "Apes", "deploymentBytecode": bytecode}
)
contract = ContractContainer(contract_type)
with pytest.raises(MissingDeploymentBytecodeError, match=expected):
contract.deploy(sender=owner)


def test_deployments(owner, eth_tester_provider, vyper_contract_container):
initial_deployed_contract = vyper_contract_container.deploy(10000000, sender=owner)
actual = vyper_contract_container.deployments[-1].address
expected = initial_deployed_contract.address
assert actual == expected

Expand Down
39 changes: 20 additions & 19 deletions tests/functional/test_contracts_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@


@pytest.fixture
def contract_0(project_with_contract):
return project_with_contract.ApeContract0
def contract_0(vyper_contract_container):
return vyper_contract_container


@pytest.fixture
def contract_1(project_with_contract):
return project_with_contract.ApeContract1
def contract_1(solidity_contract_container):
return solidity_contract_container


def test_instance_at(chain, contract_instance):
Expand Down Expand Up @@ -173,8 +173,8 @@ def test_get_deployments_local(chain, owner, contract_0, contract_1):
chain.contracts._local_contract_types = {}
starting_contracts_list_0 = chain.contracts.get_deployments(contract_0)
starting_contracts_list_1 = chain.contracts.get_deployments(contract_1)
deployed_contract_0 = owner.deploy(contract_0)
deployed_contract_1 = owner.deploy(contract_1)
deployed_contract_0 = owner.deploy(contract_0, 900000000)
deployed_contract_1 = owner.deploy(contract_1, 900000001)

# Act
contracts_list_0 = chain.contracts.get_deployments(contract_0)
Expand All @@ -195,8 +195,8 @@ def test_get_deployments_local(chain, owner, contract_0, contract_1):
def test_get_deployments_live(
chain, owner, contract_0, contract_1, remove_disk_writes_deployments, dummy_live_network
):
deployed_contract_0 = owner.deploy(contract_0, required_confirmations=0)
deployed_contract_1 = owner.deploy(contract_1, required_confirmations=0)
deployed_contract_0 = owner.deploy(contract_0, 8000000, required_confirmations=0)
deployed_contract_1 = owner.deploy(contract_1, 8000001, required_confirmations=0)

# Act
my_contracts_list_0 = chain.contracts.get_deployments(contract_0)
Expand All @@ -214,12 +214,12 @@ def test_get_multiple_deployments_live(
):
starting_contracts_list_0 = chain.contracts.get_deployments(contract_0)
starting_contracts_list_1 = chain.contracts.get_deployments(contract_1)
initial_deployed_contract_0 = owner.deploy(contract_0, required_confirmations=0)
initial_deployed_contract_1 = owner.deploy(contract_1, required_confirmations=0)
owner.deploy(contract_0, required_confirmations=0)
owner.deploy(contract_1, required_confirmations=0)
final_deployed_contract_0 = owner.deploy(contract_0, required_confirmations=0)
final_deployed_contract_1 = owner.deploy(contract_1, required_confirmations=0)
initial_deployed_contract_0 = owner.deploy(contract_0, 700000, required_confirmations=0)
initial_deployed_contract_1 = owner.deploy(contract_1, 700001, required_confirmations=0)
owner.deploy(contract_0, 700002, required_confirmations=0)
owner.deploy(contract_1, 700003, required_confirmations=0)
final_deployed_contract_0 = owner.deploy(contract_0, 600000, required_confirmations=0)
final_deployed_contract_1 = owner.deploy(contract_1, 600001, required_confirmations=0)
contracts_list_0 = chain.contracts.get_deployments(contract_0)
contracts_list_1 = chain.contracts.get_deployments(contract_1)
contract_type_map = {
Expand All @@ -239,11 +239,11 @@ def test_get_multiple_deployments_live(
def test_cache_updates_per_deploy(owner, chain, contract_0, contract_1):
# Arrange / Act
initial_contracts = chain.contracts.get_deployments(contract_0)
expected_first_contract = owner.deploy(contract_0)
expected_first_contract = owner.deploy(contract_0, 6787678)

owner.deploy(contract_0)
owner.deploy(contract_0)
expected_last_contract = owner.deploy(contract_0)
owner.deploy(contract_0, 6787679)
owner.deploy(contract_0, 6787680)
expected_last_contract = owner.deploy(contract_0, 6787681)

actual_contracts = chain.contracts.get_deployments(contract_0)
first_index = len(initial_contracts) # next index before deploys from this test
Expand Down Expand Up @@ -294,7 +294,8 @@ def test_get_non_contract_address(chain, owner):

def test_get_attempts_to_convert(chain):
with pytest.raises(ConversionError):
chain.contracts.get("test.eth")
# NOTE: using eth2 suffix so still works if ape-ens is installed.
chain.contracts.get("test.eth2")


def test_cache_non_checksum_address(chain, vyper_contract_instance):
Expand Down

0 comments on commit 8b53652

Please sign in to comment.