Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: accounts.resolve_address() method for resolving input into address types #2521

Merged
merged 3 commits into from
Feb 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 53 additions & 1 deletion src/ape/managers/accounts.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from collections.abc import Generator, Iterator
from contextlib import AbstractContextManager as ContextManager
from functools import cached_property, singledispatchmethod
from typing import Optional, Union
from typing import TYPE_CHECKING, Optional, Union

from eth_utils import is_hex

Expand All @@ -19,6 +19,9 @@
from ape.utils.basemodel import ManagerAccessMixin
from ape.utils.misc import log_instead_of_fail

if TYPE_CHECKING:
from ape.api.address import BaseAddress

_DEFAULT_SENDERS: list[AccountAPI] = []


Expand Down Expand Up @@ -465,3 +468,52 @@ def init_test_account(
self, index: int, address: AddressType, private_key: str
) -> "TestAccountAPI":
return self.test_accounts.init_test_account(index, address, private_key)

def resolve_address(
self, account_id: Union["BaseAddress", AddressType, str, int, bytes]
) -> Optional[AddressType]:
"""
Resolve the given input to an address.

Args:
account_id (:class:~ape.api.address.BaseAddress, str, int, bytes): The input to resolve.
It handles anything that converts to an AddressType like an ENS or a BaseAddress.
It also handles account aliases Ape is aware of, or int or bytes address values.

Returns:
:class:`~ape.types.AddressType` | None
"""
if isinstance(account_id, str) and account_id.startswith("0x"):
# Was given a hex-address string.
if provider := self.network_manager.active_provider:
return provider.network.ecosystem.decode_address(account_id)
else:
# Assume Ethereum-like.
return self.network_manager.ether.decode_address(account_id)

elif not isinstance(account_id, str):
# Was given either an integer, bytes, or a BaseAddress (account or contract).
return self.conversion_manager.convert(account_id, AddressType)

elif isinstance(account_id, str) and account_id in self.aliases:
# Was given an account alias.
account = self.load(account_id)
return account.address

elif (
isinstance(account_id, str)
and account_id.startswith("TEST::")
and account_id[-1].isdigit()
):
# Test account "alias".
account_idx = int(account_id[-1])
return self.test_accounts[account_idx]

elif isinstance(account_id, str) and not is_hex(account_id):
# Was maybe given an ENS name.
try:
return self.conversion_manager.convert(account_id, AddressType)
except ConversionError:
return None

return None
8 changes: 2 additions & 6 deletions src/ape/managers/chain.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
APINotImplementedError,
BlockNotFoundError,
ChainError,
ConversionError,
ProviderNotConnectedError,
QueryEngineError,
TransactionNotFoundError,
Expand Down Expand Up @@ -955,11 +954,8 @@ def get_balance(
if (isinstance(address, str) and not address.startswith("0x")) or not isinstance(
address, str
):
try:
address = self.conversion_manager.convert(address, AddressType)
except ConversionError:
# Try to get the balance anyway; maybe the provider can handle it.
address = address
# Handles accounts, ENS, integers, aliases, everything.
address = self.account_manager.resolve_address(address)

return self.provider.get_balance(address, block_id=block_id)

Expand Down
12 changes: 6 additions & 6 deletions src/ape_console/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import click
from click.testing import CliRunner
from eth_utils import is_hex
from IPython import get_ipython
from IPython.core.magic import Magics, line_magic, magics_class
from rich import print as rich_print
Expand All @@ -14,7 +13,6 @@
from ape.exceptions import Abort, ApeException, handle_ape_exception
from ape.logging import logger
from ape.managers.project import LocalProject
from ape.types.address import AddressType
from ape.utils.basemodel import ManagerAccessMixin
from ape.utils.os import clean_path

Expand Down Expand Up @@ -67,11 +65,13 @@ def bal(self, line: str = ""):
provider = ape.networks.provider
ecosystem = provider.network.ecosystem
result = eval(line, self.ipython.user_global_ns, self.ipython.user_ns)
if isinstance(result, str) and not is_hex(result):
# Check if is an account alias.
address = ape.accounts.load(result).address

if isinstance(result, str) and result.startswith("0x"):
address = result

else:
address = ape.convert(result, AddressType)
# Handles accounts, ENS, integers, BaseAddress, and aliases.
address = ManagerAccessMixin.account_manager.resolve_address(result) or f"{result}"

decimals = ecosystem.fee_token_decimals
symbol = ecosystem.fee_token_symbol
Expand Down
30 changes: 30 additions & 0 deletions tests/functional/test_accounts.py
Original file line number Diff line number Diff line change
Expand Up @@ -1004,3 +1004,33 @@ def test_call_sign_false(owner, vyper_contract_instance):
tx = vyper_contract_instance.setNumber.as_transaction(5991)
with pytest.raises(SignatureError):
owner.call(tx, sign=False)


def test_resolve_address(owner, keyfile_account, account_manager, vyper_contract_instance):
# Test test-account alias input.
actual = account_manager.resolve_address(owner.alias)
assert actual == owner.address

# Test keyfile-account alias input.
actual = account_manager.resolve_address(keyfile_account.alias)
assert actual == keyfile_account.address

# Test address input.
actual = account_manager.resolve_address(owner.address)
assert actual == owner.address

# Test account input.
actual = account_manager.resolve_address(owner)
assert actual == owner.address

# Test contract input.
actual = account_manager.resolve_address(vyper_contract_instance)
assert actual == vyper_contract_instance.address

# Test int input.
actual = account_manager.resolve_address(int(owner.address, 16))
assert actual == owner.address

# Test int input.
actual = account_manager.resolve_address(HexBytes(owner.address))
assert actual == owner.address
Loading