Skip to content

Commit 4817927

Browse files
authored
refactor!: move contract instance and container helper utilities to chain.contracts (#898)
1 parent 7efa7c5 commit 4817927

File tree

13 files changed

+61
-84
lines changed

13 files changed

+61
-84
lines changed

src/ape/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
"""The currently active project. See :class:`ape.managers.project.ProjectManager`."""
3838

3939
Contract = chain.contracts.instance_at
40-
"""User-facing class for instantiating contracts. See :class:`ape.contracts.base._Contract`."""
40+
"""User-facing class for instantiating contracts."""
4141

4242
convert = _ManagerAccessMixin.conversion_manager.convert
4343
"""Conversion utility function. See :class:`ape.managers.converters.ConversionManager`."""

src/ape/api/accounts.py

+6-7
Original file line numberDiff line numberDiff line change
@@ -174,15 +174,14 @@ def deploy(self, contract: "ContractContainer", *args, **kwargs) -> "ContractIns
174174
if not receipt.contract_address:
175175
raise AccountsError(f"'{receipt.txn_hash}' did not create a contract.")
176176

177-
address = click.style(receipt.contract_address, bold=True)
177+
address = self.provider.network.ecosystem.decode_address(receipt.contract_address)
178+
styled_address = click.style(receipt.contract_address, bold=True)
178179
contract_name = contract.contract_type.name or "<Unnamed Contract>"
179-
logger.success(f"Contract '{contract_name}' deployed to: {address}")
180-
181-
contract_instance = self.create_contract(
182-
address=receipt.contract_address, # type: ignore
183-
contract_type=contract.contract_type,
180+
logger.success(f"Contract '{contract_name}' deployed to: {styled_address}")
181+
contract_instance = self.chain_manager.contracts.instance_at(
182+
address, contract.contract_type
184183
)
185-
self.chain_manager.contracts[contract_instance.address] = contract_instance.contract_type
184+
self.chain_manager.contracts[address] = contract_instance.contract_type
186185
return contract_instance
187186

188187
def check_signature(

src/ape/api/projects.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,8 @@ def __getattr__(self, contract_name: str) -> "ContractContainer":
235235
def get(self, contract_name: str) -> Optional["ContractContainer"]:
236236
manifest = self.extract_manifest()
237237
if hasattr(manifest, contract_name):
238-
return self.create_contract_container(contract_type=getattr(manifest, contract_name))
238+
contract_type = getattr(manifest, contract_name)
239+
return self.chain_manager.contracts.get_container(contract_type)
239240

240241
return None
241242

src/ape/api/providers.py

+10-2
Original file line numberDiff line numberDiff line change
@@ -669,9 +669,17 @@ def get_balance(self, address: str) -> int:
669669
def get_code(self, address: str) -> bytes:
670670
return self.web3.eth.get_code(address)
671671

672-
def get_storage_at(self, address: str, slot: int, *args, **kwargs) -> bytes:
672+
def get_storage_at(self, address: str, slot: int, **kwargs) -> bytes:
673673
block_id = kwargs.pop("block_identifier", None)
674-
return self.web3.eth.get_storage_at(address, slot, block_identifier=block_id)
674+
try:
675+
return self.web3.eth.get_storage_at(
676+
address, slot, block_identifier=block_id
677+
) # type: ignore
678+
except ValueError as err:
679+
if "RPC Endpoint has not been implemented" in str(err):
680+
raise APINotImplementedError(str(err)) from err
681+
682+
raise # Raise original error
675683

676684
def send_call(self, txn: TransactionAPI, **kwargs) -> bytes:
677685
try:

src/ape/cli/options.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -174,13 +174,13 @@ def _load_contracts(ctx, param, value) -> Optional[Union[ContractType, List[Cont
174174
# and therefore we should also return a list.
175175
is_multiple = isinstance(value, (tuple, list))
176176

177-
def create_contract(contract_name: str) -> ContractType:
177+
def get_contract(contract_name: str) -> ContractType:
178178
if contract_name not in project.contracts:
179179
raise ContractError(f"No contract named '{value}'")
180180

181181
return project.contracts[contract_name]
182182

183-
return [create_contract(c) for c in value] if is_multiple else create_contract(value)
183+
return [get_contract(c) for c in value] if is_multiple else get_contract(value)
184184

185185

186186
def contract_option(help=None, required=False, multiple=False):

src/ape/contracts/base.py

+2-5
Original file line numberDiff line numberDiff line change
@@ -709,7 +709,7 @@ def deployments(self):
709709

710710
return self.chain_manager.contracts.get_deployments(self)
711711

712-
def at(self, address: str) -> ContractInstance:
712+
def at(self, address: AddressType) -> ContractInstance:
713713
"""
714714
Get a contract at the given address.
715715
@@ -729,10 +729,7 @@ def at(self, address: str) -> ContractInstance:
729729
:class:`~ape.contracts.ContractInstance`
730730
"""
731731

732-
return self.create_contract(
733-
address=address, # type: ignore
734-
contract_type=self.contract_type,
735-
)
732+
return self.chain_manager.contracts.instance_at(address, self.contract_type)
736733

737734
def __call__(self, *args, **kwargs) -> TransactionAPI:
738735
args = self.conversion_manager.convert(args, tuple)

src/ape/managers/chain.py

+24-19
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import pandas as pd
88
from ethpm_types import ContractType
99

10-
from ape.api import Address, BlockAPI, ReceiptAPI
10+
from ape.api import BlockAPI, ReceiptAPI
1111
from ape.api.address import BaseAddress
1212
from ape.api.networks import LOCAL_NETWORK_NAME, NetworkAPI, ProxyInfoAPI
1313
from ape.api.query import BlockQuery, validate_and_expand_columns
@@ -618,16 +618,27 @@ def get(
618618

619619
return contract_type
620620

621+
def get_container(self, contract_type: ContractType) -> ContractContainer:
622+
"""
623+
Get a contract container for the given contract type.
624+
625+
Args:
626+
contract_type (ContractType): The contract type to wrap.
627+
628+
Returns:
629+
ContractContainer: A container object you can deploy.
630+
"""
631+
632+
return ContractContainer(contract_type)
633+
621634
def instance_at(
622635
self, address: Union[str, "AddressType"], contract_type: Optional[ContractType] = None
623-
) -> BaseAddress:
636+
) -> ContractInstance:
624637
"""
625638
Get a contract at the given address. If the contract type of the contract is known,
626639
either from a local deploy or a :class:`~ape.api.explorers.ExplorerAPI`, it will use that
627640
contract type. You can also provide the contract type from which it will cache and use
628-
next time. If the contract type is not known, returns a
629-
:class:`~ape.api.address.BaseAddress` object; otherwise returns a
630-
:class:`~ape.contracts.ContractInstance` (subclass).
641+
next time.
631642
632643
Raises:
633644
TypeError: When passing an invalid type for the `contract_type` arguments
@@ -640,9 +651,7 @@ def instance_at(
640651
in case it is not already known.
641652
642653
Returns:
643-
:class:`~ape.api.address.BaseAddress`: Will be a
644-
:class:`~ape.contracts.ContractInstance` if the contract type is discovered,
645-
which is a subclass of the ``BaseAddress`` class.
654+
:class:`~ape.contracts.base.ContractInstance`
646655
"""
647656

648657
try:
@@ -653,15 +662,15 @@ def instance_at(
653662
address = self.provider.network.ecosystem.decode_address(address)
654663
contract_type = self.get(address, default=contract_type)
655664

656-
if contract_type:
657-
if not isinstance(contract_type, ContractType):
658-
raise TypeError(
659-
f"Expected type '{ContractType.__name__}' for argument 'contract_type'."
660-
)
665+
if not contract_type:
666+
raise ChainError(f"Failed to get contract type for address '{address}'.")
661667

662-
return self.create_contract(address, contract_type)
668+
elif not isinstance(contract_type, ContractType):
669+
raise TypeError(
670+
f"Expected type '{ContractType.__name__}' for argument 'contract_type'."
671+
)
663672

664-
return Address(address)
673+
return ContractInstance(address, contract_type)
665674

666675
def get_deployments(self, contract_container: ContractContainer) -> List[ContractInstance]:
667676
"""
@@ -704,10 +713,6 @@ def get_deployments(self, contract_container: ContractContainer) -> List[Contrac
704713
instance = self.instance_at(
705714
contract_addresses[deployment_index], contract_container.contract_type
706715
)
707-
708-
if not isinstance(instance, ContractInstance):
709-
continue
710-
711716
deployments.append(instance)
712717

713718
return deployments

src/ape/managers/project/manager.py

+1-3
Original file line numberDiff line numberDiff line change
@@ -440,9 +440,7 @@ def _load_dependencies(self) -> Dict[str, Dict[str, DependencyAPI]]:
440440

441441
def _get_contract(self, name: str) -> Optional[ContractContainer]:
442442
if name in self.contracts:
443-
return self.create_contract_container(
444-
contract_type=self.contracts[name],
445-
)
443+
return self.chain_manager.contracts.get_container(self.contracts[name])
446444

447445
return None
448446

src/ape/utils/basemodel.py

-34
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
11
from abc import ABC
22
from typing import TYPE_CHECKING, ClassVar, Dict, List, cast
33

4-
from ethpm_types import ContractType
54
from pydantic import BaseModel
65

76
from ape.exceptions import ProviderNotConnectedError
8-
from ape.types import AddressType
97
from ape.utils.misc import cached_property, singledispatchmethod
108

119
if TYPE_CHECKING:
1210
from ape.api.providers import ProviderAPI
13-
from ape.contracts.base import ContractContainer, ContractInstance
1411
from ape.managers.accounts import AccountManager
1512
from ape.managers.chain import ChainManager
1613
from ape.managers.compilers import CompilerManager
@@ -75,37 +72,6 @@ def provider(self) -> "ProviderAPI":
7572
raise ProviderNotConnectedError()
7673
return self.network_manager.active_provider
7774

78-
def create_contract_container(self, contract_type: ContractType) -> "ContractContainer":
79-
"""
80-
Helper method for creating a ``ContractContainer``.
81-
82-
Args:
83-
contract_type (``ContractType``): Type of contract for the container
84-
85-
Returns:
86-
:class:`~ape.contracts.ContractContainer`
87-
"""
88-
from ape.contracts.base import ContractContainer
89-
90-
return ContractContainer(contract_type=contract_type)
91-
92-
def create_contract(
93-
self, address: "AddressType", contract_type: "ContractType"
94-
) -> "ContractInstance":
95-
"""
96-
Helper method for creating a ``ContractInstance``.
97-
98-
Args:
99-
address (``AddressType``): Address of contract
100-
contract_type (``ContractType``): Type of contract
101-
102-
Returns:
103-
:class:`~ape.contracts.ContractInstance`
104-
"""
105-
from ape.contracts.base import ContractInstance
106-
107-
return ContractInstance(address=address, contract_type=contract_type)
108-
10975

11076
class BaseInterface(ManagerAccessMixin, ABC):
11177
"""

src/ape/utils/trace.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ def _dim_default_gas(call_sig: str) -> str:
132132
method = None
133133
contract_name = contract_type.name
134134
if "symbol" in contract_type.view_methods:
135-
contract = self._receipt.create_contract(address, contract_type)
135+
contract = self._receipt.chain_manager.contracts.instance_at(address, contract_type)
136136

137137
try:
138138
contract_name = contract.symbol() or contract_name

src/ape_ethereum/ecosystem.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from ape.api import BlockAPI, EcosystemAPI, PluginConfig, ReceiptAPI, TransactionAPI
1515
from ape.api.networks import LOCAL_NETWORK_NAME, ProxyInfoAPI
1616
from ape.contracts.base import ContractCall
17-
from ape.exceptions import DecodingError, TransactionError
17+
from ape.exceptions import APINotImplementedError, DecodingError, TransactionError
1818
from ape.types import AddressType, ContractLog, RawAddress, TransactionSignature
1919
from ape.utils import (
2020
LogInputABICollection,
@@ -145,7 +145,11 @@ def get_proxy_info(self, address: AddressType) -> Optional[ProxyInfo]:
145145
ProxyType.UUPS: str_to_slot("PROXIABLE"),
146146
}
147147
for type, slot in slots.items():
148-
storage = self.provider.get_storage_at(address, slot)
148+
try:
149+
storage = self.provider.get_storage_at(address, slot)
150+
except APINotImplementedError:
151+
continue
152+
149153
if sum(storage) == 0:
150154
continue
151155

tests/functional/test_contract_event.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -225,8 +225,7 @@ def test_poll_logs_timeout(vyper_contract_instance, eth_tester_provider, owner,
225225
assert "Timed out waiting for new block (time_waited=1" in str(err.value)
226226

227227

228-
def test_contract_two_events_with_same_name(owner, networks_connected_to_tester):
229-
provider = networks_connected_to_tester
228+
def test_contract_two_events_with_same_name(owner, chain, networks_connected_to_tester):
230229
base_path = Path(__file__).parent / "data" / "contracts" / "ethereum" / "local"
231230
interface_path = base_path / "Interface.json"
232231
impl_path = base_path / "InterfaceImplementation.json"
@@ -238,7 +237,7 @@ def test_contract_two_events_with_same_name(owner, networks_connected_to_tester)
238237
assert len([e for e in impl_contract_type.events if e.name == event_name]) == 2
239238
assert len([e for e in interface_contract_type.events if e.name == event_name]) == 1
240239

241-
impl_container = provider.create_contract_container(impl_contract_type)
240+
impl_container = chain.contracts.get_container(impl_contract_type)
242241
impl_instance = owner.deploy(impl_container)
243242

244243
with pytest.raises(AttributeError) as err:

tests/functional/test_contract_instance.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import re
22

3+
import pytest
34
from eth_utils import is_checksum_address
45
from hexbytes import HexBytes
56

67
from ape import Contract
7-
from ape.api import Address
8+
from ape.exceptions import ChainError
89
from ape.utils import ZERO_ADDRESS
910
from ape_ethereum.transactions import TransactionStatusEnum
1011

@@ -14,9 +15,8 @@
1415

1516

1617
def test_init_at_unknown_address():
17-
contract = Contract(SOLIDITY_CONTRACT_ADDRESS)
18-
assert type(contract) == Address
19-
assert contract.address == SOLIDITY_CONTRACT_ADDRESS
18+
with pytest.raises(ChainError):
19+
Contract(SOLIDITY_CONTRACT_ADDRESS)
2020

2121

2222
def test_init_specify_contract_type(

0 commit comments

Comments
 (0)