Skip to content

Commit

Permalink
Test new ABI implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
DanielSchiavini committed Dec 19, 2023
1 parent ec63bde commit 71058ad
Show file tree
Hide file tree
Showing 6 changed files with 397 additions and 50 deletions.
91 changes: 45 additions & 46 deletions boa/abi.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from functools import cached_property
from itertools import groupby
from os.path import basename
from typing import Optional
from typing import Any, Optional, Union

from _operator import attrgetter
from eth.abc import ComputationAPI
Expand All @@ -18,7 +18,6 @@

class ABIFunction:
def __init__(self, abi: dict, contract_name: str):
super().__init__()
self._abi = abi
self._contract_name = contract_name
self._function_visibility = FunctionVisibility.EXTERNAL
Expand Down Expand Up @@ -63,15 +62,10 @@ def __str__(self) -> str:
def is_mutable(self) -> bool:
return self._mutability > StateMutability.VIEW

def __eq__(self, other):
return isinstance(other, ABIFunction) and self._abi == other._abi

def __hash__(self):
return hash(self._abi)

def is_encodable(self, *args, **kwargs) -> bool:
"""Check whether this function accepts the given arguments after eventual encoding."""
parsed_args = self._merge_kwargs(*args, **kwargs)
assert len(parsed_args) == len(self.argument_types) # sanity check
return all(
is_abi_encodable(abi_type, arg)
for abi_type, arg in zip(self.argument_types, parsed_args)
Expand Down Expand Up @@ -99,7 +93,7 @@ def _merge_kwargs(self, *args, **kwargs) -> list:
raise TypeError(error)

# allow address objects to be passed in place of addresses
return [getattr(arg, "address", arg) for arg in merged]
return _encode_addresses(merged)

def __call__(self, *args, value=0, gas=None, sender=None, **kwargs):
if not self.contract or not self.contract.env:
Expand All @@ -116,28 +110,33 @@ def __call__(self, *args, value=0, gas=None, sender=None, **kwargs):
contract=self.contract,
)

result = self.contract.marshal_to_python(computation, self.return_type)
return result[0] if len(result) == 1 else result
match self.contract.marshal_to_python(computation, self.return_type):
case ():
return None
case (single,):
return single
case multiple:
return tuple(multiple)


class ABIOverload:
@staticmethod
def create(
functions: list[ABIFunction], contract: "ABIContract"
) -> Union["ABIOverload", ABIFunction]:
for f in functions:
f.contract = contract
if len(functions) == 1:
return functions[0]
return ABIOverload(functions)

def __init__(self, functions: list[ABIFunction]):
super().__init__()
self.functions = functions

@cached_property
def name(self):
return self.functions[0].name

@property
def contract(self):
return self.functions[0].contract

@contract.setter
def contract(self, value):
for f in self.functions:
f.contract = value

def __call__(self, *args, **kwargs):
arg_count = len(kwargs) + len(args)
candidates = [
Expand All @@ -146,22 +145,20 @@ def __call__(self, *args, **kwargs):
if f.argument_count == arg_count and f.is_encodable(*args, **kwargs)
]

if not candidates:
error = f"Could not find matching {self.name} function for given arguments."
raise Exception(error)

if len(candidates) == 1:
return candidates[0](*args, **kwargs)

matches = [f for f in candidates if f.matches(*args, **kwargs)]
if len(matches) != 1:
raise Exception(
f"Ambiguous call to {self.name}. "
f"Arguments can be encoded to multiple overloads: "
f"{', '.join(f.signature for f in matches or candidates)}."
)

return matches[0]
match candidates:
case [single_match]:
return single_match(*args, **kwargs)
case []:
error = (
f"Could not find matching {self.name} function for given arguments."
)
raise Exception(error)
case multiple:
raise Exception(
f"Ambiguous call to {self.name}. "
f"Arguments can be encoded to multiple overloads: "
f"{', '.join(f.signature for f in multiple)}."
)


class ABIContract(_EvmContract):
Expand All @@ -173,7 +170,7 @@ class ABIContract(_EvmContract):
def __init__(
self,
name: str,
functions: list["ABIFunction"],
functions: list[ABIFunction],
address: Address,
filename: Optional[str] = None,
env=None,
Expand All @@ -182,19 +179,17 @@ def __init__(
self._name = name
self._functions = functions

for name, functions in groupby(self._functions, key=attrgetter("name")):
functions = list(functions)
fn = functions[0] if len(functions) == 1 else ABIOverload(functions)
fn.contract = self
setattr(self, name, fn)
for name, group in groupby(self._functions, key=attrgetter("name")):
functions = list(group)
setattr(self, name, ABIOverload.create(functions, self))

self._address = Address(address)

@cached_property
def method_id_map(self):
return {function.method_id: function for function in self._functions}

def marshal_to_python(self, computation, abi_type: list[str]):
def marshal_to_python(self, computation, abi_type: list[str]) -> tuple[Any, ...]:
"""
Convert the output of a contract call to a Python object.
:param computation: the computation object returned by `execute_code`
Expand All @@ -205,7 +200,7 @@ def marshal_to_python(self, computation, abi_type: list[str]):

schema = f"({','.join(abi_type)})"
decoded = abi_decode(schema, computation.output)
return [_decode_addresses(typ, val) for typ, val in zip(abi_type, decoded)]
return tuple(_decode_addresses(typ, val) for typ, val in zip(abi_type, decoded))

def stack_trace(self, computation: ComputationAPI):
calldata_method_id = bytes(computation.msg.data[:4])
Expand Down Expand Up @@ -281,9 +276,13 @@ def at(self, address) -> ABIContract:
return ret


def _decode_addresses(abi_type: str, decoded: any) -> any:
def _decode_addresses(abi_type: str, decoded: Any) -> Any:
if abi_type == "address":
return Address(decoded)
if abi_type == "address[]":
return [Address(i) for i in decoded]
return decoded


def _encode_addresses(values: list) -> list:
return [getattr(arg, "address", arg) for arg in values]
7 changes: 4 additions & 3 deletions boa/util/abi.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# wrapper module around whatever encoder we are using
from typing import Any

from eth.codecs.abi.decoder import Decoder
from eth.codecs.abi.encoder import Encoder
Expand All @@ -17,14 +18,14 @@ def _get_parser(schema: str):
return ret


def abi_encode(schema: str, data: any) -> bytes:
def abi_encode(schema: str, data: Any) -> bytes:
return Encoder.encode(_get_parser(schema), data)


def abi_decode(schema: str, data: bytes) -> any:
def abi_decode(schema: str, data: bytes) -> Any:
return Decoder.decode(_get_parser(schema), data)


# todo: eth.codecs.abi does not have such a function, which one do we use?
def is_abi_encodable(abi_type: str, data: any) -> bool:
def is_abi_encodable(abi_type: str, data: Any) -> bool:
return is_encodable(abi_type, data)
3 changes: 2 additions & 1 deletion boa/util/evm.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def __init__(
def stack_trace(self, computation: ComputationAPI):
raise NotImplementedError

def handle_error(self, computation) -> None:
def handle_error(self, computation):
try:
raise BoaError(self.stack_trace(computation))
except BoaError as b:
Expand All @@ -36,4 +36,5 @@ def handle_error(self, computation) -> None:

@property
def address(self) -> Address:
assert self._address is not None
return self._address
64 changes: 64 additions & 0 deletions tests/unitary/fixtures/solidity_overload.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# The bytecode and ABI are generated based on the following source via remix.ethereum.org
source: >-
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.12 <0.9.0;
contract A {
function f(int8 _in) public pure returns (int8 out) {
out = _in;
}
function f(uint256 _in) public pure returns (uint256 out) {
out = _in;
}
}
abi: [
{
"inputs": [
{
"internalType": "int8",
"name": "_in",
"type": "int8"
}
],
"name": "f",
"outputs": [
{
"internalType": "int8",
"name": "out",
"type": "int8"
}
],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_in",
"type": "uint256"
}
],
"name": "f",
"outputs": [
{
"internalType": "uint256",
"name": "out",
"type": "uint256"
}
],
"stateMutability": "pure",
"type": "function"
}
]

bytecode: {
"functionDebugData": { },
"generatedSources": [ ],
"linkReferences": { },
"object": "608060405234801561000f575f80fd5b506101f28061001d5f395ff3fe608060405234801561000f575f80fd5b5060043610610034575f3560e01c80630a9a296314610038578063b3de648b14610068575b5f80fd5b610052600480360381019061004d91906100e3565b610098565b60405161005f919061011d565b60405180910390f35b610082600480360381019061007d9190610169565b6100a1565b60405161008f91906101a3565b60405180910390f35b5f819050919050565b5f819050919050565b5f80fd5b5f815f0b9050919050565b6100c2816100ae565b81146100cc575f80fd5b50565b5f813590506100dd816100b9565b92915050565b5f602082840312156100f8576100f76100aa565b5b5f610105848285016100cf565b91505092915050565b610117816100ae565b82525050565b5f6020820190506101305f83018461010e565b92915050565b5f819050919050565b61014881610136565b8114610152575f80fd5b50565b5f813590506101638161013f565b92915050565b5f6020828403121561017e5761017d6100aa565b5b5f61018b84828501610155565b91505092915050565b61019d81610136565b82525050565b5f6020820190506101b65f830184610194565b9291505056fea2646970667358221220a5370672f70d75b055f40e04bc9c5c4234448bbcd91a317b94c760bc1f270db564736f6c63430008160033",
"opcodes": "PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH2 0xF JUMPI PUSH0 DUP1 REVERT JUMPDEST POP PUSH2 0x1F2 DUP1 PUSH2 0x1D PUSH0 CODECOPY PUSH0 RETURN INVALID PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH2 0xF JUMPI PUSH0 DUP1 REVERT JUMPDEST POP PUSH1 0x4 CALLDATASIZE LT PUSH2 0x34 JUMPI PUSH0 CALLDATALOAD PUSH1 0xE0 SHR DUP1 PUSH4 0xA9A2963 EQ PUSH2 0x38 JUMPI DUP1 PUSH4 0xB3DE648B EQ PUSH2 0x68 JUMPI JUMPDEST PUSH0 DUP1 REVERT JUMPDEST PUSH2 0x52 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 PUSH2 0x4D SWAP2 SWAP1 PUSH2 0xE3 JUMP JUMPDEST PUSH2 0x98 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x5F SWAP2 SWAP1 PUSH2 0x11D JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x82 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 PUSH2 0x7D SWAP2 SWAP1 PUSH2 0x169 JUMP JUMPDEST PUSH2 0xA1 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x8F SWAP2 SWAP1 PUSH2 0x1A3 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH0 DUP2 SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH0 DUP2 SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH0 DUP1 REVERT JUMPDEST PUSH0 DUP2 PUSH0 SIGNEXTEND SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH2 0xC2 DUP2 PUSH2 0xAE JUMP JUMPDEST DUP2 EQ PUSH2 0xCC JUMPI PUSH0 DUP1 REVERT JUMPDEST POP JUMP JUMPDEST PUSH0 DUP2 CALLDATALOAD SWAP1 POP PUSH2 0xDD DUP2 PUSH2 0xB9 JUMP JUMPDEST SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH0 PUSH1 0x20 DUP3 DUP5 SUB SLT ISZERO PUSH2 0xF8 JUMPI PUSH2 0xF7 PUSH2 0xAA JUMP JUMPDEST JUMPDEST PUSH0 PUSH2 0x105 DUP5 DUP3 DUP6 ADD PUSH2 0xCF JUMP JUMPDEST SWAP2 POP POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH2 0x117 DUP2 PUSH2 0xAE JUMP JUMPDEST DUP3 MSTORE POP POP JUMP JUMPDEST PUSH0 PUSH1 0x20 DUP3 ADD SWAP1 POP PUSH2 0x130 PUSH0 DUP4 ADD DUP5 PUSH2 0x10E JUMP JUMPDEST SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH0 DUP2 SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH2 0x148 DUP2 PUSH2 0x136 JUMP JUMPDEST DUP2 EQ PUSH2 0x152 JUMPI PUSH0 DUP1 REVERT JUMPDEST POP JUMP JUMPDEST PUSH0 DUP2 CALLDATALOAD SWAP1 POP PUSH2 0x163 DUP2 PUSH2 0x13F JUMP JUMPDEST SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH0 PUSH1 0x20 DUP3 DUP5 SUB SLT ISZERO PUSH2 0x17E JUMPI PUSH2 0x17D PUSH2 0xAA JUMP JUMPDEST JUMPDEST PUSH0 PUSH2 0x18B DUP5 DUP3 DUP6 ADD PUSH2 0x155 JUMP JUMPDEST SWAP2 POP POP SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH2 0x19D DUP2 PUSH2 0x136 JUMP JUMPDEST DUP3 MSTORE POP POP JUMP JUMPDEST PUSH0 PUSH1 0x20 DUP3 ADD SWAP1 POP PUSH2 0x1B6 PUSH0 DUP4 ADD DUP5 PUSH2 0x194 JUMP JUMPDEST SWAP3 SWAP2 POP POP JUMP INVALID LOG2 PUSH5 0x6970667358 0x22 SLT KECCAK256 0xA5 CALLDATACOPY MOD PUSH19 0xF70D75B055F40E04BC9C5C4234448BBCD91A31 PUSH28 0x94C760BC1F270DB564736F6C63430008160033000000000000000000 ",
"sourceMap": "66:163:0:-:0;;;;;;;;;;;;;;;;;;;"
}
Loading

0 comments on commit 71058ad

Please sign in to comment.