Skip to content

Commit

Permalink
test: add another test
Browse files Browse the repository at this point in the history
  • Loading branch information
antazoey committed Jan 27, 2025
1 parent 014b9e6 commit dbb0e0c
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 46 deletions.
90 changes: 46 additions & 44 deletions src/ape/managers/_contractscache.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from contextlib import contextmanager
from functools import cached_property
from pathlib import Path
from typing import TYPE_CHECKING, Generic, Literal, Optional, TypeVar, Union
from typing import TYPE_CHECKING, Generic, Optional, TypeVar, Union

from ethpm_types import ABI, ContractType
from pydantic import BaseModel
Expand Down Expand Up @@ -273,17 +273,19 @@ def __contains__(self, address: AddressType) -> bool:
def cache_deployment(
self,
contract_instance: ContractInstance,
proxy_info: Optional[Union[ProxyInfoAPI, Literal["skip"]]] = None,
proxy_info: Optional[ProxyInfoAPI] = None,
detect_proxy: bool = True,
):
"""
Cache the given contract instance's type and deployment information.
Args:
contract_instance (:class:`~ape.contracts.base.ContractInstance`): The contract
to cache.
proxy_info (Optional[Union[ProxyInfoAPI, Literal["skip"]]]): Either pass in the proxy
info, if it is known, to avoid the potentially expensive look-up, or pass the
keyword "skip" to skip detecting proxies for this contract.
proxy_info (Optional[ProxyInfoAPI]): Pass in the proxy info, if it is known, to
avoid the potentially expensive look-up.
detect_proxy (bool): Set to ``False`` to avoid detecting if the contract is a
proxy.
"""
address = contract_instance.address
contract_type = contract_instance.contract_type # may be a proxy
Expand All @@ -292,7 +294,11 @@ def cache_deployment(
# in case it is needed somewhere. It may get overridden.
self.contract_types.memory[address] = contract_type

if proxy_info is None:
if proxy_info:
# Was given proxy info.
self._cache_proxy_contract(address, proxy_info, contract_type, contract_instance)

elif detect_proxy:
# Proxy info was not provided. Use the connected ecosystem to figure it out.
if proxy_info := self.provider.network.ecosystem.get_proxy_info(address):
# The user is caching a deployment of a proxy with the target already set.
Expand All @@ -302,13 +308,9 @@ def cache_deployment(
# Cache as normal.
self.contract_types[address] = contract_type

elif proxy_info == "skip":
# Cache as normal.
self.contract_types[address] = contract_type

else:
# Was given proxy info.
self._cache_proxy_contract(address, proxy_info, contract_type, contract_instance)
# Cache as normal; do not do expensive proxy detection.
self.contract_types[address] = contract_type

# Cache the deployment now.
txn_hash = contract_instance.txn_hash
Expand Down Expand Up @@ -517,7 +519,8 @@ def get(
address: AddressType,
default: Optional[ContractType] = None,
fetch_from_explorer: bool = True,
proxy_info: Optional[Union[ProxyInfoAPI, Literal["skip"]]] = None,
proxy_info: Optional[ProxyInfoAPI] = None,
detect_proxy: bool = True,
) -> Optional[ContractType]:
"""
Get a contract type by address.
Expand All @@ -532,9 +535,9 @@ def get(
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).
proxy_info (Optional[Union[ProxyInfoAPI, Literal["skip"]]]): Either pass in the proxy
info, if it is known, to avoid the potentially expensive look-up, or pass the
keyword "skip" to skip detecting proxies for this contract.
proxy_info (Optional[ProxyInfoAPI]): Pass in the proxy info, if it is known,
to avoid the potentially expensive look-up.
detect_proxy (bool): Set to ``False`` to avoid detecting if it is a proxy.
Returns:
Optional[ContractType]: The contract type if it was able to get one,
Expand All @@ -560,32 +563,29 @@ def get(

else:
# Contract is not cached yet. Check broader sources, such as an explorer.
if proxy_info != "skip":
if not proxy_info:
# Proxy info not provided. Attempt to detect.
if not (proxy_info := self.proxy_infos[address_key]):
if proxy_info := self.provider.network.ecosystem.get_proxy_info(
address_key
):
self.proxy_infos[address_key] = proxy_info

if proxy_info:
# Contract is a proxy (either was detected or provided).
implementation_contract_type = self.get(proxy_info.target, default=default)
proxy_contract_type = (
self._get_contract_type_from_explorer(address_key)
if fetch_from_explorer
else None
if not proxy_info and detect_proxy:
# Proxy info not provided. Attempt to detect.
if not (proxy_info := self.proxy_infos[address_key]):
if proxy_info := self.provider.network.ecosystem.get_proxy_info(address_key):
self.proxy_infos[address_key] = proxy_info

if proxy_info:
# Contract is a proxy (either was detected or provided).
implementation_contract_type = self.get(proxy_info.target, default=default)
proxy_contract_type = (
self._get_contract_type_from_explorer(address_key)
if fetch_from_explorer
else None
)
if proxy_contract_type:
contract_type_to_cache = _get_combined_contract_type(
proxy_contract_type, proxy_info, implementation_contract_type
)
if proxy_contract_type:
contract_type_to_cache = _get_combined_contract_type(
proxy_contract_type, proxy_info, implementation_contract_type
)
else:
contract_type_to_cache = implementation_contract_type
else:
contract_type_to_cache = implementation_contract_type

self.contract_types[address_key] = contract_type_to_cache
return contract_type_to_cache
self.contract_types[address_key] = contract_type_to_cache
return contract_type_to_cache

if not self.provider.get_code(address_key):
if default:
Expand Down Expand Up @@ -627,7 +627,8 @@ def instance_at(
txn_hash: Optional[Union[str, "HexBytes"]] = None,
abi: Optional[Union[list[ABI], dict, str, Path]] = None,
fetch_from_explorer: bool = True,
proxy_info: Optional[Union[ProxyInfoAPI, Literal["skip"]]] = None,
proxy_info: Optional[ProxyInfoAPI] = None,
detect_proxy: bool = True,
) -> ContractInstance:
"""
Get a contract at the given address. If the contract type of the contract is known,
Expand All @@ -652,9 +653,9 @@ def instance_at(
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).
proxy_info (Optional[Union[ProxyInfoAPI, Literal["skip"]]]): Either pass in the proxy
info, if it is known, to avoid the potentially expensive look-up, or pass the
keyword "skip" to skip detecting proxies for this contract.
proxy_info (Optional[ProxyInfoAPI]): Pass in the proxy info, if it is known, to avoid
the potentially expensive look-up.
detect_proxy (bool): Set to ``False`` to avoid detecting if the contract is a proxy.
Returns:
:class:`~ape.contracts.base.ContractInstance`
Expand All @@ -681,6 +682,7 @@ def instance_at(
default=contract_type,
fetch_from_explorer=fetch_from_explorer,
proxy_info=proxy_info,
detect_proxy=detect_proxy,
)
except Exception as err:
if contract_type or abi:
Expand Down
21 changes: 19 additions & 2 deletions tests/functional/test_contracts_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ def test_instance_at_use_abi(chain, solidity_fallback_contract, owner):
assert instance2.contract_type.abi == instance.contract_type.abi


def test_instance_at_skip_proxy_check(mocker, chain, vyper_contract_instance, owner):
def test_instance_at_provide_proxy(mocker, chain, vyper_contract_instance, owner):
address = vyper_contract_instance.address
container = _make_minimal_proxy(address=address.lower())
proxy = container.deploy(sender=owner)
Expand All @@ -135,13 +135,30 @@ def test_instance_at_skip_proxy_check(mocker, chain, vyper_contract_instance, ow
# longer knows what the contract type is. That is fine for this test!
chain.contracts.instance_at(proxy.address, proxy_info=proxy_info)

# The real test: we check the spy to ensure we never attempted to look-up
# The real test: we check the spy to ensure we never attempted to look up
# the proxy info for the given address to `instance_at()`.
for call in proxy_detection_spy.call_args_list:
for arg in call[0]:
assert proxy.address != arg


def test_instance_at_skip_proxy(mocker, chain, vyper_contract_instance, owner):
address = vyper_contract_instance.address
del chain.contracts[address]
proxy_detection_spy = mocker.spy(chain.contracts.proxy_infos, "get_type")

with pytest.raises(ContractNotFoundError):
# This just fails because we deleted it from the cache so Ape no
# longer knows what the contract type is. That is fine for this test!
chain.contracts.instance_at(address, detect_proxy=False)

# The real test: we check the spy to ensure we never attempted to look up
# the proxy info for the given address to `instance_at()`.
for call in proxy_detection_spy.call_args_list:
for arg in call[0]:
assert address != arg


def test_cache_deployment_live_network(
chain,
vyper_contract_instance,
Expand Down

0 comments on commit dbb0e0c

Please sign in to comment.