aboutsummaryrefslogtreecommitdiffstats
path: root/python-packages/order_utils
diff options
context:
space:
mode:
Diffstat (limited to 'python-packages/order_utils')
-rwxr-xr-xpython-packages/order_utils/setup.py11
-rw-r--r--python-packages/order_utils/src/zero_ex/contract_artifacts/__init__.py1
l---------python-packages/order_utils/src/zero_ex/contract_artifacts/artifacts1
-rw-r--r--python-packages/order_utils/src/zero_ex/order_utils/__init__.py215
-rw-r--r--python-packages/order_utils/test/test_doctest.py18
-rw-r--r--python-packages/order_utils/test/test_generate_order_hash_hex.py4
6 files changed, 162 insertions, 88 deletions
diff --git a/python-packages/order_utils/setup.py b/python-packages/order_utils/setup.py
index 06533e60a..01a6c7360 100755
--- a/python-packages/order_utils/setup.py
+++ b/python-packages/order_utils/setup.py
@@ -21,7 +21,7 @@ class TestCommandExtension(TestCommand):
"""Invoke pytest."""
import pytest
- exit(pytest.main())
+ exit(pytest.main(["--doctest-modules"]))
class LintCommand(distutils.command.build_py.build_py):
@@ -165,9 +165,13 @@ setup(
"ganache": GanacheCommand,
},
install_requires=[
+ "0x-contract-addresses",
+ "0x-contract-artifacts",
"0x-json-schemas",
"eth-abi",
"eth_utils",
+ "hypothesis>=3.31.2", # HACK! this is web3's dependency!
+ # above works around https://github.com/ethereum/web3.py/issues/1179
"mypy_extensions",
"web3",
],
@@ -189,10 +193,7 @@ setup(
]
},
python_requires=">=3.6, <4",
- package_data={
- "zero_ex.order_utils": ["py.typed"],
- "zero_ex.contract_artifacts": ["artifacts/*"],
- },
+ package_data={"zero_ex.order_utils": ["py.typed"]},
package_dir={"": "src"},
license="Apache 2.0",
keywords=(
diff --git a/python-packages/order_utils/src/zero_ex/contract_artifacts/__init__.py b/python-packages/order_utils/src/zero_ex/contract_artifacts/__init__.py
deleted file mode 100644
index ed45d2c8e..000000000
--- a/python-packages/order_utils/src/zero_ex/contract_artifacts/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-"""Solc-generated artifacts for 0x smart contracts."""
diff --git a/python-packages/order_utils/src/zero_ex/contract_artifacts/artifacts b/python-packages/order_utils/src/zero_ex/contract_artifacts/artifacts
deleted file mode 120000
index 82d28ba87..000000000
--- a/python-packages/order_utils/src/zero_ex/contract_artifacts/artifacts
+++ /dev/null
@@ -1 +0,0 @@
-../../../../../packages/contract-artifacts/artifacts \ No newline at end of file
diff --git a/python-packages/order_utils/src/zero_ex/order_utils/__init__.py b/python-packages/order_utils/src/zero_ex/order_utils/__init__.py
index 24c6bfd4e..4697ad99c 100644
--- a/python-packages/order_utils/src/zero_ex/order_utils/__init__.py
+++ b/python-packages/order_utils/src/zero_ex/order_utils/__init__.py
@@ -10,19 +10,22 @@ just this purpose. To start it: ``docker run -d -p 8545:8545 0xorg/ganache-cli
fence smart topic"``.
"""
+from copy import copy
from enum import auto, Enum
import json
-from typing import Dict, Tuple
+from typing import cast, Dict, NamedTuple, Tuple
from pkg_resources import resource_string
from mypy_extensions import TypedDict
-from eth_utils import keccak, to_bytes, to_checksum_address
+from eth_utils import keccak, remove_0x_prefix, to_bytes, to_checksum_address
from web3 import Web3
import web3.exceptions
from web3.providers.base import BaseProvider
from web3.utils import datatypes
+from zero_ex.contract_addresses import NETWORK_TO_ADDRESSES, NetworkId
+import zero_ex.contract_artifacts
from zero_ex.dev_utils.type_assertions import (
assert_is_address,
assert_is_hex_string,
@@ -34,34 +37,6 @@ from zero_ex.json_schemas import assert_valid
class _Constants:
"""Static data used by order utilities."""
- _contract_name_to_abi: Dict[str, Dict] = {} # class data, not instance
-
- @classmethod
- def contract_name_to_abi(cls, contract_name: str) -> Dict:
- """Return the ABI for the given contract name.
-
- First tries to get data from the class level storage
- `_contract_name_to_abi`. If it's not there, loads it from disk, stores
- it in the class data (for the next caller), and then returns it.
- """
- try:
- return cls._contract_name_to_abi[contract_name]
- except KeyError:
- cls._contract_name_to_abi[contract_name] = json.loads(
- resource_string(
- "zero_ex.contract_artifacts",
- f"artifacts/{contract_name}.json",
- )
- )["compilerOutput"]["abi"]
- return cls._contract_name_to_abi[contract_name]
-
- network_to_exchange_addr: Dict[str, str] = {
- "1": "0x4f833a24e1f95d70f028921e27040ca56e09ab0b",
- "3": "0x4530c0483a1633c7a1c97d2c53721caff2caaaaf",
- "42": "0x35dd2932454449b14cee11a94d3674a936d5d7b2",
- "50": "0x48bacb9266a570d521063ef5dd96e61686dbe788",
- }
-
null_address = "0x0000000000000000000000000000000000000000"
eip191_header = b"\x19\x01"
@@ -107,47 +82,153 @@ class _Constants:
class Order(TypedDict): # pylint: disable=too-many-instance-attributes
- """Object representation of a 0x order."""
+ """A Web3-compatible representation of the Exchange.Order struct."""
makerAddress: str
takerAddress: str
feeRecipientAddress: str
senderAddress: str
- makerAssetAmount: str
- takerAssetAmount: str
- makerFee: str
- takerFee: str
- expirationTimeSeconds: str
- salt: str
- makerAssetData: str
- takerAssetData: str
- exchangeAddress: str
+ makerAssetAmount: int
+ takerAssetAmount: int
+ makerFee: int
+ takerFee: int
+ expirationTimeSeconds: int
+ salt: int
+ makerAssetData: bytes
+ takerAssetData: bytes
def make_empty_order() -> Order:
"""Construct an empty order.
- Initializes all strings to "0x0000000000000000000000000000000000000000"
- and all numbers to 0.
+ Initializes all strings to "0x0000000000000000000000000000000000000000",
+ all numbers to 0, and all bytes to nulls.
"""
return {
"makerAddress": _Constants.null_address,
"takerAddress": _Constants.null_address,
"senderAddress": _Constants.null_address,
"feeRecipientAddress": _Constants.null_address,
- "makerAssetData": _Constants.null_address,
- "takerAssetData": _Constants.null_address,
- "salt": "0",
- "makerFee": "0",
- "takerFee": "0",
- "makerAssetAmount": "0",
- "takerAssetAmount": "0",
- "expirationTimeSeconds": "0",
- "exchangeAddress": _Constants.null_address,
+ "makerAssetData": (b"\x00") * 20,
+ "takerAssetData": (b"\x00") * 20,
+ "salt": 0,
+ "makerFee": 0,
+ "takerFee": 0,
+ "makerAssetAmount": 0,
+ "takerAssetAmount": 0,
+ "expirationTimeSeconds": 0,
}
-def generate_order_hash_hex(order: Order) -> str:
+def order_to_jsdict(
+ order: Order, exchange_address="0x0000000000000000000000000000000000000000"
+) -> dict:
+ """Convert a Web3-compatible order struct to a JSON-schema-compatible dict.
+
+ More specifically, do explicit decoding for the `bytes` fields.
+
+ >>> import pprint
+ >>> pprint.pprint(order_to_jsdict(
+ ... {
+ ... 'makerAddress': "0x0000000000000000000000000000000000000000",
+ ... 'takerAddress': "0x0000000000000000000000000000000000000000",
+ ... 'feeRecipientAddress':
+ ... "0x0000000000000000000000000000000000000000",
+ ... 'senderAddress': "0x0000000000000000000000000000000000000000",
+ ... 'makerAssetAmount': 1,
+ ... 'takerAssetAmount': 1,
+ ... 'makerFee': 0,
+ ... 'takerFee': 0,
+ ... 'expirationTimeSeconds': 1,
+ ... 'salt': 1,
+ ... 'makerAssetData': (0).to_bytes(1, byteorder='big') * 20,
+ ... 'takerAssetData': (0).to_bytes(1, byteorder='big') * 20,
+ ... },
+ ... ))
+ {'exchangeAddress': '0x0000000000000000000000000000000000000000',
+ 'expirationTimeSeconds': 1,
+ 'feeRecipientAddress': '0x0000000000000000000000000000000000000000',
+ 'makerAddress': '0x0000000000000000000000000000000000000000',
+ 'makerAssetAmount': 1,
+ 'makerAssetData': '0x0000000000000000000000000000000000000000',
+ 'makerFee': 0,
+ 'salt': 1,
+ 'senderAddress': '0x0000000000000000000000000000000000000000',
+ 'takerAddress': '0x0000000000000000000000000000000000000000',
+ 'takerAssetAmount': 1,
+ 'takerAssetData': '0x0000000000000000000000000000000000000000',
+ 'takerFee': 0}
+ """
+ jsdict = cast(Dict, copy(order))
+
+ # encode bytes fields
+ jsdict["makerAssetData"] = "0x" + order["makerAssetData"].hex()
+ jsdict["takerAssetData"] = "0x" + order["takerAssetData"].hex()
+
+ jsdict["exchangeAddress"] = exchange_address
+
+ assert_valid(jsdict, "/orderSchema")
+
+ return jsdict
+
+
+def jsdict_order_to_struct(jsdict: dict) -> Order:
+ r"""Convert a JSON-schema-compatible dict order to a Web3-compatible struct.
+
+ More specifically, do explicit encoding of the `bytes` fields.
+
+ >>> import pprint
+ >>> pprint.pprint(jsdict_order_to_struct(
+ ... {
+ ... 'makerAddress': "0x0000000000000000000000000000000000000000",
+ ... 'takerAddress': "0x0000000000000000000000000000000000000000",
+ ... 'feeRecipientAddress': "0x0000000000000000000000000000000000000000",
+ ... 'senderAddress': "0x0000000000000000000000000000000000000000",
+ ... 'makerAssetAmount': 1000000000000000000,
+ ... 'takerAssetAmount': 1000000000000000000,
+ ... 'makerFee': 0,
+ ... 'takerFee': 0,
+ ... 'expirationTimeSeconds': 12345,
+ ... 'salt': 12345,
+ ... 'makerAssetData': "0x0000000000000000000000000000000000000000",
+ ... 'takerAssetData': "0x0000000000000000000000000000000000000000",
+ ... 'exchangeAddress': "0x0000000000000000000000000000000000000000",
+ ... },
+ ... ))
+ {'expirationTimeSeconds': 12345,
+ 'feeRecipientAddress': '0x0000000000000000000000000000000000000000',
+ 'makerAddress': '0x0000000000000000000000000000000000000000',
+ 'makerAssetAmount': 1000000000000000000,
+ 'makerAssetData': b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+ b'\x00\x00\x00\x00\x00\x00\x00\x00',
+ 'makerFee': 0,
+ 'salt': 12345,
+ 'senderAddress': '0x0000000000000000000000000000000000000000',
+ 'takerAddress': '0x0000000000000000000000000000000000000000',
+ 'takerAssetAmount': 1000000000000000000,
+ 'takerAssetData': b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+ b'\x00\x00\x00\x00\x00\x00\x00\x00',
+ 'takerFee': 0}
+ """ # noqa: E501 (line too long)
+ assert_valid(jsdict, "/orderSchema")
+
+ order = cast(Order, copy(jsdict))
+
+ order["makerAssetData"] = bytes.fromhex(
+ remove_0x_prefix(jsdict["makerAssetData"])
+ )
+ order["takerAssetData"] = bytes.fromhex(
+ remove_0x_prefix(jsdict["takerAssetData"])
+ )
+
+ del order["exchangeAddress"] # type: ignore
+ # silence mypy pending release of
+ # https://github.com/python/mypy/issues/3550
+
+ return order
+
+
+def generate_order_hash_hex(order: Order, exchange_address: str) -> str:
"""Calculate the hash of the given order as a hexadecimal string.
:param order: The order to be hashed. Must conform to `the 0x order JSON schema <https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/order_schema.json>`_.
@@ -167,14 +248,15 @@ def generate_order_hash_hex(order: Order) -> str:
... 'takerFee': "0",
... 'expirationTimeSeconds': "12345",
... 'salt': "12345",
- ... 'makerAssetData': "0x0000000000000000000000000000000000000000",
- ... 'takerAssetData': "0x0000000000000000000000000000000000000000",
- ... 'exchangeAddress': "0x0000000000000000000000000000000000000000",
+ ... 'makerAssetData': (0).to_bytes(1, byteorder='big') * 20,
+ ... 'takerAssetData': (0).to_bytes(1, byteorder='big') * 20,
... },
+ ... exchange_address="0x0000000000000000000000000000000000000000",
... )
'55eaa6ec02f3224d30873577e9ddd069a288c16d6fb407210eecbc501fa76692'
""" # noqa: E501 (line too long)
- assert_valid(order, "/orderSchema")
+ assert_is_address(exchange_address, "exchange_address")
+ assert_valid(order_to_jsdict(order, exchange_address), "/orderSchema")
def pad_20_bytes_to_32(twenty_bytes: bytes):
return bytes(12) + twenty_bytes
@@ -184,7 +266,7 @@ def generate_order_hash_hex(order: Order) -> str:
eip712_domain_struct_hash = keccak(
_Constants.eip712_domain_struct_header
- + pad_20_bytes_to_32(to_bytes(hexstr=order["exchangeAddress"]))
+ + pad_20_bytes_to_32(to_bytes(hexstr=exchange_address))
)
eip712_order_struct_hash = keccak(
@@ -199,8 +281,8 @@ def generate_order_hash_hex(order: Order) -> str:
+ int_to_32_big_endian_bytes(int(order["takerFee"]))
+ int_to_32_big_endian_bytes(int(order["expirationTimeSeconds"]))
+ int_to_32_big_endian_bytes(int(order["salt"]))
- + keccak(to_bytes(hexstr=order["makerAssetData"]))
- + keccak(to_bytes(hexstr=order["takerAssetData"]))
+ + keccak(to_bytes(hexstr=order["makerAssetData"].hex()))
+ + keccak(to_bytes(hexstr=order["takerAssetData"].hex()))
)
return keccak(
@@ -210,6 +292,14 @@ def generate_order_hash_hex(order: Order) -> str:
).hex()
+class OrderInfo(NamedTuple):
+ """A Web3-compatible representation of the Exchange.OrderInfo struct."""
+
+ order_status: str
+ order_hash: bytes
+ order_taker_asset_filled_amount: int
+
+
def is_valid_signature(
provider: BaseProvider, data: str, signature: str, signer_address: str
) -> Tuple[bool, str]:
@@ -241,12 +331,13 @@ def is_valid_signature(
web3_instance = Web3(provider)
# false positive from pylint: disable=no-member
- network_id = web3_instance.net.version
- contract_address = _Constants.network_to_exchange_addr[network_id]
+ contract_address = NETWORK_TO_ADDRESSES[
+ NetworkId(int(web3_instance.net.version))
+ ].exchange
# false positive from pylint: disable=no-member
contract: datatypes.Contract = web3_instance.eth.contract(
address=to_checksum_address(contract_address),
- abi=_Constants.contract_name_to_abi("Exchange"),
+ abi=zero_ex.contract_artifacts.abi_by_name("Exchange"),
)
try:
return (
diff --git a/python-packages/order_utils/test/test_doctest.py b/python-packages/order_utils/test/test_doctest.py
deleted file mode 100644
index 297f75e75..000000000
--- a/python-packages/order_utils/test/test_doctest.py
+++ /dev/null
@@ -1,18 +0,0 @@
-"""Exercise doctests for all of our modules."""
-
-from doctest import testmod
-import pkgutil
-import importlib
-
-import zero_ex
-
-
-def test_all_doctests():
- """Gather zero_ex.* modules and doctest them."""
- for (_, modname, _) in pkgutil.walk_packages(
- path=zero_ex.__path__, prefix="zero_ex."
- ):
- module = importlib.import_module(modname)
- print(module)
- (failure_count, _) = testmod(module)
- assert failure_count == 0
diff --git a/python-packages/order_utils/test/test_generate_order_hash_hex.py b/python-packages/order_utils/test/test_generate_order_hash_hex.py
index 6869a40ed..38b503289 100644
--- a/python-packages/order_utils/test/test_generate_order_hash_hex.py
+++ b/python-packages/order_utils/test/test_generate_order_hash_hex.py
@@ -8,5 +8,7 @@ def test_get_order_hash_hex__empty_order():
expected_hash_hex = (
"faa49b35faeb9197e9c3ba7a52075e6dad19739549f153b77dfcf59408a4b422"
)
- actual_hash_hex = generate_order_hash_hex(make_empty_order())
+ actual_hash_hex = generate_order_hash_hex(
+ make_empty_order(), "0x0000000000000000000000000000000000000000"
+ )
assert actual_hash_hex == expected_hash_hex