Skip to content

Commit

Permalink
Merge branch 'main' into feat/project-flag-test
Browse files Browse the repository at this point in the history
  • Loading branch information
antazoey authored Dec 9, 2024
2 parents 126f9e1 + 5f2d28d commit 4e94fbd
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 12 deletions.
13 changes: 12 additions & 1 deletion docs/userguides/contracts.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,18 @@ from ape import Contract
contract = Contract("0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45")
```

It will fetch the `contract-type` using the explorer plugin from the active network, such as [ape-etherscan](https://github.com/ApeWorX/ape-etherscan).
If the contract ABI and/or code is cached on disk or in memory (such as from a previous deploy or retrieval), it will use it.
Otherwise, it will fetch the `ContractType` using the explorer plugin from the active network, such as [ape-etherscan](https://github.com/ApeWorX/ape-etherscan).

To avoid fetching the contract from an explorer such as Etherscan, use `fetch_from_explorer=False`.

```python
from ape import Contract

contract = Contract("0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45", fetch_from_explorer=False)
```

This also avoids checking for an updated `ContractType` and forces Ape to only use types cached to disk or in memory.

If you have the [ENS plugin](https://github.com/ApeWorX/ape-ens) installed, you can use `.eth` domain names as the argument:

Expand Down
12 changes: 9 additions & 3 deletions src/ape/contracts/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1407,7 +1407,10 @@ def deployments(self):
return self.chain_manager.contracts.get_deployments(self)

def at(
self, address: "AddressType", txn_hash: Optional[Union[str, HexBytes]] = None
self,
address: "AddressType",
txn_hash: Optional[Union[str, HexBytes]] = None,
fetch_from_explorer: bool = True,
) -> ContractInstance:
"""
Get a contract at the given address.
Expand All @@ -1425,13 +1428,16 @@ def at(
a different ABI than :attr:`~ape.contracts.ContractContainer.contract_type`.
txn_hash (Union[str, HexBytes]): The hash of the transaction that deployed the
contract, if available. Defaults to ``None``.
fetch_from_explorer (bool): Set to ``False`` to avoid fetching from an explorer.
Returns:
:class:`~ape.contracts.ContractInstance`
"""

return self.chain_manager.contracts.instance_at(
address, self.contract_type, txn_hash=txn_hash
address,
self.contract_type,
txn_hash=txn_hash,
fetch_from_explorer=fetch_from_explorer,
)

@cached_property
Expand Down
19 changes: 16 additions & 3 deletions src/ape/managers/chain.py
Original file line number Diff line number Diff line change
Expand Up @@ -1029,7 +1029,10 @@ def get_contract_type(addr: AddressType):

@nonreentrant(key_fn=lambda *args, **kwargs: args[1])
def get(
self, address: AddressType, default: Optional[ContractType] = None
self,
address: AddressType,
default: Optional[ContractType] = None,
fetch_from_explorer: bool = True,
) -> Optional[ContractType]:
"""
Get a contract type by address.
Expand All @@ -1041,6 +1044,9 @@ def get(
address (AddressType): The address of the contract.
default (Optional[ContractType]): A default contract when none is found.
Defaults to ``None``.
fetch_from_explorer (bool): Set to ``False`` to avoid fetching from an
explorer. Defaults to ``True``. Only fetches if it needs to (uses disk
& memory caching otherwise).
Returns:
Optional[ContractType]: The contract type if it was able to get one,
Expand Down Expand Up @@ -1095,7 +1101,8 @@ def get(
return default

# Also gets cached to disk for faster lookup next time.
contract_type = self._get_contract_type_from_explorer(address_key)
if fetch_from_explorer:
contract_type = self._get_contract_type_from_explorer(address_key)

# Cache locally for faster in-session look-up.
if contract_type:
Expand Down Expand Up @@ -1136,6 +1143,7 @@ def instance_at(
contract_type: Optional[ContractType] = None,
txn_hash: Optional[Union[str, "HexBytes"]] = None,
abi: Optional[Union[list[ABI], dict, str, Path]] = None,
fetch_from_explorer: bool = True,
) -> ContractInstance:
"""
Get a contract at the given address. If the contract type of the contract is known,
Expand All @@ -1157,6 +1165,9 @@ def instance_at(
deploying the contract, if known. Useful for publishing. Defaults to ``None``.
abi (Optional[Union[list[ABI], dict, str, Path]]): Use an ABI str, dict, path,
or ethpm models to create a contract instance class.
fetch_from_explorer (bool): Set to ``False`` to avoid fetching from the explorer.
Defaults to ``True``. Won't fetch unless it needs to (uses disk & memory caching
first).
Returns:
:class:`~ape.contracts.base.ContractInstance`
Expand All @@ -1172,7 +1183,9 @@ def instance_at(

try:
# Always attempt to get an existing contract type to update caches
contract_type = self.get(contract_address, default=contract_type)
contract_type = self.get(
contract_address, default=contract_type, fetch_from_explorer=fetch_from_explorer
)
except Exception as err:
if contract_type or abi:
# If a default contract type was provided, don't error and use it.
Expand Down
33 changes: 29 additions & 4 deletions tests/functional/test_contract_container.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ def test_deploy(
not_owner,
contract_container,
networks_connected_to_tester,
project,
chain,
clean_contracts_cache,
):
contract = contract_container.deploy(4, sender=not_owner, something_else="IGNORED")
Expand All @@ -35,8 +33,6 @@ def test_deploy_wrong_number_of_arguments(
not_owner,
contract_container,
networks_connected_to_tester,
project,
chain,
clean_contracts_cache,
):
expected = (
Expand Down Expand Up @@ -163,3 +159,32 @@ def test_source_id(contract_container):
expected = contract_container.contract_type.source_id
# Is just a pass-through (via extras-model), but making sure it works.
assert actual == expected


def test_at(vyper_contract_instance, vyper_contract_container):
instance = vyper_contract_container.at(vyper_contract_instance.address)
assert instance == vyper_contract_instance


def test_at_fetch_from_explorer_false(
project_with_contract, mock_explorer, eth_tester_provider, owner
):
# Grab the container - note: this always compiles!
container = project_with_contract.Contract
instance = container.deploy(sender=owner)

# Hack off the fact that it was compiled.
project_with_contract.clean()

# Simulate having an explorer plugin installed (e.g. ape-etherscan).
eth_tester_provider.network.explorer = mock_explorer

# Attempt to create an instance. It should NOT use the explorer at all!
instance2 = container.at(instance.address, fetch_from_explorer=False)

assert instance == instance2
# Ensure explorer was not used at all.
assert mock_explorer.get_contract_type.call_count == 0

# Clean up test.
eth_tester_provider.network.explorer = None
2 changes: 1 addition & 1 deletion tests/functional/test_contracts_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def test_instance_at_uses_given_contract_type_when_retrieval_fails(mocker, chain
expected_fail_message = "LOOK_FOR_THIS_FAIL_MESSAGE"
existing_fn = chain.contracts.get

def fn(addr, default=None):
def fn(addr, default=None, **kwargs):
if addr == new_address:
raise ValueError(expected_fail_message)

Expand Down

0 comments on commit 4e94fbd

Please sign in to comment.