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

fix: issue with providing str for bytes in calldata [APE-667] #1319

Merged
merged 16 commits into from
Mar 2, 2023
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
23 changes: 21 additions & 2 deletions src/ape/managers/converters.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,8 @@ def _converters(self) -> Dict[Type, List[ConverterAPI]]:
Decimal: [],
list: [ListTupleConverter()],
tuple: [ListTupleConverter()],
bool: [],
str: [],
}

for plugin_name, (conversion_type, converter_class) in self.plugin_manager.converters:
Expand Down Expand Up @@ -278,7 +280,7 @@ def is_type(self, value: Any, type: Type) -> bool:
else:
return isinstance(value, type)

def convert(self, value: Any, type: Type) -> Any:
def convert(self, value: Any, type: Union[Type, Tuple, List]) -> Any:
"""
Convert the given value to the given type. This method accesses
all :class:`~ape.api.convert.ConverterAPI` instances known to
Expand All @@ -296,7 +298,24 @@ def convert(self, value: Any, type: Type) -> Any:
any: The same given value but with the new given type.
"""

if type not in self._converters:
if isinstance(value, (list, tuple)) and isinstance(type, tuple):
# We expected to convert a tuple type, so convert each item in the tuple.
# NOTE: We allow values to be a list, just in case it is a list
return [self.convert(v, t) for v, t in zip(value, type)]

elif isinstance(value, list) and isinstance(type, list) and len(type) == 1:
# We expected to convert an array type(dynamic or static),
# so convert each item in the list.
# NOTE: type for static and dynamic array is a single item
# list containing the type of the array.
return [self.convert(v, type[0]) for v in value]

elif isinstance(type, (list, tuple)):
raise ConversionError(
f"Value '{value}' must be a list or tuple when given multiple types."
)

elif type not in self._converters:
options = ", ".join([t.__name__ for t in self._converters])
raise ConversionError(f"Type '{type}' must be one of [{options}].")

Expand Down
42 changes: 40 additions & 2 deletions src/ape_ethereum/ecosystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,13 @@
from ape.api import BlockAPI, EcosystemAPI, PluginConfig, ReceiptAPI, TransactionAPI
from ape.api.networks import LOCAL_NETWORK_NAME, ProxyInfoAPI
from ape.contracts.base import ContractCall
from ape.exceptions import ApeException, APINotImplementedError, ContractError, DecodingError
from ape.exceptions import (
ApeException,
APINotImplementedError,
ContractError,
ConversionError,
DecodingError,
)
from ape.logging import logger
from ape.types import (
AddressType,
Expand Down Expand Up @@ -338,14 +344,46 @@ def decode_block(self, data: Dict) -> BlockAPI:

return Block.parse_obj(data)

def _python_type_for_abi_type(self, abi_type: ABIType) -> Union[Type, Tuple, List]:
# NOTE: An array can be an array of tuples, so we start with an array check
if str(abi_type.type).endswith("]"):
# remove one layer of the potential onion of array
new_type = "[".join(str(abi_type.type).split("[")[:-1])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the code that I think would be better to fix in ethpm_types.abi.ABIType by adding a method to fetch the inner array type

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do itttttt

# create a new type with the inner type of array
new_abi_type = ABIType(type=new_type, **abi_type.dict(exclude={"type"}))
# NOTE: type for static and dynamic array is a single item list
# containing the type of the array
return [self._python_type_for_abi_type(new_abi_type)]

if abi_type.components is not None:
return tuple(self._python_type_for_abi_type(c) for c in abi_type.components)

if abi_type.type == "address":
return AddressType

elif abi_type.type == "bool":
return bool

elif abi_type.type == "string":
return str

elif "bytes" in abi_type.type:
return bytes

elif "int" in abi_type.type:
return int

raise ConversionError(f"Unable to convert '{abi_type}'.")

def encode_calldata(self, abi: Union[ConstructorABI, MethodABI], *args) -> HexBytes:
if not abi.inputs:
return HexBytes("")

parser = StructParser(abi)
arguments = parser.encode_input(args)
input_types = [i.canonical_type for i in abi.inputs]
converted_args = self.conversion_manager.convert(arguments, tuple)
python_types = tuple(self._python_type_for_abi_type(i) for i in abi.inputs)
converted_args = self.conversion_manager.convert(arguments, python_types)
encoded_calldata = encode(input_types, converted_args)
return HexBytes(encoded_calldata)

Expand Down
3 changes: 2 additions & 1 deletion tests/functional/conversion/test_ether.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ def test_bad_type():
convert(value="something", type=float)

expected = (
"Type '<class 'float'>' must be one of [ChecksumAddress, bytes, int, Decimal, list, tuple]."
"Type '<class 'float'>' must be one of "
"[ChecksumAddress, bytes, int, Decimal, list, tuple, bool, str]."
)
assert str(err.value) == expected

Expand Down
35 changes: 34 additions & 1 deletion tests/functional/test_ecosystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

import pytest
from eth_typing import HexAddress, HexStr
from hexbytes import HexBytes
from ethpm_types import HexBytes
from ethpm_types.abi import ABIType, MethodABI

from ape.api.networks import LOCAL_NETWORK_NAME
from ape.types import AddressType
Expand Down Expand Up @@ -52,6 +53,38 @@ def test_encode_address(ethereum):
assert actual == raw_address


def test_encode_calldata(ethereum):
abi = MethodABI(
type="function",
name="callMe",
inputs=[
ABIType(name="a", type="bytes4"),
ABIType(name="b", type="address"),
ABIType(name="c", type="uint256"),
ABIType(name="d", type="bytes4[]"),
],
)
address = "0xd8da6bf26964af9d7eed9e03e53415d37aa96045"
byte_array = ["0x456", "0x678"]
values = ("0x123", address, HexBytes(55), byte_array)

actual = ethereum.encode_calldata(abi, *values)
expected = HexBytes(
# 0x123
"0123000000000000000000000000000000000000000000000000000000000000"
# address
"000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa96045"
# HexBytes(55)
"0000000000000000000000000000000000000000000000000000000000000037"
# byte_array
"0000000000000000000000000000000000000000000000000000000000000080"
"0000000000000000000000000000000000000000000000000000000000000002"
"0456000000000000000000000000000000000000000000000000000000000000"
"0678000000000000000000000000000000000000000000000000000000000000"
)
assert actual == expected


def test_block_handles_snake_case_parent_hash(eth_tester_provider, sender, receiver):
# Transaction to change parent hash of next block
sender.transfer(receiver, "1 gwei")
Expand Down