Migrate Python libraries to v3 (#2284)

* .gitignore migrations/0x_ganache_snapshot

* .gitignore new-ish Python contract wrappers

These should have been added back when we started generating these
wrappers.

* rm superfluous contract artifact in Python package

All of the contract artifacts were removed from the Python package
recently, because now they're copied from the monorepo/packages area as
an automated build step.  Somehow this one artifact slipped through the
cracks.

* Eliminate circular dependency

This was preventing the Exchange wrapper from ever importing its
validator!

* Improve output of monorepo-level parallel script

- Capture stderr (and have it included in stdout) so that it doesn't
leak onto the console for commands that didn't actually fail.

- Include all error output in the Exception object (eliminate print
statement).

* Silence new versions of linters

Newer versions care about this stuff.  Old versions didn't, and we don't
either.

* Support Rich Reverts via Web3.py middleware

* Fix bug in generated wrappers' bytes handling

`bytes.fromhex(bytes.decode('utf-8')` is just plain wrong.  It would
work for some cases, but is not working when trying to fill orders with
the latest Exchange contract.

* Migrate to Exchange v3

* Fix typo in DevUtils documentation

* Include new contracts in docs

* Re-enable Python checks in CI

* Accept strings for bytes

* Fix CircleCI build artifacts for gen'd python

I swear the previous way was working before, but it wasn't working now,
so this fixes it.

* Accept a provider OR a Web3 object

In various places.  This allows the caller to install middleware (which
in web3.py is installed on a Web3 object, not on a provider) before
executing any RPC calls, which is important for the case where one wants
to produce signatures locally before submitting to a remote node.

* wrapper base: don't assume there are accounts

* Eliminate some inline linter directives

* make CHANGELOGs be REVERSE chronological

* Update CHANGELOG entries and bump version numbers

* @0x/contract-addresses: Put addr's in JSON, not TS

This allows easier consumption by other languages.  (Specifically, it
eliminates the overhead of keeping the Python addresses package in sync
with the TypeScript one.)

* sra_client.py: incl. docker in `./setup.py clean`

* sra_client.py: Migrate to protocol v3

Removed script that existed only to exclude runs of sra_client builds
(parallel_without_sra_client).  Now `parallel` is used by CI,
re-including sra_client in CI checks.

* abi-gen/templates/Py: clarify if/else logic

In response to
https://github.com/0xProject/0x-monorepo/pull/2284#discussion_r342200906

* sra_client.py: Update CHANGELOG and bump version

* contract_addresses/setup.py: rm unnecessary rm

* json_schemas.py: corrections to dev dependencies

* In tests against deployment, also run doctests

* contract_wrappers example: rm xtra Order attribute

Thanks to @steveklebanoff for catching this.
https://github.com/0xProject/0x-monorepo/pull/2284#pullrequestreview-312065368
This commit is contained in:
F. Eugene Aumson
2019-11-05 23:04:29 -05:00
committed by GitHub
parent cbe4c4fbf9
commit e61f23d001
69 changed files with 1996 additions and 941 deletions

View File

@@ -1,5 +1,14 @@
# Changelog
## 2.0.0 - TBD
- Updated for version 3 of the protocol.
- Allow wrappers to be instantiated with EITHER a Web3.py `BaseProvider` OR an already-instantiated `Web3` client object.
- Accept `str`ing arguments to `bytes` contract method parameters.
- Expanded documentation examples.
- Moved methods `jsdict_to_order()` and `order_to_jsdict()` from `zero_ex.contract_wrappers.exchange.types` to `zero_ex.contract_wrappers.order_conversions`.
- Changed field name `zero_ex.contract_wrappers.tx_params.TxParams.gasPrice` to `.gas_price`.
## 1.1.0 - 2019-08-14
- Added wrapper for DevUtils contract.

View File

@@ -2,11 +2,15 @@
"""setuptools module for contract_wrappers package."""
# pylint: disable=import-outside-toplevel
# we import things outside of top-level because 3rd party libs may not yet be
# installed when you invoke this script
import subprocess # nosec
from shutil import rmtree
from os import environ, path, remove
from pathlib import Path
from sys import argv
from sys import argv, exit # pylint: disable=redefined-builtin
from distutils.command.clean import clean
import distutils.command.build_py
@@ -192,7 +196,7 @@ with open("README.md", "r") as file_handle:
setup(
name="0x-contract-wrappers",
version="1.1.0",
version="2.0.0",
description="Python wrappers for 0x smart contracts",
long_description=README_MD,
long_description_content_type="text/markdown",

View File

@@ -34,7 +34,7 @@ zero_ex.contract_wrappers.coordinator_registry
zero_ex.contract_wrappers.dev_utils
=======================================
===================================
.. automodule:: zero_ex.contract_wrappers.dev_utils
:members:
@@ -49,6 +49,22 @@ zero_ex.contract_wrappers.dutch_auction
:special-members:
zero_ex.contract_wrappers.erc1155_mintable
==========================================
.. automodule:: zero_ex.contract_wrappers.erc1155_mintable
:members:
:special-members:
zero_ex.contract_wrappers.erc1155_proxy
=======================================
.. automodule:: zero_ex.contract_wrappers.erc1155_proxy
:members:
:special-members:
zero_ex.contract_wrappers.erc20_proxy
=====================================
@@ -145,6 +161,14 @@ zero_ex.contract_wrappers.order_validator
:special-members:
zero_ex.contract_wrappers.static_call_proxy
===========================================
.. automodule:: zero_ex.contract_wrappers.static_call_proxy
:members:
:special-members:
zero_ex.contract_wrappers.weth9
===============================
@@ -180,18 +204,20 @@ zero_ex.contract_wrappers.exchange.types
.. autoclass:: zero_ex.contract_wrappers.exchange.types.MatchedFillResults
.. autoclass:: zero_ex.contract_wrappers.exchange.types.ZeroExTransaction
zero_ex.contract_wrappers.exchange: Generated Tuples
====================================================
.. autoclass:: zero_ex.contract_wrappers.exchange.Tuple0x260219a2
.. autoclass:: zero_ex.contract_wrappers.exchange.Tuple0x6ca34a6f
This is the generated class representing `the Order struct <https://0x.org/docs/contracts#structs-Order>`_.
.. autoclass:: zero_ex.contract_wrappers.exchange.Tuple0xbb41e5b3
.. autoclass:: zero_ex.contract_wrappers.exchange.Tuple0x735c43e3
This is the generated class representing `the FillResults struct <https://0x.org/docs/contracts#structs-FillResults>`_.
.. autoclass:: zero_ex.contract_wrappers.exchange.Tuple0x054ca44e
.. autoclass:: zero_ex.contract_wrappers.exchange.Tuple0x4c5ca29b
This is the generated class representing `the MatchedFillResults struct <https://0x.org/docs/contracts#structs-MatchedFillResults>`_.
@@ -199,6 +225,10 @@ zero_ex.contract_wrappers.exchange: Generated Tuples
This is the generated class representing `the OrderInfo struct <https://0x.org/docs/contracts#structs-OrderInfo>`_.
.. autoclass:: zero_ex.contract_wrappers.exchange.Tuple0xdabc15fe
This is the generated class representing `the ZeroExTransaction struct <https://0x.org/docs/contracts#structs-ZeroExTransaction>`_.
Indices and tables
==================

View File

@@ -1,2 +1,2 @@
"""0x Python API."""
__import__("pkg_resources").declare_namespace(__name__)
__import__("pkg_resources").declare_namespace(__name__) # type: ignore

View File

@@ -52,10 +52,10 @@ the addresses of the 0x contracts on each network, including those that come
pre-deployed deployed in the `0xorg/ganache-cli`:code: docker image. Let's
capture the addresses we'll use throughout the examples below:
>>> from zero_ex.contract_addresses import NETWORK_TO_ADDRESSES, NetworkId
>>> weth_address = NETWORK_TO_ADDRESSES[NetworkId.GANACHE].ether_token
>>> zrx_address = NETWORK_TO_ADDRESSES[NetworkId.GANACHE].zrx_token
>>> exchange_address = NETWORK_TO_ADDRESSES[NetworkId.GANACHE].exchange
>>> from zero_ex.contract_addresses import network_to_addresses, NetworkId
>>> weth_address = network_to_addresses(NetworkId.GANACHE).ether_token
>>> zrx_address = network_to_addresses(NetworkId.GANACHE).zrx_token
>>> exchange_address = network_to_addresses(NetworkId.GANACHE).exchange
Wrapping ETH
------------
@@ -92,15 +92,15 @@ balance:
>>> from zero_ex.contract_wrappers.erc20_token import ERC20Token
>>> zrx_token = ERC20Token(
... provider=ganache,
... contract_address=NETWORK_TO_ADDRESSES[NetworkId.GANACHE].zrx_token,
... web3_or_provider=ganache,
... contract_address=network_to_addresses(NetworkId.GANACHE).zrx_token,
... )
>>> weth_token = ERC20Token(
... provider=ganache,
... contract_address=NETWORK_TO_ADDRESSES[NetworkId.GANACHE].ether_token,
... web3_or_provider=ganache,
... contract_address=network_to_addresses(NetworkId.GANACHE).ether_token,
... )
>>> erc20_proxy_addr = NETWORK_TO_ADDRESSES[NetworkId.GANACHE].erc20_proxy
>>> erc20_proxy_addr = network_to_addresses(NetworkId.GANACHE).erc20_proxy
>>> tx = zrx_token.approve.send_transaction(
... erc20_proxy_addr,
@@ -135,16 +135,20 @@ Constructing an order
... takerAssetAmount=to_wei(0.1, 'ether'),
... expirationTimeSeconds=round(
... (datetime.utcnow() + timedelta(days=1)).timestamp()
... )
... ),
... makerFeeAssetData='0x',
... takerFeeAssetData='0x',
... )
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)
>>> order_hash_hex = generate_order_hash_hex(
... order, exchange_address, Web3(ganache).eth.chainId
... )
>>> from zero_ex.order_utils import sign_hash_to_bytes
>>> maker_signature = sign_hash_to_bytes(
>>> from zero_ex.order_utils import sign_hash
>>> maker_signature = sign_hash(
... ganache, Web3.toChecksumAddress(maker_address), order_hash_hex
... )
@@ -156,16 +160,37 @@ more information on working with Relayers, see `the documentation for
Filling an order
----------------
Now our Taker will fill the order. The `takerAssetAmount`:code: parameter
specifies the amount of tokens (in this case WETH) that the taker wants to
fill. This example fills the order completely, but partial fills are possible
too.
Now we'll have our Taker fill the order.
>>> from zero_ex.contract_wrappers.exchange import Exchange
>>> exchange = Exchange(
... provider=ganache,
... contract_address=NETWORK_TO_ADDRESSES[NetworkId.GANACHE].exchange,
... web3_or_provider=ganache,
... contract_address=network_to_addresses(NetworkId.GANACHE).exchange,
... )
But before filling an order, one may wish to check that it's actually fillable:
>>> from zero_ex.contract_wrappers.exchange.types import OrderStatus
>>> OrderStatus(exchange.get_order_info.call(order)[0])
<OrderStatus.FILLABLE: 3>
The `takerAssetAmount`:code: parameter specifies the amount of tokens (in this
case WETH) that the taker wants to fill. This example fills the order
completely, but partial fills are possible too.
One may wish to first call the method in a read-only way, to ensure that it
will not revert, and to validate that the return data is as expected:
>>> exchange.fill_order.call(
... order=order,
... taker_asset_fill_amount=order["takerAssetAmount"],
... signature=maker_signature,
... tx_params=TxParams(from_=taker_address)
... )
(100000000000000000, 100000000000000000, 0, 0, 0)
Finally, submit the transaction:
>>> tx_hash = exchange.fill_order.send_transaction(
... order=order,
... taker_asset_fill_amount=order["takerAssetAmount"],
@@ -184,12 +209,15 @@ the exchange wrapper:
'makerAddress': '0x...',
'makerAssetData': b...,
'makerAssetFilledAmount': 100000000000000000,
'makerFeeAssetData': b...,
'makerFeePaid': 0,
'orderHash': b...,
'protocolFeePaid': ...,
'senderAddress': '0x...',
'takerAddress': '0x...',
'takerAssetData': b...,
'takerAssetFilledAmount': 100000000000000000,
'takerFeeAssetData': b...,
'takerFeePaid': 0}
>>> exchange.get_fill_event(tx_hash)[0].args.takerAssetFilledAmount
100000000000000000
@@ -206,7 +234,9 @@ A Maker can cancel an order that has yet to be filled.
... senderAddress='0x0000000000000000000000000000000000000000',
... feeRecipientAddress='0x0000000000000000000000000000000000000000',
... makerAssetData=asset_data_utils.encode_erc20(weth_address),
... makerFeeAssetData=asset_data_utils.encode_erc20('0x' + '00'*20),
... takerAssetData=asset_data_utils.encode_erc20(weth_address),
... takerFeeAssetData=asset_data_utils.encode_erc20('0x' + '00'*20),
... salt=random.randint(1, 100000000000000000),
... makerFee=0,
... takerFee=0,
@@ -248,7 +278,9 @@ is an example where the taker fills two orders in one transaction:
... senderAddress='0x0000000000000000000000000000000000000000',
... feeRecipientAddress='0x0000000000000000000000000000000000000000',
... makerAssetData=asset_data_utils.encode_erc20(zrx_address),
... makerFeeAssetData=asset_data_utils.encode_erc20('0x' + '00'*20),
... takerAssetData=asset_data_utils.encode_erc20(weth_address),
... takerFeeAssetData=asset_data_utils.encode_erc20('0x' + '00'*20),
... salt=random.randint(1, 100000000000000000),
... makerFee=0,
... takerFee=0,
@@ -258,10 +290,12 @@ is an example where the taker fills two orders in one transaction:
... (datetime.utcnow() + timedelta(days=1)).timestamp()
... )
... )
>>> signature_1 = sign_hash_to_bytes(
>>> signature_1 = sign_hash(
... ganache,
... Web3.toChecksumAddress(maker_address),
... generate_order_hash_hex(order_1, exchange.contract_address)
... generate_order_hash_hex(
... order_1, exchange.contract_address, Web3(ganache).eth.chainId
... ),
... )
>>> order_2 = Order(
... makerAddress=maker_address,
@@ -269,7 +303,9 @@ is an example where the taker fills two orders in one transaction:
... senderAddress='0x0000000000000000000000000000000000000000',
... feeRecipientAddress='0x0000000000000000000000000000000000000000',
... makerAssetData=asset_data_utils.encode_erc20(zrx_address),
... makerFeeAssetData=asset_data_utils.encode_erc20('0x' + '00'*20),
... takerAssetData=asset_data_utils.encode_erc20(weth_address),
... takerFeeAssetData=asset_data_utils.encode_erc20('0x' + '00'*20),
... salt=random.randint(1, 100000000000000000),
... makerFee=0,
... takerFee=0,
@@ -279,10 +315,12 @@ is an example where the taker fills two orders in one transaction:
... (datetime.utcnow() + timedelta(days=1)).timestamp()
... )
... )
>>> signature_2 = sign_hash_to_bytes(
>>> signature_2 = sign_hash(
... ganache,
... Web3.toChecksumAddress(maker_address),
... generate_order_hash_hex(order_2, exchange.contract_address)
... generate_order_hash_hex(
... order_2, exchange.contract_address, Web3(ganache).eth.chainId
... ),
... )
Fill order_1 and order_2 together:
@@ -308,7 +346,9 @@ will be consumed.
... senderAddress='0x0000000000000000000000000000000000000000',
... feeRecipientAddress='0x0000000000000000000000000000000000000000',
... makerAssetData=asset_data_utils.encode_erc20(weth_address),
... makerFeeAssetData=asset_data_utils.encode_erc20('0x' + '00'*20),
... takerAssetData=asset_data_utils.encode_erc20(weth_address),
... takerFeeAssetData=asset_data_utils.encode_erc20('0x' + '00'*20),
... salt=random.randint(1, 100000000000000000),
... makerFee=0,
... takerFee=0,
@@ -320,7 +360,7 @@ will be consumed.
... ),
... tx_params=TxParams(from_=maker_address),
... )
73...
74...
"""
from .tx_params import TxParams

View File

@@ -1,6 +1,6 @@
"""Base wrapper class for accessing ethereum smart contracts."""
from typing import Any
from typing import Any, Union
from eth_utils import is_address, to_checksum_address
from web3 import Web3
@@ -12,7 +12,11 @@ from .tx_params import TxParams
class Validator:
"""Base class for validating inputs to methods."""
def __init__(self, provider: BaseProvider, contract_address: str):
def __init__(
self,
web3_or_provider: Union[Web3, BaseProvider],
contract_address: str,
):
"""Initialize the instance."""
def assert_valid(
@@ -32,7 +36,7 @@ class ContractMethod:
def __init__(
self,
provider: BaseProvider,
web3_or_provider: Union[Web3, BaseProvider],
contract_address: str,
validator: Validator = None,
):
@@ -42,9 +46,20 @@ class ContractMethod:
:param contract_address: Where the contract has been deployed to.
:param validator: Used to validate method inputs.
"""
self._web3_eth = Web3(provider).eth # pylint: disable=no-member
web3 = None
if isinstance(web3_or_provider, BaseProvider):
web3 = Web3(web3_or_provider)
elif isinstance(web3_or_provider, Web3):
web3 = web3_or_provider
if web3 is None:
raise TypeError(
"Expected parameter 'web3_or_provider' to be an instance of either"
+ " Web3 or BaseProvider"
)
self._web3_eth = web3.eth # pylint: disable=no-member
if validator is None:
validator = Validator(provider, contract_address)
validator = Validator(web3_or_provider, contract_address)
self.validator = validator
@staticmethod
@@ -59,8 +74,13 @@ class ContractMethod:
if not tx_params:
tx_params = TxParams()
if not tx_params.from_:
tx_params.from_ = (
self._web3_eth.defaultAccount or self._web3_eth.accounts[0]
tx_params.from_ = self._web3_eth.defaultAccount or (
self._web3_eth.accounts[0]
if len(self._web3_eth.accounts) > 0
else None
)
if tx_params.from_:
tx_params.from_ = self.validate_and_checksum_address(
tx_params.from_
)
tx_params.from_ = self.validate_and_checksum_address(tx_params.from_)
return tx_params

View File

@@ -0,0 +1,80 @@
"""Exception classes common to all wrappers."""
from inspect import isclass
from typing import List
from eth_abi import decode_abi
class RichRevert(Exception):
"""Raised when a contract method returns a rich revert error."""
def __init__(
self, abi_signature: str, param_names: List[str], return_data: str
):
"""Populate instance variables with decoded return data values."""
arg_start_index = abi_signature.index("(") + 1
arg_end_index = abi_signature.index(")")
arguments = decode_abi(
abi_signature[arg_start_index:arg_end_index].split(","),
bytes.fromhex(return_data[10:]),
)
for (param_name, argument) in zip(param_names, arguments):
setattr(self, param_name, argument)
super().__init__(vars(self))
class NoExceptionForSelector(Exception):
"""Indicates that no exception could be found for the given selector."""
def exception_class_from_rich_revert_selector(
selector: str, exceptions_module
) -> RichRevert:
"""Return the appropriate exception class.
:param selector: A string of the format '0xffffffff' which indicates the
4-byte ABI function selector of a rich revert error type, which is
expected to be found as a class attribute on some class in
`exceptions_module`:code:.
:param exceptions_module: The Python module in which to look for a class
with a `selector`:code: attribute matching the value of the
`selector`:code: argument.
"""
# noqa: D202 (No blank lines allowed after function docstring
def _get_rich_revert_exception_classes():
def _exception_name_is_class_with_selector(name: str):
if not isclass(getattr(exceptions_module, name)):
return False
try:
getattr(exceptions_module, name).selector
except AttributeError:
return False
return True
def _convert_class_name_to_class(name: str):
return getattr(exceptions_module, name)
return list(
map(
_convert_class_name_to_class,
filter(
_exception_name_is_class_with_selector,
dir(exceptions_module),
),
)
)
rich_reverts = _get_rich_revert_exception_classes()
try:
return next(
filter(
lambda rich_revert: rich_revert.selector == selector,
rich_reverts,
)
)
except StopIteration:
raise NoExceptionForSelector(selector)

View File

@@ -0,0 +1,341 @@
"""Exchange-specific exception classes."""
from enum import auto, Enum
from zero_ex.contract_wrappers.exceptions import RichRevert
# pylint: disable=missing-docstring
class AssetProxyDispatchErrorCodes(Enum): # noqa: D101 (missing docstring)
INVALID_ASSET_DATA_LENGTH = 0
UNKNOWN_ASSET_PROXY = auto()
class BatchMatchOrdersErrorCodes(Enum): # noqa: D101 (missing docstring)
ZERO_LEFT_ORDERS = 0
ZERO_RIGHT_ORDERS = auto()
INVALID_LENGTH_LEFT_SIGNATURES = auto()
INVALID_LENGTH_RIGHT_SIGNATURES = auto()
class ExchangeContextErrorCodes(Enum): # noqa: D101 (missing docstring)
INVALID_MAKER = 0
INVALID_TAKER = auto()
INVALID_SENDER = auto()
class FillErrorCodes(Enum): # noqa: D101 (missing docstring)
INVALID_TAKER_AMOUNT = 0
TAKER_OVERPAY = auto()
OVERFILL = auto()
INVALID_FILL_PRICE = auto()
class SignatureErrorCodes(Enum): # noqa: D101 (missing docstring)
BAD_ORDER_SIGNATURE = 0
BAD_TRANSACTION_SIGNATURE = auto()
INVALID_LENGTH = auto()
UNSUPPORTED = auto()
ILLEGAL = auto()
INAPPROPRIATE_SIGNATURE_TYPE = auto()
INVALID_SIGNER = auto()
class TransactionErrorCodes(Enum): # noqa: D101 (missing docstring)
ALREADY_EXECUTED = 0
EXPIRED = auto()
class IncompleteFillErrorCode(Enum): # noqa: D101 (missing docstring)
INCOMPLETE_MARKET_BUY_ORDERS = 0
INCOMPLETE_MARKET_SELL_ORDERS = auto()
INCOMPLETE_FILL_ORDER = auto()
class SignatureError(RichRevert): # noqa: D101 (missing docstring)
def __init__(self, return_data): # noqa: D107 (missing docstring)
super().__init__(
"SignatureError(uint8,bytes32,address,bytes)",
["errorCode", "hash", "signerAddress", "signature"],
return_data,
)
errorCode: SignatureErrorCodes
hash: bytes
signerAddress: str
signature: bytes
selector = "0x7e5a2318"
class SignatureValidatorNotApprovedError(
RichRevert
): # noqa: D101 (missing docstring)
def __init__(self, return_data): # noqa: D107 (missing docstring)
super().__init__(
"SignatureValidatorNotApprovedError(address,address)",
["signerAddress", "validatorAddress"],
return_data,
)
signerAddress: str
validatorAddress: str
selector = "0xa15c0d06"
class EIP1271SignatureError(RichRevert): # noqa: D101 (missing docstring)
def __init__(self, return_data): # noqa: D107 (missing docstring)
super().__init__(
"EIP1271SignatureError(address,bytes,bytes,bytes)",
["verifyingContractAddress", "data", "signature", "errorData"],
return_data,
)
verifyingContractAddress: str
data: bytes
signature: bytes
errorData: bytes
selector = "0x5bd0428d"
class SignatureWalletError(RichRevert): # noqa: D101 (missing docstring)
def __init__(self, return_data): # noqa: D107 (missing docstring)
super().__init__(
"SignatureWalletError(bytes32,address,bytes,bytes)",
["hash", "walletAddress", "signature", "errorData"],
return_data,
)
hash: bytes
walletAddress: str
signature: bytes
errorData: bytes
selector = "0x1b8388f7"
class OrderStatusError(RichRevert): # noqa: D101 (missing docstring)
def __init__(self, return_data): # noqa: D107 (missing docstring)
super().__init__(
"OrderStatusError(bytes32,uint8)",
["orderHash", "orderStatus"],
return_data,
)
orderHash: bytes
orderStatus: int
selector = "0xfdb6ca8d"
class ExchangeInvalidContextError(
RichRevert
): # noqa: D101 (missing docstring)
def __init__(self, return_data): # noqa: D107 (missing docstring)
super().__init__(
"ExchangeInvalidContextError(uint8,bytes32,address)",
["errorCode", "orderHash", "contextAddress"],
return_data,
)
errorCode: ExchangeContextErrorCodes
orderHash: bytes
contextAddress: str
selector = "0xe53c76c8"
class FillError(RichRevert): # noqa: D101 (missing docstring)
def __init__(self, return_data): # noqa: D107 (missing docstring)
super().__init__(
"FillError(uint8,bytes32)", ["errorCode", "orderHash"], return_data
)
errorCode: FillErrorCodes
orderHash: bytes
selector = "0xe94a7ed0"
class OrderEpochError(RichRevert): # noqa: D101 (missing docstring)
def __init__(self, return_data): # noqa: D107 (missing docstring)
super().__init__(
"OrderEpochError(address,address,uint256)",
["makerAddress", "orderSenderAddress", "currentEpoch"],
return_data,
)
makerAddress: str
orderSenderAddress: str
currentEpoch: int
selector = "0x4ad31275"
class AssetProxyExistsError(RichRevert): # noqa: D101 (missing docstring)
def __init__(self, return_data): # noqa: D107 (missing docstring)
super().__init__(
"AssetProxyExistsError(bytes4,address)",
["assetProxyId", "assetProxyAddress"],
return_data,
)
assetProxyId: bytes
assetProxyAddress: str
selector = "0x11c7b720"
class AssetProxyDispatchError(RichRevert): # noqa: D101 (missing docstring)
def __init__(self, return_data): # noqa: D107 (missing docstring)
super().__init__(
"AssetProxyDispatchError(uint8,bytes32,bytes)",
["errorCode", "orderHash", "assetData"],
return_data,
)
errorCode: AssetProxyDispatchErrorCodes
orderHash: bytes
assetData: bytes
selector = "0x488219a6"
class AssetProxyTransferError(RichRevert): # noqa: D101 (missing docstring)
def __init__(self, return_data): # noqa: D107 (missing docstring)
super().__init__(
"AssetProxyTransferError(bytes32,bytes,bytes)",
["orderHash", "assetData", "errorData"],
return_data,
)
orderHash: bytes
assetData: bytes
errorData: bytes
selector = "0x4678472b"
class NegativeSpreadError(RichRevert): # noqa: D101 (missing docstring)
def __init__(self, return_data): # noqa: D107 (missing docstring)
super().__init__(
"NegativeSpreadError(bytes32,bytes32)",
["leftOrderHash", "rightOrderHash"],
return_data,
)
leftOrderHash: bytes
rightOrderHash: bytes
selector = "0xb6555d6f"
class TransactionError(RichRevert): # noqa: D101 (missing docstring)
def __init__(self, return_data): # noqa: D107 (missing docstring)
super().__init__(
"TransactionError(uint8,bytes32)",
["errorCode", "transactionHash"],
return_data,
)
errorCode: TransactionErrorCodes
transactionHash: bytes
selector = "0xf5985184"
class TransactionExecutionError(RichRevert): # noqa: D101 (missing docstring)
def __init__(self, return_data): # noqa: D107 (missing docstring)
super().__init__(
"TransactionExecutionError(bytes32,bytes)",
["transactionHash", "errorData"],
return_data,
)
transactionHash: bytes
errorData: bytes
selector = "0x20d11f61"
class TransactionGasPriceError(RichRevert): # noqa: D101 (missing docstring)
def __init__(self, return_data): # noqa: D107 (missing docstring)
super().__init__(
"TransactionGasPriceError(bytes32,uint256,uint256)",
["transactionHash", "actualGasPrice", "requiredGasPrice"],
return_data,
)
transactionHash: bytes
actualGasPrice: int
requiredGasPrice: int
selector = "0xa26dac09"
class TransactionInvalidContextError(
RichRevert
): # noqa: D101 (missing docstring)
def __init__(self, return_data): # noqa: D107 (missing docstring)
super().__init__(
"TransactionInvalidContextError(bytes32,address)",
["transactionHash", "currentContextAddress"],
return_data,
)
transactionHash: bytes
currentContextAddress: str
selector = "0xdec4aedf"
class IncompleteFillError(RichRevert): # noqa: D101 (missing docstring)
def __init__(self, return_data): # noqa: D107 (missing docstring)
super().__init__(
"IncompleteFillError(uint8,uint256,uint256)",
["errorCode", "expectedAssetAmount", "actualAssetAmount"],
return_data,
)
errorCode: IncompleteFillErrorCode
expectedAssetAmount: int
actualAssetAmount: int
selector = "0x18e4b141"
class BatchMatchOrdersError(RichRevert): # noqa: D101 (missing docstring)
def __init__(self, return_data): # noqa: D107 (missing docstring)
super().__init__(
"BatchMatchOrdersError(uint8)", ["errorCode"], return_data
)
errorCode: BatchMatchOrdersErrorCodes
selector = "0xd4092f4f"
class PayProtocolFeeError(RichRevert): # noqa: D101 (missing docstring)
def __init__(self, return_data): # noqa: D107 (missing docstring)
super().__init__(
"PayProtocolFeeError(bytes32,uint256,address,address,bytes)",
[
"orderHash",
"protocolFee",
"makerAddress",
"takerAddress",
"errorData",
],
return_data,
)
orderHash: bytes
protocolFee: int
makerAddress: str
takerAddress: str
errorData: bytes
selector = "0x87cb1e75"

View File

@@ -0,0 +1,36 @@
"""Web3.py-compatible middleware to be injected upon contract instantiation."""
from zero_ex.contract_wrappers.exceptions import (
exception_class_from_rich_revert_selector,
NoExceptionForSelector,
)
from . import exceptions
def rich_revert_handler(make_request, _):
"""Return a middlware to raise exceptions for rich revert return data."""
# noqa: D202 (No blank lines allowed after function docstring
def middleware(method, params):
response = make_request(method, params)
try:
raise exception_class_from_rich_revert_selector(
response["result"][0:10], exceptions
)(response["result"])
except NoExceptionForSelector:
# response prefix didn't indicate a known error
pass
except TypeError:
# eg "unhashable type: 'slice'". if response["result"] isn't
# sliceable (eg if it's a dict), then it definitely isn't a rich
# revert.
pass
except KeyError:
# response doesn't have a "result" key
pass
return response
return middleware
MIDDLEWARE = [{"layer": 0, "function": rich_revert_handler}]

View File

@@ -11,17 +11,13 @@ Converting between the JSON wire format and the types accepted by Web3.py (eg
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 enum import auto, Enum
from . import (
Tuple0xbb41e5b3,
Tuple0x260219a2,
Tuple0x054ca44e,
Tuple0x735c43e3,
Tuple0x6ca34a6f,
Tuple0x4c5ca29b,
Tuple0xdabc15fe,
Tuple0xb1e4a1ae,
)
@@ -33,27 +29,35 @@ from . import (
# of each of these classes.
class FillResults(Tuple0xbb41e5b3):
class FillResults(Tuple0x735c43e3):
"""The `FillResults`:code: Solidity struct.
Also known as
`zero_ex.contract_wrappers.exchange.Tuple0xbb41e5b3`:py:class:.
`zero_ex.contract_wrappers.exchange.Tuple0x735c43e3`:py:class:.
"""
class Order(Tuple0x260219a2):
class Order(Tuple0x6ca34a6f):
"""The `Order`:code: Solidity struct.
Also known as
`zero_ex.contract_wrappers.exchange.Tuple0x260219a2`:py:class:.
`zero_ex.contract_wrappers.exchange.Tuple0x6ca34a6f`:py:class:.
"""
class MatchedFillResults(Tuple0x054ca44e):
class MatchedFillResults(Tuple0x4c5ca29b):
"""The `MatchedFillResults`:code: Solidity struct.
Also known as
`zero_ex.contract_wrappers.exchange.Tuple0x054ca44e`:py:class:.
`zero_ex.contract_wrappers.exchange.Tuple0x4c5ca29b`:py:class:.
"""
class ZeroExTransaction(Tuple0xdabc15fe):
"""The `ZeroExTransaction`:code: Solidity struct.
Also known as
`zero_ex.contract_wrappers.exchange.Tuple0xdabc15fe`:py:class:.
"""
@@ -65,136 +69,11 @@ class OrderInfo(Tuple0xb1e4a1ae):
"""
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
class OrderStatus(Enum): # noqa: D101 # pylint: disable=missing-docstring
INVALID = 0
INVALID_MAKER_ASSET_AMOUNT = auto()
INVALID_TAKER_ASSET_AMOUNT = auto()
FILLABLE = auto()
EXPIRED = auto()
FULLY_FILLED = auto()
CANCELLED = auto()

View File

@@ -1,22 +1,40 @@
"""Validate inputs to the Exchange contract."""
from typing import Any
from typing import Any, Union
from web3 import Web3
from web3.providers.base import BaseProvider
from zero_ex import json_schemas
from zero_ex.contract_wrappers.order_conversions import order_to_jsdict
from ..bases import Validator
from .types import order_to_jsdict
class ExchangeValidator(Validator):
"""Validate inputs to Exchange methods."""
def __init__(self, provider: BaseProvider, contract_address: str):
def __init__(
self,
web3_or_provider: Union[Web3, BaseProvider],
contract_address: str,
):
"""Initialize the class."""
super().__init__(provider, contract_address)
super().__init__(web3_or_provider, contract_address)
web3 = None
if isinstance(web3_or_provider, BaseProvider):
web3 = Web3(web3_or_provider)
elif isinstance(web3_or_provider, Web3):
web3 = web3_or_provider
if web3 is None:
raise TypeError(
"Expected parameter 'web3_or_provider' to be an instance of either"
+ " Web3 or BaseProvider"
)
self.contract_address = contract_address
self.chain_id = web3.eth.chainId
def assert_valid(
self, method_name: str, parameter_name: str, argument_value: Any
@@ -30,13 +48,17 @@ class ExchangeValidator(Validator):
"""
if parameter_name == "order":
json_schemas.assert_valid(
order_to_jsdict(argument_value, self.contract_address),
order_to_jsdict(
argument_value, self.chain_id, self.contract_address
),
"/orderSchema",
)
if parameter_name == "orders":
for order in argument_value:
json_schemas.assert_valid(
order_to_jsdict(order, self.contract_address),
order_to_jsdict(
order, self.chain_id, self.contract_address
),
"/orderSchema",
)

View File

@@ -0,0 +1,180 @@
"""Utilities to convert between JSON and Python-native objects."""
from copy import copy
from typing import cast, Dict, Union
from eth_utils import remove_0x_prefix
from zero_ex.json_schemas import assert_valid
from zero_ex.contract_wrappers.exchange.types import Order
def order_to_jsdict(
order: Order,
chain_id: int,
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,
... 'makerFeeAssetData': (0).to_bytes(1, byteorder='big') * 20,
... 'takerFeeAssetData': (0).to_bytes(1, byteorder='big') * 20,
... },
... chain_id=50
... ))
{'chainId': 50,
'exchangeAddress': '0x0000000000000000000000000000000000000000',
'expirationTimeSeconds': '1',
'feeRecipientAddress': '0x0000000000000000000000000000000000000000',
'makerAddress': '0x0000000000000000000000000000000000000000',
'makerAssetAmount': '1',
'makerAssetData': '0x0000000000000000000000000000000000000000',
'makerFee': '0',
'makerFeeAssetData': '0x0000000000000000000000000000000000000000',
'salt': '1',
'senderAddress': '0x0000000000000000000000000000000000000000',
'takerAddress': '0x0000000000000000000000000000000000000000',
'takerAssetAmount': '1',
'takerAssetData': '0x0000000000000000000000000000000000000000',
'takerFee': '0',
'takerFeeAssetData': '0x0000000000000000000000000000000000000000'}
"""
jsdict = cast(Dict, copy(order))
def encode_bytes(bytes_or_str: Union[bytes, str]) -> bytes:
def ensure_hex_prefix(hex_str: str):
if hex_str[0:2] != "0x":
hex_str = "0x" + hex_str
return hex_str
return ensure_hex_prefix(
cast(bytes, bytes_or_str).hex()
if isinstance(bytes_or_str, bytes)
else bytes_or_str
)
jsdict["makerAssetData"] = encode_bytes(order["makerAssetData"])
jsdict["takerAssetData"] = encode_bytes(order["takerAssetData"])
jsdict["makerFeeAssetData"] = encode_bytes(order["makerFeeAssetData"])
jsdict["takerFeeAssetData"] = encode_bytes(order["takerFeeAssetData"])
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"])
jsdict["chainId"] = chain_id
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",
... 'makerFeeAssetData': "0x0000000000000000000000000000000000000000",
... 'takerFeeAssetData': "0x0000000000000000000000000000000000000000",
... 'exchangeAddress': "0x0000000000000000000000000000000000000000",
... 'chainId': 50
... },
... ))
{'chainId': 50,
'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,
'makerFeeAssetData': b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
b'\x00\x00\x00\x00\x00\x00\x00\x00',
'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,
'takerFeeAssetData': b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
b'\x00\x00\x00\x00\x00\x00\x00\x00'}
""" # noqa: E501 (line too long)
assert_valid(jsdict, "/orderSchema")
order = cast(Order, copy(jsdict))
order["makerAssetData"] = bytes.fromhex(
remove_0x_prefix(jsdict["makerAssetData"])
)
order["makerFeeAssetData"] = bytes.fromhex(
remove_0x_prefix(jsdict["makerFeeAssetData"])
)
order["takerAssetData"] = bytes.fromhex(
remove_0x_prefix(jsdict["takerAssetData"])
)
order["takerFeeAssetData"] = bytes.fromhex(
remove_0x_prefix(jsdict["takerFeeAssetData"])
)
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

View File

@@ -23,7 +23,7 @@ class TxParams:
gas: Optional[int] = attr.ib(
default=None, converter=attr.converters.optional(int)
)
gasPrice: Optional[int] = attr.ib(
gas_price: Optional[int] = attr.ib(
default=None, converter=attr.converters.optional(int)
)
nonce: Optional[int] = attr.ib(
@@ -36,4 +36,7 @@ class TxParams:
if "from_" in res:
res["from"] = res["from_"]
del res["from_"]
if "gas_price" in res:
res["gasPrice"] = res["gas_price"]
del res["gas_price"]
return res

View File

@@ -26,16 +26,24 @@ class Web3:
class middleware_stack:
@staticmethod
def get(key: str) -> Callable: ...
def inject(
self, middleware_func: object, layer: object
) -> None: ...
...
middleware_onion: middleware_stack
class net:
version: str
...
class eth:
class Eth:
defaultAccount: str
accounts: List[str]
chainId: int
...
class account:
@@ -53,4 +61,7 @@ class Web3:
@staticmethod
def isAddress(address: str) -> bool: ...
...
eth: Eth
...

View File

@@ -5,7 +5,7 @@ from eth_utils import to_checksum_address
from web3 import Web3
from zero_ex.order_utils import asset_data_utils
from zero_ex.contract_addresses import NETWORK_TO_ADDRESSES, NetworkId
from zero_ex.contract_addresses import network_to_addresses, NetworkId
from zero_ex.contract_artifacts import abi_by_name
@@ -36,14 +36,14 @@ def accounts(web3_eth): # pylint: disable=redefined-outer-name
@pytest.fixture(scope="module")
def erc20_proxy_address():
"""Get the 0x ERC20 Proxy address."""
return NETWORK_TO_ADDRESSES[NetworkId.GANACHE].erc20_proxy
return network_to_addresses(NetworkId.GANACHE).erc20_proxy
@pytest.fixture(scope="module")
def weth_asset_data(): # pylint: disable=redefined-outer-name
"""Get 0x asset data for Wrapped Ether (WETH) token."""
return asset_data_utils.encode_erc20(
NETWORK_TO_ADDRESSES[NetworkId.GANACHE].ether_token
network_to_addresses(NetworkId.GANACHE).ether_token
)
@@ -52,7 +52,7 @@ def weth_instance(web3_eth): # pylint: disable=redefined-outer-name
"""Get an instance of the WrapperEther contract."""
return web3_eth.contract(
address=to_checksum_address(
NETWORK_TO_ADDRESSES[NetworkId.GANACHE].ether_token
network_to_addresses(NetworkId.GANACHE).ether_token
),
abi=abi_by_name("WETH9"),
)
@@ -61,7 +61,7 @@ def weth_instance(web3_eth): # pylint: disable=redefined-outer-name
@pytest.fixture(scope="module")
def zrx_address():
"""Get address of ZRX token for Ganache network."""
return NETWORK_TO_ADDRESSES[NetworkId.GANACHE].zrx_token
return network_to_addresses(NetworkId.GANACHE).zrx_token
@pytest.fixture(scope="module")

View File

@@ -2,7 +2,7 @@
import pytest
from zero_ex.contract_addresses import NETWORK_TO_ADDRESSES, NetworkId
from zero_ex.contract_addresses import network_to_addresses, NetworkId
from zero_ex.contract_wrappers.bases import ContractMethod
@@ -10,6 +10,6 @@ from zero_ex.contract_wrappers.bases import ContractMethod
def contract_wrapper(ganache_provider):
"""Get a ContractMethod instance for testing."""
return ContractMethod(
provider=ganache_provider,
contract_address=NETWORK_TO_ADDRESSES[NetworkId.GANACHE].ether_token,
web3_or_provider=ganache_provider,
contract_address=network_to_addresses(NetworkId.GANACHE).ether_token,
)

View File

@@ -4,7 +4,7 @@ from decimal import Decimal
import pytest
from zero_ex.contract_addresses import NETWORK_TO_ADDRESSES, NetworkId
from zero_ex.contract_addresses import network_to_addresses, NetworkId
from zero_ex.contract_wrappers import TxParams
from zero_ex.contract_wrappers.erc20_token import ERC20Token
@@ -16,7 +16,7 @@ MAX_ALLOWANCE = int("{:.0f}".format(Decimal(2) ** 256 - 1))
def erc20_wrapper(ganache_provider):
"""Get an instance of ERC20Token wrapper class for testing."""
return ERC20Token(
ganache_provider, NETWORK_TO_ADDRESSES[NetworkId.GANACHE].ether_token
ganache_provider, network_to_addresses(NetworkId.GANACHE).ether_token
)

View File

@@ -5,20 +5,24 @@ import random
import pytest
from eth_utils import remove_0x_prefix
from zero_ex.contract_addresses import NETWORK_TO_ADDRESSES, NetworkId
from zero_ex.contract_addresses import network_to_addresses, NetworkId
from zero_ex.contract_wrappers import TxParams
from zero_ex.contract_wrappers.exchange import Exchange
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, sign_hash_to_bytes
from zero_ex.order_utils import (
asset_data_utils,
generate_order_hash_hex,
sign_hash,
)
@pytest.fixture(scope="module")
def exchange_wrapper(ganache_provider):
"""Get an Exchange wrapper instance."""
return Exchange(
provider=ganache_provider,
contract_address=NETWORK_TO_ADDRESSES[NetworkId.GANACHE].exchange,
web3_or_provider=ganache_provider,
contract_address=network_to_addresses(NetworkId.GANACHE).exchange,
)
@@ -43,6 +47,8 @@ def create_test_order(
salt=random.randint(1, 1000000000),
makerAssetData=maker_asset_data,
takerAssetData=taker_asset_data,
makerFeeAssetData=asset_data_utils.encode_erc20("0x" + "00" * 20),
takerFeeAssetData=asset_data_utils.encode_erc20("0x" + "00" * 20),
)
return order
@@ -67,16 +73,29 @@ def test_exchange_wrapper__fill_order(
exchange_wrapper, # pylint: disable=redefined-outer-name
ganache_provider,
weth_asset_data,
zrx_asset_data,
):
"""Test filling an order."""
taker = accounts[0]
maker = accounts[1]
exchange_address = exchange_wrapper.contract_address
order = create_test_order(maker, 1, weth_asset_data, 1, weth_asset_data)
order = create_test_order(maker, 1, weth_asset_data, 1, zrx_asset_data)
order_hash = generate_order_hash_hex(
order=order, exchange_address=exchange_address
order=order, exchange_address=exchange_address, chain_id=1337
)
order_signature = sign_hash_to_bytes(ganache_provider, maker, order_hash)
order_signature = sign_hash(ganache_provider, maker, order_hash)
fill_results = exchange_wrapper.fill_order.call(
order=order,
taker_asset_fill_amount=order["takerAssetAmount"],
signature=order_signature,
tx_params=TxParams(from_=taker),
)
assert fill_results[0] == 1
assert fill_results[1] == 1
assert fill_results[2] == 0
assert fill_results[3] == 0
assert fill_results[4] == 0
tx_hash = exchange_wrapper.fill_order.send_transaction(
order=order,
@@ -107,11 +126,13 @@ def test_exchange_wrapper__batch_fill_orders(
orders.append(order_1)
orders.append(order_2)
order_hashes = [
generate_order_hash_hex(order=order, exchange_address=exchange_address)
generate_order_hash_hex(
order=order, exchange_address=exchange_address, chain_id=1337
)
for order in orders
]
order_signatures = [
sign_hash_to_bytes(ganache_provider, maker, order_hash)
sign_hash(ganache_provider, maker, order_hash)
for order_hash in order_hashes
]
taker_amounts = [order["takerAssetAmount"] for order in orders]
@@ -128,3 +149,20 @@ def test_exchange_wrapper__batch_fill_orders(
assert_fill_log(
fill_events[index].args, maker, taker, order, order_hashes[index]
)
def test_two_instantiations_with_web3_objects(web3_instance):
"""Test that instantiating two Exchange objects doesn't raise.
When instantiating an Exchange object with a web3 client (rather than a
provider) there was a bug encountered where web3.py was giving an error
when trying to install the rich-revert-handling middleware on the web3
client, an error saying "can't install this same middleware instance
again." Test that that bug isn't occurring.
"""
exchange = Exchange( # pylint: disable=unused-variable
web3_instance, network_to_addresses(NetworkId.GANACHE).exchange
)
exchange2 = Exchange( # pylint: disable=unused-variable
web3_instance, network_to_addresses(NetworkId.GANACHE).exchange
)

View File

@@ -20,6 +20,7 @@ commands =
pytest test
[testenv:run_tests_against_deployment]
setenv = PY_IGNORE_IMPORTMISMATCH = 1
commands =
pip install 0x-contract-wrappers
pytest test
pip install 0x-contract-wrappers[dev]
pytest --doctest-modules src test