Auto-gen Python Exchange wrapper (#1919)
* Rename existing wrapper, to match contract name * base contract: make member var public * json_schemas.py: stop storing copies of schemas! * .gitignore generated erc20_token.py wrapper * json schemas: allow uppercase digits in address * existing exchange wrapper: re-order methods to match method order in Solidity contract, to reduce noise in upcoming diffs of newly generated code vs. old manually-written code. * existing exchange wrapper: rename method params To match contract method param names * existing exchange wrapper: remove redundant member * existing exchange wrapper: make signatures bytes Not strings. * abi-gen/test-cli: show context on diff failure * abi-gen-templates/Py: fix broken event interface Previous changes had removed the `token_address` parameter from all generated methods, but this instance was missed because there weren't tests/examples using events for the first contract for which wrappers were generated (ERC20Token). * abi-gen: remove unused method parameters * abi-gen: convert Py method params to snake case * abi-gen: rewrite Python tuple handling * python-generated-wrappers: include Exchange * abi-gen-templates/Py: easy linter fixes * abi-gen-templates/Py: satisfy docstring linters * abi-gen-templates/Py: normalize bytes before use * contract_wrappers.py: replace Exchange w/generated * contract_wrappers.py: rm manually written Exchange * contract_wrappers.py/doctest: rename variables * abi-gen: fix misspelling in docstring Co-Authored-By: Fabio B <me@fabioberger.com> * Py docs: error on warning, and test build in CI * abi-gen: doc Py bytes params as requiring UTF-8 * abi-gen: git mv diff.sh test-cli/ * abi-gen: put Py wrapper in module folder, not file This leaves space for user-defined additions to the same module, such as for custom types, as shown herein. * abi-gen: customizable param validation for Python * contract_wrappers.py: JSON schema Order validation * CircleCI Build Artifacts For abi-gen command-line test output, for generated Python contract wrappers as output by abi-gen, for generated Python contract wrappers as reformatted and included in the Python package area, and for the "build" output folder in each Python package, which includes the generated documentation. * CHANGELOG updates for all components * abi-gen: grammar in comments Co-Authored-By: Fabio B <me@fabioberger.com> * abi-gen: CHANGELOG spelling correction Co-Authored-By: Fabio B <me@fabioberger.com> * order_utils.py: reverse (chronological) CHANGELOG * abi-gen-templates: reset CHANGELOG patch version * CHANGELOGs: use multiple entries where appropriate * abi-gen: enable devdoc solc output in test-cli * abi-gen-templates/Py: consolidate return type * abi-gen/test-cli: non-pure fixture contract method Added a method to the "dummy" test fixture contract that isn't pure. All of the other prior method cases were pure. * abi-gen/Py: fix const methods missing return type * abi-gen/Py: fix wrong return types on some methods Specifically, wrapper methods wrapping contract methods that modify contract state and return no return value. There was no test case for this. Now there is. * contract_wrappers.py: rm generated code in `clean` * Parallelize Py monorepo scripts (test, lint, etc)
This commit is contained in:
3
python-packages/build_docs
Executable file
3
python-packages/build_docs
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
./parallel ./setup.py build_sphinx
|
||||
@@ -186,6 +186,7 @@ setup(
|
||||
"build_sphinx": {
|
||||
"source_dir": ("setup.py", "src"),
|
||||
"build_dir": ("setup.py", "build/docs"),
|
||||
"warning_is_error": ("setup.py", "true"),
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@@ -60,15 +60,6 @@ class LintCommand(distutils.command.build_py.build_py):
|
||||
"mypy src setup.py".split(),
|
||||
# security issue checker:
|
||||
"bandit -r src ./setup.py".split(),
|
||||
# HACK: ensure contract artifacts match the authoritative copies:
|
||||
# this is a hack. ideally we would symlink to the authoritative
|
||||
# copies, but a problem with setuptools is preventing it from
|
||||
# following symlinks when gathering package_data. see
|
||||
# https://github.com/pypa/setuptools/issues/415.
|
||||
(
|
||||
"diff src/zero_ex/contract_artifacts/artifacts"
|
||||
+ " ../../packages/contract-artifacts/artifacts"
|
||||
).split(),
|
||||
# general linter:
|
||||
"pylint src setup.py".split(),
|
||||
# pylint takes relatively long to run, so it runs last, to enable
|
||||
@@ -225,6 +216,7 @@ setup(
|
||||
"build_sphinx": {
|
||||
"source_dir": ("setup.py", "src"),
|
||||
"build_dir": ("setup.py", "build/docs"),
|
||||
"warning_is_error": ("setup.py", "true"),
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
[MESSAGES CONTROL]
|
||||
disable=C0330,line-too-long,fixme,too-few-public-methods,too-many-ancestors
|
||||
disable=C0330,line-too-long,fixme,too-few-public-methods,too-many-ancestors,duplicate-code
|
||||
# C0330 is "bad hanging indent". we use indents per `black`.
|
||||
|
||||
[SIMILARITIES]
|
||||
min-similarity-lines=12
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# Changelog
|
||||
|
||||
## 2.0.0 - TBD
|
||||
|
||||
- Completely new implementation of the Exchange wrapper, virtually all auto-generated from the Solidity contract. Breaking changes include method parameter name changes and accepting of signatures as bytes.
|
||||
|
||||
## 1.0.0 - 2019-04-30
|
||||
|
||||
- Initial release.
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
import subprocess # nosec
|
||||
from shutil import copy, rmtree
|
||||
from os import environ, path
|
||||
from os import environ, path, remove
|
||||
from pathlib import Path
|
||||
from sys import argv
|
||||
from importlib.util import find_spec
|
||||
@@ -34,17 +34,37 @@ class PreInstallCommand(distutils.command.build_py.build_py):
|
||||
"packages",
|
||||
"python-contract-wrappers",
|
||||
"generated",
|
||||
"erc20_token.py",
|
||||
"erc20_token",
|
||||
"__init__.py",
|
||||
),
|
||||
path.join(
|
||||
pkgdir, "src", "zero_ex", "contract_wrappers", "erc20_token"
|
||||
),
|
||||
)
|
||||
copy(
|
||||
path.join(
|
||||
pkgdir,
|
||||
"..",
|
||||
"..",
|
||||
"packages",
|
||||
"python-contract-wrappers",
|
||||
"generated",
|
||||
"exchange",
|
||||
"__init__.py",
|
||||
),
|
||||
path.join(
|
||||
pkgdir, "src", "zero_ex", "contract_wrappers", "exchange"
|
||||
),
|
||||
path.join(pkgdir, "src", "zero_ex", "contract_wrappers"),
|
||||
)
|
||||
if find_spec("black") is None:
|
||||
subprocess.check_call("pip install black".split()) # nosec
|
||||
subprocess.check_call( # nosec
|
||||
(
|
||||
BLACK_COMMAND + " src/zero_ex/contract_wrappers/erc20_token.py"
|
||||
).split()
|
||||
black_command = (
|
||||
BLACK_COMMAND
|
||||
+ " src/zero_ex/contract_wrappers/erc20_token/__init__.py"
|
||||
+ " src/zero_ex/contract_wrappers/exchange/__init__.py"
|
||||
)
|
||||
print(f"Running command `{black_command}`...")
|
||||
subprocess.check_call(black_command.split()) # nosec
|
||||
|
||||
|
||||
class TestCommandExtension(TestCommand):
|
||||
@@ -112,6 +132,9 @@ class CleanCommandExtension(clean):
|
||||
rmtree(".tox", ignore_errors=True)
|
||||
rmtree(".pytest_cache", ignore_errors=True)
|
||||
rmtree("src/0x_contract_wrappers.egg-info", ignore_errors=True)
|
||||
# generated files:
|
||||
remove("src/zero_ex/contract_wrappers/exchange/__init__.py")
|
||||
remove("src/zero_ex/contract_wrappers/erc20_token/__init__.py")
|
||||
|
||||
|
||||
class TestPublishCommand(distutils.command.build_py.build_py):
|
||||
@@ -255,6 +278,7 @@ setup(
|
||||
"build_sphinx": {
|
||||
"source_dir": ("setup.py", "src"),
|
||||
"build_dir": ("setup.py", "build/docs"),
|
||||
"warning_is_error": ("setup.py", "true"),
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@@ -31,6 +31,38 @@ zero_ex.contract_wrappers.TxParams
|
||||
.. autoclass:: zero_ex.contract_wrappers.TxParams
|
||||
:members:
|
||||
|
||||
zero_ex.contract_wrappers.exchange.types
|
||||
========================================
|
||||
|
||||
.. automodule:: zero_ex.contract_wrappers.exchange.types
|
||||
|
||||
.. autoclass:: zero_ex.contract_wrappers.exchange.types.Order
|
||||
|
||||
.. autoclass:: zero_ex.contract_wrappers.exchange.types.OrderInfo
|
||||
|
||||
.. autoclass:: zero_ex.contract_wrappers.exchange.types.FillResults
|
||||
|
||||
.. autoclass:: zero_ex.contract_wrappers.exchange.types.MatchedFillResults
|
||||
|
||||
zero_ex.contract_wrappers.exchange: Generated Tuples
|
||||
====================================================
|
||||
|
||||
.. autoclass:: zero_ex.contract_wrappers.exchange.Tuple0x260219a2
|
||||
|
||||
This is the generated class representing `the Order struct <https://0x.org/docs/contracts#structs-Order>`_.
|
||||
|
||||
.. autoclass:: zero_ex.contract_wrappers.exchange.Tuple0xbb41e5b3
|
||||
|
||||
This is the generated class representing `the FillResults struct <https://0x.org/docs/contracts#structs-FillResults>`_.
|
||||
|
||||
.. autoclass:: zero_ex.contract_wrappers.exchange.Tuple0x054ca44e
|
||||
|
||||
This is the generated class representing `the MatchedFillResults struct <https://0x.org/docs/contracts#structs-MatchedFillResults>`_.
|
||||
|
||||
.. autoclass:: zero_ex.contract_wrappers.exchange.Tuple0xb1e4a1ae
|
||||
|
||||
This is the generated class representing `the OrderInfo struct <https://0x.org/docs/contracts#structs-OrderInfo>`_.
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
|
||||
@@ -91,24 +91,24 @@ we need to tell the WETH token contract to let the 0x contracts transfer our
|
||||
balance:
|
||||
|
||||
>>> from zero_ex.contract_wrappers import ERC20Token
|
||||
>>> zrx_wrapper = ERC20Token(
|
||||
>>> zrx_token = ERC20Token(
|
||||
... provider=ganache,
|
||||
... contract_address=NETWORK_TO_ADDRESSES[NetworkId.GANACHE].zrx_token,
|
||||
... )
|
||||
>>> weth_wrapper = ERC20Token(
|
||||
>>> weth_token = ERC20Token(
|
||||
... provider=ganache,
|
||||
... contract_address=NETWORK_TO_ADDRESSES[NetworkId.GANACHE].ether_token,
|
||||
... )
|
||||
|
||||
>>> erc20_proxy_addr = NETWORK_TO_ADDRESSES[NetworkId.GANACHE].erc20_proxy
|
||||
|
||||
>>> tx = zrx_wrapper.approve(
|
||||
>>> tx = zrx_token.approve(
|
||||
... erc20_proxy_addr,
|
||||
... to_wei(100, 'ether'),
|
||||
... tx_params=TxParams(from_=maker_address),
|
||||
... )
|
||||
|
||||
>>> tx = weth_wrapper.approve(
|
||||
>>> tx = weth_token.approve(
|
||||
... erc20_proxy_addr,
|
||||
... to_wei(100, 'ether'),
|
||||
... tx_params=TxParams(from_=taker_address),
|
||||
@@ -117,8 +117,8 @@ balance:
|
||||
Constructing an order
|
||||
---------------------
|
||||
|
||||
>>> from zero_ex.order_utils import asset_data_utils, Order
|
||||
>>> from eth_utils import remove_0x_prefix
|
||||
>>> from zero_ex.contract_wrappers.exchange.types import Order
|
||||
>>> from zero_ex.order_utils import asset_data_utils
|
||||
>>> from datetime import datetime, timedelta
|
||||
>>> import random
|
||||
>>> order = Order(
|
||||
@@ -143,8 +143,8 @@ For this order to be valid, our Maker must sign a hash of it:
|
||||
>>> from zero_ex.order_utils import generate_order_hash_hex
|
||||
>>> order_hash_hex = generate_order_hash_hex(order, exchange_address)
|
||||
|
||||
>>> from zero_ex.order_utils import sign_hash
|
||||
>>> maker_signature = sign_hash(
|
||||
>>> from zero_ex.order_utils import sign_hash_to_bytes
|
||||
>>> maker_signature = sign_hash_to_bytes(
|
||||
... ganache, Web3.toChecksumAddress(maker_address), order_hash_hex
|
||||
... )
|
||||
|
||||
@@ -162,13 +162,13 @@ fill. This example fills the order completely, but partial fills are possible
|
||||
too.
|
||||
|
||||
>>> from zero_ex.contract_wrappers import Exchange
|
||||
>>> exchange_contract = Exchange(
|
||||
>>> exchange = Exchange(
|
||||
... provider=ganache,
|
||||
... contract_address=NETWORK_TO_ADDRESSES[NetworkId.GANACHE].exchange,
|
||||
... )
|
||||
>>> tx_hash = exchange_contract.fill_order(
|
||||
>>> tx_hash = exchange.fill_order(
|
||||
... order=order,
|
||||
... taker_amount=order["takerAssetAmount"],
|
||||
... taker_asset_fill_amount=order["takerAssetAmount"],
|
||||
... signature=maker_signature,
|
||||
... tx_params=TxParams(from_=taker_address)
|
||||
... )
|
||||
@@ -176,10 +176,10 @@ too.
|
||||
Once the transaction is mined, we can get the details of our exchange through
|
||||
the exchange wrapper:
|
||||
|
||||
>>> exchange_contract.get_fill_event(tx_hash)
|
||||
>>> exchange.get_fill_event(tx_hash)
|
||||
(AttributeDict({'args': ...({'makerAddress': ...}), 'event': 'Fill', ...}),)
|
||||
>>> from pprint import pprint
|
||||
>>> pprint(exchange_contract.get_fill_event(tx_hash)[0].args.__dict__)
|
||||
>>> pprint(exchange.get_fill_event(tx_hash)[0].args.__dict__)
|
||||
{'feeRecipientAddress': '0x0000000000000000000000000000000000000000',
|
||||
'makerAddress': '0x...',
|
||||
'makerAssetData': b...,
|
||||
@@ -191,7 +191,7 @@ the exchange wrapper:
|
||||
'takerAssetData': b...,
|
||||
'takerAssetFilledAmount': 100000000000000000,
|
||||
'takerFeePaid': 0}
|
||||
>>> exchange_contract.get_fill_event(tx_hash)[0].args.takerAssetFilledAmount
|
||||
>>> exchange.get_fill_event(tx_hash)[0].args.takerAssetFilledAmount
|
||||
100000000000000000
|
||||
|
||||
Cancelling an order
|
||||
@@ -217,23 +217,23 @@ A Maker can cancel an order that has yet to be filled.
|
||||
... )
|
||||
... )
|
||||
|
||||
>>> tx_hash = exchange_contract.cancel_order(
|
||||
>>> tx_hash = exchange.cancel_order(
|
||||
... order=order, tx_params=TxParams(from_=maker_address)
|
||||
... )
|
||||
|
||||
Once the transaction is mined, we can get the details of the cancellation
|
||||
through the Exchange wrapper:
|
||||
|
||||
>>> exchange_contract.get_cancel_event(tx_hash)
|
||||
>>> exchange.get_cancel_event(tx_hash)
|
||||
(AttributeDict({'args': ...({'makerAddress': ...}), 'event': 'Cancel', ...}),)
|
||||
>>> pprint(exchange_contract.get_cancel_event(tx_hash)[0].args.__dict__)
|
||||
>>> pprint(exchange.get_cancel_event(tx_hash)[0].args.__dict__)
|
||||
{'feeRecipientAddress': '0x0000000000000000000000000000000000000000',
|
||||
'makerAddress': '0x...',
|
||||
'makerAssetData': b...,
|
||||
'orderHash': b...,
|
||||
'senderAddress': '0x...',
|
||||
'takerAssetData': b...}
|
||||
>>> exchange_contract.get_cancel_event(tx_hash)[0].args.feeRecipientAddress
|
||||
>>> exchange.get_cancel_event(tx_hash)[0].args.feeRecipientAddress
|
||||
'0x0000000000000000000000000000000000000000'
|
||||
|
||||
Batching orders
|
||||
@@ -258,10 +258,10 @@ is an example where the taker fills two orders in one transaction:
|
||||
... (datetime.utcnow() + timedelta(days=1)).timestamp()
|
||||
... )
|
||||
... )
|
||||
>>> signature_1 = sign_hash(
|
||||
>>> signature_1 = sign_hash_to_bytes(
|
||||
... ganache,
|
||||
... Web3.toChecksumAddress(maker_address),
|
||||
... generate_order_hash_hex(order_1, exchange_contract.address)
|
||||
... generate_order_hash_hex(order_1, exchange.contract_address)
|
||||
... )
|
||||
>>> order_2 = Order(
|
||||
... makerAddress=maker_address,
|
||||
@@ -279,17 +279,17 @@ is an example where the taker fills two orders in one transaction:
|
||||
... (datetime.utcnow() + timedelta(days=1)).timestamp()
|
||||
... )
|
||||
... )
|
||||
>>> signature_2 = sign_hash(
|
||||
>>> signature_2 = sign_hash_to_bytes(
|
||||
... ganache,
|
||||
... Web3.toChecksumAddress(maker_address),
|
||||
... generate_order_hash_hex(order_2, exchange_contract.address)
|
||||
... generate_order_hash_hex(order_2, exchange.contract_address)
|
||||
... )
|
||||
|
||||
Fill order_1 and order_2 together:
|
||||
|
||||
>>> exchange_contract.batch_fill_orders(
|
||||
>>> exchange.batch_fill_orders(
|
||||
... orders=[order_1, order_2],
|
||||
... taker_amounts=[1, 2],
|
||||
... taker_asset_fill_amounts=[1, 2],
|
||||
... signatures=[signature_1, signature_2],
|
||||
... tx_params=TxParams(from_=taker_address))
|
||||
HexBytes('0x...')
|
||||
@@ -297,4 +297,4 @@ HexBytes('0x...')
|
||||
|
||||
from .tx_params import TxParams
|
||||
from .erc20_token import ERC20Token
|
||||
from .exchange_wrapper import Exchange
|
||||
from .exchange import Exchange
|
||||
|
||||
@@ -34,7 +34,7 @@ class BaseContractWrapper:
|
||||
self._private_key = private_key
|
||||
self._web3 = Web3(provider)
|
||||
self._web3_eth = self._web3.eth # pylint: disable=no-member
|
||||
self._contract_address = self._validate_and_checksum_address(
|
||||
self.contract_address = self._validate_and_checksum_address(
|
||||
contract_address
|
||||
)
|
||||
|
||||
@@ -120,7 +120,7 @@ class BaseContractWrapper:
|
||||
:returns: str of transaction hash
|
||||
"""
|
||||
contract_instance = self._contract_instance(
|
||||
address=self._contract_address, abi=abi
|
||||
address=self.contract_address, abi=abi
|
||||
)
|
||||
if args is None:
|
||||
args = []
|
||||
@@ -131,6 +131,6 @@ class BaseContractWrapper:
|
||||
)
|
||||
raise Exception(
|
||||
"No method {} found on contract {}.".format(
|
||||
self._contract_address, method
|
||||
self.contract_address, method
|
||||
)
|
||||
)
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
"""Conveniences for handling types representing Exchange Solidity structs.
|
||||
|
||||
The `TypedDict`:code: classes in the .exchange module represent tuples
|
||||
encountered in the Exchange contract's ABI. However, they have weird names,
|
||||
containing hashes of the tuple's field names, because the name of a Solidity
|
||||
`struct`:code: isn't conveyed through the ABI. This module provides type
|
||||
aliases with human-friendly names.
|
||||
|
||||
Converting between the JSON wire format and the types accepted by Web3.py (eg
|
||||
`bytes` vs `str`) can be onerous. This module provides conveniences for
|
||||
converting Exchange structs between JSON and Python objects.
|
||||
"""
|
||||
|
||||
from copy import copy
|
||||
from typing import cast, Dict
|
||||
|
||||
from eth_utils import remove_0x_prefix
|
||||
|
||||
from zero_ex.json_schemas import assert_valid
|
||||
|
||||
from . import (
|
||||
Tuple0xbb41e5b3,
|
||||
Tuple0x260219a2,
|
||||
Tuple0x054ca44e,
|
||||
Tuple0xb1e4a1ae,
|
||||
)
|
||||
|
||||
|
||||
# Would rather not repeat ourselves below, but base classes are mentioned in
|
||||
# the class docstrings because of a bug in sphinx rendering. Using the `..
|
||||
# autoclass` directive, with the `:show-inheritance:` role, results in docs
|
||||
# being rendered with just "Bases: dict", and no mention of the direct ancestor
|
||||
# of each of these classes.
|
||||
|
||||
|
||||
class FillResults(Tuple0xbb41e5b3):
|
||||
"""The `FillResults`:code: Solidity struct.
|
||||
|
||||
Also known as
|
||||
`zero_ex.contract_wrappers.exchange.Tuple0xbb41e5b3`:py:class:.
|
||||
"""
|
||||
|
||||
|
||||
class Order(Tuple0x260219a2):
|
||||
"""The `Order`:code: Solidity struct.
|
||||
|
||||
Also known as
|
||||
`zero_ex.contract_wrappers.exchange.Tuple0x260219a2`:py:class:.
|
||||
"""
|
||||
|
||||
|
||||
class MatchedFillResults(Tuple0x054ca44e):
|
||||
"""The `MatchedFillResults`:code: Solidity struct.
|
||||
|
||||
Also known as
|
||||
`zero_ex.contract_wrappers.exchange.Tuple0x054ca44e`:py:class:.
|
||||
"""
|
||||
|
||||
|
||||
class OrderInfo(Tuple0xb1e4a1ae):
|
||||
"""The `OrderInfo`:code: Solidity struct.
|
||||
|
||||
Also known as
|
||||
`zero_ex.contract_wrappers.exchange.Tuple0xb1e4a1ae`:py:class:.
|
||||
"""
|
||||
|
||||
|
||||
def order_to_jsdict(
|
||||
order: Order,
|
||||
exchange_address="0x0000000000000000000000000000000000000000",
|
||||
signature: str = None,
|
||||
) -> dict:
|
||||
"""Convert a Web3-compatible order struct to a JSON-schema-compatible dict.
|
||||
|
||||
More specifically, do explicit decoding for the `bytes`:code: fields, and
|
||||
convert numerics to strings.
|
||||
|
||||
>>> 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
|
||||
|
||||
jsdict["expirationTimeSeconds"] = str(order["expirationTimeSeconds"])
|
||||
|
||||
jsdict["makerAssetAmount"] = str(order["makerAssetAmount"])
|
||||
jsdict["takerAssetAmount"] = str(order["takerAssetAmount"])
|
||||
|
||||
jsdict["makerFee"] = str(order["makerFee"])
|
||||
jsdict["takerFee"] = str(order["takerFee"])
|
||||
|
||||
jsdict["salt"] = str(order["salt"])
|
||||
|
||||
if signature is not None:
|
||||
jsdict["signature"] = signature
|
||||
|
||||
assert_valid(jsdict, "/orderSchema")
|
||||
|
||||
return jsdict
|
||||
|
||||
|
||||
def jsdict_to_order(jsdict: dict) -> Order:
|
||||
r"""Convert a JSON-schema-compatible dict order to a Web3-compatible struct.
|
||||
|
||||
More specifically, do explicit encoding of the `bytes`:code: fields, and
|
||||
parse integers from strings.
|
||||
|
||||
>>> import pprint
|
||||
>>> pprint.pprint(jsdict_to_order(
|
||||
... {
|
||||
... '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"])
|
||||
)
|
||||
|
||||
order["makerAssetAmount"] = int(jsdict["makerAssetAmount"])
|
||||
order["takerAssetAmount"] = int(jsdict["takerAssetAmount"])
|
||||
|
||||
order["makerFee"] = int(jsdict["makerFee"])
|
||||
order["takerFee"] = int(jsdict["takerFee"])
|
||||
|
||||
order["expirationTimeSeconds"] = int(jsdict["expirationTimeSeconds"])
|
||||
|
||||
order["salt"] = int(jsdict["salt"])
|
||||
|
||||
del order["exchangeAddress"] # type: ignore
|
||||
# silence mypy pending release of
|
||||
# https://github.com/python/mypy/issues/3550
|
||||
|
||||
return order
|
||||
@@ -0,0 +1,47 @@
|
||||
"""Validate inputs to the Exchange contract."""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from web3.providers.base import BaseProvider
|
||||
|
||||
from zero_ex import json_schemas
|
||||
|
||||
from . import ExchangeValidatorBase
|
||||
from .types import order_to_jsdict
|
||||
|
||||
|
||||
class ExchangeValidator(ExchangeValidatorBase):
|
||||
"""Validate inputs to Exchange methods."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
provider: BaseProvider,
|
||||
contract_address: str,
|
||||
private_key: str = None,
|
||||
):
|
||||
"""Initialize the class."""
|
||||
super().__init__(provider, contract_address, private_key)
|
||||
self.contract_address = contract_address
|
||||
|
||||
def assert_valid(
|
||||
self, method_name: str, parameter_name: str, argument_value: Any
|
||||
) -> None:
|
||||
"""Raise an exception if method input is not valid.
|
||||
|
||||
:param method_name: Name of the method whose input is to be validated.
|
||||
:param parameter_name: Name of the parameter whose input is to be
|
||||
validated.
|
||||
:param argument_value: Value of argument to parameter to be validated.
|
||||
"""
|
||||
if parameter_name == "order":
|
||||
json_schemas.assert_valid(
|
||||
order_to_jsdict(argument_value, self.contract_address),
|
||||
"/orderSchema",
|
||||
)
|
||||
|
||||
if parameter_name == "orders":
|
||||
for order in argument_value:
|
||||
json_schemas.assert_valid(
|
||||
order_to_jsdict(order, self.contract_address),
|
||||
"/orderSchema",
|
||||
)
|
||||
@@ -1,327 +0,0 @@
|
||||
"""Wrapper for 0x Exchange smart contract."""
|
||||
|
||||
from typing import List, Optional, Tuple, Union
|
||||
from itertools import repeat
|
||||
|
||||
from eth_utils import remove_0x_prefix
|
||||
from hexbytes import HexBytes
|
||||
from web3.providers.base import BaseProvider
|
||||
from web3.datastructures import AttributeDict
|
||||
|
||||
from zero_ex.contract_addresses import NETWORK_TO_ADDRESSES, NetworkId
|
||||
from zero_ex.contract_artifacts import abi_by_name
|
||||
from zero_ex.json_schemas import assert_valid
|
||||
from zero_ex.order_utils import (
|
||||
Order,
|
||||
generate_order_hash_hex,
|
||||
is_valid_signature,
|
||||
order_to_jsdict,
|
||||
)
|
||||
|
||||
from ._base_contract_wrapper import BaseContractWrapper
|
||||
from .tx_params import TxParams
|
||||
|
||||
|
||||
class CancelDisallowedError(Exception):
|
||||
"""Exception for when Cancel is not allowed."""
|
||||
|
||||
|
||||
class Exchange(BaseContractWrapper):
|
||||
"""Wrapper class for 0x Exchange smart contract."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
provider: BaseProvider,
|
||||
contract_address: str,
|
||||
private_key: str = None,
|
||||
):
|
||||
"""Get an instance of the 0x Exchange smart contract wrapper.
|
||||
|
||||
:param provider: instance of :class:`web3.providers.base.BaseProvider`
|
||||
:param private_key: str of private_key
|
||||
"""
|
||||
super(Exchange, self).__init__(
|
||||
provider=provider,
|
||||
contract_address=contract_address,
|
||||
private_key=private_key,
|
||||
)
|
||||
self._web3_net = self._web3.net # pylint: disable=no-member
|
||||
self.address = NETWORK_TO_ADDRESSES[
|
||||
NetworkId(int(self._web3_net.version))
|
||||
].exchange
|
||||
self._exchange = self._contract_instance(
|
||||
address=self.address, abi=abi_by_name("Exchange")
|
||||
)
|
||||
|
||||
# pylint: disable=too-many-arguments
|
||||
def fill_order(
|
||||
self,
|
||||
order: Order,
|
||||
taker_amount: int,
|
||||
signature: str,
|
||||
tx_params: Optional[TxParams] = None,
|
||||
view_only: bool = False,
|
||||
) -> Union[HexBytes, bytes]:
|
||||
"""Fill a signed order with given amount of taker asset.
|
||||
|
||||
This is the most basic way to fill an order. All of the other methods
|
||||
call fillOrder under the hood with additional logic. This function
|
||||
will attempt to fill the amount specified by the caller. However, if
|
||||
the remaining fillable amount is less than the amount specified, the
|
||||
remaining amount will be filled. Partial fills are allowed when
|
||||
filling orders.
|
||||
|
||||
See the specification docs for `fillOrder
|
||||
<https://github.com/0xProject/0x-protocol-specification/blob/master
|
||||
/v2/v2-specification.md#fillorder>`_.
|
||||
|
||||
:param order: instance of :class:`zero_ex.order_utils.Order`
|
||||
:param taker_amount: integer taker amount in Wei (1 Wei is 10e-18 ETH)
|
||||
:param signature: str or hexstr or bytes of order hash signature
|
||||
:param tx_params: default None, :class:`TxParams` transaction params
|
||||
:param view_only: default False, boolean of whether to transact or
|
||||
view only
|
||||
|
||||
:returns: transaction hash
|
||||
"""
|
||||
assert_valid(order_to_jsdict(order, self.address), "/orderSchema")
|
||||
is_valid_signature(
|
||||
self._provider,
|
||||
generate_order_hash_hex(order, self.address),
|
||||
signature,
|
||||
order["makerAddress"],
|
||||
)
|
||||
# safeguard against fractional inputs
|
||||
taker_fill_amount = int(taker_amount)
|
||||
normalized_signature = bytes.fromhex(remove_0x_prefix(signature))
|
||||
func = self._exchange.functions.fillOrder(
|
||||
order, taker_fill_amount, normalized_signature
|
||||
)
|
||||
return self._invoke_function_call(
|
||||
func=func, tx_params=tx_params, view_only=view_only
|
||||
)
|
||||
|
||||
# pylint: disable=too-many-arguments
|
||||
def batch_fill_orders(
|
||||
self,
|
||||
orders: List[Order],
|
||||
taker_amounts: List[int],
|
||||
signatures: List[str],
|
||||
tx_params: Optional[TxParams] = None,
|
||||
view_only: bool = False,
|
||||
) -> Union[HexBytes, bytes]:
|
||||
"""Call `fillOrder` sequentially for orders, amounts and signatures.
|
||||
|
||||
:param orders: list of instances of :class:`zero_ex.order_utils.Order`
|
||||
:param taker_amounts: list of integer taker amounts in Wei
|
||||
:param signatures: list of str|hexstr|bytes of order hash signature
|
||||
:param tx_params: default None, :class:`TxParams` transaction params
|
||||
:param view_only: default False, boolean of whether to transact or
|
||||
view only
|
||||
|
||||
:returns: transaction hash
|
||||
"""
|
||||
order_jsdicts = [
|
||||
order_to_jsdict(order, self.address) for order in orders
|
||||
]
|
||||
map(assert_valid, order_jsdicts, repeat("/orderSchema"))
|
||||
# safeguard against fractional inputs
|
||||
normalized_fill_amounts = [
|
||||
int(taker_fill_amount) for taker_fill_amount in taker_amounts
|
||||
]
|
||||
normalized_signatures = [
|
||||
bytes.fromhex(remove_0x_prefix(signature))
|
||||
for signature in signatures
|
||||
]
|
||||
func = self._exchange.functions.batchFillOrders(
|
||||
orders, normalized_fill_amounts, normalized_signatures
|
||||
)
|
||||
return self._invoke_function_call(
|
||||
func=func, tx_params=tx_params, view_only=view_only
|
||||
)
|
||||
|
||||
# pylint: disable=too-many-arguments
|
||||
def fill_or_kill_order(
|
||||
self,
|
||||
order: Order,
|
||||
taker_amount: int,
|
||||
signature: str,
|
||||
tx_params: Optional[TxParams] = None,
|
||||
view_only: bool = False,
|
||||
) -> Union[HexBytes, bytes]:
|
||||
"""Attemp to `fillOrder`, revert if fill is not exact amount.
|
||||
|
||||
:param order: instance of :class:`zero_ex.order_utils.Order`
|
||||
:param taker_amount: integer taker amount in Wei (1 Wei is 10e-18 ETH)
|
||||
:param signature: str or hexstr or bytes of order hash signature
|
||||
:param tx_params: default None, :class:`TxParams` transaction params
|
||||
:param view_only: default False, boolean of whether to transact or
|
||||
view only
|
||||
|
||||
:returns: transaction hash
|
||||
"""
|
||||
assert_valid(order_to_jsdict(order, self.address), "/orderSchema")
|
||||
is_valid_signature(
|
||||
self._provider,
|
||||
generate_order_hash_hex(order, self.address),
|
||||
signature,
|
||||
order["makerAddress"],
|
||||
)
|
||||
# safeguard against fractional inputs
|
||||
taker_fill_amount = int(taker_amount)
|
||||
normalized_signature = bytes.fromhex(remove_0x_prefix(signature))
|
||||
func = self._exchange.functions.fillOrKillOrder(
|
||||
order, taker_fill_amount, normalized_signature
|
||||
)
|
||||
return self._invoke_function_call(
|
||||
func=func, tx_params=tx_params, view_only=view_only
|
||||
)
|
||||
|
||||
# pylint: disable=too-many-arguments
|
||||
def batch_fill_or_kill_orders(
|
||||
self,
|
||||
orders: List[Order],
|
||||
taker_amounts: List[int],
|
||||
signatures: List[str],
|
||||
tx_params: Optional[TxParams] = None,
|
||||
view_only: bool = False,
|
||||
) -> Union[HexBytes, bytes]:
|
||||
"""Call `fillOrKillOrder` sequentially for orders.
|
||||
|
||||
:param orders: list of instances of :class:`zero_ex.order_utils.Order`
|
||||
:param taker_amounts: list of integer taker amounts in Wei
|
||||
:param signatures: list of str|hexstr|bytes of order hash signature
|
||||
:param tx_params: default None, :class:`TxParams` transaction params
|
||||
:param view_only: default False, boolean of whether to transact or
|
||||
view only
|
||||
|
||||
:returns: transaction hash
|
||||
"""
|
||||
order_jsdicts = [
|
||||
order_to_jsdict(order, self.address) for order in orders
|
||||
]
|
||||
map(assert_valid, order_jsdicts, repeat("/orderSchema"))
|
||||
# safeguard against fractional inputs
|
||||
normalized_fill_amounts = [
|
||||
int(taker_fill_amount) for taker_fill_amount in taker_amounts
|
||||
]
|
||||
normalized_signatures = [
|
||||
bytes.fromhex(remove_0x_prefix(signature))
|
||||
for signature in signatures
|
||||
]
|
||||
func = self._exchange.functions.batchFillOrKillOrders(
|
||||
orders, normalized_fill_amounts, normalized_signatures
|
||||
)
|
||||
return self._invoke_function_call(
|
||||
func=func, tx_params=tx_params, view_only=view_only
|
||||
)
|
||||
|
||||
def cancel_order(
|
||||
self,
|
||||
order: Order,
|
||||
tx_params: Optional[TxParams] = None,
|
||||
view_only: bool = False,
|
||||
) -> Union[HexBytes, bytes]:
|
||||
"""Cancel an order.
|
||||
|
||||
See the specification docs for `cancelOrder
|
||||
<https://github.com/0xProject/0x-protocol-specification/blob/master
|
||||
/v2/v2-specification.md#cancelorder>`_.
|
||||
|
||||
:param order: instance of :class:`zero_ex.order_utils.Order`
|
||||
:param tx_params: default None, :class:`TxParams` transaction params
|
||||
:param view_only: default False, boolean of whether to transact or
|
||||
view only
|
||||
|
||||
:returns: transaction hash
|
||||
"""
|
||||
assert_valid(order_to_jsdict(order, self.address), "/orderSchema")
|
||||
maker_address = self._validate_and_checksum_address(
|
||||
order["makerAddress"]
|
||||
)
|
||||
|
||||
if tx_params and tx_params.from_:
|
||||
self._raise_if_maker_not_canceller(
|
||||
maker_address,
|
||||
self._validate_and_checksum_address(tx_params.from_),
|
||||
)
|
||||
elif self._web3_eth.defaultAccount:
|
||||
self._raise_if_maker_not_canceller(
|
||||
maker_address, self._web3_eth.defaultAccount
|
||||
)
|
||||
func = self._exchange.functions.cancelOrder(order)
|
||||
return self._invoke_function_call(
|
||||
func=func, tx_params=tx_params, view_only=view_only
|
||||
)
|
||||
|
||||
def batch_cancel_orders(
|
||||
self,
|
||||
orders: List[Order],
|
||||
tx_params: Optional[TxParams] = None,
|
||||
view_only: bool = False,
|
||||
) -> Union[HexBytes, bytes]:
|
||||
"""Call `cancelOrder` sequentially for provided orders.
|
||||
|
||||
:param orders: list of instance of :class:`zero_ex.order_utils.Order`
|
||||
:param tx_params: default None, :class:`TxParams` transaction params
|
||||
:param view_only: default False, boolean of whether to transact or
|
||||
view only
|
||||
|
||||
:returns: transaction hash
|
||||
"""
|
||||
order_jsdicts = [
|
||||
order_to_jsdict(order, self.address) for order in orders
|
||||
]
|
||||
map(assert_valid, order_jsdicts, repeat("/orderSchema"))
|
||||
maker_addresses = [
|
||||
self._validate_and_checksum_address(order["makerAddress"])
|
||||
for order in orders
|
||||
]
|
||||
if tx_params and tx_params.from_:
|
||||
map(
|
||||
self._raise_if_maker_not_canceller,
|
||||
maker_addresses,
|
||||
repeat(tx_params.from_),
|
||||
)
|
||||
elif self._web3_eth.defaultAccount:
|
||||
map(
|
||||
self._raise_if_maker_not_canceller,
|
||||
maker_addresses,
|
||||
repeat(self._web3_eth.defaultAccount),
|
||||
)
|
||||
func = self._exchange.functions.batchCancelOrders(orders)
|
||||
return self._invoke_function_call(
|
||||
func=func, tx_params=tx_params, view_only=view_only
|
||||
)
|
||||
|
||||
def get_fill_event(
|
||||
self, tx_hash: Union[HexBytes, bytes]
|
||||
) -> Tuple[AttributeDict]:
|
||||
"""Get fill event for a fill transaction.
|
||||
|
||||
:param tx_hash: `HexBytes` hash of fill transaction
|
||||
|
||||
:returns: fill event
|
||||
"""
|
||||
tx_receipt = self._web3_eth.getTransactionReceipt(tx_hash)
|
||||
return self._exchange.events.Fill().processReceipt(tx_receipt)
|
||||
|
||||
def get_cancel_event(
|
||||
self, tx_hash: Union[HexBytes, bytes]
|
||||
) -> Tuple[AttributeDict]:
|
||||
"""Get cancel event for cancel transaction.
|
||||
|
||||
:param tx_hash: `HexBytes` hash of cancel transaction
|
||||
"""
|
||||
tx_receipt = self._web3_eth.getTransactionReceipt(tx_hash)
|
||||
return self._exchange.events.Cancel().processReceipt(tx_receipt)
|
||||
|
||||
@staticmethod
|
||||
def _raise_if_maker_not_canceller(maker_address, canceller_address):
|
||||
"""Raise exception is maker is not same as canceller."""
|
||||
if maker_address != canceller_address:
|
||||
raise CancelDisallowedError(
|
||||
"Order with makerAddress {} can not be cancelled by {}".format(
|
||||
maker_address, canceller_address
|
||||
)
|
||||
)
|
||||
@@ -7,8 +7,9 @@ from eth_utils import remove_0x_prefix
|
||||
|
||||
from zero_ex.contract_addresses import NETWORK_TO_ADDRESSES, NetworkId
|
||||
from zero_ex.contract_wrappers import Exchange, TxParams
|
||||
from zero_ex.contract_wrappers.exchange.types import Order
|
||||
from zero_ex.json_schemas import assert_valid
|
||||
from zero_ex.order_utils import generate_order_hash_hex, Order, sign_hash
|
||||
from zero_ex.order_utils import generate_order_hash_hex, sign_hash_to_bytes
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
@@ -28,20 +29,20 @@ def create_test_order(
|
||||
taker_asset_data,
|
||||
):
|
||||
"""Create a test order."""
|
||||
order: Order = {
|
||||
"makerAddress": maker_address.lower(),
|
||||
"takerAddress": "0x0000000000000000000000000000000000000000",
|
||||
"feeRecipientAddress": "0x0000000000000000000000000000000000000000",
|
||||
"senderAddress": "0x0000000000000000000000000000000000000000",
|
||||
"makerAssetAmount": maker_asset_amount,
|
||||
"takerAssetAmount": taker_asset_amount,
|
||||
"makerFee": 0,
|
||||
"takerFee": 0,
|
||||
"expirationTimeSeconds": 100000000000000,
|
||||
"salt": random.randint(1, 1000000000),
|
||||
"makerAssetData": maker_asset_data,
|
||||
"takerAssetData": taker_asset_data,
|
||||
}
|
||||
order = Order(
|
||||
makerAddress=maker_address.lower(),
|
||||
takerAddress="0x0000000000000000000000000000000000000000",
|
||||
feeRecipientAddress="0x0000000000000000000000000000000000000000",
|
||||
senderAddress="0x0000000000000000000000000000000000000000",
|
||||
makerAssetAmount=maker_asset_amount,
|
||||
takerAssetAmount=taker_asset_amount,
|
||||
makerFee=0,
|
||||
takerFee=0,
|
||||
expirationTimeSeconds=100000000000000,
|
||||
salt=random.randint(1, 1000000000),
|
||||
makerAssetData=maker_asset_data,
|
||||
takerAssetData=taker_asset_data,
|
||||
)
|
||||
return order
|
||||
|
||||
|
||||
@@ -69,16 +70,16 @@ def test_exchange_wrapper__fill_order(
|
||||
"""Test filling an order."""
|
||||
taker = accounts[0]
|
||||
maker = accounts[1]
|
||||
exchange_address = exchange_wrapper.address
|
||||
exchange_address = exchange_wrapper.contract_address
|
||||
order = create_test_order(maker, 1, weth_asset_data, 1, weth_asset_data)
|
||||
order_hash = generate_order_hash_hex(
|
||||
order=order, exchange_address=exchange_address
|
||||
)
|
||||
order_signature = sign_hash(ganache_provider, maker, order_hash)
|
||||
order_signature = sign_hash_to_bytes(ganache_provider, maker, order_hash)
|
||||
|
||||
tx_hash = exchange_wrapper.fill_order(
|
||||
order=order,
|
||||
taker_amount=order["takerAssetAmount"],
|
||||
taker_asset_fill_amount=order["takerAssetAmount"],
|
||||
signature=order_signature,
|
||||
tx_params=TxParams(from_=taker),
|
||||
)
|
||||
@@ -98,7 +99,7 @@ def test_exchange_wrapper__batch_fill_orders(
|
||||
"""Test filling a batch of orders."""
|
||||
taker = accounts[0]
|
||||
maker = accounts[1]
|
||||
exchange_address = exchange_wrapper.address
|
||||
exchange_address = exchange_wrapper.contract_address
|
||||
orders = []
|
||||
order_1 = create_test_order(maker, 1, weth_asset_data, 1, weth_asset_data)
|
||||
order_2 = create_test_order(maker, 1, weth_asset_data, 1, weth_asset_data)
|
||||
@@ -109,13 +110,13 @@ def test_exchange_wrapper__batch_fill_orders(
|
||||
for order in orders
|
||||
]
|
||||
order_signatures = [
|
||||
sign_hash(ganache_provider, maker, order_hash)
|
||||
sign_hash_to_bytes(ganache_provider, maker, order_hash)
|
||||
for order_hash in order_hashes
|
||||
]
|
||||
taker_amounts = [order["takerAssetAmount"] for order in orders]
|
||||
tx_hash = exchange_wrapper.batch_fill_orders(
|
||||
orders=orders,
|
||||
taker_amounts=taker_amounts,
|
||||
taker_asset_fill_amounts=taker_amounts,
|
||||
signatures=order_signatures,
|
||||
tx_params=TxParams(from_=taker),
|
||||
)
|
||||
|
||||
@@ -1,13 +1,3 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env bash
|
||||
|
||||
"""Script to install all packages in editable mode (pip install -e .)."""
|
||||
|
||||
from os import path
|
||||
import subprocess
|
||||
|
||||
# install all packages
|
||||
subprocess.check_call(
|
||||
(
|
||||
path.join(".", "cmd_pkgs_in_dep_order.py") + " pip install -e .[dev]"
|
||||
).split()
|
||||
)
|
||||
./parallel pip install -e .[dev]
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import distutils.command.build_py
|
||||
from distutils.command.clean import clean
|
||||
import subprocess # nosec
|
||||
from shutil import rmtree
|
||||
from shutil import copytree, rmtree
|
||||
from os import environ, path
|
||||
from sys import argv
|
||||
|
||||
@@ -13,6 +13,26 @@ from setuptools import find_packages, setup
|
||||
from setuptools.command.test import test as TestCommand
|
||||
|
||||
|
||||
class PreInstallCommand(distutils.command.build_py.build_py):
|
||||
"""Custom setuptools command class for pulling in schemas."""
|
||||
|
||||
description = "Pull in the schemas that live in the TypeScript package."
|
||||
|
||||
def run(self):
|
||||
"""Copy files from TS area to local src."""
|
||||
pkgdir = path.dirname(path.realpath(argv[0]))
|
||||
rmtree(
|
||||
path.join(pkgdir, "src", "zero_ex", "json_schemas", "schemas"),
|
||||
ignore_errors=True,
|
||||
)
|
||||
copytree(
|
||||
path.join(
|
||||
pkgdir, "..", "..", "packages", "json-schemas", "schemas"
|
||||
),
|
||||
path.join(pkgdir, "src", "zero_ex", "json_schemas", "schemas"),
|
||||
)
|
||||
|
||||
|
||||
class TestCommandExtension(TestCommand):
|
||||
"""Run pytest tests."""
|
||||
|
||||
@@ -41,15 +61,6 @@ class LintCommand(distutils.command.build_py.build_py):
|
||||
"mypy src test setup.py".split(),
|
||||
# security issue checker:
|
||||
"bandit -r src ./setup.py".split(),
|
||||
# HACK: ensure json schemas don't differ from the authoritative
|
||||
# copies: this is a hack. ideally we would symlink to the
|
||||
# authoritative copies, but a problem with setuptools is preventing
|
||||
# it from following symlinks when gathering package_data. see
|
||||
# https://github.com/pypa/setuptools/issues/415.
|
||||
(
|
||||
"diff src/zero_ex/json_schemas/schemas"
|
||||
+ " ../../packages/json-schemas/schemas"
|
||||
).split(),
|
||||
# general linter:
|
||||
"pylint src test setup.py".split(),
|
||||
# pylint takes relatively long to run, so it runs last, to enable
|
||||
@@ -138,6 +149,7 @@ setup(
|
||||
author="F. Eugene Aumson",
|
||||
author_email="feuGeneA@users.noreply.github.com",
|
||||
cmdclass={
|
||||
"pre_install": PreInstallCommand,
|
||||
"clean": CleanCommandExtension,
|
||||
"lint": LintCommand,
|
||||
"test": TestCommandExtension,
|
||||
@@ -196,6 +208,7 @@ setup(
|
||||
"build_sphinx": {
|
||||
"source_dir": ("setup.py", "src"),
|
||||
"build_dir": ("setup.py", "build/docs"),
|
||||
"warning_is_error": ("setup.py", "true"),
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@@ -61,9 +61,10 @@ def assert_valid(data: Mapping, schema_id: str) -> None:
|
||||
|
||||
>>> from zero_ex.json_schemas import assert_valid
|
||||
>>> from zero_ex.contract_addresses import NETWORK_TO_ADDRESSES, NetworkId
|
||||
>>> from zero_ex.order_utils import (
|
||||
... asset_data_utils, Order, order_to_jsdict
|
||||
>>> from zero_ex.contract_wrappers.exchange.types import (
|
||||
... Order, order_to_jsdict
|
||||
... )
|
||||
>>> from zero_ex.order_utils import asset_data_utils
|
||||
>>> from eth_utils import remove_0x_prefix
|
||||
>>> import random
|
||||
>>> from datetime import datetime, timedelta
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"id": "/addressSchema",
|
||||
"type": "string",
|
||||
"pattern": "^0x[0-9a-f]{40}$"
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"id": "/AssetPairsRequestOptsSchema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"assetDataA": { "$ref": "/hexSchema" },
|
||||
"assetDataB": { "$ref": "/hexSchema" }
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"id": "/blockParamSchema",
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"enum": ["latest", "earliest", "pending"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"id": "/blockRangeSchema",
|
||||
"properties": {
|
||||
"fromBlock": { "$ref": "/blockParamSchema" },
|
||||
"toBlock": { "$ref": "/blockParamSchema" }
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
{
|
||||
"id": "/callDataSchema",
|
||||
"properties": {
|
||||
"from": { "$ref": "/addressSchema" },
|
||||
"to": { "$ref": "/addressSchema" },
|
||||
"value": {
|
||||
"oneOf": [{ "$ref": "/numberSchema" }, { "$ref": "/jsNumberSchema" }]
|
||||
},
|
||||
"gas": {
|
||||
"oneOf": [{ "$ref": "/numberSchema" }, { "$ref": "/jsNumberSchema" }]
|
||||
},
|
||||
"gasPrice": {
|
||||
"oneOf": [{ "$ref": "/numberSchema" }, { "$ref": "/jsNumberSchema" }]
|
||||
},
|
||||
"data": {
|
||||
"type": "string",
|
||||
"pattern": "^0x[0-9a-f]*$"
|
||||
},
|
||||
"nonce": {
|
||||
"type": "number",
|
||||
"minimum": 0
|
||||
}
|
||||
},
|
||||
"required": [],
|
||||
"type": "object",
|
||||
"additionalProperties": false
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"id": "/ecSignatureParameterSchema",
|
||||
"type": "string",
|
||||
"pattern": "^0[xX][0-9A-Fa-f]{64}$"
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"id": "/ecSignatureSchema",
|
||||
"properties": {
|
||||
"v": {
|
||||
"type": "number",
|
||||
"minimum": 27,
|
||||
"maximum": 28
|
||||
},
|
||||
"r": { "$ref": "/ecSignatureParameterSchema" },
|
||||
"s": { "$ref": "/ecSignatureParameterSchema" }
|
||||
},
|
||||
"required": ["v", "r", "s"],
|
||||
"type": "object"
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
{
|
||||
"id": "/eip712TypedDataSchema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"types": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"EIP712Domain": { "type": "array" }
|
||||
},
|
||||
"additionalProperties": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "type": "string" },
|
||||
"type": { "type": "string" }
|
||||
},
|
||||
"required": ["name", "type"]
|
||||
}
|
||||
},
|
||||
"required": ["EIP712Domain"]
|
||||
},
|
||||
"primaryType": { "type": "string" },
|
||||
"domain": { "type": "object" },
|
||||
"message": { "type": "object" }
|
||||
},
|
||||
"required": ["types", "primaryType", "domain", "message"]
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"id": "/hexSchema",
|
||||
"type": "string",
|
||||
"pattern": "^0x(([0-9a-f][0-9a-f])+)?$"
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"id": "/indexFilterValuesSchema",
|
||||
"additionalProperties": {
|
||||
"oneOf": [{ "$ref": "/numberSchema" }, { "$ref": "/addressSchema" }, { "$ref": "/orderHashSchema" }]
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"id": "/jsNumberSchema",
|
||||
"type": "number",
|
||||
"minimum": 0
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"id": "/numberSchema",
|
||||
"type": "string",
|
||||
"pattern": "^\\d+(\\.\\d+)?$"
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"id": "/orderCancellationRequestsSchema",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"properties": {
|
||||
"order": { "$ref": "/orderSchema" },
|
||||
"takerTokenCancelAmount": { "$ref": "/wholeNumberSchema" }
|
||||
},
|
||||
"required": ["order", "takerTokenCancelAmount"],
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"id": "/OrderConfigRequestSchema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"makerAddress": { "$ref": "/addressSchema" },
|
||||
"takerAddress": { "$ref": "/addressSchema" },
|
||||
"makerAssetAmount": { "$ref": "/wholeNumberSchema" },
|
||||
"takerAssetAmount": { "$ref": "/wholeNumberSchema" },
|
||||
"makerAssetData": { "$ref": "/hexSchema" },
|
||||
"takerAssetData": { "$ref": "/hexSchema" },
|
||||
"exchangeAddress": { "$ref": "/addressSchema" },
|
||||
"expirationTimeSeconds": { "$ref": "/wholeNumberSchema" }
|
||||
},
|
||||
"required": [
|
||||
"makerAddress",
|
||||
"takerAddress",
|
||||
"makerAssetAmount",
|
||||
"takerAssetAmount",
|
||||
"makerAssetData",
|
||||
"takerAssetData",
|
||||
"exchangeAddress",
|
||||
"expirationTimeSeconds"
|
||||
]
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"id": "/orderFillOrKillRequestsSchema",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"properties": {
|
||||
"signedOrder": { "$ref": "/signedOrderSchema" },
|
||||
"fillTakerAmount": { "$ref": "/wholeNumberSchema" }
|
||||
},
|
||||
"required": ["signedOrder", "fillTakerAmount"],
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"id": "/orderFillRequestsSchema",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"properties": {
|
||||
"signedOrder": { "$ref": "/signedOrderSchema" },
|
||||
"takerTokenFillAmount": { "$ref": "/wholeNumberSchema" }
|
||||
},
|
||||
"required": ["signedOrder", "takerTokenFillAmount"],
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"id": "/orderHashSchema",
|
||||
"type": "string",
|
||||
"pattern": "^0x[0-9a-fA-F]{64}$"
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
{
|
||||
"id": "/orderSchema",
|
||||
"properties": {
|
||||
"makerAddress": { "$ref": "/addressSchema" },
|
||||
"takerAddress": { "$ref": "/addressSchema" },
|
||||
"makerFee": { "$ref": "/wholeNumberSchema" },
|
||||
"takerFee": { "$ref": "/wholeNumberSchema" },
|
||||
"senderAddress": { "$ref": "/addressSchema" },
|
||||
"makerAssetAmount": { "$ref": "/wholeNumberSchema" },
|
||||
"takerAssetAmount": { "$ref": "/wholeNumberSchema" },
|
||||
"makerAssetData": { "$ref": "/hexSchema" },
|
||||
"takerAssetData": { "$ref": "/hexSchema" },
|
||||
"salt": { "$ref": "/wholeNumberSchema" },
|
||||
"exchangeAddress": { "$ref": "/addressSchema" },
|
||||
"feeRecipientAddress": { "$ref": "/addressSchema" },
|
||||
"expirationTimeSeconds": { "$ref": "/wholeNumberSchema" }
|
||||
},
|
||||
"required": [
|
||||
"makerAddress",
|
||||
"takerAddress",
|
||||
"makerFee",
|
||||
"takerFee",
|
||||
"senderAddress",
|
||||
"makerAssetAmount",
|
||||
"takerAssetAmount",
|
||||
"makerAssetData",
|
||||
"takerAssetData",
|
||||
"salt",
|
||||
"exchangeAddress",
|
||||
"feeRecipientAddress",
|
||||
"expirationTimeSeconds"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
{
|
||||
"id": "/orderWatcherWebSocketRequestSchema",
|
||||
"type": "object",
|
||||
"definitions": {
|
||||
"signedOrderParam": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"signedOrder": { "$ref": "/signedOrderSchema" }
|
||||
},
|
||||
"required": ["signedOrder"]
|
||||
},
|
||||
"orderHashParam": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"orderHash": { "$ref": "/hexSchema" }
|
||||
},
|
||||
"required": ["orderHash"]
|
||||
}
|
||||
},
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": { "type": "number" },
|
||||
"jsonrpc": { "type": "string" },
|
||||
"method": { "enum": ["ADD_ORDER"] },
|
||||
"params": { "$ref": "#/definitions/signedOrderParam" }
|
||||
},
|
||||
"required": ["id", "jsonrpc", "method", "params"]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": { "type": "number" },
|
||||
"jsonrpc": { "type": "string" },
|
||||
"method": { "enum": ["REMOVE_ORDER"] },
|
||||
"params": { "$ref": "#/definitions/orderHashParam" }
|
||||
},
|
||||
"required": ["id", "jsonrpc", "method", "params"]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": { "type": "number" },
|
||||
"jsonrpc": { "type": "string" },
|
||||
"method": { "enum": ["GET_STATS"] },
|
||||
"params": {}
|
||||
},
|
||||
"required": ["id", "jsonrpc", "method"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"id": "/orderWatcherWebSocketUtf8MessageSchema",
|
||||
"properties": {
|
||||
"utf8Data": { "type": "string" }
|
||||
},
|
||||
"required": [
|
||||
"utf8Data"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"id": "/OrderbookRequestSchema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"baseAssetData": { "$ref": "/hexSchema" },
|
||||
"quoteAssetData": { "$ref": "/hexSchema" }
|
||||
},
|
||||
"required": ["baseAssetData", "quoteAssetData"]
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"id": "/OrdersRequestOptsSchema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"makerAssetProxyId": { "$ref": "/hexSchema" },
|
||||
"takerAssetProxyId": { "$ref": "/hexSchema" },
|
||||
"makerAssetAddress": { "$ref": "/addressSchema" },
|
||||
"takerAssetAddress": { "$ref": "/addressSchema" },
|
||||
"exchangeAddress": { "$ref": "/addressSchema" },
|
||||
"senderAddress": { "$ref": "/addressSchema" },
|
||||
"makerAssetData": { "$ref": "/hexSchema" },
|
||||
"takerAssetData": { "$ref": "/hexSchema" },
|
||||
"traderAssetData": { "$ref": "/hexSchema" },
|
||||
"makerAddress": { "$ref": "/addressSchema" },
|
||||
"takerAddress": { "$ref": "/addressSchema" },
|
||||
"traderAddress": { "$ref": "/addressSchema" },
|
||||
"feeRecipientAddress": { "$ref": "/addressSchema" }
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"id": "/ordersSchema",
|
||||
"type": "array",
|
||||
"items": { "$ref": "/orderSchema" }
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"id": "/PagedRequestOptsSchema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"page": { "type": "number" },
|
||||
"perPage": { "type": "number" }
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"id": "/paginatedCollectionSchema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"total": { "type": "number" },
|
||||
"perPage": { "type": "number" },
|
||||
"page": { "type": "number" }
|
||||
},
|
||||
"required": ["total", "perPage", "page"]
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"id": "/relayerApiAssetDataPairsResponseSchema",
|
||||
"type": "object",
|
||||
"allOf": [
|
||||
{ "$ref": "/paginatedCollectionSchema" },
|
||||
{
|
||||
"properties": {
|
||||
"records": { "$ref": "/relayerApiAssetDataPairsSchema" }
|
||||
},
|
||||
"required": ["records"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"id": "/relayerApiAssetDataPairsSchema",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"properties": {
|
||||
"assetDataA": { "$ref": "/relayerApiAssetDataTradeInfoSchema" },
|
||||
"assetDataB": { "$ref": "/relayerApiAssetDataTradeInfoSchema" }
|
||||
},
|
||||
"required": ["assetDataA", "assetDataB"],
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"id": "/relayerApiAssetDataTradeInfoSchema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"assetData": { "$ref": "/hexSchema" },
|
||||
"minAmount": { "$ref": "/wholeNumberSchema" },
|
||||
"maxAmount": { "$ref": "/wholeNumberSchema" },
|
||||
"precision": { "type": "number" }
|
||||
},
|
||||
"required": ["assetData"]
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"id": "/relayerApiErrorResponseSchema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": { "type": "integer", "minimum": 100, "maximum": 103 },
|
||||
"reason": { "type": "string" },
|
||||
"validationErrors": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"field": { "type": "string" },
|
||||
"code": { "type": "integer", "minimum": 1000, "maximum": 1006 },
|
||||
"reason": { "type": "string" }
|
||||
},
|
||||
"required": ["field", "code", "reason"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["code", "reason"]
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"id": "/relayerApiFeeRecipientsResponseSchema",
|
||||
"type": "object",
|
||||
"allOf": [
|
||||
{ "$ref": "/paginatedCollectionSchema" },
|
||||
{
|
||||
"properties": {
|
||||
"records": {
|
||||
"type": "array",
|
||||
"items": { "$ref": "/addressSchema" }
|
||||
}
|
||||
},
|
||||
"required": ["records"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"id": "/relayerApiOrderConfigPayloadSchema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"makerAddress": { "$ref": "/addressSchema" },
|
||||
"takerAddress": { "$ref": "/addressSchema" },
|
||||
"makerAssetAmount": { "$ref": "/wholeNumberSchema" },
|
||||
"takerAssetAmount": { "$ref": "/wholeNumberSchema" },
|
||||
"makerAssetData": { "$ref": "/hexSchema" },
|
||||
"takerAssetData": { "$ref": "/hexSchema" },
|
||||
"exchangeAddress": { "$ref": "/addressSchema" },
|
||||
"expirationTimeSeconds": { "$ref": "/wholeNumberSchema" }
|
||||
},
|
||||
"required": [
|
||||
"makerAddress",
|
||||
"takerAddress",
|
||||
"makerAssetAmount",
|
||||
"takerAssetAmount",
|
||||
"makerAssetData",
|
||||
"takerAssetData",
|
||||
"exchangeAddress",
|
||||
"expirationTimeSeconds"
|
||||
]
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"id": "/relayerApiOrderConfigResponseSchema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"makerFee": { "$ref": "/wholeNumberSchema" },
|
||||
"takerFee": { "$ref": "/wholeNumberSchema" },
|
||||
"feeRecipientAddress": { "$ref": "/addressSchema" },
|
||||
"senderAddress": { "$ref": "/addressSchema" }
|
||||
},
|
||||
"required": ["makerFee", "takerFee", "feeRecipientAddress", "senderAddress"]
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"id": "/relayerApiOrderSchema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"order": { "$ref": "/orderSchema" },
|
||||
"metaData": { "type": "object" }
|
||||
},
|
||||
"required": ["order", "metaData"]
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"id": "/relayerApiOrderbookResponseSchema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"bids": { "$ref": "/relayerApiOrdersResponseSchema" },
|
||||
"asks": { "$ref": "/relayerApiOrdersResponseSchema" }
|
||||
},
|
||||
"required": ["bids", "asks"]
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"id": "/relayerApiOrdersChannelSubscribePayloadSchema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"makerAssetProxyId": { "$ref": "/hexSchema" },
|
||||
"takerAssetProxyId": { "$ref": "/hexSchema" },
|
||||
"networkId": { "type": "number" },
|
||||
"makerAssetAddress": { "$ref": "/addressSchema" },
|
||||
"takerAssetAddress": { "$ref": "/addressSchema" },
|
||||
"makerAssetData": { "$ref": "/hexSchema" },
|
||||
"takerAssetData": { "$ref": "/hexSchema" },
|
||||
"traderAssetData": { "$ref": "/hexSchema" }
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"id": "/relayerApiOrdersChannelSubscribeSchema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": { "enum": ["subscribe"] },
|
||||
"channel": { "enum": ["orders"] },
|
||||
"requestId": { "type": "string" },
|
||||
"payload": { "$ref": "/relayerApiOrdersChannelSubscribePayloadSchema" }
|
||||
},
|
||||
"required": ["type", "channel", "requestId"]
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"id": "/relayerApiOrdersChannelUpdateSchema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": { "enum": ["update"] },
|
||||
"channel": { "enum": ["orders"] },
|
||||
"requestId": { "type": "string" },
|
||||
"payload": { "$ref": "/relayerApiOrdersSchema" }
|
||||
},
|
||||
"required": ["type", "channel", "requestId"]
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"id": "/relayerApiOrdersResponseSchema",
|
||||
"type": "object",
|
||||
"allOf": [
|
||||
{ "$ref": "/paginatedCollectionSchema" },
|
||||
{
|
||||
"properties": {
|
||||
"records": { "$ref": "/relayerApiOrdersSchema" }
|
||||
},
|
||||
"required": ["records"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"id": "/relayerApiOrdersSchema",
|
||||
"type": "array",
|
||||
"items": { "$ref": "/relayerApiOrderSchema" }
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"id": "/RequestOptsSchema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"networkId": { "type": "number" }
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"id": "/signedOrderSchema",
|
||||
"allOf": [
|
||||
{ "$ref": "/orderSchema" },
|
||||
{
|
||||
"properties": {
|
||||
"signature": { "$ref": "/hexSchema" }
|
||||
},
|
||||
"required": ["signature"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"id": "/signedOrdersSchema",
|
||||
"type": "array",
|
||||
"items": { "$ref": "/signedOrderSchema" }
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"id": "/tokenSchema",
|
||||
"properties": {
|
||||
"name": { "type": "string" },
|
||||
"symbol": { "type": "string" },
|
||||
"decimals": { "type": "number" },
|
||||
"address": { "$ref": "/addressSchema" }
|
||||
},
|
||||
"required": ["name", "symbol", "decimals", "address"],
|
||||
"type": "object"
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
{
|
||||
"id": "/txDataSchema",
|
||||
"properties": {
|
||||
"from": { "$ref": "/addressSchema" },
|
||||
"to": { "$ref": "/addressSchema" },
|
||||
"value": {
|
||||
"oneOf": [{ "$ref": "/numberSchema" }, { "$ref": "/jsNumberSchema" }]
|
||||
},
|
||||
"gas": {
|
||||
"oneOf": [{ "$ref": "/numberSchema" }, { "$ref": "/jsNumberSchema" }]
|
||||
},
|
||||
"gasPrice": {
|
||||
"oneOf": [{ "$ref": "/numberSchema" }, { "$ref": "/jsNumberSchema" }]
|
||||
},
|
||||
"data": {
|
||||
"type": "string",
|
||||
"pattern": "^0x[0-9a-f]*$"
|
||||
},
|
||||
"nonce": {
|
||||
"type": "number",
|
||||
"minimum": 0
|
||||
}
|
||||
},
|
||||
"required": ["from"],
|
||||
"type": "object"
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"id": "/wholeNumberSchema",
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"pattern": "^\\d+$"
|
||||
},
|
||||
{
|
||||
"type": "integer"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"id": "/zeroExTransactionSchema",
|
||||
"properties": {
|
||||
"verifyingContractAddress": { "$ref": "/addressSchema" },
|
||||
"data": { "$ref": "/hexSchema" },
|
||||
"signerAddress": { "$ref": "/addressSchema" },
|
||||
"salt": { "$ref": "/wholeNumberSchema" }
|
||||
},
|
||||
"required": ["verifyingContractAddress", "data", "salt", "signerAddress"],
|
||||
"type": "object"
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
"""Tests of zero_ex.json_schemas"""
|
||||
|
||||
|
||||
from zero_ex.order_utils import make_empty_order, order_to_jsdict
|
||||
from zero_ex.json_schemas import _LOCAL_RESOLVER, assert_valid
|
||||
|
||||
|
||||
@@ -34,7 +33,7 @@ def test_assert_valid_caches_resources():
|
||||
"""
|
||||
_LOCAL_RESOLVER._remote_cache.cache_clear() # pylint: disable=W0212
|
||||
|
||||
assert_valid(order_to_jsdict(make_empty_order()), "/orderSchema")
|
||||
assert_valid(EMPTY_ORDER, "/orderSchema")
|
||||
cache_info = (
|
||||
_LOCAL_RESOLVER._remote_cache.cache_info() # pylint: disable=W0212
|
||||
)
|
||||
|
||||
@@ -1,13 +1,3 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env bash
|
||||
|
||||
"""Script to run linters against local copy of all components."""
|
||||
|
||||
from os import path
|
||||
import subprocess
|
||||
|
||||
subprocess.check_call(
|
||||
(
|
||||
f"{path.join('.', 'cmd_pkgs_in_dep_order.py')}"
|
||||
+ f" {path.join('.', 'setup.py')} lint"
|
||||
).split()
|
||||
)
|
||||
./parallel ./setup.py lint
|
||||
|
||||
@@ -212,6 +212,7 @@ setup(
|
||||
"build_sphinx": {
|
||||
"source_dir": ("setup.py", "src"),
|
||||
"build_dir": ("setup.py", "build/docs"),
|
||||
"warning_is_error": ("setup.py", "true"),
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@@ -10,7 +10,6 @@ from zero_ex.middlewares.local_message_signer import (
|
||||
from zero_ex.order_utils import (
|
||||
generate_order_hash_hex,
|
||||
is_valid_signature,
|
||||
make_empty_order,
|
||||
sign_hash,
|
||||
)
|
||||
|
||||
@@ -31,7 +30,20 @@ def test_local_message_signer__sign_order():
|
||||
web3_instance.middleware_stack.add(
|
||||
construct_local_message_signer(private_key)
|
||||
)
|
||||
order = make_empty_order()
|
||||
order = {
|
||||
"makerAddress": "0x0000000000000000000000000000000000000000",
|
||||
"takerAddress": "0x0000000000000000000000000000000000000000",
|
||||
"senderAddress": "0x0000000000000000000000000000000000000000",
|
||||
"feeRecipientAddress": "0x0000000000000000000000000000000000000000",
|
||||
"makerAssetData": (b"\x00") * 20,
|
||||
"takerAssetData": (b"\x00") * 20,
|
||||
"salt": 0,
|
||||
"makerFee": 0,
|
||||
"takerFee": 0,
|
||||
"makerAssetAmount": 0,
|
||||
"takerAssetAmount": 0,
|
||||
"expirationTimeSeconds": 0,
|
||||
}
|
||||
order_hash = generate_order_hash_hex(order, exchange)
|
||||
signature = sign_hash(ganache, to_checksum_address(address), order_hash)
|
||||
assert signature == expected_signature
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# Changelog
|
||||
|
||||
## 1.1.1 - 2019-02-26
|
||||
## 3.0.0 - TBD
|
||||
|
||||
- Replaced dependency on web3 with dependency on 0x-web3, to ease coexistence of those two packages.
|
||||
- Major breaking changes: removal of definitions for Order, OrderInfo, order_to_jsdict, jsdict_to_order, all of which have been moved to contract_wrappers.exchange.types; removal of signature validation.
|
||||
|
||||
## 2.0.0 - 2019-04-30
|
||||
|
||||
@@ -10,3 +10,7 @@
|
||||
- Deprecated methods `encode_erc20_asset_data()` and `encode_erc721_asset_data()`, in favor of new methods `encode_erc20()` and `encode_erc721()`. The old methods return a string, which is less than convenient for building orders using the provided `Order` type, which expects asset data to be `bytes`. The new methods return `bytes`.
|
||||
- Expanded documentation.
|
||||
- Stopped using deprecated web3.py interface `contract.call()` in favor of `contract.functions.X.call()`. This provides compatibility with the upcoming 5.x release of web3.py, and it also eliminates some runtime warning messages.
|
||||
|
||||
## 1.1.1 - 2019-02-26
|
||||
|
||||
- Replaced dependency on web3 with dependency on 0x-web3, to ease coexistence of those two packages.
|
||||
|
||||
@@ -229,6 +229,7 @@ setup(
|
||||
"build_sphinx": {
|
||||
"source_dir": ("setup.py", "src"),
|
||||
"build_dir": ("setup.py", "build/docs"),
|
||||
"warning_is_error": ("setup.py", "true"),
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@@ -10,10 +10,6 @@ Python zero_ex.order_utils
|
||||
.. automodule:: zero_ex.order_utils
|
||||
:members:
|
||||
|
||||
.. autoclass:: zero_ex.order_utils.Order
|
||||
|
||||
See source for class properties. Sphinx is having problems generating docs for ``TypedDict`` declarations; pull requests welcome.
|
||||
|
||||
zero_ex.order_utils.asset_data_utils
|
||||
------------------------------------
|
||||
|
||||
|
||||
@@ -14,58 +14,11 @@ 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:2.2.2`:code:.
|
||||
|
||||
Constructing an order
|
||||
---------------------
|
||||
"""
|
||||
|
||||
Here is a short demonstration on how to create a 0x order.
|
||||
|
||||
>>> from zero_ex.contract_addresses import NETWORK_TO_ADDRESSES, NetworkId
|
||||
>>> from zero_ex.order_utils import asset_data_utils, Order
|
||||
>>> from datetime import datetime, timedelta
|
||||
>>> import random
|
||||
>>> my_address = "0x5409ed021d9299bf6814279a6a1411a7e866a631"
|
||||
>>> example_order = Order(
|
||||
... makerAddress=my_address,
|
||||
... takerAddress="0x0000000000000000000000000000000000000000",
|
||||
... exchangeAddress=NETWORK_TO_ADDRESSES[NetworkId.MAINNET].exchange,
|
||||
... senderAddress="0x0000000000000000000000000000000000000000",
|
||||
... feeRecipientAddress="0x0000000000000000000000000000000000000000",
|
||||
... makerAssetData=asset_data_utils.encode_erc20(
|
||||
... NETWORK_TO_ADDRESSES[NetworkId.MAINNET].ether_token
|
||||
... ),
|
||||
... takerAssetData=asset_data_utils.encode_erc20(
|
||||
... NETWORK_TO_ADDRESSES[NetworkId.MAINNET].zrx_token
|
||||
... ),
|
||||
... salt=random.randint(1, 100000000000000000),
|
||||
... makerFee=0,
|
||||
... takerFee=0,
|
||||
... makerAssetAmount=1 * 10 ** 18, # Convert token amount to base unit with 18 decimals
|
||||
... takerAssetAmount=500 * 10 ** 18, # Convert token amount to base unit with 18 decimals
|
||||
... expirationTimeSeconds=round(
|
||||
... (datetime.utcnow() + timedelta(days=1)).timestamp()
|
||||
... )
|
||||
... )
|
||||
>>> import pprint
|
||||
>>> pprint.pprint(example_order)
|
||||
{'exchangeAddress': '0x...',
|
||||
'expirationTimeSeconds': ...,
|
||||
'feeRecipientAddress': '0x0000000000000000000000000000000000000000',
|
||||
'makerAddress': '0x...',
|
||||
'makerAssetAmount': 1000000000000000000,
|
||||
'makerAssetData': b...,
|
||||
'makerFee': 0,
|
||||
'salt': ...,
|
||||
'senderAddress': '0x0000000000000000000000000000000000000000',
|
||||
'takerAddress': '0x0000000000000000000000000000000000000000',
|
||||
'takerAssetAmount': 500000000000000000000,
|
||||
'takerAssetData': b...,
|
||||
'takerFee': 0}
|
||||
""" # noqa E501
|
||||
|
||||
from copy import copy
|
||||
from enum import auto, Enum
|
||||
import json
|
||||
from typing import cast, Dict, NamedTuple, Tuple
|
||||
from typing import Tuple
|
||||
from pkg_resources import resource_string
|
||||
|
||||
from mypy_extensions import TypedDict
|
||||
@@ -78,6 +31,7 @@ from web3.utils import datatypes
|
||||
|
||||
from zero_ex.contract_addresses import NETWORK_TO_ADDRESSES, NetworkId
|
||||
import zero_ex.contract_artifacts
|
||||
from zero_ex.contract_wrappers.exchange.types import Order, order_to_jsdict
|
||||
from zero_ex.dev_utils.type_assertions import (
|
||||
assert_is_address,
|
||||
assert_is_hex_string,
|
||||
@@ -133,238 +87,6 @@ class _Constants:
|
||||
N_SIGNATURE_TYPES = auto()
|
||||
|
||||
|
||||
class Order(TypedDict): # pylint: disable=too-many-instance-attributes
|
||||
"""A Web3-compatible representation of the Exchange.Order struct.
|
||||
|
||||
>>> from zero_ex.order_utils import asset_data_utils
|
||||
>>> from eth_utils import remove_0x_prefix
|
||||
>>> from datetime import datetime, timedelta
|
||||
>>> import random
|
||||
>>> order = Order(
|
||||
... makerAddress=maker_address,
|
||||
... takerAddress='0x0000000000000000000000000000000000000000',
|
||||
... senderAddress='0x0000000000000000000000000000000000000000',
|
||||
... feeRecipientAddress='0x0000000000000000000000000000000000000000',
|
||||
... makerAssetData=asset_data_utils.encode_erc20(zrx_address),
|
||||
... takerAssetData=asset_data_utils.encode_erc20(weth_address),
|
||||
... salt=random.randint(1, 100000000000000000),
|
||||
... makerFee=0,
|
||||
... takerFee=0,
|
||||
... makerAssetAmount=1,
|
||||
... takerAssetAmount=1,
|
||||
... expirationTimeSeconds=round(
|
||||
... (datetime.utcnow() + timedelta(days=1)).timestamp()
|
||||
... )
|
||||
... )
|
||||
"""
|
||||
|
||||
makerAddress: str
|
||||
"""Address that created the order."""
|
||||
|
||||
takerAddress: str
|
||||
"""Address that is allowed to fill the order.
|
||||
|
||||
If set to 0, any address is allowed to fill the order.
|
||||
"""
|
||||
|
||||
feeRecipientAddress: str
|
||||
"""Address that will recieve fees when order is filled."""
|
||||
|
||||
senderAddress: str
|
||||
"""Address that is allowed to call Exchange contract methods that affect
|
||||
this order. If set to 0, any address is allowed to call these methods.
|
||||
"""
|
||||
|
||||
makerAssetAmount: int
|
||||
"""Amount of makerAsset being offered by maker. Must be greater than 0."""
|
||||
|
||||
takerAssetAmount: int
|
||||
"""Amount of takerAsset being bid on by maker. Must be greater than 0."""
|
||||
|
||||
makerFee: int
|
||||
"""Amount of ZRX paid to feeRecipient by maker when order is filled. If
|
||||
set to 0, no transfer of ZRX from maker to feeRecipient will be attempted.
|
||||
"""
|
||||
|
||||
takerFee: int
|
||||
"""Amount of ZRX paid to feeRecipient by taker when order is filled. If
|
||||
set to 0, no transfer of ZRX from taker to feeRecipient will be attempted.
|
||||
"""
|
||||
|
||||
expirationTimeSeconds: int
|
||||
"""Timestamp in seconds at which order expires."""
|
||||
|
||||
salt: int
|
||||
"""Arbitrary number to facilitate uniqueness of the order's hash."""
|
||||
|
||||
makerAssetData: bytes
|
||||
"""Encoded data that can be decoded by a specified proxy contract when
|
||||
transferring makerAsset. The last byte references the id of this proxy.
|
||||
"""
|
||||
|
||||
takerAssetData: bytes
|
||||
"""Encoded data that can be decoded by a specified proxy contract when
|
||||
transferring takerAsset. The last byte references the id of this proxy.
|
||||
"""
|
||||
|
||||
|
||||
def make_empty_order() -> Order:
|
||||
"""Construct an empty order.
|
||||
|
||||
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": (b"\x00") * 20,
|
||||
"takerAssetData": (b"\x00") * 20,
|
||||
"salt": 0,
|
||||
"makerFee": 0,
|
||||
"takerFee": 0,
|
||||
"makerAssetAmount": 0,
|
||||
"takerAssetAmount": 0,
|
||||
"expirationTimeSeconds": 0,
|
||||
}
|
||||
|
||||
|
||||
def order_to_jsdict(
|
||||
order: Order,
|
||||
exchange_address="0x0000000000000000000000000000000000000000",
|
||||
signature: str = None,
|
||||
) -> dict:
|
||||
"""Convert a Web3-compatible order struct to a JSON-schema-compatible dict.
|
||||
|
||||
More specifically, do explicit decoding for the `bytes`:code: fields, and
|
||||
convert numerics to strings.
|
||||
|
||||
>>> 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
|
||||
|
||||
jsdict["expirationTimeSeconds"] = str(order["expirationTimeSeconds"])
|
||||
|
||||
jsdict["makerAssetAmount"] = str(order["makerAssetAmount"])
|
||||
jsdict["takerAssetAmount"] = str(order["takerAssetAmount"])
|
||||
|
||||
jsdict["makerFee"] = str(order["makerFee"])
|
||||
jsdict["takerFee"] = str(order["takerFee"])
|
||||
|
||||
jsdict["salt"] = str(order["salt"])
|
||||
|
||||
if signature is not None:
|
||||
jsdict["signature"] = signature
|
||||
|
||||
assert_valid(jsdict, "/orderSchema")
|
||||
|
||||
return jsdict
|
||||
|
||||
|
||||
def jsdict_to_order(jsdict: dict) -> Order:
|
||||
r"""Convert a JSON-schema-compatible dict order to a Web3-compatible struct.
|
||||
|
||||
More specifically, do explicit encoding of the `bytes`:code: fields, and
|
||||
parse integers from strings.
|
||||
|
||||
>>> import pprint
|
||||
>>> pprint.pprint(jsdict_to_order(
|
||||
... {
|
||||
... '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"])
|
||||
)
|
||||
|
||||
order["makerAssetAmount"] = int(jsdict["makerAssetAmount"])
|
||||
order["takerAssetAmount"] = int(jsdict["takerAssetAmount"])
|
||||
|
||||
order["makerFee"] = int(jsdict["makerFee"])
|
||||
order["takerFee"] = int(jsdict["takerFee"])
|
||||
|
||||
order["expirationTimeSeconds"] = int(jsdict["expirationTimeSeconds"])
|
||||
|
||||
order["salt"] = int(jsdict["salt"])
|
||||
|
||||
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.
|
||||
|
||||
@@ -374,20 +96,20 @@ def generate_order_hash_hex(order: Order, exchange_address: str) -> str:
|
||||
:returns: A string, of ASCII hex digits, representing the order hash.
|
||||
|
||||
>>> generate_order_hash_hex(
|
||||
... {
|
||||
... 'makerAddress': "0x0000000000000000000000000000000000000000",
|
||||
... 'takerAddress': "0x0000000000000000000000000000000000000000",
|
||||
... 'feeRecipientAddress': "0x0000000000000000000000000000000000000000",
|
||||
... 'senderAddress': "0x0000000000000000000000000000000000000000",
|
||||
... 'makerAssetAmount': "1000000000000000000",
|
||||
... 'takerAssetAmount': "1000000000000000000",
|
||||
... 'makerFee': "0",
|
||||
... 'takerFee': "0",
|
||||
... 'expirationTimeSeconds': "12345",
|
||||
... 'salt': "12345",
|
||||
... 'makerAssetData': (0).to_bytes(1, byteorder='big') * 20,
|
||||
... 'takerAssetData': (0).to_bytes(1, byteorder='big') * 20,
|
||||
... },
|
||||
... Order(
|
||||
... makerAddress="0x0000000000000000000000000000000000000000",
|
||||
... takerAddress="0x0000000000000000000000000000000000000000",
|
||||
... feeRecipientAddress="0x0000000000000000000000000000000000000000",
|
||||
... senderAddress="0x0000000000000000000000000000000000000000",
|
||||
... makerAssetAmount="1000000000000000000",
|
||||
... takerAssetAmount="1000000000000000000",
|
||||
... makerFee="0",
|
||||
... takerFee="0",
|
||||
... expirationTimeSeconds="12345",
|
||||
... salt="12345",
|
||||
... makerAssetData=((0).to_bytes(1, byteorder='big') * 20),
|
||||
... takerAssetData=((0).to_bytes(1, byteorder='big') * 20),
|
||||
... ),
|
||||
... exchange_address="0x0000000000000000000000000000000000000000",
|
||||
... )
|
||||
'55eaa6ec02f3224d30873577e9ddd069a288c16d6fb407210eecbc501fa76692'
|
||||
@@ -429,19 +151,6 @@ def generate_order_hash_hex(order: Order, exchange_address: str) -> str:
|
||||
).hex()
|
||||
|
||||
|
||||
class OrderInfo(NamedTuple):
|
||||
"""A Web3-compatible representation of the Exchange.OrderInfo struct."""
|
||||
|
||||
order_status: str
|
||||
"""A `str`:code: describing the order's validity and fillability."""
|
||||
|
||||
order_hash: bytes
|
||||
"""A `bytes`:code: object representing the EIP712 hash of the order."""
|
||||
|
||||
order_taker_asset_filled_amount: int
|
||||
"""An `int`:code: indicating the amount that has already been filled."""
|
||||
|
||||
|
||||
def is_valid_signature(
|
||||
provider: BaseProvider, data: str, signature: str, signer_address: str
|
||||
) -> Tuple[bool, str]:
|
||||
@@ -636,3 +345,21 @@ def sign_hash(
|
||||
"Signature returned from web3 provider is in an unknown format."
|
||||
+ " Attempted to parse as RSV and as VRS."
|
||||
)
|
||||
|
||||
|
||||
def sign_hash_to_bytes(
|
||||
provider: BaseProvider, signer_address: str, hash_hex: str
|
||||
) -> bytes:
|
||||
"""Sign a message with the given hash, and return the signature.
|
||||
|
||||
>>> provider = Web3.HTTPProvider("http://127.0.0.1:8545")
|
||||
>>> sign_hash_to_bytes(
|
||||
... provider,
|
||||
... Web3(provider).personal.listAccounts[0],
|
||||
... '0x34decbedc118904df65f379a175bb39ca18209d6ce41d5ed549d54e6e0a95004',
|
||||
... ).decode(encoding='utf_8')
|
||||
'1b117902c86dfb95fe0d1badd983ee166ad259b27acb220174cbb4460d872871137feabdfe76e05924b484789f79af4ee7fa29ec006cedce1bbf369320d034e10b03'
|
||||
""" # noqa: E501 (line too long)
|
||||
return remove_0x_prefix(
|
||||
sign_hash(provider, signer_address, hash_hex)
|
||||
).encode(encoding="utf_8")
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Test zero_ex.order_utils.get_order_hash_hex()."""
|
||||
|
||||
from zero_ex.order_utils import generate_order_hash_hex, make_empty_order
|
||||
from zero_ex.order_utils import generate_order_hash_hex
|
||||
|
||||
|
||||
def test_get_order_hash_hex__empty_order():
|
||||
@@ -9,6 +9,22 @@ def test_get_order_hash_hex__empty_order():
|
||||
"faa49b35faeb9197e9c3ba7a52075e6dad19739549f153b77dfcf59408a4b422"
|
||||
)
|
||||
actual_hash_hex = generate_order_hash_hex(
|
||||
make_empty_order(), "0x0000000000000000000000000000000000000000"
|
||||
{
|
||||
"makerAddress": "0x0000000000000000000000000000000000000000",
|
||||
"takerAddress": "0x0000000000000000000000000000000000000000",
|
||||
"senderAddress": "0x0000000000000000000000000000000000000000",
|
||||
"feeRecipientAddress": (
|
||||
"0x0000000000000000000000000000000000000000"
|
||||
),
|
||||
"makerAssetData": (b"\x00") * 20,
|
||||
"takerAssetData": (b"\x00") * 20,
|
||||
"salt": 0,
|
||||
"makerFee": 0,
|
||||
"takerFee": 0,
|
||||
"makerAssetAmount": 0,
|
||||
"takerAssetAmount": 0,
|
||||
"expirationTimeSeconds": 0,
|
||||
},
|
||||
"0x0000000000000000000000000000000000000000",
|
||||
)
|
||||
assert actual_hash_hex == expected_hash_hex
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import pytest
|
||||
from web3 import Web3
|
||||
|
||||
from zero_ex.order_utils import is_valid_signature
|
||||
from zero_ex.order_utils import is_valid_signature, sign_hash_to_bytes
|
||||
|
||||
|
||||
def test_is_valid_signature__provider_wrong_type():
|
||||
@@ -126,3 +126,17 @@ def test_is_valid_signature__unsupported_sig_types():
|
||||
)
|
||||
assert is_valid is False
|
||||
assert reason == "SIGNATURE_UNSUPPORTED"
|
||||
|
||||
|
||||
def test_sign_hash_to_bytes__golden_path():
|
||||
"""Test the happy path through sign_hash_to_bytes()."""
|
||||
provider = Web3.HTTPProvider("http://127.0.0.1:8545")
|
||||
signature = sign_hash_to_bytes(
|
||||
provider,
|
||||
Web3(provider).personal.listAccounts[0], # pylint: disable=no-member
|
||||
"0x34decbedc118904df65f379a175bb39ca18209d6ce41d5ed549d54e6e0a95004",
|
||||
)
|
||||
assert (
|
||||
signature
|
||||
== b"1b117902c86dfb95fe0d1badd983ee166ad259b27acb220174cbb4460d872871137feabdfe76e05924b484789f79af4ee7fa29ec006cedce1bbf369320d034e10b03" # noqa: E501 (line too long)
|
||||
)
|
||||
|
||||
44
python-packages/parallel
Executable file
44
python-packages/parallel
Executable file
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""Run the given command in all packages in parallel.
|
||||
|
||||
Handy for quick verification test runs, but annoying in that all of the output
|
||||
is interleaved.
|
||||
|
||||
$ ./parallel ./setup.py lint
|
||||
|
||||
This will `cd` into each package, run `./setup.py lint`, then `cd ..`, all in
|
||||
parallel, in a separate process for each package. The number of processes is
|
||||
decided by ProcessPoolExecutor. Replace "lint" with any of "test", "clean",
|
||||
"build_sphinx" (for docs), etc.
|
||||
|
||||
Also consider:
|
||||
|
||||
$ ./parallel pip install -e .[dev] # install all the packages in editable mode
|
||||
|
||||
$ ./parallel pip uninstall $(basename $(pwd))
|
||||
|
||||
>>>"""
|
||||
|
||||
from concurrent.futures import ProcessPoolExecutor
|
||||
from os import chdir
|
||||
from subprocess import check_call
|
||||
from sys import argv
|
||||
|
||||
PACKAGES = [
|
||||
"contract_addresses",
|
||||
"contract_artifacts",
|
||||
"contract_wrappers",
|
||||
"json_schemas",
|
||||
"sra_client",
|
||||
"order_utils",
|
||||
"middlewares",
|
||||
]
|
||||
|
||||
def run_cmd_on_package(package: str):
|
||||
"""cd to the package dir, ./setup.py lint, cd .."""
|
||||
chdir(package)
|
||||
check_call(f"{' '.join(argv[1:])}".split())
|
||||
chdir("..")
|
||||
|
||||
ProcessPoolExecutor().map(run_cmd_on_package, PACKAGES)
|
||||
@@ -5,17 +5,21 @@
|
||||
from os import chdir, path
|
||||
import subprocess
|
||||
|
||||
from concurrent.futures import ProcessPoolExecutor
|
||||
from os import chdir
|
||||
from subprocess import check_call
|
||||
from sys import argv
|
||||
|
||||
PACKAGES = [
|
||||
"contract_wrappers",
|
||||
"contract_artifacts",
|
||||
"json_schemas",
|
||||
]
|
||||
|
||||
for package in PACKAGES:
|
||||
print(f"Running command `pre_install` in package {package}")
|
||||
def run_cmd_on_package(package: str):
|
||||
"""cd to the package dir, ./setup.py pre_install, cd .."""
|
||||
chdir(package)
|
||||
subprocess.check_call(
|
||||
(
|
||||
path.join(".", "setup.py") + " pre_install"
|
||||
).split()
|
||||
)
|
||||
check_call(f"{path.join('.', 'setup.py')} pre_install".split())
|
||||
chdir("..")
|
||||
|
||||
ProcessPoolExecutor().map(run_cmd_on_package, PACKAGES)
|
||||
|
||||
@@ -193,6 +193,7 @@ setup(
|
||||
"build_sphinx": {
|
||||
"source_dir": ("setup.py", "src"),
|
||||
"build_dir": ("setup.py", "build/docs"),
|
||||
"warning_is_error": ("setup.py", "true"),
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@@ -105,10 +105,9 @@ Post Order
|
||||
|
||||
Post an order for our Maker to trade ZRX for WETH:
|
||||
|
||||
>>> from zero_ex.contract_wrappers.exchange.types import Order, order_to_jsdict
|
||||
>>> from zero_ex.order_utils import (
|
||||
... asset_data_utils,
|
||||
... Order,
|
||||
... order_to_jsdict,
|
||||
... sign_hash)
|
||||
>>> import random
|
||||
>>> from datetime import datetime, timedelta
|
||||
@@ -254,7 +253,7 @@ consists just of our order):
|
||||
Select an order from the orderbook
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
>>> from zero_ex.order_utils import jsdict_to_order
|
||||
>>> from zero_ex.contract_wrappers.exchange.types import jsdict_to_order
|
||||
>>> order = jsdict_to_order(orderbook.bids.records[0].order)
|
||||
>>> from pprint import pprint
|
||||
>>> pprint(order)
|
||||
@@ -322,8 +321,8 @@ book. Now let's have the taker fill it:
|
||||
... )
|
||||
>>> exchange.fill_order(
|
||||
... order=order,
|
||||
... taker_amount=order['makerAssetAmount']/2, # note the half fill
|
||||
... signature=order['signature'],
|
||||
... taker_asset_fill_amount=order['makerAssetAmount']/2, # note the half fill
|
||||
... signature=order['signature'].replace('0x', '').encode('utf-8'),
|
||||
... tx_params=TxParams(from_=taker_address)
|
||||
... )
|
||||
HexBytes('0x...')
|
||||
|
||||
@@ -1,13 +1,3 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env bash
|
||||
|
||||
"""Script to run tests against local copy of all components."""
|
||||
|
||||
from os import path
|
||||
import subprocess
|
||||
|
||||
subprocess.check_call(
|
||||
(
|
||||
f"{path.join('.', 'cmd_pkgs_in_dep_order.py')}"
|
||||
+ f" {path.join('.', 'setup.py')} test"
|
||||
).split()
|
||||
)
|
||||
./parallel ./setup.py test
|
||||
|
||||
Reference in New Issue
Block a user