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: add conversion api #89

Merged
merged 9 commits into from
Jul 23, 2021
2 changes: 1 addition & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:
python-version: 3.8

- name: Install Dependencies
run: pip install .[lint]
run: pip install .[lint,test]

- name: Run MyPy
run: mypy .
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ markers = "fuzzing: Run Hypothesis fuzz test suite"
line_length = 100
force_grid_wrap = 0
include_trailing_comma = true
known_third_party = ["IPython", "click", "dataclassy", "eth_abi", "eth_account", "eth_utils", "github", "hexbytes", "hypothesis", "hypothesis_jsonschema", "importlib_metadata", "pluggy", "pytest", "requests", "setuptools", "web3", "yaml"]
known_third_party = ["IPython", "ape_ethereum", "click", "dataclassy", "eth_abi", "eth_account", "eth_typing", "eth_utils", "github", "hexbytes", "hypothesis", "hypothesis_jsonschema", "importlib_metadata", "pluggy", "pytest", "requests", "setuptools", "web3", "yaml"]
known_first_party = ["ape_accounts", "ape_console", "ape"]
multi_line_output = 3
use_parentheses = true
8 changes: 6 additions & 2 deletions src/ape/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from .managers.accounts import AccountManager as _AccountManager
from .managers.compilers import CompilerManager as _CompilerManager
from .managers.config import ConfigManager as _ConfigManager
from .managers.converters import ConversionManager as _ConversionManager
from .managers.networks import NetworkManager as _NetworkManager
from .managers.project import ProjectManager as _ProjectManager
from .plugins import PluginManager as _PluginManager
Expand Down Expand Up @@ -37,18 +38,21 @@
# Main types we export for the user
compilers = _CompilerManager(config, plugin_manager) # type: ignore
networks = _NetworkManager(config, plugin_manager) # type: ignore
accounts = _AccountManager(config, plugin_manager, networks) # type: ignore
_converters = _ConversionManager(config, plugin_manager, networks) # type: ignore
accounts = _AccountManager(config, _converters, plugin_manager, networks) # type: ignore

Project = _partial(_ProjectManager, config=config, compilers=compilers)
project = Project(config.PROJECT_FOLDER)

Contract = _partial(_Contract, networks=networks)
Contract = _partial(_Contract, networks=networks, converters=_converters)

convert = _converters.convert

__all__ = [
"accounts",
"compilers",
"config",
"convert",
"Contract",
"networks",
"project",
Expand Down
2 changes: 2 additions & 0 deletions src/ape/api/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from .accounts import AccountAPI, AccountContainerAPI
from .address import Address, AddressAPI
from .contracts import ContractLog
from .convert import ConverterAPI
from .explorers import ExplorerAPI
from .networks import EcosystemAPI, NetworkAPI, ProviderContextManager, create_network_type
from .providers import ProviderAPI, ReceiptAPI, TransactionAPI, TransactionStatusEnum
Expand All @@ -12,6 +13,7 @@
"AddressAPI",
"ContractInstance",
"ContractLog",
"ConverterAPI",
"EcosystemAPI",
"ExplorerAPI",
"ProviderAPI",
Expand Down
32 changes: 22 additions & 10 deletions src/ape/api/accounts.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from pathlib import Path
from typing import Iterator, List, Optional, Type, Union
from typing import Callable, Iterator, List, Optional, Type, Union

from eth_account.datastructures import SignedMessage # type: ignore
from eth_account.messages import SignableMessage # type: ignore

from ape.types import ContractType
from ape.types import AddressType, ContractType
from ape.utils import cached_property

from .address import AddressAPI
from .base import abstractdataclass, abstractmethod
Expand Down Expand Up @@ -57,20 +58,31 @@ def call(self, txn: TransactionAPI) -> ReceiptAPI:

return self.provider.send_transaction(signed_txn)

@cached_property
def _convert(self) -> Callable:
# NOTE: Need to differ loading this property
from ape import convert

return convert

def transfer(
self,
account: Union[str, "AddressAPI"],
value: int = None,
account: Union[str, AddressType, "AddressAPI"],
value: Union[str, int, None] = None,
data: Union[bytes, str, None] = None,
**kwargs,
) -> ReceiptAPI:
txn = self._transaction_class( # type: ignore
sender=self.address,
receiver=account.address if isinstance(account, AddressAPI) else account,
receiver=self._convert(account, AddressType),
**kwargs,
)

if data:
txn.data = self._convert(data, bytes)

if value:
txn.value = value
txn.value = self._convert(value, int)

else:
# NOTE: If `value` is `None`, send everything
Expand Down Expand Up @@ -116,7 +128,7 @@ def __len__(self) -> int:
def __iter__(self) -> Iterator[AccountAPI]:
...

def __getitem__(self, address: str) -> AccountAPI:
def __getitem__(self, address: AddressType) -> AccountAPI:
for account in self.__iter__():
if account.address == address:
return account
Expand All @@ -135,7 +147,7 @@ def append(self, account: AccountAPI):

self.__setitem__(account.address, account)

def __setitem__(self, address: str, account: AccountAPI):
def __setitem__(self, address: AddressType, account: AccountAPI):
raise NotImplementedError("Must define this method to use `container.append(acct)`")

def remove(self, account: AccountAPI):
Expand All @@ -150,10 +162,10 @@ def remove(self, account: AccountAPI):

self.__delitem__(account.address)

def __delitem__(self, address: str):
def __delitem__(self, address: AddressType):
raise NotImplementedError("Must define this method to use `container.remove(acct)`")

def __contains__(self, address: str) -> bool:
def __contains__(self, address: AddressType) -> bool:
try:
self.__getitem__(address)
return True
Expand Down
8 changes: 5 additions & 3 deletions src/ape/api/address.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from typing import List, Optional, Type

from ape.types import AddressType

from .base import abstractdataclass, abstractmethod
from .providers import ProviderAPI, ReceiptAPI, TransactionAPI

Expand All @@ -25,7 +27,7 @@ def _transaction_class(self) -> Type[TransactionAPI]:

@property
@abstractmethod
def address(self) -> str:
def address(self) -> AddressType:
...

def __dir__(self) -> List[str]:
Expand Down Expand Up @@ -69,8 +71,8 @@ def is_contract(self) -> bool:


class Address(AddressAPI):
_address: str
_address: AddressType

@property
def address(self) -> str:
def address(self) -> AddressType:
return self._address
24 changes: 13 additions & 11 deletions src/ape/api/contracts.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
from typing import TYPE_CHECKING, Any, Dict, List, Optional
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union

from eth_utils import to_bytes

from ape.types import ABI, ContractType
from ape.types import ABI, AddressType, ContractType
from ape.utils import notify

from .address import Address, AddressAPI
from .base import dataclass
from .providers import ProviderAPI, ReceiptAPI, TransactionAPI

if TYPE_CHECKING:
from ape.managers.converters import ConversionManager
from ape.managers.networks import NetworkManager


Expand Down Expand Up @@ -47,7 +48,7 @@ def __call__(self, *args, **kwargs) -> ReceiptAPI:
@dataclass
class ContractCall:
abi: ABI
address: str
address: AddressType
provider: ProviderAPI

def __repr__(self) -> str:
Expand Down Expand Up @@ -82,7 +83,7 @@ def __call__(self, *args, **kwargs) -> Any:
@dataclass
class ContractCallHandler:
provider: ProviderAPI
address: str
address: AddressType
abis: List[ABI]

def __repr__(self) -> str:
Expand All @@ -108,7 +109,7 @@ def __call__(self, *args, **kwargs) -> Any:
@dataclass
class ContractTransaction:
abi: ABI
address: str
address: AddressType
provider: ProviderAPI

def __repr__(self) -> str:
Expand All @@ -135,7 +136,7 @@ def __call__(self, *args, **kwargs) -> ReceiptAPI:
@dataclass
class ContractTransactionHandler:
provider: ProviderAPI
address: str
address: AddressType
abis: List[ABI]

def __repr__(self) -> str:
Expand Down Expand Up @@ -173,14 +174,14 @@ class ContractEvent:


class ContractInstance(AddressAPI):
_address: str
_address: AddressType
_contract_type: ContractType

def __repr__(self) -> str:
return f"<{self._contract_type.contractName} {self.address}>"

@property
def address(self) -> str:
def address(self) -> AddressType:
return self._address

def __dir__(self) -> List[str]:
Expand Down Expand Up @@ -272,8 +273,9 @@ def __call__(self, *args, **kwargs) -> TransactionAPI:


def _Contract(
address: str,
address: Union[str, AddressAPI, AddressType],
networks: "NetworkManager",
converters: "ConversionManager",
contract_type: Optional[ContractType] = None,
) -> AddressAPI:
"""
Expand Down Expand Up @@ -301,7 +303,7 @@ def _Contract(
# 3) from explorer
if contract_type:
return ContractInstance( # type: ignore
_address=address,
_address=converters.convert(address, AddressType),
_provider=networks.active_provider,
_contract_type=contract_type,
)
Expand All @@ -310,6 +312,6 @@ def _Contract(
# We don't have a contract type from any source, provide raw address instead
notify("WARNING", f"No contract type found for {address}")
return Address( # type: ignore
_address=address,
_address=converters.convert(address, AddressType),
_provider=networks.active_provider,
)
33 changes: 33 additions & 0 deletions src/ape/api/convert.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from typing import TYPE_CHECKING, Any, Generic, TypeVar

from .base import abstractdataclass, abstractmethod
from .config import ConfigItem

if TYPE_CHECKING:
from ape.managers.networks import NetworkManager

ConvertedType = TypeVar("ConvertedType")


@abstractdataclass
class ConverterAPI(Generic[ConvertedType]):
# NOTE: In case we need to store info e.g. tokenlists
config: ConfigItem

# NOTE: In case we need access to a network e.g. ENS
networks: "NetworkManager"

@abstractmethod
def is_convertible(self, value: Any) -> bool:
"""
Returns `True` if string value provided by `value` is convertible using
`self.convert(value)`
"""

@abstractmethod
def convert(self, value: Any) -> ConvertedType:
"""
Implements any conversion logic on `value` to produce `ABIType`.

Must throw if not convertible.
"""
6 changes: 4 additions & 2 deletions src/ape/api/networks.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from pluggy import PluginManager # type: ignore

from ape.types import ABI
from ape.types import ABI, AddressType
from ape.utils import cached_property

from .base import abstractdataclass, abstractmethod
Expand Down Expand Up @@ -106,7 +106,9 @@ def encode_deployment(
...

@abstractmethod
def encode_transaction(self, address: str, abi: ABI, *args, **kwargs) -> "TransactionAPI":
def encode_transaction(
self, address: AddressType, abi: ABI, *args, **kwargs
) -> "TransactionAPI":
...

@abstractmethod
Expand Down
12 changes: 10 additions & 2 deletions src/ape/managers/accounts.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
from pluggy import PluginManager # type: ignore

from ape.api.accounts import AccountAPI, AccountContainerAPI
from ape.types import AddressType
from ape.utils import cached_property, singledispatchmethod

from .config import ConfigManager
from .converters import ConversionManager
from .networks import NetworkManager


Expand All @@ -18,6 +20,7 @@ class AccountManager:
"""

config: ConfigManager
converters: ConversionManager
plugin_manager: PluginManager
network_manager: NetworkManager

Expand Down Expand Up @@ -49,6 +52,9 @@ def __iter__(self) -> Iterator[AccountAPI]:
account._provider = self.network_manager.active_provider
yield account

def __repr__(self) -> str:
return "[" + ", ".join(repr(a) for a in self) + "]"

def load(self, alias: str) -> AccountAPI:
if alias == "":
raise ValueError("Cannot use empty string as alias!")
Expand Down Expand Up @@ -76,7 +82,9 @@ def __getitem_int(self, account_id: int) -> AccountAPI:
raise IndexError(f"No account at index `{account_id}`.")

@__getitem__.register
def __getitem_str(self, account_id: str) -> AccountAPI:
def __getitem_str(self, account_str: str) -> AccountAPI:
account_id = self.converters.convert(account_str, AddressType)

for container in self.containers.values():
if account_id in container:
account = container[account_id]
Expand All @@ -86,5 +94,5 @@ def __getitem_str(self, account_id: str) -> AccountAPI:

raise IndexError(f"No account with address `{account_id}`.")

def __contains__(self, address: str) -> bool:
def __contains__(self, address: AddressType) -> bool:
return any(address in container for container in self.containers.values())
Loading