feat(order_utils.py): ERC20 asset data encoding and decoding
In addition to the ERC20 codec, also: Stopped ignoring type errors on 3rd party imports, by including interface stubs for them; Removed the unimplemented signature-utils module, which was just a stand-in when the python project support was first put in place. https://github.com/0xProject/0x-monorepo/pull/1144
This commit is contained in:
@@ -0,0 +1 @@
|
||||
"""Dev utils to be shared across 0x projects and packages."""
|
102
python-packages/order_utils/src/zero_ex/dev_utils/abi_utils.py
Normal file
102
python-packages/order_utils/src/zero_ex/dev_utils/abi_utils.py
Normal file
@@ -0,0 +1,102 @@
|
||||
"""Ethereum ABI utilities.
|
||||
|
||||
Builds on the eth-abi package, adding some convenience methods like those found
|
||||
in npmjs.com/package/ethereumjs-abi. Ideally, all of this code should be
|
||||
pushed upstream into eth-abi.
|
||||
"""
|
||||
|
||||
import re
|
||||
from typing import Any, List
|
||||
|
||||
from mypy_extensions import TypedDict
|
||||
|
||||
from eth_abi import encode_abi
|
||||
from web3 import Web3
|
||||
|
||||
from .type_assertions import assert_is_string, assert_is_list
|
||||
|
||||
|
||||
class MethodSignature(TypedDict, total=False):
|
||||
"""Object interface to an ABI method signature."""
|
||||
|
||||
method: str
|
||||
args: List[str]
|
||||
|
||||
|
||||
def parse_signature(signature: str) -> MethodSignature:
|
||||
"""Parse a method signature into its constituent parts.
|
||||
|
||||
>>> parse_signature("ERC20Token(address)")
|
||||
{'method': 'ERC20Token', 'args': ['address']}
|
||||
"""
|
||||
assert_is_string(signature, "signature")
|
||||
|
||||
matches = re.match(r"^(\w+)\((.+)\)$", signature)
|
||||
if matches is None:
|
||||
raise ValueError(f"Invalid method signature {signature}")
|
||||
return {"method": matches[1], "args": matches[2].split(",")}
|
||||
|
||||
|
||||
def elementary_name(name: str) -> str:
|
||||
"""Convert from short to canonical names; barely implemented.
|
||||
|
||||
Modeled after ethereumjs-abi's ABI.elementaryName(), but only implemented
|
||||
to support our particular use case and a few other simple ones.
|
||||
|
||||
>>> elementary_name("address")
|
||||
'address'
|
||||
>>> elementary_name("uint")
|
||||
'uint256'
|
||||
"""
|
||||
assert_is_string(name, "name")
|
||||
|
||||
return {
|
||||
"int": "int256",
|
||||
"uint": "uint256",
|
||||
"fixed": "fixed128x128",
|
||||
"ufixed": "ufixed128x128",
|
||||
}.get(name, name)
|
||||
|
||||
|
||||
def event_id(name: str, types: List[str]) -> str:
|
||||
"""Return the Keccak-256 hash of the given method.
|
||||
|
||||
>>> event_id("ERC20Token", ["address"])
|
||||
'0xf47261b06eedbfce68afd46d0f3c27c60b03faad319eaf33103611cf8f6456ad'
|
||||
"""
|
||||
assert_is_string(name, "name")
|
||||
assert_is_list(types, "types")
|
||||
|
||||
signature = f"{name}({','.join(list(map(elementary_name, types)))})"
|
||||
return Web3.sha3(text=signature).hex()
|
||||
|
||||
|
||||
def method_id(name: str, types: List[str]) -> str:
|
||||
"""Return the 4-byte method identifier.
|
||||
|
||||
>>> method_id("ERC20Token", ["address"])
|
||||
'0xf47261b0'
|
||||
"""
|
||||
assert_is_string(name, "name")
|
||||
assert_is_list(types, "types")
|
||||
|
||||
return event_id(name, types)[0:10]
|
||||
|
||||
|
||||
def simple_encode(method: str, *args: Any) -> bytes:
|
||||
# docstring considered all one line by pylint: disable=line-too-long
|
||||
r"""Encode a method ABI.
|
||||
|
||||
>>> simple_encode("ERC20Token(address)", "0x1dc4c1cefef38a777b15aa20260a54e584b16c48")
|
||||
b'\xf4ra\xb0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1d\xc4\xc1\xce\xfe\xf3\x8aw{\x15\xaa &\nT\xe5\x84\xb1lH'
|
||||
""" # noqa: E501 (line too long)
|
||||
assert_is_string(method, "method")
|
||||
|
||||
signature: MethodSignature = parse_signature(method)
|
||||
|
||||
return bytes.fromhex(
|
||||
(
|
||||
method_id(signature["method"], signature["args"])
|
||||
+ encode_abi(signature["args"], args).hex()
|
||||
)[2:]
|
||||
)
|
@@ -0,0 +1,33 @@
|
||||
"""Assertions for runtime type checking of function arguments."""
|
||||
|
||||
from typing import Any
|
||||
|
||||
|
||||
def assert_is_string(value: Any, name: str) -> None:
|
||||
"""If :param value: isn't of type str, raise a TypeError.
|
||||
|
||||
>>> try: assert_is_string(123, 'var')
|
||||
... except TypeError as type_error: print(str(type_error))
|
||||
...
|
||||
expected variable 'var', with value 123, to have type 'str', not 'int'
|
||||
"""
|
||||
if not isinstance(value, str):
|
||||
raise TypeError(
|
||||
f"expected variable '{name}', with value {str(value)}, to have"
|
||||
+ f" type 'str', not '{type(value).__name__}'"
|
||||
)
|
||||
|
||||
|
||||
def assert_is_list(value: Any, name: str) -> None:
|
||||
"""If :param value: isn't of type list, raise a TypeError.
|
||||
|
||||
>>> try: assert_is_list(123, 'var')
|
||||
... except TypeError as type_error: print(str(type_error))
|
||||
...
|
||||
expected variable 'var', with value 123, to have type 'list', not 'int'
|
||||
"""
|
||||
if not isinstance(value, list):
|
||||
raise TypeError(
|
||||
f"expected variable '{name}', with value {str(value)}, to have"
|
||||
+ f" type 'list', not '{type(value).__name__}'"
|
||||
)
|
Reference in New Issue
Block a user