Skip to content

Commit

Permalink
Refactor decoding (#142)
Browse files Browse the repository at this point in the history
  • Loading branch information
kigawas authored Sep 2, 2023
1 parent 021e4da commit 261462b
Show file tree
Hide file tree
Showing 23 changed files with 715 additions and 707 deletions.
2 changes: 1 addition & 1 deletion .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ updates:
- package-ecosystem: pip
directory: "/"
schedule:
interval: daily
interval: monthly
open-pull-requests-limit: 10
10 changes: 7 additions & 3 deletions .github/workflows/cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,16 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- run: pipx install poetry

- uses: actions/setup-python@v4
with:
python-version: "3.11"
- uses: abatilo/[email protected]
with:
poetry-version: 1.4.1
cache: "poetry"

- run: poetry install

- name: Upload to pypi
run: |
poetry build
Expand Down
17 changes: 6 additions & 11 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,16 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
python-version: ["3.8", "3.9", "3.10", "3.11"]
steps:
- uses: actions/checkout@v3

- run: pipx install poetry

- uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- uses: abatilo/[email protected]
with:
poetry-version: 1.4.1
- uses: actions/cache@v3
with:
path: |
~/.cache/pypoetry/virtualenvs
key: ${{ runner.os }}-${{ matrix.python-version }}-poetry-${{ hashFiles('**/poetry.lock') }}
cache: "poetry"

- run: brew install automake
if: matrix.os == 'macos-latest'
Expand All @@ -38,6 +34,5 @@ jobs:

- name: Upload to codecov
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}

- run: poetry build
8 changes: 4 additions & 4 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ repos:
- id: isort

- repo: https://github.com/ambv/black
rev: 23.3.0
rev: 23.7.0
hooks:
- id: black
exclude: venv
exclude: .venv

- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.2.0
rev: v1.5.1
hooks:
- id: mypy
entry: mypy web3_input_decoder/
Expand All @@ -24,7 +24,7 @@ repos:
- id: check-yaml

- repo: https://github.com/PyCQA/flake8
rev: 6.0.0
rev: 6.1.0
hooks:
- id: flake8
# allow "imported but unused" for pre-commit, forbid it elsewhere e.g. in vscode
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ You can also play with it [here](https://replit.com/@kigawas/Web3-input-decoder-

### Performance enhancement

If you have lots of inputs in the same contract to decode, consider using [`InputDecoder`](web3_input_decoder/decoder.py#L22).
If you have lots of inputs in the same contract to decode, consider using [`InputDecoder`](web3_input_decoder/decoder.py#L26).

```python
>>> from web3_input_decoder import InputDecoder
Expand Down
923 changes: 409 additions & 514 deletions poetry.lock

Large diffs are not rendered by default.

16 changes: 8 additions & 8 deletions 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.9"
version = "0.1.10"
# doc
authors = ["Weiliang Li <[email protected]>"]
description = "A simple offline web3 transaction input decoder for functions and constructors"
Expand All @@ -24,26 +24,26 @@ keywords = [
include = ["web3_input_decoder/py.typed"]

[tool.poetry.dependencies]
python = "^3.7.2"
python = "^3.8"

# 3rd party
eth-abi = "^4.0.0"
eth-utils = "^2.1.0"
eth-abi = "^4.2.0"
eth-utils = "^2.2.0"
pycryptodome = "^3.17.0"

[tool.poetry.dev-dependencies]
black = "^23.3.0"
[tool.poetry.group.dev.dependencies]
black = "^23.7"
flake8 = {version = "^6.0.0", python = "^3.9"}
ipython = {version = "^8.13.1", python = "^3.9"}
mypy = "^1.4"
mypy = "^1.5"

# stubs
eth-typing = "^3.3.0"

# test
pyinstrument = "^4.5.0"
pytest = "^7.4.0"
pytest-cov = "^4.0.0"
pytest-cov = "^4.1.0"

[build-system]
build-backend = "poetry.core.masonry.api"
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[flake8]
ignore = E501, W605, E203, W503
exclude = .git,__pycache__,docs/source/conf.py,old,build,dist,venv
exclude = .git,__pycache__,docs/source/conf.py,old,build,dist,.venv
max-complexity = 12
max-line-length = 88
6 changes: 4 additions & 2 deletions tests/data/example.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,10 @@
}
]
EXAMPLE_BYTECODE = "0x608060405234801561001057600080fd5b50604051610353380380610353833981810160405260c081101561003357600080fd5b8151602083015160408085018051915193959294830192918464010000000082111561005e57600080fd5b90830190602082018581111561007357600080fd5b825164010000000081118282018810171561008d57600080fd5b82525081516020918201929091019080838360005b838110156100ba5781810151838201526020016100a2565b50505050905090810190601f1680156100e75780820380516001836020036101000a031916815260200191505b506040526020018051604051939291908464010000000082111561010a57600080fd5b90830190602082018581111561011f57600080fd5b825186602082028301116401000000008211171561013c57600080fd5b82525081516020918201928201910280838360005b83811015610169578181015183820152602001610151565b50505050919091016040908152602083810151939091015160008a90556001805460ff19168a1515179055875193955093506101ac926002925090870190610210565b5082516101c090600390602086019061029c565b506004805483919060ff191660018360038111156101da57fe5b0217905550600480546001600160a01b0390921661010002610100600160a81b0319909216919091179055506103069350505050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282610246576000855561028c565b82601f1061025f57805160ff191683800117855561028c565b8280016001018555821561028c579182015b8281111561028c578251825591602001919060010190610271565b506102989291506102f1565b5090565b82805482825590600052602060002090810192821561028c579160200282015b8281111561028c57825182546001600160a01b0319166001600160a01b039091161782556020909201916001909101906102bc565b5b8082111561029857600081556001016102f2565b603f806103146000396000f3fe6080604052600080fdfea2646970667358221220a75435b85057a239995042116d1f84a8c1d97199bcc159a3927fccf5b9bb703364736f6c63430007060033"

EXAMPLE_CONSTRUCTOR_CALL_INPUT = "0x608060405234801561001057600080fd5b50604051610353380380610353833981810160405260c081101561003357600080fd5b8151602083015160408085018051915193959294830192918464010000000082111561005e57600080fd5b90830190602082018581111561007357600080fd5b825164010000000081118282018810171561008d57600080fd5b82525081516020918201929091019080838360005b838110156100ba5781810151838201526020016100a2565b50505050905090810190601f1680156100e75780820380516001836020036101000a031916815260200191505b506040526020018051604051939291908464010000000082111561010a57600080fd5b90830190602082018581111561011f57600080fd5b825186602082028301116401000000008211171561013c57600080fd5b82525081516020918201928201910280838360005b83811015610169578181015183820152602001610151565b50505050919091016040908152602083810151939091015160008a90556001805460ff19168a1515179055875193955093506101ac926002925090870190610210565b5082516101c090600390602086019061029c565b506004805483919060ff191660018360038111156101da57fe5b0217905550600480546001600160a01b0390921661010002610100600160a81b0319909216919091179055506103069350505050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282610246576000855561028c565b82601f1061025f57805160ff191683800117855561028c565b8280016001018555821561028c579182015b8281111561028c578251825591602001919060010190610271565b506102989291506102f1565b5090565b82805482825590600052602060002090810192821561028c579160200282015b8281111561028c57825182546001600160a01b0319166001600160a01b039091161782556020909201916001909101906102bc565b5b8082111561029857600081556001016102f2565b603f806103146000396000f3fe6080604052600080fdfea2646970667358221220a75435b85057a239995042116d1f84a8c1d97199bcc159a3927fccf5b9bb703364736f6c634300070600330000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000036161610000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001"
EXAMPLE_CONSTRUCTOR_CALL_INPUT_WITHOUT_BYTECODE = "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000036161610000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001"
EXAMPLE_CONSTRUCTOR_CALL_INPUT = (
EXAMPLE_BYTECODE + EXAMPLE_CONSTRUCTOR_CALL_INPUT_WITHOUT_BYTECODE[2:]
)
EXAMPLE_CONSTRUCTOR_CALL_ARGUMENT = [
("uint256", "num", 0),
("bool", "_enable", False),
Expand Down
4 changes: 2 additions & 2 deletions tests/data/tether.py
Original file line number Diff line number Diff line change
Expand Up @@ -332,11 +332,11 @@
TETHER_BYTECODE + TETHER_CONSTRUCTOR_CALL_INPUT_WITHOUT_BYTECODE[2:]
)

THTHER_TRANSFER_CALL_INPUT = (
TETHER_TRANSFER_CALL_INPUT = (
"0xa9059cbb000000000000000000000000f050227be1a7ce587aa83d5013f900dbc3b"
"e0611000000000000000000000000000000000000000000000000000000000ecdd350"
)
THTHER_TRANSFER_CALL_ARGUMENT = [
TETHER_TRANSFER_CALL_ARGUMENT = [
("address", "_to", "0xf050227be1a7ce587aa83d5013f900dbc3be0611"),
("uint256", "_value", 248370000),
]
78 changes: 52 additions & 26 deletions tests/test_decode_constructor.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import pytest

from web3_input_decoder import decode_constructor

from .data.caller import (
Expand All @@ -10,6 +12,7 @@
EXAMPLE_BYTECODE,
EXAMPLE_CONSTRUCTOR_CALL_ARGUMENT,
EXAMPLE_CONSTRUCTOR_CALL_INPUT,
EXAMPLE_CONSTRUCTOR_CALL_INPUT_WITHOUT_BYTECODE,
)
from .data.storage import (
STORAGE_ABI,
Expand All @@ -26,31 +29,54 @@
)


def test_decode_constructor_without_bytecode():
abis = [TETHER_ABI, CALLER_ABI]
inputs = [
TETHER_CONSTRUCTOR_CALL_INPUT_WITHOUT_BYTECODE,
CALLER_CONSTRUCTOR_CALL_INPUT_WITHOUT_BYTECODE,
]
expected_args = [TETHER_CONSTRUCTOR_CALL_ARGUMENT, CALLER_CONSTRUCTOR_CALL_ARGUMENT]

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

@pytest.mark.parametrize(
"abi,input,expected",
[
(
TETHER_ABI,
TETHER_CONSTRUCTOR_CALL_INPUT_WITHOUT_BYTECODE,
TETHER_CONSTRUCTOR_CALL_ARGUMENT,
),
(
CALLER_ABI,
CALLER_CONSTRUCTOR_CALL_INPUT_WITHOUT_BYTECODE,
CALLER_CONSTRUCTOR_CALL_ARGUMENT,
),
(
EXAMPLE_ABI,
EXAMPLE_CONSTRUCTOR_CALL_INPUT_WITHOUT_BYTECODE,
EXAMPLE_CONSTRUCTOR_CALL_ARGUMENT,
),
],
ids=["tether", "caller", "example"],
)
def test_decode_constructor_without_bytecode(abi, input, expected):
assert decode_constructor(abi, input) == expected

def test_decode_constructor_with_bytecode():
abis = [TETHER_ABI, EXAMPLE_ABI, STORAGE_ABI]
inputs = [
TETHER_CONSTRUCTOR_CALL_INPUT,
EXAMPLE_CONSTRUCTOR_CALL_INPUT,
STORAGE_CONSTRUCTOR_CALL_INPUT,
]
bytecodes = [TETHER_BYTECODE, EXAMPLE_BYTECODE, STORAGE_BYTECODE]
expected_args = [
TETHER_CONSTRUCTOR_CALL_ARGUMENT,
EXAMPLE_CONSTRUCTOR_CALL_ARGUMENT,
STORAGE_CONSTRUCTOR_CALL_ARGUMENT,
]

for abi, input, bytecode, expected in zip(abis, inputs, bytecodes, expected_args):
assert decode_constructor(abi, input, bytecode) == expected
@pytest.mark.parametrize(
"abi,input,bytecode,expected",
[
(
TETHER_ABI,
TETHER_CONSTRUCTOR_CALL_INPUT,
TETHER_BYTECODE,
TETHER_CONSTRUCTOR_CALL_ARGUMENT,
),
(
EXAMPLE_ABI,
EXAMPLE_CONSTRUCTOR_CALL_INPUT,
EXAMPLE_BYTECODE,
EXAMPLE_CONSTRUCTOR_CALL_ARGUMENT,
),
(
STORAGE_ABI,
STORAGE_CONSTRUCTOR_CALL_INPUT,
STORAGE_BYTECODE,
STORAGE_CONSTRUCTOR_CALL_ARGUMENT,
),
],
ids=["tether", "example", "storage"],
)
def test_decode_constructor_with_bytecode(abi, input, bytecode, expected):
assert decode_constructor(abi, input, bytecode) == expected
2 changes: 2 additions & 0 deletions tests/test_decode_error.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
(EXAMPLE_ABI, EXAMPLE_CONSTRUCTOR_CALL_INPUT, UNABLE_TO_DETECT),
(STORAGE_ABI, STORAGE_CONSTRUCTOR_CALL_INPUT, UNABLE_TO_DETECT),
],
ids=["invalid", "example", "storage"],
)
def test_decode_constructor_error(abi, input, match):
with pytest.raises(InputDataError, match=match):
Expand All @@ -37,6 +38,7 @@ def test_decode_constructor_error(abi, input, match):
(INVALID_ABI, INVALID_CALL_INPUT, METHOD_NOT_FOUND),
(ROUTER_V2_ABI, ROUTER_V2_TOKENS_SWAP_CALL_ERROR_INPUT, INVALID_INPUT),
],
ids=["tether", "invalid", "router-v2-tokens-swap"],
)
def test_decode_function_error(abi, input, match):
with pytest.raises(InputDataError, match=match):
Expand Down
39 changes: 20 additions & 19 deletions tests/test_decode_function.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import pytest

from web3_input_decoder import decode_function

from .data.defi import (
Expand All @@ -14,25 +16,24 @@
)
from .data.tether import (
TETHER_ABI,
THTHER_TRANSFER_CALL_ARGUMENT,
THTHER_TRANSFER_CALL_INPUT,
TETHER_TRANSFER_CALL_ARGUMENT,
TETHER_TRANSFER_CALL_INPUT,
)


def test_decode_function():
abis = [TETHER_ABI, ROUTER_ABI, ROUTER_ABI, SEAPORT_ABI]
inputs = [
THTHER_TRANSFER_CALL_INPUT,
ROUTER_SWAP_CALL_INPUT,
ROUTER_TOKENS_SWAP_CALL_INPUT,
SEAPORT_FULFILL_ORDER_CALL_INPUT,
]
expected_args = [
THTHER_TRANSFER_CALL_ARGUMENT,
ROUTER_SWAP_CALL_ARGUMENT,
ROUTER_TOKENS_SWAP_CALL_ARGUMENT,
SEAPORT_FULFILL_ORDER_CALL_ARGUMENT,
]

for abi, input, expected in zip(abis, inputs, expected_args):
assert decode_function(abi, input) == expected
@pytest.mark.parametrize(
"abi,input,expected",
[
(TETHER_ABI, TETHER_TRANSFER_CALL_INPUT, TETHER_TRANSFER_CALL_ARGUMENT),
(ROUTER_ABI, ROUTER_SWAP_CALL_INPUT, ROUTER_SWAP_CALL_ARGUMENT),
(ROUTER_ABI, ROUTER_TOKENS_SWAP_CALL_INPUT, ROUTER_TOKENS_SWAP_CALL_ARGUMENT),
(
SEAPORT_ABI,
SEAPORT_FULFILL_ORDER_CALL_INPUT,
SEAPORT_FULFILL_ORDER_CALL_ARGUMENT,
),
],
ids=["tether", "router-swap", "router-tokens-swap", "seaport-fulfill-order"],
)
def test_decode_function(abi, input, expected):
assert decode_function(abi, input) == expected
26 changes: 22 additions & 4 deletions tests/test_performance.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import pytest
from pyinstrument.profiler import Profiler
from pyinstrument import Profiler

from web3_input_decoder import InputDecoder

from .data.defi import ROUTER_ABI, ROUTER_TOKENS_SWAP_CALL_INPUT
from .data.tether import TETHER_ABI


Expand All @@ -11,14 +12,31 @@
[10, 100, 1000, 10000],
)
def test_profiling_tether_transfer_decode(count: int):
p = Profiler()
tx = (
"0xa9059cbb000000000000000000000000f050227be1a7ce587aa83d5013f900dbc3b"
"e0611000000000000000000000000000000000000000000000000000000000ecdd350"
)
__check(TETHER_ABI, tx, "transfer", count)


@pytest.mark.parametrize(
"count",
[10, 100, 1000, 10000],
)
def test_profiling_router_swap(count: int):
__check(
ROUTER_ABI,
ROUTER_TOKENS_SWAP_CALL_INPUT,
"swapExactTokensForTokens",
count,
)


def __check(abi, tx: str, func_name: str, count: int):
p = Profiler()
with p:
decoder = InputDecoder(TETHER_ABI)
decoder = InputDecoder(abi)
for _ in range(count):
func_call = decoder.decode_function(tx)
assert func_call.name == "transfer"
assert func_call.name == func_name
p.print()
1 change: 1 addition & 0 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
TETHER_CONSTRUCTOR_CALL_INPUT_WITHOUT_BYTECODE,
),
],
ids=["with-bytecode", "without-bytecode"],
)
def test_detect_arguments(abi, input_with_bytecode, input):
assert detect_constructor_arguments(
Expand Down
Loading

0 comments on commit 261462b

Please sign in to comment.