Skip to content

Commit

Permalink
remove constructor error on decoding functions (#61)
Browse files Browse the repository at this point in the history
* remove constructor error on decoding functions

* add defi test data
  • Loading branch information
kigawas authored Apr 20, 2022
1 parent c39c159 commit 49f225e
Show file tree
Hide file tree
Showing 9 changed files with 75 additions and 27 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
python-version: "3.10"
- uses: abatilo/[email protected]
with:
poetry-version: 1.1.12
poetry-version: 1.1.13
- name: Upload to pypi
run: |
poetry build
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ If you have lots of inputs in the same contract to decode, consider using [`Inpu
def decode_constructor(
abi: List[dict],
tx_input: Union[str, bytes],
bytecode: Union[str, bytes] = None,
bytecode: Optional[Union[str, bytes]] = None,
) -> List[Tuple[str, str, Any]]
```

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "web3-input-decoder"
version = "0.1.3"
version = "0.1.4"
# doc
authors = ["Weiliang Li <[email protected]>"]
description = "A simple offline web3 transaction input decoder for functions and constructors"
Expand Down
15 changes: 15 additions & 0 deletions tests/data/defi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
ROUTER_ABI = [
{
"name": "swapExactAVAXForTokens",
"type": "function",
"inputs": [
{"name": "amountOutMin", "type": "uint256"},
{"name": "path", "type": "address[]"},
{"name": "to", "type": "address"},
{"name": "deadline", "type": "uint256"},
],
"outputs": [{"name": "amounts", "type": "uint256[]"}],
}
]

ROUTER_SWAP_CALL_INPUT = "0xa2a1623d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000006ef4158bf7304b966929945248927fb400ece8b500000000000000000000000000000000000000000000000000000000622bc5e10000000000000000000000000000000000000000000000000000000000000002000000000000000000000000b31f66aa3c1e785363f0875a1b74e27b85fd66c70000000000000000000000003df307e8e9a897da488211682430776cdf0f17cc"
32 changes: 26 additions & 6 deletions tests/test_decode.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

from web3_input_decoder import InputDecoder, decode_constructor, decode_function
from web3_input_decoder.exceptions import InputDataError
from web3_input_decoder.utils import get_constructor_type

from .data.caller import (
CALLER_CONSTRUCTOR_CALL_ARGUMENT,
CALLER_CONSTRUCTOR_CALL_INPUT,
CALLER_CONTRACT_ABI,
)
from .data.defi import ROUTER_ABI, ROUTER_SWAP_CALL_INPUT
from .data.example import (
EXAMPLE_CONSTRUCTOR_CALL_ARGUMENT,
EXAMPLE_CONSTRUCTOR_CALL_INPUT,
Expand Down Expand Up @@ -36,9 +36,23 @@ def test_decode_function():
("uint256", "_value", 248370000),
]

with pytest.raises(InputDataError):
with pytest.raises(InputDataError, match="Specified method is not found in ABI"):
decode_function(TETHER_ABI, "0x00000000")

assert decode_function(ROUTER_ABI, ROUTER_SWAP_CALL_INPUT) == [
("uint256", "amountOutMin", 0),
(
"address[]",
"path",
(
"0xb31f66aa3c1e785363f0875a1b74e27b85fd66c7",
"0x3df307e8e9a897da488211682430776cdf0f17cc",
),
),
("address", "to", "0x6ef4158bf7304b966929945248927fb400ece8b5"),
("uint256", "deadline", 1647035873),
]


def test_decode_constructor():
assert (
Expand Down Expand Up @@ -71,11 +85,16 @@ def test_decode_constructor():
== CALLER_CONSTRUCTOR_CALL_ARGUMENT
)

with pytest.raises(InputDataError):
with pytest.raises(
InputDataError, match="Unable to detect arguments including array"
):
decode_constructor(EXAMPLE_CONTRACT_ABI, EXAMPLE_CONSTRUCTOR_CALL_INPUT)

with pytest.raises(InputDataError):
get_constructor_type([{"type": "function"}])
with pytest.raises(InputDataError, match="Constructor is not found in ABI"):
decode_constructor([{"type": "function", "name": "test"}], "0x00")

with pytest.raises(InputDataError, match="Specified method is not found in ABI"):
decode_function([{"type": "function", "name": "test"}], "0x00")


def test_performance():
Expand All @@ -85,10 +104,11 @@ def test_performance():
with p:
decoder = InputDecoder(TETHER_ABI)
for _ in range(10000):
decoder.decode_function(
func_call = decoder.decode_function(
(
"0xa9059cbb000000000000000000000000f050227be1a7ce587aa83d5013f900dbc3be"
"0611000000000000000000000000000000000000000000000000000000000ecdd350"
),
)
assert func_call.name == "transfer"
p.print()
5 changes: 5 additions & 0 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from web3_input_decoder.utils import (
detect_constructor_arguments,
get_constructor_type,
get_selector_to_function_type,
hex_to_bytes,
)

Expand All @@ -21,3 +22,7 @@ def test_detect_arguments():
constructor_type_def,
hex_to_bytes(TETHER_CONSTRUCTOR_TX_INPUT),
) == hex_to_bytes(TETHER_CONSTRUCTOR_TX_INPUT)


def test_selector_to_func_type():
assert get_selector_to_function_type(TETHER_ABI) != {}
4 changes: 2 additions & 2 deletions web3_input_decoder/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Any, List, Tuple, Union
from typing import Any, List, Optional, Tuple, Union

from .decoder import InputDecoder

Expand All @@ -12,7 +12,7 @@
def decode_constructor(
abi: List[dict],
tx_input: Union[str, bytes],
bytecode: Union[str, bytes] = None,
bytecode: Optional[Union[str, bytes]] = None,
) -> List[Tuple[str, str, Any]]:
"""Decode constructor transaction input
Expand Down
27 changes: 12 additions & 15 deletions web3_input_decoder/decoder.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
from dataclasses import dataclass
from typing import Any, List, Tuple, Union
from typing import Any, List, Optional, Tuple, Union

from eth_abi.abi import decode_abi
from eth_utils.abi import function_abi_to_4byte_selector

from .exceptions import InputDataError
from .utils import (
detect_constructor_arguments,
get_constructor_type,
get_selector_to_function_type,
get_types_names,
hex_to_bytes,
)
Expand All @@ -21,19 +21,15 @@ class ContractCall:

class InputDecoder:
def __init__(self, abi: List[dict]):
self._constructor_type_def = get_constructor_type(abi)
self._selector_to_type_def = {}
for type_def in abi:
if type_def["type"] == "function":
selector = function_abi_to_4byte_selector(type_def)
self._selector_to_type_def[selector] = type_def
self._constructor_type = get_constructor_type(abi)
self._selector_to_func_type = get_selector_to_function_type(abi)

def decode_function(self, tx_input: Union[str, bytes]):
tx_input = hex_to_bytes(tx_input)
selector, args = tx_input[:4], tx_input[4:]
type_def = self._selector_to_type_def.get(selector, None)
type_def = self._selector_to_func_type.get(selector, None)
if not type_def:
raise InputDataError("Specified method not found in ABI")
raise InputDataError("Specified method is not found in ABI")

types, names = get_types_names(type_def["inputs"])
values = decode_abi(types, args)
Expand All @@ -43,19 +39,20 @@ def decode_function(self, tx_input: Union[str, bytes]):
def decode_constructor(
self,
tx_input: Union[str, bytes],
bytecode: Union[str, bytes] = None,
bytecode: Optional[Union[str, bytes]] = None,
):
tx_input = hex_to_bytes(tx_input)

if not self._constructor_type:
raise InputDataError("Constructor is not found in ABI")

if bytecode is not None:
bytecode_len = len(hex_to_bytes(bytecode))
tx_input = tx_input[bytecode_len:]
else:
tx_input = detect_constructor_arguments(
self._constructor_type_def, tx_input
)
tx_input = detect_constructor_arguments(self._constructor_type, tx_input)

types, names = get_types_names(self._constructor_type_def["inputs"])
types, names = get_types_names(self._constructor_type["inputs"])
values = decode_abi(types, tx_input)

return ContractCall("constructor", list(zip(types, names, values)))
13 changes: 12 additions & 1 deletion web3_input_decoder/utils.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
from typing import Any, List, Tuple, Union

from eth_abi.abi import encode_abi
from eth_utils.abi import function_abi_to_4byte_selector

from .exceptions import InputDataError

__all__ = (
"get_constructor_type",
"get_selector_to_function_type",
"get_types_names",
"hex_to_bytes",
"detect_constructor_arguments",
Expand All @@ -16,7 +18,16 @@ def get_constructor_type(abi: List[dict]) -> dict:
for type_def in abi:
if type_def["type"] == "constructor":
return type_def
raise InputDataError("Constructor is not found in ABI")
return {}


def get_selector_to_function_type(abi: List[dict]) -> dict:
type_defs = {}
for type_def in abi:
if type_def["type"] == "function":
selector = function_abi_to_4byte_selector(type_def)
type_defs[selector] = type_def
return type_defs


def get_types_names(type_def: dict) -> Tuple[List[str], List[str]]:
Expand Down

0 comments on commit 49f225e

Please sign in to comment.