Skip to content

Commit

Permalink
Support tuple type (#84)
Browse files Browse the repository at this point in the history
  • Loading branch information
kigawas authored Oct 19, 2022
1 parent 8b7eb78 commit feb9300
Show file tree
Hide file tree
Showing 10 changed files with 359 additions and 124 deletions.
6 changes: 3 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,20 @@ repos:
- id: isort

- repo: https://github.com/ambv/black
rev: 22.3.0
rev: 22.10.0
hooks:
- id: black
exclude: venv

- repo: https://github.com/pre-commit/mirrors-mypy
rev: v0.942
rev: v0.982
hooks:
- id: mypy
entry: mypy web3_input_decoder/
pass_filenames: false

- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.2.0
rev: v4.3.0
hooks:
- id: trailing-whitespace
- id: check-yaml
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ If you have lots of inputs in the same contract to decode, consider using [`Inpu
>>> for _ in range(10000):
>>> decoder.decode_function(
(
"0xa9059cbb000000000000000000000000f050227be1a7ce587aa83d5013f900dbc3be"
"0611000000000000000000000000000000000000000000000000000000000ecdd350"
"0xa9059cbb000000000000000000000000f050227be1a7ce587aa83d5013f900dbc3b"
"e0611000000000000000000000000000000000000000000000000000000000ecdd350"
),
)
```
Expand Down
107 changes: 74 additions & 33 deletions poetry.lock

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion tests/data/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +0,0 @@

13 changes: 13 additions & 0 deletions tests/data/defi.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,16 @@
]

ROUTER_SWAP_CALL_INPUT = "0xa2a1623d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000006ef4158bf7304b966929945248927fb400ece8b500000000000000000000000000000000000000000000000000000000622bc5e10000000000000000000000000000000000000000000000000000000000000002000000000000000000000000b31f66aa3c1e785363f0875a1b74e27b85fd66c70000000000000000000000003df307e8e9a897da488211682430776cdf0f17cc"
ROUTER_SWAP_CALL_ARGUMENT = [
("uint256", "amountOutMin", 0),
(
"address[]",
"path",
(
"0xb31f66aa3c1e785363f0875a1b74e27b85fd66c7",
"0x3df307e8e9a897da488211682430776cdf0f17cc",
),
),
("address", "to", "0x6ef4158bf7304b966929945248927fb400ece8b5"),
("uint256", "deadline", 1647035873),
]
144 changes: 144 additions & 0 deletions tests/data/nft.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
# Contract: 0x00000000006c3852cbef3e08e8df289169ede581
SEAPORT_ABI = [
{
"inputs": [
{
"components": [
{
"internalType": "address",
"name": "considerationToken",
"type": "address",
},
{
"internalType": "uint256",
"name": "considerationIdentifier",
"type": "uint256",
},
{
"internalType": "uint256",
"name": "considerationAmount",
"type": "uint256",
},
{
"internalType": "address payable",
"name": "offerer",
"type": "address",
},
{"internalType": "address", "name": "zone", "type": "address"},
{
"internalType": "address",
"name": "offerToken",
"type": "address",
},
{
"internalType": "uint256",
"name": "offerIdentifier",
"type": "uint256",
},
{
"internalType": "uint256",
"name": "offerAmount",
"type": "uint256",
},
{
"internalType": "enum BasicOrderType",
"name": "basicOrderType",
"type": "uint8",
},
{"internalType": "uint256", "name": "startTime", "type": "uint256"},
{"internalType": "uint256", "name": "endTime", "type": "uint256"},
{"internalType": "bytes32", "name": "zoneHash", "type": "bytes32"},
{"internalType": "uint256", "name": "salt", "type": "uint256"},
{
"internalType": "bytes32",
"name": "offererConduitKey",
"type": "bytes32",
},
{
"internalType": "bytes32",
"name": "fulfillerConduitKey",
"type": "bytes32",
},
{
"internalType": "uint256",
"name": "totalOriginalAdditionalRecipients",
"type": "uint256",
},
{
"components": [
{
"internalType": "uint256",
"name": "amount",
"type": "uint256",
},
{
"internalType": "address payable",
"name": "recipient",
"type": "address",
},
],
"internalType": "struct AdditionalRecipient[]",
"name": "additionalRecipients",
"type": "tuple[]",
},
{"internalType": "bytes", "name": "signature", "type": "bytes"},
],
"internalType": "struct BasicOrderParameters",
"name": "parameters",
"type": "tuple",
}
],
"name": "fulfillBasicOrder",
"outputs": [{"internalType": "bool", "name": "fulfilled", "type": "bool"}],
"stateMutability": "payable",
"type": "function",
},
]

# TX: 0xa139231454fd021dd227a94fff6a1b6260890bb95e5f5bf8517af36e228575e6
SEAPORT_FULFILL_ORDER_CALL_INPUT = (
"0xfb0f3ee1000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000"
"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
"00000000000000000000000000000000000000000003b53d9d99ecb800000000000000000000000000001850dd8fb9323b01c34"
"0d0eb1da1ec16cc8ee1a2000000000000000000000000004c00500000ad104d7dbd00e3ae0a5c00560c00000000000000000000"
"000000bc4ca0eda7647a8ab7c2061c2e118a18a936f13d000000000000000000000000000000000000000000000000000000000"
"0000561000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000"
"00000000000000000000000000000002000000000000000000000000000000000000000000000000000000006346e1d20000000"
"0000000000000000000000000000000000000000000000000636fadcd0000000000000000000000000000000000000000000000"
"000000000000000000360c6ebe00000000000000000000000000000000000000000589c7ee474bc5850000007b02230091a7ed0"
"1230072f7006a004d60a8d4e71d599b8104250f00000000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f"
"0000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000"
"0000000000000000000000000024000000000000000000000000000000000000000000000000000000000000002e00000000000"
"0000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000001"
"8fae27693b400000000000000000000000000000000a26b00c1f0df003000390027140000faa719000000000000000000000000"
"00000000000000000000000018fae27693b40000000000000000000000000000a858ddc0445d8131dac4d1de01f834ffcba52ef"
"10000000000000000000000000000000000000000000000000000000000000041046bd0fda5b934a96ef4700da1b64e03e7451e"
"6a6ee45a5004b93823ff3baae34b9f1c4f667781b5fedd27dc67339d4fd3e4ae2a873b315090e6312853732f9a1c00000000000"
"000000000000000000000000000000000000000000000000000360c6ebe"
)
SEAPORT_FULFILL_ORDER_CALL_ARGUMENT = [
(
"(address,uint256,uint256,address,address,address,uint256,uint256,uint8,uint256,uint256,bytes32,uint256,bytes32,bytes32,uint256,(uint256,address),bytes)",
"parameters",
(
"0x0000000000000000000000000000000000000000",
0,
68400000000000000000,
"0x1850dd8fb9323b01c340d0eb1da1ec16cc8ee1a2",
"0x004c00500000ad104d7dbd00e3ae0a5c00560c00",
"0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d",
1377,
1,
2,
1665589714,
1668263373,
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
24446860302761739304752683030156737591518664810215442929800781758303463785861,
b"\x00\x00\x00{\x02#\x00\x91\xa7\xed\x01#\x00r\xf7\x00j\x00M`\xa8\xd4\xe7\x1dY\x9b\x81\x04%\x0f\x00\x00",
b"\x00\x00\x00{\x02#\x00\x91\xa7\xed\x01#\x00r\xf7\x00j\x00M`\xa8\xd4\xe7\x1dY\x9b\x81\x04%\x0f\x00\x00",
2,
(576, "0x00000000000000000000000000000000000002e0"),
b"",
),
)
]
135 changes: 53 additions & 82 deletions tests/test_decode.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
import pytest

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

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.defi import ROUTER_ABI, ROUTER_SWAP_CALL_ARGUMENT, ROUTER_SWAP_CALL_INPUT
from .data.example import (
EXAMPLE_CONSTRUCTOR_CALL_ARGUMENT,
EXAMPLE_CONSTRUCTOR_CALL_INPUT,
EXAMPLE_CONTRACT_ABI,
EXAMPLE_CONTRACT_BYTECODE,
)
from .data.nft import (
SEAPORT_ABI,
SEAPORT_FULFILL_ORDER_CALL_ARGUMENT,
SEAPORT_FULFILL_ORDER_CALL_INPUT,
)
from .data.tether import (
TETHER_ABI,
TETHER_BYTECODE,
Expand All @@ -25,90 +27,59 @@


def test_decode_function():
assert decode_function(
TETHER_ABI,
(
"0xa9059cbb000000000000000000000000f050227be1a7ce587aa83d5013f900dbc3be"
"0611000000000000000000000000000000000000000000000000000000000ecdd350"
),
) == [
tether_input = (
"0xa9059cbb000000000000000000000000f050227be1a7ce587aa83d5013f900dbc3b"
"e0611000000000000000000000000000000000000000000000000000000000ecdd350"
)
tether_args = [
("address", "_to", "0xf050227be1a7ce587aa83d5013f900dbc3be0611"),
("uint256", "_value", 248370000),
]

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),
abis = [
TETHER_ABI,
ROUTER_ABI,
SEAPORT_ABI,
]
inputs = [
tether_input,
ROUTER_SWAP_CALL_INPUT,
SEAPORT_FULFILL_ORDER_CALL_INPUT,
]
expected_args = [
tether_args,
ROUTER_SWAP_CALL_ARGUMENT,
SEAPORT_FULFILL_ORDER_CALL_ARGUMENT,
]

for abi, input, expected in zip(abis, inputs, expected_args):
assert decode_function(abi, input) == expected

def test_decode_constructor():
assert (
decode_constructor(TETHER_ABI, TETHER_CONSTRUCTOR_TX_INPUT)
== TETHER_CONSTRUCTOR_ARGS
)

assert (
decode_constructor(
TETHER_ABI,
TETHER_CONSTRUCTOR_TX_INPUT_WITH_BYTECODE,
TETHER_BYTECODE,
)
== TETHER_CONSTRUCTOR_ARGS
)

assert (
decode_constructor(
EXAMPLE_CONTRACT_ABI,
EXAMPLE_CONSTRUCTOR_CALL_INPUT,
EXAMPLE_CONTRACT_BYTECODE,
)
== EXAMPLE_CONSTRUCTOR_CALL_ARGUMENT
)
assert (
decode_constructor(
CALLER_CONTRACT_ABI,
CALLER_CONSTRUCTOR_CALL_INPUT,
)
== CALLER_CONSTRUCTOR_CALL_ARGUMENT
)

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

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_decode_constructor():
abis = [TETHER_ABI, CALLER_CONTRACT_ABI]
inputs = [TETHER_CONSTRUCTOR_TX_INPUT, CALLER_CONSTRUCTOR_CALL_INPUT]
expected_args = [TETHER_CONSTRUCTOR_ARGS, CALLER_CONSTRUCTOR_CALL_ARGUMENT]
for abi, input, expected in zip(abis, inputs, expected_args):
assert decode_constructor(abi, input) == expected

def test_performance():
from pyinstrument import Profiler

p = Profiler()
with p:
decoder = InputDecoder(TETHER_ABI)
for _ in range(10000):
func_call = decoder.decode_function(
(
"0xa9059cbb000000000000000000000000f050227be1a7ce587aa83d5013f900dbc3be"
"0611000000000000000000000000000000000000000000000000000000000ecdd350"
),
)
assert func_call.name == "transfer"
p.print()
def test_decode_constructor_with_bytecode():
abis = [
TETHER_ABI,
EXAMPLE_CONTRACT_ABI,
]
inputs = [
TETHER_CONSTRUCTOR_TX_INPUT_WITH_BYTECODE,
EXAMPLE_CONSTRUCTOR_CALL_INPUT,
]
bytecodes = [
TETHER_BYTECODE,
EXAMPLE_CONTRACT_BYTECODE,
]
expected_args = [
TETHER_CONSTRUCTOR_ARGS,
EXAMPLE_CONSTRUCTOR_CALL_ARGUMENT,
]
for abi, input, bytecode, expected in zip(abis, inputs, bytecodes, expected_args):
assert decode_constructor(abi, input, bytecode) == expected
25 changes: 25 additions & 0 deletions tests/test_decode_error.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import pytest

from web3_input_decoder import decode_constructor, decode_function
from web3_input_decoder.exceptions import InputDataError

from .data.example import EXAMPLE_CONSTRUCTOR_CALL_INPUT, EXAMPLE_CONTRACT_ABI
from .data.tether import TETHER_ABI


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

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


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

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

0 comments on commit feb9300

Please sign in to comment.