1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
|
"""Asset data encoding and decoding utilities."""
from mypy_extensions import TypedDict
import eth_abi
from zero_ex.dev_utils import abi_utils
from zero_ex.dev_utils.type_assertions import assert_is_string, assert_is_int
ERC20_ASSET_DATA_BYTE_LENGTH = 36
ERC721_ASSET_DATA_MINIMUM_BYTE_LENGTH = 53
SELECTOR_LENGTH = 10
class ERC20AssetData(TypedDict):
"""Object interface to ERC20 asset data."""
asset_proxy_id: str
token_address: str
class ERC721AssetData(TypedDict):
"""Object interface to ERC721 asset data."""
asset_proxy_id: str
token_address: str
token_id: int
def encode_erc20_asset_data(token_address: str) -> str:
"""Encode an ERC20 token address into an asset data string.
:param token_address: the ERC20 token's contract address.
:rtype: hex encoded asset data string, usable in the makerAssetData or
takerAssetData fields in a 0x order.
>>> encode_erc20_asset_data('0x1dc4c1cefef38a777b15aa20260a54e584b16c48')
'0xf47261b00000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c48'
"""
assert_is_string(token_address, "token_address")
return (
"0x"
+ abi_utils.simple_encode("ERC20Token(address)", token_address).hex()
)
def decode_erc20_asset_data(asset_data: str) -> ERC20AssetData:
# docstring considered all one line by pylint: disable=line-too-long
"""Decode an ERC20 asset data hex string.
:param asset_data: String produced by prior call to encode_erc20_asset_data()
>>> decode_erc20_asset_data("0xf47261b00000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c48")
{'asset_proxy_id': '0xf47261b0', 'token_address': '0x1dc4c1cefef38a777b15aa20260a54e584b16c48'}
""" # noqa: E501 (line too long)
assert_is_string(asset_data, "asset_data")
if len(asset_data) < ERC20_ASSET_DATA_BYTE_LENGTH:
raise ValueError(
"Could not decode ERC20 Proxy Data. Expected length of encoded"
+ f" data to be at least {str(ERC20_ASSET_DATA_BYTE_LENGTH)}."
+ f" Got {str(len(asset_data))}."
)
asset_proxy_id: str = asset_data[0:SELECTOR_LENGTH]
if asset_proxy_id != abi_utils.method_id("ERC20Token", ["address"]):
raise ValueError(
"Could not decode ERC20 Proxy Data. Expected Asset Proxy Id to be"
+ f" ERC20 ({abi_utils.method_id('ERC20Token', ['address'])})"
+ f" but got {asset_proxy_id}."
)
# workaround for https://github.com/PyCQA/pylint/issues/1498
# pylint: disable=unsubscriptable-object
token_address = eth_abi.decode_abi(
["address"], bytes.fromhex(asset_data[SELECTOR_LENGTH:])
)[0]
return {"asset_proxy_id": asset_proxy_id, "token_address": token_address}
def encode_erc721_asset_data(token_address: str, token_id: int) -> str:
# docstring considered all one line by pylint: disable=line-too-long
"""Encode an ERC721 asset data hex string.
:param token_address: the ERC721 token's contract address.
:param token_id: the identifier of the asset's instance of the token.
:rtype: hex encoded asset data string, usable in the makerAssetData or
takerAssetData fields in a 0x order.
>>> encode_erc721_asset_data('0x1dc4c1cefef38a777b15aa20260a54e584b16c48', 1)
'0x025717920000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c480000000000000000000000000000000000000000000000000000000000000001'
""" # noqa: E501 (line too long)
assert_is_string(token_address, "token_address")
assert_is_int(token_id, "token_id")
return (
"0x"
+ abi_utils.simple_encode(
"ERC721Token(address,uint256)", token_address, token_id
).hex()
)
def decode_erc721_asset_data(asset_data: str) -> ERC721AssetData:
# docstring considered all one line by pylint: disable=line-too-long
"""Decode an ERC721 asset data hex string.
>>> decode_erc721_asset_data('0x025717920000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c480000000000000000000000000000000000000000000000000000000000000001')
{'asset_proxy_id': '0x02571792', 'token_address': '0x1dc4c1cefef38a777b15aa20260a54e584b16c48', 'token_id': 1}
""" # noqa: E501 (line too long)
assert_is_string(asset_data, "asset_data")
if len(asset_data) < ERC721_ASSET_DATA_MINIMUM_BYTE_LENGTH:
raise ValueError(
"Could not decode ERC721 Asset Data. Expected length of encoded"
+ f"data to be at least {ERC721_ASSET_DATA_MINIMUM_BYTE_LENGTH}. "
+ f"Got {len(asset_data)}."
)
asset_proxy_id: str = asset_data[0:SELECTOR_LENGTH]
# prefer `black` formatting. pylint: disable=C0330
if asset_proxy_id != abi_utils.method_id(
"ERC721Token", ["address", "uint256"]
):
raise ValueError(
"Could not decode ERC721 Asset Data. Expected Asset Proxy Id to be"
+ f" ERC721 ("
+ f"{abi_utils.method_id('ERC721Token', ['address', 'uint256'])}"
+ f"), but got {asset_proxy_id}"
)
(token_address, token_id) = eth_abi.decode_abi(
["address", "uint256"], bytes.fromhex(asset_data[SELECTOR_LENGTH:])
)
return {
"asset_proxy_id": asset_proxy_id,
"token_address": token_address,
"token_id": token_id,
}
|