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.py3
-rw-r--r--python-packages/order_utils/src/index.rst3
-rw-r--r--python-packages/order_utils/src/zero_ex/json_schemas/__init__.py61
l---------python-packages/order_utils/src/zero_ex/json_schemas/schemas1
-rw-r--r--python-packages/order_utils/src/zero_ex/order_utils/__init__.py108
-rw-r--r--python-packages/order_utils/stubs/jsonschema/__init__.pyi5
-rw-r--r--python-packages/order_utils/stubs/jsonschema/exceptions.pyi0
-rw-r--r--python-packages/order_utils/test/test_generate_order_hash_hex.py10
8 files changed, 130 insertions, 61 deletions
diff --git a/python-packages/order_utils/setup.py b/python-packages/order_utils/setup.py
index 5a8db2b64..679bfb4b2 100755
--- a/python-packages/order_utils/setup.py
+++ b/python-packages/order_utils/setup.py
@@ -159,7 +159,7 @@ setup(
install_requires=[
"eth-abi",
"eth_utils",
- "ethereum",
+ "jsonschema",
"mypy_extensions",
"web3",
],
@@ -184,6 +184,7 @@ setup(
package_data={
"zero_ex.order_utils": ["py.typed"],
"zero_ex.contract_artifacts": ["artifacts/*"],
+ "zero_ex.json_schemas": ["schemas/*"],
},
package_dir={"": "src"},
license="Apache 2.0",
diff --git a/python-packages/order_utils/src/index.rst b/python-packages/order_utils/src/index.rst
index 4d27a4b17..551487ab1 100644
--- a/python-packages/order_utils/src/index.rst
+++ b/python-packages/order_utils/src/index.rst
@@ -22,6 +22,9 @@ See source for class properties. Sphinx does not easily generate class property
.. autoclass:: zero_ex.order_utils.asset_data_utils.ERC721AssetData
+.. automodule:: zero_ex.json_schemas
+ :members:
+
Indices and tables
==================
diff --git a/python-packages/order_utils/src/zero_ex/json_schemas/__init__.py b/python-packages/order_utils/src/zero_ex/json_schemas/__init__.py
new file mode 100644
index 000000000..2a1728b8a
--- /dev/null
+++ b/python-packages/order_utils/src/zero_ex/json_schemas/__init__.py
@@ -0,0 +1,61 @@
+"""JSON schemas and associated utilities."""
+
+from os import path
+import json
+from typing import Mapping
+
+from pkg_resources import resource_string
+import jsonschema
+
+
+def assert_valid(data: Mapping, schema_id: str) -> None:
+ """Validate the given `data` against the specified `schema`.
+
+ :param data: Python dictionary to be validated as a JSON object.
+ :param schema_id: id property of the JSON schema to validate against. Must
+ be one of those listed in `the 0x JSON schema files
+ <https://github.com/0xProject/0x-monorepo/tree/development/packages/json-schemas/schemas>`_.
+
+ Raises an exception if validation fails.
+
+ >>> assert_valid(
+ ... {'v': 27, 'r': '0x'+'f'*64, 's': '0x'+'f'*64},
+ ... '/ECSignature',
+ ... )
+ """
+ # noqa
+ class LocalRefResolver(jsonschema.RefResolver):
+ """Resolve package-local JSON schema id's."""
+
+ def __init__(self):
+ self.ref_to_file = {
+ "/addressSchema": "address_schema.json",
+ "/hexSchema": "hex_schema.json",
+ "/orderSchema": "order_schema.json",
+ "/wholeNumberSchema": "whole_number_schema.json",
+ "/ECSignature": "ec_signature_schema.json",
+ "/ecSignatureParameterSchema": (
+ "ec_signature_parameter_schema.json" + ""
+ ),
+ }
+ jsonschema.RefResolver.__init__(self, "", "")
+
+ def resolve_from_url(self, url):
+ """Resolve the given URL."""
+ ref = url.replace("file://", "")
+ if ref in self.ref_to_file:
+ return json.loads(
+ resource_string(
+ "zero_ex.json_schemas",
+ f"schemas/{self.ref_to_file[ref]}",
+ )
+ )
+ raise jsonschema.ValidationError(
+ f"Unknown ref '{ref}'. "
+ + f"Known refs: {list(self.ref_to_file.keys())}."
+ )
+
+ resolver = LocalRefResolver()
+ jsonschema.validate(
+ data, resolver.resolve_from_url(schema_id), resolver=resolver
+ )
diff --git a/python-packages/order_utils/src/zero_ex/json_schemas/schemas b/python-packages/order_utils/src/zero_ex/json_schemas/schemas
new file mode 120000
index 000000000..b8257372c
--- /dev/null
+++ b/python-packages/order_utils/src/zero_ex/json_schemas/schemas
@@ -0,0 +1 @@
+../../../../../packages/json-schemas/schemas/ \ 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 c736d3567..a86128e0e 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
@@ -28,6 +28,7 @@ from zero_ex.dev_utils.type_assertions import (
assert_is_hex_string,
assert_is_provider,
)
+from zero_ex.json_schemas import assert_valid
class _Constants:
@@ -95,18 +96,19 @@ class _Constants:
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
+ 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
def make_empty_order() -> Order:
@@ -116,22 +118,23 @@ def make_empty_order() -> Order:
and all numbers to 0.
"""
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,
+ "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,
}
-def generate_order_hash_hex(order: Order, exchange_address: str) -> str:
+def generate_order_hash_hex(order: Order) -> 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>`_.
@@ -141,24 +144,25 @@ def generate_order_hash_hex(order: Order, exchange_address: str) -> str:
>>> 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",
+ ... '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",
... },
- ... exchange_address="0x0000000000000000000000000000000000000000",
... )
'55eaa6ec02f3224d30873577e9ddd069a288c16d6fb407210eecbc501fa76692'
""" # noqa: E501 (line too long)
- # TODO: use JSON schema validation to validate order. pylint: disable=fixme
+ assert_valid(order, "/orderSchema")
+
def pad_20_bytes_to_32(twenty_bytes: bytes):
return bytes(12) + twenty_bytes
@@ -167,23 +171,23 @@ def generate_order_hash_hex(order: Order, exchange_address: str) -> str:
eip712_domain_struct_hash = keccak(
_Constants.eip712_domain_struct_header
- + pad_20_bytes_to_32(to_bytes(hexstr=exchange_address))
+ + pad_20_bytes_to_32(to_bytes(hexstr=order["exchangeAddress"]))
)
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"]))
+ + pad_20_bytes_to_32(to_bytes(hexstr=order["makerAddress"]))
+ + pad_20_bytes_to_32(to_bytes(hexstr=order["takerAddress"]))
+ + pad_20_bytes_to_32(to_bytes(hexstr=order["feeRecipientAddress"]))
+ + pad_20_bytes_to_32(to_bytes(hexstr=order["senderAddress"]))
+ + int_to_32_big_endian_bytes(int(order["makerAssetAmount"]))
+ + int_to_32_big_endian_bytes(int(order["takerAssetAmount"]))
+ + int_to_32_big_endian_bytes(int(order["makerFee"]))
+ + 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"]))
)
return keccak(
diff --git a/python-packages/order_utils/stubs/jsonschema/__init__.pyi b/python-packages/order_utils/stubs/jsonschema/__init__.pyi
new file mode 100644
index 000000000..762b58b22
--- /dev/null
+++ b/python-packages/order_utils/stubs/jsonschema/__init__.pyi
@@ -0,0 +1,5 @@
+from typing import Any, Dict
+
+class RefResolver: pass
+
+def validate(instance: Any, schema: Dict, cls=None, *args, **kwargs) -> None: pass
diff --git a/python-packages/order_utils/stubs/jsonschema/exceptions.pyi b/python-packages/order_utils/stubs/jsonschema/exceptions.pyi
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/python-packages/order_utils/stubs/jsonschema/exceptions.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
index af78d208c..6869a40ed 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
@@ -1,10 +1,6 @@
"""Test zero_ex.order_utils.get_order_hash_hex()."""
-from zero_ex.order_utils import (
- generate_order_hash_hex,
- make_empty_order,
- _Constants,
-)
+from zero_ex.order_utils import generate_order_hash_hex, make_empty_order
def test_get_order_hash_hex__empty_order():
@@ -12,7 +8,5 @@ def test_get_order_hash_hex__empty_order():
expected_hash_hex = (
"faa49b35faeb9197e9c3ba7a52075e6dad19739549f153b77dfcf59408a4b422"
)
- actual_hash_hex = generate_order_hash_hex(
- make_empty_order(), _Constants.null_address
- )
+ actual_hash_hex = generate_order_hash_hex(make_empty_order())
assert actual_hash_hex == expected_hash_hex