aboutsummaryrefslogtreecommitdiffstats
path: root/python-packages/order_utils/src/zero_ex/order_utils/__init__.py
blob: fb5bc2f5d14a24af8e3d68d13cda2f45cfbecac4 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
"""Order utilities for 0x applications.

Some methods require the caller to pass in a `Web3.HTTPProvider` object.  For
local testing one may construct such a provider pointing at an instance of
`ganache-cli <https://www.npmjs.com/package/ganache-cli>`_ which has the 0x
contracts deployed on it.  For convenience, a docker container is provided for
just this purpose.  To start it: ``docker run -d -p 8545:8545 0xorg/ganache-cli
--gasLimit 10000000 --db /snapshot --noVMErrorsOnRPCResponse -p 8545
--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()