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.py8
-rw-r--r--python-packages/order_utils/src/zero_ex/dev_utils/abi_utils.py2
-rw-r--r--python-packages/order_utils/src/zero_ex/order_utils/__init__.py154
-rw-r--r--python-packages/order_utils/src/zero_ex/order_utils/signature_utils.py23
-rw-r--r--python-packages/order_utils/stubs/sha3/__init__.pyi0
-rw-r--r--python-packages/order_utils/test/test_generate_order_hash_hex.py18
6 files changed, 185 insertions, 20 deletions
diff --git a/python-packages/order_utils/setup.py b/python-packages/order_utils/setup.py
index 1b07b612c..7f1da2f34 100755
--- a/python-packages/order_utils/setup.py
+++ b/python-packages/order_utils/setup.py
@@ -160,7 +160,13 @@ setup(
"publish": PublishCommand,
"ganache": GanacheCommand,
},
- install_requires=["eth-abi", "eth_utils", "mypy_extensions", "web3"],
+ install_requires=[
+ "eth-abi",
+ "eth_utils",
+ "ethereum",
+ "mypy_extensions",
+ "web3",
+ ],
extras_require={
"dev": [
"bandit",
diff --git a/python-packages/order_utils/src/zero_ex/dev_utils/abi_utils.py b/python-packages/order_utils/src/zero_ex/dev_utils/abi_utils.py
index 71b6128ca..9afeacfdf 100644
--- a/python-packages/order_utils/src/zero_ex/dev_utils/abi_utils.py
+++ b/python-packages/order_utils/src/zero_ex/dev_utils/abi_utils.py
@@ -10,8 +10,8 @@ from typing import Any, List
from mypy_extensions import TypedDict
-from eth_abi import encode_abi
from web3 import Web3
+from eth_abi import encode_abi
from .type_assertions import assert_is_string, assert_is_list
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 80445cb6e..fb5bc2f5d 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
@@ -9,3 +9,157 @@ just this purpose. To start it: ``docker run -d -p 8545:8545 0xorg/ganache-cli
--networkId 50 -m "concert load couple harbor equip island argue ramp clarify
fence smart topic"``.
"""
+
+import json
+from typing import Dict
+from pkg_resources import resource_string
+
+from mypy_extensions import TypedDict
+
+from eth_utils import is_address, keccak, to_checksum_address, to_bytes
+from web3 import Web3
+from web3.utils import datatypes
+import web3.exceptions
+
+
+class Constants: # pylint: disable=too-few-public-methods
+ """Static data used by order utilities."""
+
+ contract_name_to_abi = {
+ "Exchange": json.loads(
+ resource_string(
+ "zero_ex.contract_artifacts", "artifacts/Exchange.json"
+ )
+ )["compilerOutput"]["abi"]
+ }
+
+ network_to_exchange_addr: Dict[str, str] = {
+ "1": "0x4f833a24e1f95d70f028921e27040ca56e09ab0b",
+ "3": "0x4530c0483a1633c7a1c97d2c53721caff2caaaaf",
+ "42": "0x35dd2932454449b14cee11a94d3674a936d5d7b2",
+ "50": "0x48bacb9266a570d521063ef5dd96e61686dbe788",
+ }
+
+ null_address = "0x0000000000000000000000000000000000000000"
+
+ eip191_header = b"\x19\x01"
+
+ eip712_domain_separator_schema_hash = keccak(
+ b"EIP712Domain(string name,string version,address verifyingContract)"
+ )
+
+ eip712_domain_struct_header = (
+ eip712_domain_separator_schema_hash
+ + keccak(b"0x Protocol")
+ + keccak(b"2")
+ )
+
+ eip712_order_schema_hash = keccak(
+ b"Order("
+ + b"address makerAddress,"
+ + b"address takerAddress,"
+ + b"address feeRecipientAddress,"
+ + b"address senderAddress,"
+ + b"uint256 makerAssetAmount,"
+ + b"uint256 takerAssetAmount,"
+ + b"uint256 makerFee,"
+ + b"uint256 takerFee,"
+ + b"uint256 expirationTimeSeconds,"
+ + b"uint256 salt,"
+ + b"bytes makerAssetData,"
+ + b"bytes takerAssetData"
+ + b")"
+ )
+
+
+class Order(TypedDict): # pylint: disable=too-many-instance-attributes
+ """Object representation of a 0x order."""
+
+ maker_address: str
+ taker_address: str
+ fee_recipient_address: str
+ sender_address: str
+ maker_asset_amount: int
+ taker_asset_amount: int
+ maker_fee: int
+ taker_fee: int
+ expiration_time_seconds: int
+ salt: int
+ maker_asset_data: str
+ taker_asset_data: str
+
+
+def make_empty_order() -> Order:
+ """Construct an empty order."""
+ return {
+ "maker_address": Constants.null_address,
+ "taker_address": Constants.null_address,
+ "sender_address": Constants.null_address,
+ "fee_recipient_address": Constants.null_address,
+ "maker_asset_data": Constants.null_address,
+ "taker_asset_data": Constants.null_address,
+ "salt": 0,
+ "maker_fee": 0,
+ "taker_fee": 0,
+ "maker_asset_amount": 0,
+ "taker_asset_amount": 0,
+ "expiration_time_seconds": 0,
+ }
+
+
+def generate_order_hash_hex(order: Order, exchange_address: str) -> str:
+ # docstring considered all one line by pylint: disable=line-too-long
+ """Calculate the hash of the given order as a hexadecimal string.
+
+ >>> generate_order_hash_hex(
+ ... {
+ ... 'maker_address': "0x0000000000000000000000000000000000000000",
+ ... 'taker_address': "0x0000000000000000000000000000000000000000",
+ ... 'fee_recipient_address': "0x0000000000000000000000000000000000000000",
+ ... 'sender_address': "0x0000000000000000000000000000000000000000",
+ ... 'maker_asset_amount': 1000000000000000000,
+ ... 'taker_asset_amount': 1000000000000000000,
+ ... 'maker_fee': 0,
+ ... 'taker_fee': 0,
+ ... 'expiration_time_seconds': 12345,
+ ... 'salt': 12345,
+ ... 'maker_asset_data': "0000000000000000000000000000000000000000",
+ ... 'taker_asset_data': "0000000000000000000000000000000000000000",
+ ... },
+ ... exchange_address="0x0000000000000000000000000000000000000000",
+ ... )
+ '55eaa6ec02f3224d30873577e9ddd069a288c16d6fb407210eecbc501fa76692'
+ """ # noqa: E501 (line too long)
+ # TODO: use JSON schema validation to validate order. pylint: disable=fixme
+ def pad_20_bytes_to_32(twenty_bytes: bytes):
+ return bytes(12) + twenty_bytes
+
+ def int_to_32_big_endian_bytes(i: int):
+ return i.to_bytes(32, byteorder="big")
+
+ eip712_domain_struct_hash = keccak(
+ Constants.eip712_domain_struct_header
+ + pad_20_bytes_to_32(to_bytes(hexstr=exchange_address))
+ )
+
+ eip712_order_struct_hash = keccak(
+ Constants.eip712_order_schema_hash
+ + pad_20_bytes_to_32(to_bytes(hexstr=order["maker_address"]))
+ + pad_20_bytes_to_32(to_bytes(hexstr=order["taker_address"]))
+ + pad_20_bytes_to_32(to_bytes(hexstr=order["fee_recipient_address"]))
+ + pad_20_bytes_to_32(to_bytes(hexstr=order["sender_address"]))
+ + int_to_32_big_endian_bytes(order["maker_asset_amount"])
+ + int_to_32_big_endian_bytes(order["taker_asset_amount"])
+ + int_to_32_big_endian_bytes(order["maker_fee"])
+ + int_to_32_big_endian_bytes(order["taker_fee"])
+ + int_to_32_big_endian_bytes(order["expiration_time_seconds"])
+ + int_to_32_big_endian_bytes(order["salt"])
+ + keccak(to_bytes(hexstr=order["maker_asset_data"]))
+ + keccak(to_bytes(hexstr=order["taker_asset_data"]))
+ )
+
+ return keccak(
+ Constants.eip191_header
+ + eip712_domain_struct_hash
+ + eip712_order_struct_hash
+ ).hex()
diff --git a/python-packages/order_utils/src/zero_ex/order_utils/signature_utils.py b/python-packages/order_utils/src/zero_ex/order_utils/signature_utils.py
index 12525ba88..2e75be6d5 100644
--- a/python-packages/order_utils/src/zero_ex/order_utils/signature_utils.py
+++ b/python-packages/order_utils/src/zero_ex/order_utils/signature_utils.py
@@ -1,31 +1,17 @@
"""Signature utilities."""
-from typing import Dict, Tuple
-import json
-from pkg_resources import resource_string
+from typing import Tuple
from eth_utils import is_address, to_checksum_address
from web3 import Web3
import web3.exceptions
from web3.utils import datatypes
+from zero_ex.order_utils import Constants
from zero_ex.dev_utils.type_assertions import assert_is_hex_string
# prefer `black` formatting. pylint: disable=C0330
-EXCHANGE_ABI = json.loads(
- resource_string("zero_ex.contract_artifacts", "artifacts/Exchange.json")
-)["compilerOutput"]["abi"]
-
-network_to_exchange_addr: Dict[str, str] = {
- "1": "0x4f833a24e1f95d70f028921e27040ca56e09ab0b",
- "3": "0x4530c0483a1633c7a1c97d2c53721caff2caaaaf",
- "42": "0x35dd2932454449b14cee11a94d3674a936d5d7b2",
- "50": "0x48bacb9266a570d521063ef5dd96e61686dbe788",
-}
-
-
-# prefer `black` formatting. pylint: disable=C0330
def is_valid_signature(
provider: Web3.HTTPProvider, data: str, signature: str, signer_address: str
) -> Tuple[bool, str]:
@@ -63,10 +49,11 @@ def is_valid_signature(
web3_instance = Web3(provider)
# false positive from pylint: disable=no-member
network_id = web3_instance.net.version
- contract_address = network_to_exchange_addr[network_id]
+ contract_address = Constants.network_to_exchange_addr[network_id]
# false positive from pylint: disable=no-member
contract: datatypes.Contract = web3_instance.eth.contract(
- address=to_checksum_address(contract_address), abi=EXCHANGE_ABI
+ address=to_checksum_address(contract_address),
+ abi=Constants.contract_name_to_abi["Exchange"],
)
try:
return (
diff --git a/python-packages/order_utils/stubs/sha3/__init__.pyi b/python-packages/order_utils/stubs/sha3/__init__.pyi
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/python-packages/order_utils/stubs/sha3/__init__.pyi
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
new file mode 100644
index 000000000..e393f38d7
--- /dev/null
+++ b/python-packages/order_utils/test/test_generate_order_hash_hex.py
@@ -0,0 +1,18 @@
+"""Test zero_ex.order_utils.get_order_hash_hex()."""
+
+from zero_ex.order_utils import (
+ generate_order_hash_hex,
+ make_empty_order,
+ Constants,
+)
+
+
+def test_get_order_hash_hex__empty_order():
+ """Test the hashing of an uninitialized order."""
+ expected_hash_hex = (
+ "faa49b35faeb9197e9c3ba7a52075e6dad19739549f153b77dfcf59408a4b422"
+ )
+ actual_hash_hex = generate_order_hash_hex(
+ make_empty_order(), Constants.null_address
+ )
+ assert actual_hash_hex == expected_hash_hex