fix(order_utils.py): validate order w/json schema (#1260)
This commit is contained in:
parent
e1d64def20
commit
b961cb1952
@ -6,6 +6,7 @@ lib
|
||||
/packages/contract-artifacts/artifacts
|
||||
/python-packages/order_utils/src/zero_ex/contract_artifacts/artifacts
|
||||
/packages/json-schemas/schemas
|
||||
/python-packages/order_utils/src/zero_ex/json_schemas/schemas
|
||||
/packages/metacoin/src/contract_wrappers
|
||||
/packages/metacoin/artifacts
|
||||
/packages/sra-spec/public/
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
==================
|
||||
|
||||
|
@ -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
|
||||
)
|
1
python-packages/order_utils/src/zero_ex/json_schemas/schemas
Symbolic link
1
python-packages/order_utils/src/zero_ex/json_schemas/schemas
Symbolic link
@ -0,0 +1 @@
|
||||
../../../../../packages/json-schemas/schemas/
|
@ -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(
|
||||
|
@ -0,0 +1,5 @@
|
||||
from typing import Any, Dict
|
||||
|
||||
class RefResolver: pass
|
||||
|
||||
def validate(instance: Any, schema: Dict, cls=None, *args, **kwargs) -> None: pass
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user