diff --git a/mev_inspect/decode.py b/mev_inspect/decode.py index 4fa2b57..19121a5 100644 --- a/mev_inspect/decode.py +++ b/mev_inspect/decode.py @@ -1,5 +1,7 @@ from typing import Dict, Optional +import eth_utils.abi + from hexbytes import HexBytes from eth_abi import decode_abi from eth_abi.exceptions import InsufficientDataBytes, NonEmptyPaddingBytes @@ -26,7 +28,12 @@ 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..9cb16e4 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,12 @@ 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..a83e3bc --- /dev/null +++ b/tests/test_decode.py @@ -0,0 +1,69 @@ +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,)}