diff --git a/mev_inspect/decode.py b/mev_inspect/decode.py index 4fa2b57..79f97e4 100644 --- a/mev_inspect/decode.py +++ b/mev_inspect/decode.py @@ -1,3 +1,4 @@ +import eth_utils.abi from typing import Dict, Optional from hexbytes import HexBytes @@ -26,7 +27,11 @@ class ABIDecoder: return None names = [input.name for input in func.inputs] - types = [input.type for input in func.inputs] + types = [ + input.type if input.type != 'tuple' + else eth_utils.abi.collapse_if_tuple(input.dict()) + for input in func.inputs + ] try: decoded = decode_abi(types, params) diff --git a/mev_inspect/schemas/abi.py b/mev_inspect/schemas/abi.py index 7d91130..0f38776 100644 --- a/mev_inspect/schemas/abi.py +++ b/mev_inspect/schemas/abi.py @@ -1,7 +1,8 @@ from enum import Enum -from typing import List, Union +from typing import List, Optional, Union from typing_extensions import Literal +import eth_utils.abi from hexbytes import HexBytes from pydantic import BaseModel from web3 import Web3 @@ -26,6 +27,10 @@ NON_FUNCTION_DESCRIPTION_TYPES = Union[ class ABIDescriptionInput(BaseModel): name: str type: str + components: Optional[List["ABIDescriptionInput"]] + + +ABIDescriptionInput.update_forward_refs() class ABIGenericDescription(BaseModel): @@ -42,7 +47,10 @@ class ABIFunctionDescription(BaseModel): return Web3.sha3(text=signature)[0:4] def get_signature(self) -> str: - joined_input_types = ",".join(input.type for input in self.inputs) + joined_input_types = ",".join( + input.type if input.type != "tuple" + else eth_utils.abi.collapse_if_tuple(input.dict()) + for input in self.inputs) return f"{self.name}({joined_input_types})" diff --git a/tests/test_decode.py b/tests/test_decode.py new file mode 100644 index 0000000..e1cc4bf --- /dev/null +++ b/tests/test_decode.py @@ -0,0 +1,59 @@ +import pydantic + +from mev_inspect import decode +from mev_inspect.schemas import abi + + +def test_decode_function_with_simple_argument(): + test_function_name = "testFunction" + test_parameter_name = "testParameter" + test_abi = pydantic.parse_obj_as(abi.ABI, [{ + "name": test_function_name, + "type": "function", + "inputs": [{ + "name": test_parameter_name, + "type": "uint256" + }] + }]) + # 4byte signature of the test function. + # https://www.4byte.directory/signatures/?bytes4_signature=0x350c530b + test_function_selector = "350c530b" + test_function_argument = ( + "0000000000000000000000000000000000000000000000000000000000000001" + ) + abi_decoder = decode.ABIDecoder(test_abi) + call_data = abi_decoder.decode( + "0x" + test_function_selector + test_function_argument) + assert call_data.function_name == test_function_name + assert call_data.function_signature == "testFunction(uint256)" + assert call_data.inputs == {test_parameter_name: 1} + + +def test_decode_function_with_tuple_argument(): + test_function_name = "testFunction" + test_tuple_name = "testTuple" + test_parameter_name = "testParameter" + test_abi = pydantic.parse_obj_as(abi.ABI, [{ + "name": test_function_name, + "type": "function", + "inputs": [{ + "name": test_tuple_name, + "type": "tuple", + "components": [{ + "name": test_parameter_name, + "type": "uint256" + }] + }] + }]) + # 4byte signature of the test function. + # https://www.4byte.directory/signatures/?bytes4_signature=0x98568079 + test_function_selector = "98568079" + test_function_argument = ( + "0000000000000000000000000000000000000000000000000000000000000001" + ) + abi_decoder = decode.ABIDecoder(test_abi) + call_data = abi_decoder.decode( + "0x" + test_function_selector + test_function_argument) + assert call_data.function_name == test_function_name + assert call_data.function_signature == "testFunction((uint256))" + assert call_data.inputs == {test_tuple_name: (1,)}