diff options
author | Steve Klebanoff <steve.klebanoff@gmail.com> | 2019-01-10 00:54:55 +0800 |
---|---|---|
committer | Steve Klebanoff <steve.klebanoff@gmail.com> | 2019-01-10 00:54:55 +0800 |
commit | fb3605026ef63ef6897010a52bf3f4c116cdf271 (patch) | |
tree | af3887e5f785e049daeaf795e0f3e4a9ad370d93 /python-packages/order_utils/src | |
parent | 76dde294f10bb3d5c1074cd6599668bcb1cdd8ec (diff) | |
parent | 5b8c9122a292f6558c45440b053ceae52d36d495 (diff) | |
download | dexon-0x-contracts-fb3605026ef63ef6897010a52bf3f4c116cdf271.tar dexon-0x-contracts-fb3605026ef63ef6897010a52bf3f4c116cdf271.tar.gz dexon-0x-contracts-fb3605026ef63ef6897010a52bf3f4c116cdf271.tar.bz2 dexon-0x-contracts-fb3605026ef63ef6897010a52bf3f4c116cdf271.tar.lz dexon-0x-contracts-fb3605026ef63ef6897010a52bf3f4c116cdf271.tar.xz dexon-0x-contracts-fb3605026ef63ef6897010a52bf3f4c116cdf271.tar.zst dexon-0x-contracts-fb3605026ef63ef6897010a52bf3f4c116cdf271.zip |
Merge branch 'development' into feature/instant/tell-amount-available
Diffstat (limited to 'python-packages/order_utils/src')
6 files changed, 153 insertions, 142 deletions
diff --git a/python-packages/order_utils/src/index.rst b/python-packages/order_utils/src/index.rst index 551487ab1..4d27a4b17 100644 --- a/python-packages/order_utils/src/index.rst +++ b/python-packages/order_utils/src/index.rst @@ -22,9 +22,6 @@ 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/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/json_schemas/__init__.py b/python-packages/order_utils/src/zero_ex/json_schemas/__init__.py deleted file mode 100644 index a76a2fa3b..000000000 --- a/python-packages/order_utils/src/zero_ex/json_schemas/__init__.py +++ /dev/null @@ -1,74 +0,0 @@ -"""JSON schemas and associated utilities.""" - -from os import path -import json -from typing import Mapping - -from pkg_resources import resource_string -import jsonschema - - -class _LocalRefResolver(jsonschema.RefResolver): - """Resolve package-local JSON schema id's.""" - - def __init__(self): - """Initialize a new instance.""" - 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", - "/signedOrderSchema": "signed_order_schema.json", - "/ecSignatureParameterSchema": ( - "ec_signature_parameter_schema.json" + "" - ), - } - jsonschema.RefResolver.__init__(self, "", "") - - def resolve_from_url(self, url: str) -> str: - """Resolve the given URL. - - :param url: a string representing the URL of the JSON schema to fetch. - :returns: a string representing the deserialized JSON schema - :raises jsonschema.ValidationError: when the resource associated with - `url` does not exist. - """ - 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())}." - ) - - -# Instantiate the `_LocalRefResolver()` only once so that `assert_valid()` can -# perform multiple schema validations without reading from disk the schema -# every time. -_LOCAL_RESOLVER = _LocalRefResolver() - - -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 - - _, schema = _LOCAL_RESOLVER.resolve(schema_id) - jsonschema.validate(data, schema, resolver=_LOCAL_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 deleted file mode 120000 index b8257372c..000000000 --- a/python-packages/order_utils/src/zero_ex/json_schemas/schemas +++ /dev/null @@ -1 +0,0 @@ -../../../../../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 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 ( |