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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
69 changed files with 1996 additions and 941 deletions

View File

@ -192,14 +192,33 @@ jobs:
working_directory: ~/repo
docker:
- image: nikolaik/python-nodejs:python3.7-nodejs8
- image: 0xorg/ganache-cli:2.2.2
- image: 0xorg/launch-kit-backend:74bcc39
- image: 0xorg/ganache-cli:4.4.0-beta.1
environment:
RPC_URL: http://localhost:8545
VERSION: 4.4.0-beta.1
SNAPSHOT_NAME: 0x_ganache_snapshot-v3-beta
- image: 0xorg/mesh:6.0.0-beta-0xv3
environment:
ETHEREUM_RPC_URL: 'http://localhost:8545'
ETHEREUM_NETWORK_ID: '50'
ETHEREUM_CHAIN_ID: '1337'
USE_BOOTSTRAP_LIST: 'true'
VERBOSITY: 3
PRIVATE_KEY_PATH: ''
BLOCK_POLLING_INTERVAL: '5s'
P2P_LISTEN_PORT: '60557'
command: |
sh -c "waitForGanache () { until printf 'POST /\r\nContent-Length: 26\r\n\r\n{\"method\":\"net_listening\"}' | nc localhost 8545 | grep true; do continue; done }; waitForGanache && ./mesh"
- image: 0xorg/launch-kit-backend:v3
environment:
RPC_URL: 'http://localhost:8545'
NETWORK_ID: 50
WHITELIST_ALL_TOKENS: True
FEE_RECIPIENT: '0x0000000000000000000000000000000000000001'
MAKER_FEE_UNIT_AMOUNT: 0
TAKER_FEE_UNIT_AMOUNT: 0
MESH_ENDPOINT: 'ws://localhost:60557'
command: |
sh -c "until printf 'POST /\r\nContent-Length: 26\r\n\r\n{\"method\":\"net_listening\"}' | nc localhost 8545 | grep true; do continue; done; node_modules/.bin/forever ts/lib/index.js"
sh -c "waitForMesh () { sleep 5; }; waitForMesh && node_modules/.bin/forever ts/lib/index.js"
steps:
- checkout
- restore_cache:
@ -221,8 +240,14 @@ jobs:
- run:
command: |
cd python-packages
./parallel_without_sra_client coverage run setup.py test
./parallel coverage run setup.py test
./build_docs
- run:
command: |
# copy generated wrappers into contract_wrappers/build,
# JUST so CircleCI will persist them as build artifacts.
cd python-packages/contract_wrappers/src/zero_ex
for i in contract_wrappers/[^__]*/; do mkdir -p ../../build/$i; cp $i/__init__.py ../../build/$i; done
- save_cache:
key: coverage-python-contract-addresses-{{ .Environment.CIRCLE_SHA1 }}
paths:
@ -247,8 +272,6 @@ jobs:
key: coverage-python-sra-client-{{ .Environment.CIRCLE_SHA1 }}
paths:
- ~/repo/python-packages/sra_client/.coverage
- store_artifacts:
path: ~/repo/python-packages/contract_wrappers/src/zero_ex/contract_wrappers/*/__init__.py
- store_artifacts:
path: ~/repo/python-packages/contract_addresses/build
- store_artifacts:
@ -426,12 +449,11 @@ workflows:
- test-exchange-ganache-3.0
- test-rest
- static-tests
# - test-python:
# requires:
# - build
# - test-rest
# - static-tests-python:
# requires:
# - test-python
- test-python:
requires:
- build
- static-tests-python:
requires:
- build
# skip python tox run for now, as we don't yet have multiple test environments to support.
# - test-rest-python

10
.gitignore vendored
View File

@ -130,6 +130,7 @@ contracts/erc1155/generated-wrappers/
contracts/extensions/generated-wrappers/
contracts/exchange-forwarder/generated-wrappers/
contracts/dev-utils/generated-wrappers/
python-packages/contract_wrappers/src/zero_ex/contract_wrappers/dev_utils/__init__.py
python-packages/contract_wrappers/src/zero_ex/contract_wrappers/erc20_token/__init__.py
python-packages/contract_wrappers/src/zero_ex/contract_wrappers/exchange/__init__.py
python-packages/contract_wrappers/src/zero_ex/contract_wrappers/asset_proxy_owner/__init__.py
@ -138,6 +139,8 @@ python-packages/contract_wrappers/src/zero_ex/contract_wrappers/coordinator_regi
python-packages/contract_wrappers/src/zero_ex/contract_wrappers/dummy_erc20_token/__init__.py
python-packages/contract_wrappers/src/zero_ex/contract_wrappers/dummy_erc721_token/__init__.py
python-packages/contract_wrappers/src/zero_ex/contract_wrappers/dutch_auction/__init__.py
python-packages/contract_wrappers/src/zero_ex/contract_wrappers/erc1155_mintable/__init__.py
python-packages/contract_wrappers/src/zero_ex/contract_wrappers/erc1155_proxy/__init__.py
python-packages/contract_wrappers/src/zero_ex/contract_wrappers/erc20_proxy/__init__.py
python-packages/contract_wrappers/src/zero_ex/contract_wrappers/erc721_proxy/__init__.py
python-packages/contract_wrappers/src/zero_ex/contract_wrappers/erc721_token/__init__.py
@ -148,6 +151,7 @@ python-packages/contract_wrappers/src/zero_ex/contract_wrappers/i_validator/__in
python-packages/contract_wrappers/src/zero_ex/contract_wrappers/i_wallet/__init__.py
python-packages/contract_wrappers/src/zero_ex/contract_wrappers/multi_asset_proxy/__init__.py
python-packages/contract_wrappers/src/zero_ex/contract_wrappers/order_validator/__init__.py
python-packages/contract_wrappers/src/zero_ex/contract_wrappers/static_call_proxy/__init__.py
python-packages/contract_wrappers/src/zero_ex/contract_wrappers/weth9/__init__.py
python-packages/contract_wrappers/src/zero_ex/contract_wrappers/zrx_token/__init__.py
@ -164,10 +168,14 @@ __pycache__
python-packages/*/src/*.egg-info
python-packages/*/.coverage
# python keeps package-local copies of json schemas
# python keeps package-local copies of json schemas and contract addresses
python-packages/json_schemas/src/zero_ex/json_schemas/schemas
python-packages/contract_addresses/src/zero_ex/contract_addresses/addresses.json
# Doc README copy
packages/*/docs/README.md
.DS_Store
# the snapshot that gets built for migrations sure does have a ton of files
packages/migrations/0x_ganache_snapshot*

View File

@ -1,4 +1,25 @@
[
{
"version": "4.5.0-beta.0",
"changes": [
{
"note": "In Python wrappers, accept string arguments to bytes parameters",
"pr": 2284
},
{
"note": "In Python wrappers, support module-local, Web3.py-compatible middleware",
"pr": 2284
},
{
"note": "In Python wrappers, allow contracts to be instantiated with EITHER a Web3.py BaseProvider OR a Web3 client object",
"pr": 2284
},
{
"note": "In Python wrappers, fix bug with casting some bytes objects using bytes.fromhex()",
"pr": 2284
}
]
},
{
"version": "4.4.0-beta.0",
"changes": [

View File

@ -115,7 +115,7 @@ export const utils = {
{ regex: '^address$', pyType: 'str' },
{ regex: '^bool$', pyType: 'bool' },
{ regex: '^u?int\\d*$', pyType: 'int' },
{ regex: '^bytes\\d*$', pyType: 'bytes' },
{ regex: '^bytes\\d*$', pyType: 'Union[bytes, str]' },
];
for (const regexAndTxType of solTypeRegexToPyType) {
const { regex, pyType } = regexAndTxType;

View File

@ -40,6 +40,12 @@ except ImportError:
"""No-op input validator."""
try:
from .middleware import MIDDLEWARE # type: ignore
except ImportError:
pass
{{tupleDefinitions ABIString}}
{{#each methods}}
@ -59,30 +65,57 @@ class {{contractName}}:
def __init__(
self,
provider: BaseProvider,
web3_or_provider: Union[Web3, BaseProvider],
contract_address: str,
validator: {{contractName}}Validator = None,
):
"""Get an instance of wrapper for smart contract.
:param provider: instance of :class:`web3.providers.base.BaseProvider`
:param web3_or_provider: Either an instance of `web3.Web3`:code: or
`web3.providers.base.BaseProvider`:code:
:param contract_address: where the contract has been deployed
:param validator: for validation of method inputs.
"""
# pylint: disable=too-many-statements
self.contract_address = contract_address
if not validator:
validator = {{contractName}}Validator(provider, contract_address)
validator = {{contractName}}Validator(web3_or_provider, contract_address)
self._web3_eth = Web3( # type: ignore # pylint: disable=no-member
provider
).eth
web3 = None
if isinstance(web3_or_provider, BaseProvider):
web3 = Web3(web3_or_provider)
elif isinstance(web3_or_provider, Web3):
web3 = web3_or_provider
else:
raise TypeError(
"Expected parameter 'web3_or_provider' to be an instance of either"
+ " Web3 or BaseProvider"
)
# if any middleware was imported, inject it
try:
MIDDLEWARE
except NameError:
pass
else:
try:
for middleware in MIDDLEWARE:
web3.middleware_onion.inject(
middleware['function'], layer=middleware['layer'],
)
except ValueError as value_error:
if value_error.args == ("You can't add the same un-named instance twice",):
pass
self._web3_eth = web3.eth
{{#if methods}}
functions = self._web3_eth.contract(address=to_checksum_address(contract_address), abi={{contractName}}.abi()).functions
{{#each methods}}
self.{{toPythonIdentifier this.languageSpecificName}} = {{toPythonClassname this.languageSpecificName}}Method(provider, contract_address, functions.{{this.name}}, validator)
self.{{toPythonIdentifier this.languageSpecificName}} = {{toPythonClassname this.languageSpecificName}}Method(web3_or_provider, contract_address, functions.{{this.name}}, validator)
{{/each}}
{{/if}}

View File

@ -2,9 +2,9 @@
class {{toPythonClassname this.languageSpecificName}}Method(ContractMethod):
"""Various interfaces to the {{this.name}} method."""
def __init__(self, provider: BaseProvider, contract_address: str, contract_function: ContractFunction, validator: Validator=None):
def __init__(self, web3_or_provider: Union[Web3, BaseProvider], contract_address: str, contract_function: ContractFunction, validator: Validator=None):
"""Persist instance data."""
super().__init__(provider, contract_address, validator)
super().__init__(web3_or_provider, contract_address, validator)
self.underlying_method = contract_function
{{#if inputs}}
@ -21,13 +21,6 @@ class {{toPythonClassname this.languageSpecificName}}Method(ContractMethod):
{{else if (equal type 'uint256')}}
# safeguard against fractional inputs
{{toPythonIdentifier this.name}} = int({{toPythonIdentifier this.name}})
{{else if (equal type 'bytes')}}
{{toPythonIdentifier this.name}} = bytes.fromhex({{toPythonIdentifier this.name}}.decode("utf-8"))
{{else if (equal type 'bytes[]')}}
{{toPythonIdentifier this.name}} = [
bytes.fromhex({{toPythonIdentifier this.name}}_element.decode("utf-8"))
for {{toPythonIdentifier this.name}}_element in {{toPythonIdentifier this.name}}
]
{{/if}}
{{/each}}
return ({{> params }})

View File

@ -40,6 +40,12 @@ except ImportError:
"""No-op input validator."""
try:
from .middleware import MIDDLEWARE # type: ignore
except ImportError:
pass
class Tuple0x246f9407(TypedDict):
"""Python representation of a tuple or struct.
@ -94,11 +100,11 @@ class Tuple0xcf8ad995(TypedDict):
accomplished via `str.encode("utf_8")`:code:
"""
someBytes: bytes
someBytes: Union[bytes, str]
anInteger: int
aDynamicArrayOfBytes: List[bytes]
aDynamicArrayOfBytes: List[Union[bytes, str]]
aString: str
@ -142,7 +148,7 @@ class Tuple0xf95128ef(TypedDict):
foo: int
bar: bytes
bar: Union[bytes, str]
car: str
@ -165,9 +171,9 @@ class Tuple0xa057bf41(TypedDict):
input: Tuple0xf95128ef
lorem: bytes
lorem: Union[bytes, str]
ipsum: bytes
ipsum: Union[bytes, str]
dolor: str
@ -177,13 +183,13 @@ class SimpleRequireMethod(ContractMethod):
def __init__(
self,
provider: BaseProvider,
web3_or_provider: Union[Web3, BaseProvider],
contract_address: str,
contract_function: ContractFunction,
validator: Validator = None,
):
"""Persist instance data."""
super().__init__(provider, contract_address, validator)
super().__init__(web3_or_provider, contract_address, validator)
self.underlying_method = contract_function
def call(self, tx_params: Optional[TxParams] = None) -> None:
@ -217,27 +223,26 @@ class AcceptsAnArrayOfBytesMethod(ContractMethod):
def __init__(
self,
provider: BaseProvider,
web3_or_provider: Union[Web3, BaseProvider],
contract_address: str,
contract_function: ContractFunction,
validator: Validator = None,
):
"""Persist instance data."""
super().__init__(provider, contract_address, validator)
super().__init__(web3_or_provider, contract_address, validator)
self.underlying_method = contract_function
def validate_and_normalize_inputs(self, a: List[bytes]):
def validate_and_normalize_inputs(self, a: List[Union[bytes, str]]):
"""Validate the inputs to the acceptsAnArrayOfBytes method."""
self.validator.assert_valid(
method_name="acceptsAnArrayOfBytes",
parameter_name="a",
argument_value=a,
)
a = [bytes.fromhex(a_element.decode("utf-8")) for a_element in a]
return a
def call(
self, a: List[bytes], tx_params: Optional[TxParams] = None
self, a: List[Union[bytes, str]], tx_params: Optional[TxParams] = None
) -> None:
"""Execute underlying contract method via eth_call.
@ -252,7 +257,7 @@ class AcceptsAnArrayOfBytesMethod(ContractMethod):
return self.underlying_method(a).call(tx_params.as_dict())
def send_transaction(
self, a: List[bytes], tx_params: Optional[TxParams] = None
self, a: List[Union[bytes, str]], tx_params: Optional[TxParams] = None
) -> Union[HexBytes, bytes]:
"""Execute underlying contract method via eth_sendTransaction.
@ -267,7 +272,7 @@ class AcceptsAnArrayOfBytesMethod(ContractMethod):
return self.underlying_method(a).transact(tx_params.as_dict())
def estimate_gas(
self, a: List[bytes], tx_params: Optional[TxParams] = None
self, a: List[Union[bytes, str]], tx_params: Optional[TxParams] = None
) -> int:
"""Estimate gas consumption of method call."""
(a) = self.validate_and_normalize_inputs(a)
@ -280,13 +285,13 @@ class SimpleInputSimpleOutputMethod(ContractMethod):
def __init__(
self,
provider: BaseProvider,
web3_or_provider: Union[Web3, BaseProvider],
contract_address: str,
contract_function: ContractFunction,
validator: Validator = None,
):
"""Persist instance data."""
super().__init__(provider, contract_address, validator)
super().__init__(web3_or_provider, contract_address, validator)
self.underlying_method = contract_function
def validate_and_normalize_inputs(self, index_0: int):
@ -340,13 +345,13 @@ class WithdrawMethod(ContractMethod):
def __init__(
self,
provider: BaseProvider,
web3_or_provider: Union[Web3, BaseProvider],
contract_address: str,
contract_function: ContractFunction,
validator: Validator = None,
):
"""Persist instance data."""
super().__init__(provider, contract_address, validator)
super().__init__(web3_or_provider, contract_address, validator)
self.underlying_method = contract_function
def validate_and_normalize_inputs(self, wad: int):
@ -395,17 +400,17 @@ class MultiInputMultiOutputMethod(ContractMethod):
def __init__(
self,
provider: BaseProvider,
web3_or_provider: Union[Web3, BaseProvider],
contract_address: str,
contract_function: ContractFunction,
validator: Validator = None,
):
"""Persist instance data."""
super().__init__(provider, contract_address, validator)
super().__init__(web3_or_provider, contract_address, validator)
self.underlying_method = contract_function
def validate_and_normalize_inputs(
self, index_0: int, index_1: bytes, index_2: str
self, index_0: int, index_1: Union[bytes, str], index_2: str
):
"""Validate the inputs to the multiInputMultiOutput method."""
self.validator.assert_valid(
@ -420,7 +425,6 @@ class MultiInputMultiOutputMethod(ContractMethod):
parameter_name="index_1",
argument_value=index_1,
)
index_1 = bytes.fromhex(index_1.decode("utf-8"))
self.validator.assert_valid(
method_name="multiInputMultiOutput",
parameter_name="index_2",
@ -431,10 +435,10 @@ class MultiInputMultiOutputMethod(ContractMethod):
def call(
self,
index_0: int,
index_1: bytes,
index_1: Union[bytes, str],
index_2: str,
tx_params: Optional[TxParams] = None,
) -> Tuple[bytes, bytes, str]:
) -> Tuple[Union[bytes, str], Union[bytes, str], str]:
"""Execute underlying contract method via eth_call.
Tests decoding when the input and output are complex and have more than
@ -454,7 +458,7 @@ class MultiInputMultiOutputMethod(ContractMethod):
def send_transaction(
self,
index_0: int,
index_1: bytes,
index_1: Union[bytes, str],
index_2: str,
tx_params: Optional[TxParams] = None,
) -> Union[HexBytes, bytes]:
@ -477,7 +481,7 @@ class MultiInputMultiOutputMethod(ContractMethod):
def estimate_gas(
self,
index_0: int,
index_1: bytes,
index_1: Union[bytes, str],
index_2: str,
tx_params: Optional[TxParams] = None,
) -> int:
@ -496,17 +500,21 @@ class EcrecoverFnMethod(ContractMethod):
def __init__(
self,
provider: BaseProvider,
web3_or_provider: Union[Web3, BaseProvider],
contract_address: str,
contract_function: ContractFunction,
validator: Validator = None,
):
"""Persist instance data."""
super().__init__(provider, contract_address, validator)
super().__init__(web3_or_provider, contract_address, validator)
self.underlying_method = contract_function
def validate_and_normalize_inputs(
self, _hash: bytes, v: int, r: bytes, s: bytes
self,
_hash: Union[bytes, str],
v: int,
r: Union[bytes, str],
s: Union[bytes, str],
):
"""Validate the inputs to the ecrecoverFn method."""
self.validator.assert_valid(
@ -527,10 +535,10 @@ class EcrecoverFnMethod(ContractMethod):
def call(
self,
_hash: bytes,
_hash: Union[bytes, str],
v: int,
r: bytes,
s: bytes,
r: Union[bytes, str],
s: Union[bytes, str],
tx_params: Optional[TxParams] = None,
) -> str:
"""Execute underlying contract method via eth_call.
@ -555,10 +563,10 @@ class EcrecoverFnMethod(ContractMethod):
def send_transaction(
self,
_hash: bytes,
_hash: Union[bytes, str],
v: int,
r: bytes,
s: bytes,
r: Union[bytes, str],
s: Union[bytes, str],
tx_params: Optional[TxParams] = None,
) -> Union[HexBytes, bytes]:
"""Execute underlying contract method via eth_sendTransaction.
@ -585,10 +593,10 @@ class EcrecoverFnMethod(ContractMethod):
def estimate_gas(
self,
_hash: bytes,
_hash: Union[bytes, str],
v: int,
r: bytes,
s: bytes,
r: Union[bytes, str],
s: Union[bytes, str],
tx_params: Optional[TxParams] = None,
) -> int:
"""Estimate gas consumption of method call."""
@ -604,24 +612,25 @@ class AcceptsBytesMethod(ContractMethod):
def __init__(
self,
provider: BaseProvider,
web3_or_provider: Union[Web3, BaseProvider],
contract_address: str,
contract_function: ContractFunction,
validator: Validator = None,
):
"""Persist instance data."""
super().__init__(provider, contract_address, validator)
super().__init__(web3_or_provider, contract_address, validator)
self.underlying_method = contract_function
def validate_and_normalize_inputs(self, a: bytes):
def validate_and_normalize_inputs(self, a: Union[bytes, str]):
"""Validate the inputs to the acceptsBytes method."""
self.validator.assert_valid(
method_name="acceptsBytes", parameter_name="a", argument_value=a
)
a = bytes.fromhex(a.decode("utf-8"))
return a
def call(self, a: bytes, tx_params: Optional[TxParams] = None) -> None:
def call(
self, a: Union[bytes, str], tx_params: Optional[TxParams] = None
) -> None:
"""Execute underlying contract method via eth_call.
:param tx_params: transaction parameters
@ -632,7 +641,7 @@ class AcceptsBytesMethod(ContractMethod):
return self.underlying_method(a).call(tx_params.as_dict())
def send_transaction(
self, a: bytes, tx_params: Optional[TxParams] = None
self, a: Union[bytes, str], tx_params: Optional[TxParams] = None
) -> Union[HexBytes, bytes]:
"""Execute underlying contract method via eth_sendTransaction.
@ -644,7 +653,7 @@ class AcceptsBytesMethod(ContractMethod):
return self.underlying_method(a).transact(tx_params.as_dict())
def estimate_gas(
self, a: bytes, tx_params: Optional[TxParams] = None
self, a: Union[bytes, str], tx_params: Optional[TxParams] = None
) -> int:
"""Estimate gas consumption of method call."""
(a) = self.validate_and_normalize_inputs(a)
@ -657,13 +666,13 @@ class NoInputSimpleOutputMethod(ContractMethod):
def __init__(
self,
provider: BaseProvider,
web3_or_provider: Union[Web3, BaseProvider],
contract_address: str,
contract_function: ContractFunction,
validator: Validator = None,
):
"""Persist instance data."""
super().__init__(provider, contract_address, validator)
super().__init__(web3_or_provider, contract_address, validator)
self.underlying_method = contract_function
def call(self, tx_params: Optional[TxParams] = None) -> int:
@ -701,13 +710,13 @@ class RevertWithConstantMethod(ContractMethod):
def __init__(
self,
provider: BaseProvider,
web3_or_provider: Union[Web3, BaseProvider],
contract_address: str,
contract_function: ContractFunction,
validator: Validator = None,
):
"""Persist instance data."""
super().__init__(provider, contract_address, validator)
super().__init__(web3_or_provider, contract_address, validator)
self.underlying_method = contract_function
def call(self, tx_params: Optional[TxParams] = None) -> None:
@ -741,13 +750,13 @@ class SimpleRevertMethod(ContractMethod):
def __init__(
self,
provider: BaseProvider,
web3_or_provider: Union[Web3, BaseProvider],
contract_address: str,
contract_function: ContractFunction,
validator: Validator = None,
):
"""Persist instance data."""
super().__init__(provider, contract_address, validator)
super().__init__(web3_or_provider, contract_address, validator)
self.underlying_method = contract_function
def call(self, tx_params: Optional[TxParams] = None) -> None:
@ -783,13 +792,13 @@ class MethodUsingNestedStructWithInnerStructNotUsedElsewhereMethod(
def __init__(
self,
provider: BaseProvider,
web3_or_provider: Union[Web3, BaseProvider],
contract_address: str,
contract_function: ContractFunction,
validator: Validator = None,
):
"""Persist instance data."""
super().__init__(provider, contract_address, validator)
super().__init__(web3_or_provider, contract_address, validator)
self.underlying_method = contract_function
def call(self, tx_params: Optional[TxParams] = None) -> Tuple0x1b9da225:
@ -823,13 +832,13 @@ class NestedStructOutputMethod(ContractMethod):
def __init__(
self,
provider: BaseProvider,
web3_or_provider: Union[Web3, BaseProvider],
contract_address: str,
contract_function: ContractFunction,
validator: Validator = None,
):
"""Persist instance data."""
super().__init__(provider, contract_address, validator)
super().__init__(web3_or_provider, contract_address, validator)
self.underlying_method = contract_function
def call(self, tx_params: Optional[TxParams] = None) -> Tuple0xc9bdd2d5:
@ -863,13 +872,13 @@ class RequireWithConstantMethod(ContractMethod):
def __init__(
self,
provider: BaseProvider,
web3_or_provider: Union[Web3, BaseProvider],
contract_address: str,
contract_function: ContractFunction,
validator: Validator = None,
):
"""Persist instance data."""
super().__init__(provider, contract_address, validator)
super().__init__(web3_or_provider, contract_address, validator)
self.underlying_method = contract_function
def call(self, tx_params: Optional[TxParams] = None) -> None:
@ -903,13 +912,13 @@ class WithAddressInputMethod(ContractMethod):
def __init__(
self,
provider: BaseProvider,
web3_or_provider: Union[Web3, BaseProvider],
contract_address: str,
contract_function: ContractFunction,
validator: Validator = None,
):
"""Persist instance data."""
super().__init__(provider, contract_address, validator)
super().__init__(web3_or_provider, contract_address, validator)
self.underlying_method = contract_function
def validate_and_normalize_inputs(
@ -1011,13 +1020,13 @@ class StructInputMethod(ContractMethod):
def __init__(
self,
provider: BaseProvider,
web3_or_provider: Union[Web3, BaseProvider],
contract_address: str,
contract_function: ContractFunction,
validator: Validator = None,
):
"""Persist instance data."""
super().__init__(provider, contract_address, validator)
super().__init__(web3_or_provider, contract_address, validator)
self.underlying_method = contract_function
def validate_and_normalize_inputs(self, s: Tuple0xcf8ad995):
@ -1065,13 +1074,13 @@ class NonPureMethodMethod(ContractMethod):
def __init__(
self,
provider: BaseProvider,
web3_or_provider: Union[Web3, BaseProvider],
contract_address: str,
contract_function: ContractFunction,
validator: Validator = None,
):
"""Persist instance data."""
super().__init__(provider, contract_address, validator)
super().__init__(web3_or_provider, contract_address, validator)
self.underlying_method = contract_function
def call(
@ -1106,13 +1115,13 @@ class ComplexInputComplexOutputMethod(ContractMethod):
def __init__(
self,
provider: BaseProvider,
web3_or_provider: Union[Web3, BaseProvider],
contract_address: str,
contract_function: ContractFunction,
validator: Validator = None,
):
"""Persist instance data."""
super().__init__(provider, contract_address, validator)
super().__init__(web3_or_provider, contract_address, validator)
self.underlying_method = contract_function
def validate_and_normalize_inputs(self, complex_input: Tuple0xf95128ef):
@ -1176,13 +1185,13 @@ class NoInputNoOutputMethod(ContractMethod):
def __init__(
self,
provider: BaseProvider,
web3_or_provider: Union[Web3, BaseProvider],
contract_address: str,
contract_function: ContractFunction,
validator: Validator = None,
):
"""Persist instance data."""
super().__init__(provider, contract_address, validator)
super().__init__(web3_or_provider, contract_address, validator)
self.underlying_method = contract_function
def call(self, tx_params: Optional[TxParams] = None) -> None:
@ -1220,13 +1229,13 @@ class SimplePureFunctionWithInputMethod(ContractMethod):
def __init__(
self,
provider: BaseProvider,
web3_or_provider: Union[Web3, BaseProvider],
contract_address: str,
contract_function: ContractFunction,
validator: Validator = None,
):
"""Persist instance data."""
super().__init__(provider, contract_address, validator)
super().__init__(web3_or_provider, contract_address, validator)
self.underlying_method = contract_function
def validate_and_normalize_inputs(self, x: int):
@ -1276,13 +1285,13 @@ class NonPureMethodThatReturnsNothingMethod(ContractMethod):
def __init__(
self,
provider: BaseProvider,
web3_or_provider: Union[Web3, BaseProvider],
contract_address: str,
contract_function: ContractFunction,
validator: Validator = None,
):
"""Persist instance data."""
super().__init__(provider, contract_address, validator)
super().__init__(web3_or_provider, contract_address, validator)
self.underlying_method = contract_function
def call(
@ -1317,13 +1326,13 @@ class SimplePureFunctionMethod(ContractMethod):
def __init__(
self,
provider: BaseProvider,
web3_or_provider: Union[Web3, BaseProvider],
contract_address: str,
contract_function: ContractFunction,
validator: Validator = None,
):
"""Persist instance data."""
super().__init__(provider, contract_address, validator)
super().__init__(web3_or_provider, contract_address, validator)
self.underlying_method = contract_function
def call(self, tx_params: Optional[TxParams] = None) -> int:
@ -1357,13 +1366,13 @@ class NestedStructInputMethod(ContractMethod):
def __init__(
self,
provider: BaseProvider,
web3_or_provider: Union[Web3, BaseProvider],
contract_address: str,
contract_function: ContractFunction,
validator: Validator = None,
):
"""Persist instance data."""
super().__init__(provider, contract_address, validator)
super().__init__(web3_or_provider, contract_address, validator)
self.underlying_method = contract_function
def validate_and_normalize_inputs(self, n: Tuple0xc9bdd2d5):
@ -1413,13 +1422,13 @@ class MethodReturningMultipleValuesMethod(ContractMethod):
def __init__(
self,
provider: BaseProvider,
web3_or_provider: Union[Web3, BaseProvider],
contract_address: str,
contract_function: ContractFunction,
validator: Validator = None,
):
"""Persist instance data."""
super().__init__(provider, contract_address, validator)
super().__init__(web3_or_provider, contract_address, validator)
self.underlying_method = contract_function
def call(self, tx_params: Optional[TxParams] = None) -> Tuple[int, str]:
@ -1453,13 +1462,13 @@ class MethodReturningArrayOfStructsMethod(ContractMethod):
def __init__(
self,
provider: BaseProvider,
web3_or_provider: Union[Web3, BaseProvider],
contract_address: str,
contract_function: ContractFunction,
validator: Validator = None,
):
"""Persist instance data."""
super().__init__(provider, contract_address, validator)
super().__init__(web3_or_provider, contract_address, validator)
self.underlying_method = contract_function
def call(
@ -1495,13 +1504,13 @@ class EmitSimpleEventMethod(ContractMethod):
def __init__(
self,
provider: BaseProvider,
web3_or_provider: Union[Web3, BaseProvider],
contract_address: str,
contract_function: ContractFunction,
validator: Validator = None,
):
"""Persist instance data."""
super().__init__(provider, contract_address, validator)
super().__init__(web3_or_provider, contract_address, validator)
self.underlying_method = contract_function
def call(
@ -1536,13 +1545,13 @@ class StructOutputMethod(ContractMethod):
def __init__(
self,
provider: BaseProvider,
web3_or_provider: Union[Web3, BaseProvider],
contract_address: str,
contract_function: ContractFunction,
validator: Validator = None,
):
"""Persist instance data."""
super().__init__(provider, contract_address, validator)
super().__init__(web3_or_provider, contract_address, validator)
self.underlying_method = contract_function
def call(self, tx_params: Optional[TxParams] = None) -> Tuple0xcf8ad995:
@ -1580,13 +1589,13 @@ class PureFunctionWithConstantMethod(ContractMethod):
def __init__(
self,
provider: BaseProvider,
web3_or_provider: Union[Web3, BaseProvider],
contract_address: str,
contract_function: ContractFunction,
validator: Validator = None,
):
"""Persist instance data."""
super().__init__(provider, contract_address, validator)
super().__init__(web3_or_provider, contract_address, validator)
self.underlying_method = contract_function
def call(self, tx_params: Optional[TxParams] = None) -> int:
@ -1620,13 +1629,13 @@ class SimpleInputNoOutputMethod(ContractMethod):
def __init__(
self,
provider: BaseProvider,
web3_or_provider: Union[Web3, BaseProvider],
contract_address: str,
contract_function: ContractFunction,
validator: Validator = None,
):
"""Persist instance data."""
super().__init__(provider, contract_address, validator)
super().__init__(web3_or_provider, contract_address, validator)
self.underlying_method = contract_function
def validate_and_normalize_inputs(self, index_0: int):
@ -1680,13 +1689,13 @@ class OverloadedMethod2Method(ContractMethod):
def __init__(
self,
provider: BaseProvider,
web3_or_provider: Union[Web3, BaseProvider],
contract_address: str,
contract_function: ContractFunction,
validator: Validator = None,
):
"""Persist instance data."""
super().__init__(provider, contract_address, validator)
super().__init__(web3_or_provider, contract_address, validator)
self.underlying_method = contract_function
def validate_and_normalize_inputs(self, a: str):
@ -1734,13 +1743,13 @@ class OverloadedMethod1Method(ContractMethod):
def __init__(
self,
provider: BaseProvider,
web3_or_provider: Union[Web3, BaseProvider],
contract_address: str,
contract_function: ContractFunction,
validator: Validator = None,
):
"""Persist instance data."""
super().__init__(provider, contract_address, validator)
super().__init__(web3_or_provider, contract_address, validator)
self.underlying_method = contract_function
def validate_and_normalize_inputs(self, a: int):
@ -1943,24 +1952,55 @@ class AbiGenDummy:
def __init__(
self,
provider: BaseProvider,
web3_or_provider: Union[Web3, BaseProvider],
contract_address: str,
validator: AbiGenDummyValidator = None,
):
"""Get an instance of wrapper for smart contract.
:param provider: instance of :class:`web3.providers.base.BaseProvider`
:param web3_or_provider: Either an instance of `web3.Web3`:code: or
`web3.providers.base.BaseProvider`:code:
:param contract_address: where the contract has been deployed
:param validator: for validation of method inputs.
"""
# pylint: disable=too-many-statements
self.contract_address = contract_address
if not validator:
validator = AbiGenDummyValidator(provider, contract_address)
validator = AbiGenDummyValidator(
web3_or_provider, contract_address
)
self._web3_eth = Web3( # type: ignore # pylint: disable=no-member
provider
).eth
web3 = None
if isinstance(web3_or_provider, BaseProvider):
web3 = Web3(web3_or_provider)
elif isinstance(web3_or_provider, Web3):
web3 = web3_or_provider
else:
raise TypeError(
"Expected parameter 'web3_or_provider' to be an instance of either"
+ " Web3 or BaseProvider"
)
# if any middleware was imported, inject it
try:
MIDDLEWARE
except NameError:
pass
else:
try:
for middleware in MIDDLEWARE:
web3.middleware_onion.inject(
middleware["function"], layer=middleware["layer"]
)
except ValueError as value_error:
if value_error.args == (
"You can't add the same un-named instance twice",
):
pass
self._web3_eth = web3.eth
functions = self._web3_eth.contract(
address=to_checksum_address(contract_address),
@ -1968,162 +2008,210 @@ class AbiGenDummy:
).functions
self.simple_require = SimpleRequireMethod(
provider, contract_address, functions.simpleRequire, validator
web3_or_provider,
contract_address,
functions.simpleRequire,
validator,
)
self.accepts_an_array_of_bytes = AcceptsAnArrayOfBytesMethod(
provider,
web3_or_provider,
contract_address,
functions.acceptsAnArrayOfBytes,
validator,
)
self.simple_input_simple_output = SimpleInputSimpleOutputMethod(
provider,
web3_or_provider,
contract_address,
functions.simpleInputSimpleOutput,
validator,
)
self.withdraw = WithdrawMethod(
provider, contract_address, functions.withdraw, validator
web3_or_provider, contract_address, functions.withdraw, validator
)
self.multi_input_multi_output = MultiInputMultiOutputMethod(
provider,
web3_or_provider,
contract_address,
functions.multiInputMultiOutput,
validator,
)
self.ecrecover_fn = EcrecoverFnMethod(
provider, contract_address, functions.ecrecoverFn, validator
web3_or_provider,
contract_address,
functions.ecrecoverFn,
validator,
)
self.accepts_bytes = AcceptsBytesMethod(
provider, contract_address, functions.acceptsBytes, validator
web3_or_provider,
contract_address,
functions.acceptsBytes,
validator,
)
self.no_input_simple_output = NoInputSimpleOutputMethod(
provider,
web3_or_provider,
contract_address,
functions.noInputSimpleOutput,
validator,
)
self.revert_with_constant = RevertWithConstantMethod(
provider, contract_address, functions.revertWithConstant, validator
web3_or_provider,
contract_address,
functions.revertWithConstant,
validator,
)
self.simple_revert = SimpleRevertMethod(
provider, contract_address, functions.simpleRevert, validator
web3_or_provider,
contract_address,
functions.simpleRevert,
validator,
)
self.method_using_nested_struct_with_inner_struct_not_used_elsewhere = MethodUsingNestedStructWithInnerStructNotUsedElsewhereMethod(
provider,
web3_or_provider,
contract_address,
functions.methodUsingNestedStructWithInnerStructNotUsedElsewhere,
validator,
)
self.nested_struct_output = NestedStructOutputMethod(
provider, contract_address, functions.nestedStructOutput, validator
web3_or_provider,
contract_address,
functions.nestedStructOutput,
validator,
)
self.require_with_constant = RequireWithConstantMethod(
provider,
web3_or_provider,
contract_address,
functions.requireWithConstant,
validator,
)
self.with_address_input = WithAddressInputMethod(
provider, contract_address, functions.withAddressInput, validator
web3_or_provider,
contract_address,
functions.withAddressInput,
validator,
)
self.struct_input = StructInputMethod(
provider, contract_address, functions.structInput, validator
web3_or_provider,
contract_address,
functions.structInput,
validator,
)
self.non_pure_method = NonPureMethodMethod(
provider, contract_address, functions.nonPureMethod, validator
web3_or_provider,
contract_address,
functions.nonPureMethod,
validator,
)
self.complex_input_complex_output = ComplexInputComplexOutputMethod(
provider,
web3_or_provider,
contract_address,
functions.complexInputComplexOutput,
validator,
)
self.no_input_no_output = NoInputNoOutputMethod(
provider, contract_address, functions.noInputNoOutput, validator
web3_or_provider,
contract_address,
functions.noInputNoOutput,
validator,
)
self.simple_pure_function_with_input = SimplePureFunctionWithInputMethod(
provider,
web3_or_provider,
contract_address,
functions.simplePureFunctionWithInput,
validator,
)
self.non_pure_method_that_returns_nothing = NonPureMethodThatReturnsNothingMethod(
provider,
web3_or_provider,
contract_address,
functions.nonPureMethodThatReturnsNothing,
validator,
)
self.simple_pure_function = SimplePureFunctionMethod(
provider, contract_address, functions.simplePureFunction, validator
web3_or_provider,
contract_address,
functions.simplePureFunction,
validator,
)
self.nested_struct_input = NestedStructInputMethod(
provider, contract_address, functions.nestedStructInput, validator
web3_or_provider,
contract_address,
functions.nestedStructInput,
validator,
)
self.method_returning_multiple_values = MethodReturningMultipleValuesMethod(
provider,
web3_or_provider,
contract_address,
functions.methodReturningMultipleValues,
validator,
)
self.method_returning_array_of_structs = MethodReturningArrayOfStructsMethod(
provider,
web3_or_provider,
contract_address,
functions.methodReturningArrayOfStructs,
validator,
)
self.emit_simple_event = EmitSimpleEventMethod(
provider, contract_address, functions.emitSimpleEvent, validator
web3_or_provider,
contract_address,
functions.emitSimpleEvent,
validator,
)
self.struct_output = StructOutputMethod(
provider, contract_address, functions.structOutput, validator
web3_or_provider,
contract_address,
functions.structOutput,
validator,
)
self.pure_function_with_constant = PureFunctionWithConstantMethod(
provider,
web3_or_provider,
contract_address,
functions.pureFunctionWithConstant,
validator,
)
self.simple_input_no_output = SimpleInputNoOutputMethod(
provider,
web3_or_provider,
contract_address,
functions.simpleInputNoOutput,
validator,
)
self.overloaded_method2 = OverloadedMethod2Method(
provider, contract_address, functions.overloadedMethod, validator
web3_or_provider,
contract_address,
functions.overloadedMethod,
validator,
)
self.overloaded_method1 = OverloadedMethod1Method(
provider, contract_address, functions.overloadedMethod, validator
web3_or_provider,
contract_address,
functions.overloadedMethod,
validator,
)
def get_withdrawal_event(

View File

@ -40,30 +40,65 @@ except ImportError:
"""No-op input validator."""
try:
from .middleware import MIDDLEWARE # type: ignore
except ImportError:
pass
# pylint: disable=too-many-public-methods,too-many-instance-attributes
class LibDummy:
"""Wrapper class for LibDummy Solidity contract."""
def __init__(
self,
provider: BaseProvider,
web3_or_provider: Union[Web3, BaseProvider],
contract_address: str,
validator: LibDummyValidator = None,
):
"""Get an instance of wrapper for smart contract.
:param provider: instance of :class:`web3.providers.base.BaseProvider`
:param web3_or_provider: Either an instance of `web3.Web3`:code: or
`web3.providers.base.BaseProvider`:code:
:param contract_address: where the contract has been deployed
:param validator: for validation of method inputs.
"""
# pylint: disable=too-many-statements
self.contract_address = contract_address
if not validator:
validator = LibDummyValidator(provider, contract_address)
validator = LibDummyValidator(web3_or_provider, contract_address)
self._web3_eth = Web3( # type: ignore # pylint: disable=no-member
provider
).eth
web3 = None
if isinstance(web3_or_provider, BaseProvider):
web3 = Web3(web3_or_provider)
elif isinstance(web3_or_provider, Web3):
web3 = web3_or_provider
else:
raise TypeError(
"Expected parameter 'web3_or_provider' to be an instance of either"
+ " Web3 or BaseProvider"
)
# if any middleware was imported, inject it
try:
MIDDLEWARE
except NameError:
pass
else:
try:
for middleware in MIDDLEWARE:
web3.middleware_onion.inject(
middleware["function"], layer=middleware["layer"]
)
except ValueError as value_error:
if value_error.args == (
"You can't add the same un-named instance twice",
):
pass
self._web3_eth = web3.eth
@staticmethod
def abi():

View File

@ -40,18 +40,24 @@ except ImportError:
"""No-op input validator."""
try:
from .middleware import MIDDLEWARE # type: ignore
except ImportError:
pass
class PublicAddConstantMethod(ContractMethod):
"""Various interfaces to the publicAddConstant method."""
def __init__(
self,
provider: BaseProvider,
web3_or_provider: Union[Web3, BaseProvider],
contract_address: str,
contract_function: ContractFunction,
validator: Validator = None,
):
"""Persist instance data."""
super().__init__(provider, contract_address, validator)
super().__init__(web3_or_provider, contract_address, validator)
self.underlying_method = contract_function
def validate_and_normalize_inputs(self, x: int):
@ -101,13 +107,13 @@ class PublicAddOneMethod(ContractMethod):
def __init__(
self,
provider: BaseProvider,
web3_or_provider: Union[Web3, BaseProvider],
contract_address: str,
contract_function: ContractFunction,
validator: Validator = None,
):
"""Persist instance data."""
super().__init__(provider, contract_address, validator)
super().__init__(web3_or_provider, contract_address, validator)
self.underlying_method = contract_function
def validate_and_normalize_inputs(self, x: int):
@ -166,24 +172,55 @@ class TestLibDummy:
def __init__(
self,
provider: BaseProvider,
web3_or_provider: Union[Web3, BaseProvider],
contract_address: str,
validator: TestLibDummyValidator = None,
):
"""Get an instance of wrapper for smart contract.
:param provider: instance of :class:`web3.providers.base.BaseProvider`
:param web3_or_provider: Either an instance of `web3.Web3`:code: or
`web3.providers.base.BaseProvider`:code:
:param contract_address: where the contract has been deployed
:param validator: for validation of method inputs.
"""
# pylint: disable=too-many-statements
self.contract_address = contract_address
if not validator:
validator = TestLibDummyValidator(provider, contract_address)
validator = TestLibDummyValidator(
web3_or_provider, contract_address
)
self._web3_eth = Web3( # type: ignore # pylint: disable=no-member
provider
).eth
web3 = None
if isinstance(web3_or_provider, BaseProvider):
web3 = Web3(web3_or_provider)
elif isinstance(web3_or_provider, Web3):
web3 = web3_or_provider
else:
raise TypeError(
"Expected parameter 'web3_or_provider' to be an instance of either"
+ " Web3 or BaseProvider"
)
# if any middleware was imported, inject it
try:
MIDDLEWARE
except NameError:
pass
else:
try:
for middleware in MIDDLEWARE:
web3.middleware_onion.inject(
middleware["function"], layer=middleware["layer"]
)
except ValueError as value_error:
if value_error.args == (
"You can't add the same un-named instance twice",
):
pass
self._web3_eth = web3.eth
functions = self._web3_eth.contract(
address=to_checksum_address(contract_address),
@ -191,11 +228,17 @@ class TestLibDummy:
).functions
self.public_add_constant = PublicAddConstantMethod(
provider, contract_address, functions.publicAddConstant, validator
web3_or_provider,
contract_address,
functions.publicAddConstant,
validator,
)
self.public_add_one = PublicAddOneMethod(
provider, contract_address, functions.publicAddOne, validator
web3_or_provider,
contract_address,
functions.publicAddOne,
validator,
)
@staticmethod

View File

@ -0,0 +1,117 @@
{
"1": {
"exchangeV2": "0x080bf510fcbf18b91105470639e9561022937712",
"exchange": "0x0000000000000000000000000000000000000000",
"erc20Proxy": "0x95e6f48254609a6ee006f7d493c8e5fb97094cef",
"erc721Proxy": "0xefc70a1b18c432bdc64b596838b4d138f6bc6cad",
"forwarder": "0x0000000000000000000000000000000000000000",
"orderValidator": "0x0000000000000000000000000000000000000000",
"zrxToken": "0xe41d2489571d322189246dafa5ebde1f4699f498",
"etherToken": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
"assetProxyOwner": "0xdffe798c7172dd6deb32baee68af322e8f495ce0",
"zeroExGovernor": "0x0000000000000000000000000000000000000000",
"dutchAuction": "0x0000000000000000000000000000000000000000",
"coordinatorRegistry": "0x45797531b873fd5e519477a070a955764c1a5b07",
"coordinator": "0x0000000000000000000000000000000000000000",
"multiAssetProxy": "0xef701d5389ae74503d633396c4d654eabedc9d78",
"staticCallProxy": "0x3517b88c19508c08650616019062b898ab65ed29",
"erc1155Proxy": "0x7eefbd48fd63d441ec7435d024ec7c5131019add",
"zrxVault": "0x0000000000000000000000000000000000000000",
"staking": "0x0000000000000000000000000000000000000000",
"stakingProxy": "0x0000000000000000000000000000000000000000",
"devUtils": "0x0000000000000000000000000000000000000000",
"erc20BridgeProxy": "0x0000000000000000000000000000000000000000"
},
"3": {
"erc20Proxy": "0xb1408f4c245a23c31b98d2c626777d4c0d766caa",
"erc721Proxy": "0xe654aac058bfbf9f83fcaee7793311dd82f6ddb4",
"zrxToken": "0xff67881f8d12f372d91baae9752eb3631ff0ed00",
"etherToken": "0xc778417e063141139fce010982780140aa0cd5ab",
"exchangeV2": "0xbff9493f92a3df4b0429b6d00743b3cfb4c85831",
"exchange": "0xc56388332ddfc98701fefed94535100c6166956c",
"assetProxyOwner": "0x0000000000000000000000000000000000000000",
"zeroExGovernor": "0xdcf20f7b447d51f2b3e5499b7f6cbbf7295a5d26",
"forwarder": "0xe66ae6162b3e9067d6ce9e5b9799cca1ba0d09f8",
"orderValidator": "0x0000000000000000000000000000000000000000",
"dutchAuction": "0x0000000000000000000000000000000000000000",
"coordinatorRegistry": "0x403cc23e88c17c4652fb904784d1af640a6722d9",
"coordinator": "0xad8464022213a618c96a1178a927a5ed15ad6949",
"multiAssetProxy": "0xab8fbd189c569ccdee3a4d929bb7f557be4028f6",
"staticCallProxy": "0xe1b97e47aa3796276033a5341e884d2ba46b6ac1",
"erc1155Proxy": "0x19bb6caa3bc34d39e5a23cedfa3e6c7e7f3c931d",
"devUtils": "0x9a8590eebcfc53f0cc7ab5ebb8c079e9e7d4e0f5",
"zrxVault": "0xffd161026865ad8b4ab28a76840474935eec4dfa",
"staking": "0x3f46b98061a3e1e1f41dff296ec19402c298f8a9",
"stakingProxy": "0xfaabcee42ab6b9c649794ac6c133711071897ee9",
"erc20BridgeProxy": "0x599b340b5045436a99b1f0c718d30f5a0c8519dd"
},
"4": {
"exchangeV2": "0xbff9493f92a3df4b0429b6d00743b3cfb4c85831",
"exchange": "0x3afe8aa355e086d898447732cfa5d931cfb2a792",
"erc20Proxy": "0x2f5ae4f6106e89b4147651688a92256885c5f410",
"erc721Proxy": "0x7656d773e11ff7383a14dcf09a9c50990481cd10",
"zrxToken": "0x8080c7e4b81ecf23aa6f877cfbfd9b0c228c6ffa",
"etherToken": "0xc778417e063141139fce010982780140aa0cd5ab",
"assetProxyOwner": "0x0000000000000000000000000000000000000000",
"zeroExGovernor": "0x5d751aa855a1aee5fe44cf5350ed25b5727b66ae",
"forwarder": "0xf36eabdfe986b35b62c8fd5a98a7f2aebb79b291",
"orderValidator": "0x0000000000000000000000000000000000000000",
"dutchAuction": "0x0000000000000000000000000000000000000000",
"coordinatorRegistry": "0x1084b6a398e47907bae43fec3ff4b677db6e4fee",
"coordinator": "0x9ae7a6e4e4d58c36b7aa573fc06ce46dd3cb0d44",
"multiAssetProxy": "0xb34cde0ad3a83d04abebc0b66e75196f22216621",
"staticCallProxy": "0xe1b97e47aa3796276033a5341e884d2ba46b6ac1",
"erc1155Proxy": "0x19bb6caa3bc34d39e5a23cedfa3e6c7e7f3c931d",
"devUtils": "0xfcbb258112485f18dd68f4b1016e48c23542fdc5",
"zrxVault": "0xa5bf6ac73bc40790fc6ffc9dbbbce76c9176e224",
"staking": "0x344d4f661a82afdd84d31456c291822d90d5dc3a",
"stakingProxy": "0xc6ad5277ea225ac05e271eb14a7ebb480cd9dd9f",
"erc20BridgeProxy": "0x31b8653642110f17bdb1f719901d7e7d49b08141"
},
"42": {
"erc20Proxy": "0xf1ec01d6236d3cd881a0bf0130ea25fe4234003e",
"erc721Proxy": "0x2a9127c745688a165106c11cd4d647d2220af821",
"zrxToken": "0x2002d3812f58e35f0ea1ffbf80a75a38c32175fa",
"etherToken": "0xd0a1e359811322d97991e03f863a0c30c2cf029c",
"exchangeV2": "0x30589010550762d2f0d06f650d8e8b6ade6dbf4b",
"exchange": "0xca8b1626b3b7a0da722ca9f264c4630c7d34d3b8",
"assetProxyOwner": "0x0000000000000000000000000000000000000000",
"zeroExGovernor": "0x3654e5363cd75c8974c76208137df9691e820e97",
"forwarder": "0xd6330f9d2073e1889a295dd1dd2e28d42dec4bff",
"orderValidator": "0x0000000000000000000000000000000000000000",
"dutchAuction": "0x0000000000000000000000000000000000000000",
"coordinatorRegistry": "0x09fb99968c016a3ff537bf58fb3d9fe55a7975d5",
"coordinator": "0x10e0b1c2e6065ec7f290c7e3731264f9a2bf2b2d",
"multiAssetProxy": "0xf6313a772c222f51c28f2304c0703b8cf5428fd8",
"staticCallProxy": "0x48e94bdb9033640d45ea7c721e25f380f8bffa43",
"erc1155Proxy": "0x64517fa2b480ba3678a2a3c0cf08ef7fd4fad36f",
"devUtils": "0x58c4fbdf9222f10ad2bef8f4d374f209135e71a5",
"zrxVault": "0xf36eabdfe986b35b62c8fd5a98a7f2aebb79b291",
"staking": "0x89150f5eed50b3528f79bfb539f29d727f92821c",
"stakingProxy": "0xbab9145f1d57cd4bb0c9aa2d1ece0a5b6e734d34",
"erc20BridgeProxy": "0xfb2dd2a1366de37f7241c83d47da58fd503e2c64"
},
"50": {
"erc20Proxy": "0x1dc4c1cefef38a777b15aa20260a54e584b16c48",
"erc721Proxy": "0x1d7022f5b17d2f8b695918fb48fa1089c9f85401",
"erc1155Proxy": "0x6a4a62e5a7ed13c361b176a5f62c2ee620ac0df8",
"zrxToken": "0x871dd7c2b4b25e1aa18728e9d5f2af4c4e431f5c",
"etherToken": "0x0b1ba0af832d7c05fd64161e0db78e85978e8082",
"exchangeV2": "0x48bacb9266a570d521063ef5dd96e61686dbe788",
"exchange": "0x48bacb9266a570d521063ef5dd96e61686dbe788",
"zeroExGovernor": "0x0000000000000000000000000000000000000000",
"assetProxyOwner": "0x0000000000000000000000000000000000000000",
"forwarder": "0x0000000000000000000000000000000000000000",
"orderValidator": "0x0000000000000000000000000000000000000000",
"dutchAuction": "0x0000000000000000000000000000000000000000",
"coordinatorRegistry": "0x1941ff73d1154774d87521d2d0aaad5d19c8df60",
"coordinator": "0x0000000000000000000000000000000000000000",
"multiAssetProxy": "0xcfc18cec799fbd1793b5c43e773c98d4d61cc2db",
"staticCallProxy": "0x6dfff22588be9b3ef8cf0ad6dc9b84796f9fb45f",
"devUtils": "0x38ef19fdf8e8415f18c307ed71967e19aac28ba1",
"zrxVault": "0x0000000000000000000000000000000000000000",
"staking": "0x0000000000000000000000000000000000000000",
"stakingProxy": "0x0000000000000000000000000000000000000000",
"erc20BridgeProxy": "0x0000000000000000000000000000000000000000"
}
}

View File

@ -1,5 +1,7 @@
import * as _ from 'lodash';
import addresses from '../addresses.json';
export interface ContractAddresses {
erc20Proxy: string;
erc721Proxy: string;
@ -32,127 +34,6 @@ export enum NetworkId {
Ganache = 50,
}
const NULL_ADDRESS = '0x0000000000000000000000000000000000000000';
const networkToAddresses: { [networkId: number]: ContractAddresses } = {
1: {
exchangeV2: '0x080bf510fcbf18b91105470639e9561022937712',
exchange: NULL_ADDRESS,
erc20Proxy: '0x95e6f48254609a6ee006f7d493c8e5fb97094cef',
erc721Proxy: '0xefc70a1b18c432bdc64b596838b4d138f6bc6cad',
forwarder: NULL_ADDRESS,
orderValidator: NULL_ADDRESS,
zrxToken: '0xe41d2489571d322189246dafa5ebde1f4699f498',
etherToken: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
assetProxyOwner: '0xdffe798c7172dd6deb32baee68af322e8f495ce0',
zeroExGovernor: NULL_ADDRESS,
dutchAuction: NULL_ADDRESS,
coordinatorRegistry: '0x45797531b873fd5e519477a070a955764c1a5b07',
coordinator: NULL_ADDRESS,
multiAssetProxy: '0xef701d5389ae74503d633396c4d654eabedc9d78',
staticCallProxy: '0x3517b88c19508c08650616019062b898ab65ed29',
erc1155Proxy: '0x7eefbd48fd63d441ec7435d024ec7c5131019add',
zrxVault: NULL_ADDRESS,
staking: NULL_ADDRESS,
stakingProxy: NULL_ADDRESS,
devUtils: NULL_ADDRESS,
erc20BridgeProxy: NULL_ADDRESS,
},
3: {
erc20Proxy: '0xb1408f4c245a23c31b98d2c626777d4c0d766caa',
erc721Proxy: '0xe654aac058bfbf9f83fcaee7793311dd82f6ddb4',
zrxToken: '0xff67881f8d12f372d91baae9752eb3631ff0ed00',
etherToken: '0xc778417e063141139fce010982780140aa0cd5ab',
exchangeV2: '0xbff9493f92a3df4b0429b6d00743b3cfb4c85831',
exchange: '0xc56388332ddfc98701fefed94535100c6166956c',
assetProxyOwner: NULL_ADDRESS,
zeroExGovernor: '0xdcf20f7b447d51f2b3e5499b7f6cbbf7295a5d26',
forwarder: '0xe66ae6162b3e9067d6ce9e5b9799cca1ba0d09f8',
orderValidator: NULL_ADDRESS,
dutchAuction: NULL_ADDRESS,
coordinatorRegistry: '0x403cc23e88c17c4652fb904784d1af640a6722d9',
coordinator: '0xad8464022213a618c96a1178a927a5ed15ad6949',
multiAssetProxy: '0xab8fbd189c569ccdee3a4d929bb7f557be4028f6',
staticCallProxy: '0xe1b97e47aa3796276033a5341e884d2ba46b6ac1',
erc1155Proxy: '0x19bb6caa3bc34d39e5a23cedfa3e6c7e7f3c931d',
devUtils: '0x9a8590eebcfc53f0cc7ab5ebb8c079e9e7d4e0f5',
zrxVault: '0xffd161026865ad8b4ab28a76840474935eec4dfa',
staking: '0x3f46b98061a3e1e1f41dff296ec19402c298f8a9',
stakingProxy: '0xfaabcee42ab6b9c649794ac6c133711071897ee9',
erc20BridgeProxy: '0x599b340b5045436a99b1f0c718d30f5a0c8519dd',
},
4: {
exchangeV2: '0xbff9493f92a3df4b0429b6d00743b3cfb4c85831',
exchange: '0x3afe8aa355e086d898447732cfa5d931cfb2a792',
erc20Proxy: '0x2f5ae4f6106e89b4147651688a92256885c5f410',
erc721Proxy: '0x7656d773e11ff7383a14dcf09a9c50990481cd10',
zrxToken: '0x8080c7e4b81ecf23aa6f877cfbfd9b0c228c6ffa',
etherToken: '0xc778417e063141139fce010982780140aa0cd5ab',
assetProxyOwner: NULL_ADDRESS,
zeroExGovernor: '0x5d751aa855a1aee5fe44cf5350ed25b5727b66ae',
forwarder: '0xf36eabdfe986b35b62c8fd5a98a7f2aebb79b291',
orderValidator: NULL_ADDRESS,
dutchAuction: NULL_ADDRESS,
coordinatorRegistry: '0x1084b6a398e47907bae43fec3ff4b677db6e4fee',
coordinator: '0x9ae7a6e4e4d58c36b7aa573fc06ce46dd3cb0d44',
multiAssetProxy: '0xb34cde0ad3a83d04abebc0b66e75196f22216621',
staticCallProxy: '0xe1b97e47aa3796276033a5341e884d2ba46b6ac1',
erc1155Proxy: '0x19bb6caa3bc34d39e5a23cedfa3e6c7e7f3c931d',
devUtils: '0xfcbb258112485f18dd68f4b1016e48c23542fdc5',
zrxVault: '0xa5bf6ac73bc40790fc6ffc9dbbbce76c9176e224',
staking: '0x344d4f661a82afdd84d31456c291822d90d5dc3a',
stakingProxy: '0xc6ad5277ea225ac05e271eb14a7ebb480cd9dd9f',
erc20BridgeProxy: '0x31b8653642110f17bdb1f719901d7e7d49b08141',
},
42: {
erc20Proxy: '0xf1ec01d6236d3cd881a0bf0130ea25fe4234003e',
erc721Proxy: '0x2a9127c745688a165106c11cd4d647d2220af821',
zrxToken: '0x2002d3812f58e35f0ea1ffbf80a75a38c32175fa',
etherToken: '0xd0a1e359811322d97991e03f863a0c30c2cf029c',
exchangeV2: '0x30589010550762d2f0d06f650d8e8b6ade6dbf4b',
exchange: '0xca8b1626b3b7a0da722ca9f264c4630c7d34d3b8',
assetProxyOwner: NULL_ADDRESS,
zeroExGovernor: '0x3654e5363cd75c8974c76208137df9691e820e97',
forwarder: '0xd6330f9d2073e1889a295dd1dd2e28d42dec4bff',
orderValidator: NULL_ADDRESS,
dutchAuction: NULL_ADDRESS,
coordinatorRegistry: '0x09fb99968c016a3ff537bf58fb3d9fe55a7975d5',
coordinator: '0x10e0b1c2e6065ec7f290c7e3731264f9a2bf2b2d',
multiAssetProxy: '0xf6313a772c222f51c28f2304c0703b8cf5428fd8',
staticCallProxy: '0x48e94bdb9033640d45ea7c721e25f380f8bffa43',
erc1155Proxy: '0x64517fa2b480ba3678a2a3c0cf08ef7fd4fad36f',
devUtils: '0x58c4fbdf9222f10ad2bef8f4d374f209135e71a5',
zrxVault: '0xf36eabdfe986b35b62c8fd5a98a7f2aebb79b291',
staking: '0x89150f5eed50b3528f79bfb539f29d727f92821c',
stakingProxy: '0xbab9145f1d57cd4bb0c9aa2d1ece0a5b6e734d34',
erc20BridgeProxy: '0xfb2dd2a1366de37f7241c83d47da58fd503e2c64',
},
// NetworkId 50 represents our Ganache snapshot generated from migrations.
50: {
erc20Proxy: '0x1dc4c1cefef38a777b15aa20260a54e584b16c48',
erc721Proxy: '0x1d7022f5b17d2f8b695918fb48fa1089c9f85401',
erc1155Proxy: '0x6a4a62e5a7ed13c361b176a5f62c2ee620ac0df8',
zrxToken: '0x871dd7c2b4b25e1aa18728e9d5f2af4c4e431f5c',
etherToken: '0x0b1ba0af832d7c05fd64161e0db78e85978e8082',
exchangeV2: '0x48bacb9266a570d521063ef5dd96e61686dbe788',
exchange: '0x48bacb9266a570d521063ef5dd96e61686dbe788',
zeroExGovernor: NULL_ADDRESS,
assetProxyOwner: NULL_ADDRESS,
forwarder: NULL_ADDRESS,
orderValidator: NULL_ADDRESS,
dutchAuction: NULL_ADDRESS,
coordinatorRegistry: '0x1941ff73d1154774d87521d2d0aaad5d19c8df60',
coordinator: NULL_ADDRESS,
multiAssetProxy: '0xcfc18cec799fbd1793b5c43e773c98d4d61cc2db',
staticCallProxy: '0x6dfff22588be9b3ef8cf0ad6dc9b84796f9fb45f',
devUtils: '0x38ef19fdf8e8415f18c307ed71967e19aac28ba1',
zrxVault: NULL_ADDRESS,
staking: NULL_ADDRESS,
stakingProxy: NULL_ADDRESS,
erc20BridgeProxy: NULL_ADDRESS,
},
};
/**
* Used to get addresses of contracts that have been deployed to either the
* Ethereum mainnet or a supported testnet. Throws if there are no known
@ -162,6 +43,8 @@ const networkToAddresses: { [networkId: number]: ContractAddresses } = {
* given networkId.
*/
export function getContractAddressesForNetworkOrThrow(networkId: NetworkId): ContractAddresses {
const networkToAddresses: { [networkId: number]: ContractAddresses } = addresses;
if (networkToAddresses[networkId] === undefined) {
throw new Error(`Unknown network id (${networkId}). No known 0x contracts have been deployed on this network.`);
}

View File

@ -2,7 +2,10 @@
"extends": "../../tsconfig",
"compilerOptions": {
"outDir": "lib",
"rootDir": "."
"rootDir": ".",
"resolveJsonModule": true,
"esModuleInterop": true
},
"include": ["./src/**/*"]
"include": ["./src/**/*"],
"files": ["./addresses.json"]
}

View File

@ -2,10 +2,14 @@
"""setuptools module for contract_addresses 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 shutil import copyfile, rmtree
from os import environ, 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
@ -13,6 +17,34 @@ 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 addresses.json."""
description = (
"Pull in addresses.json from ../../packages/contract-addresses"
)
def run(self):
"""Copy over addresses.json."""
pkgdir = path.dirname(path.realpath(argv[0]))
destination_path = path.join(
pkgdir, "src", "zero_ex", "contract_addresses"
)
copyfile(
path.join(
pkgdir,
"..",
"..",
"packages",
"contract-addresses",
"addresses.json",
),
path.join(destination_path, "addresses.json"),
)
class LintCommand(distutils.command.build_py.build_py):
"""Custom setuptools command class for running linters."""
@ -131,6 +163,7 @@ setup(
author_email="feuGeneA@users.noreply.github.com",
cmdclass={
"clean": CleanCommandExtension,
"pre_install": PreInstallCommand,
"lint": LintCommand,
"test": TestCommandExtension,
"test_publish": TestPublishCommand,
@ -156,7 +189,9 @@ setup(
]
},
python_requires=">=3.6, <4",
package_data={"zero_ex.contract_addresses": ["py.typed"]},
package_data={
"zero_ex.contract_addresses": ["py.typed", "addresses.json"]
},
package_dir={"": "src"},
license="Apache 2.0",
keywords=(

View File

@ -19,7 +19,7 @@ Python zero_ex.contract_addresses
:members:
:show-inheritance:
.. autodata:: zero_ex.contract_addresses.NETWORK_TO_ADDRESSES
.. autodata:: zero_ex.contract_addresses.network_to_addresses
:annotation: : Dict[NetworkId, ContractAddresses]
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

@ -10,8 +10,11 @@ Install the package with pip::
"""
from enum import Enum
import json
from typing import Dict, NamedTuple
from pkg_resources import resource_string
class ContractAddresses(NamedTuple):
"""An abstract record listing all the contracts that have addresses."""
@ -20,10 +23,7 @@ class ContractAddresses(NamedTuple):
"""Address of the ERC20Proxy contract."""
erc721_proxy: str
"""Address of the ERC20Proxy contract."""
erc1155_proxy: str
"""Address of the ERC1155Proxy contract."""
"""Address of the ERC721Proxy contract."""
zrx_token: str
"""Address of the ZRX token contract."""
@ -31,27 +31,57 @@ class ContractAddresses(NamedTuple):
ether_token: str
"""Address of the WETH token contract."""
exchange_v2: str
"""Address of the v2 Exchange contract."""
exchange: str
"""Address of the Exchange contract."""
"""Address of the v3 Exchange contract."""
asset_proxy_owner: str
"""Address of the AssetProxyOwner contract."""
zero_ex_governor: str
"""Address of the ZeroExGovernor contract."""
forwarder: str
"""Address of the Forwarder contract."""
order_validator: str
"""Address of the OrderValidator contract."""
dutch_auction: str
"""Address of the DutchAuction contract."""
coordinator_registry: str
"""Address of the CoordinatorRegistry contract."""
coordinator: str
"""Address of the Coordinator contract."""
multi_asset_proxy: str
"""Address of the MultiAssetProxy contract."""
static_call_proxy: str
"""Address of the StaticCallProxy contract."""
erc1155_proxy: str
"""Address of the ERC1155Proxy contract."""
dev_utils: str
"""Address of the DevUtils contract."""
zrx_vault: str
"""Address of the ZRXVault contract."""
staking: str
"""Address of the Staking contract."""
staking_proxy: str
"""Address of the StakingProxy contract."""
erc20_bridge_proxy: str
"""Address of the ERC20BridgeProxy contract."""
class NetworkId(Enum):
"""Network names correlated to their network identification numbers.
@ -70,83 +100,62 @@ class NetworkId(Enum):
GANACHE = 50
NETWORK_TO_ADDRESSES: Dict[NetworkId, ContractAddresses] = {
NetworkId.MAINNET: ContractAddresses( # nosec
erc20_proxy="0x95e6f48254609a6ee006f7d493c8e5fb97094cef",
erc721_proxy="0xefc70a1b18c432bdc64b596838b4d138f6bc6cad",
zrx_token="0xe41d2489571d322189246dafa5ebde1f4699f498",
ether_token="0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
exchange="0x080bf510fcbf18b91105470639e9561022937712",
asset_proxy_owner="0xdffe798c7172dd6deb32baee68af322e8f495ce0",
forwarder="0x76481caa104b5f6bccb540dae4cefaf1c398ebea",
order_validator="0xa09329c6003c9a5402102e226417738ee22cf1f2",
coordinator_registry="0x45797531b873fd5e519477a070a955764c1a5b07",
coordinator="0xa14857e8930acd9a882d33ec20559beb5479c8a6",
erc1155_proxy="0x7eefbd48fd63d441ec7435d024ec7c5131019add",
dev_utils="0x92d9a4d50190ae04e03914db2ee650124af844e6",
),
NetworkId.ROPSTEN: ContractAddresses( # nosec
erc20_proxy="0xb1408f4c245a23c31b98d2c626777d4c0d766caa",
erc721_proxy="0xe654aac058bfbf9f83fcaee7793311dd82f6ddb4",
zrx_token="0xff67881f8d12f372d91baae9752eb3631ff0ed00",
ether_token="0xc778417e063141139fce010982780140aa0cd5ab",
exchange="0xbff9493f92a3df4b0429b6d00743b3cfb4c85831",
asset_proxy_owner="0xf5fa5b5fed2727a0e44ac67f6772e97977aa358b",
forwarder="0x1ebdc9758e85c1c6a85af06cc96cf89000a31913",
order_validator="0x90431a90516ab49af23a0530e04e8c7836e7122f",
coordinator_registry="0x403cc23e88c17c4652fb904784d1af640a6722d9",
coordinator="0x2ba02e03ee0029311e0f43715307870a3e701b53",
erc1155_proxy="0x19bb6caa3bc34d39e5a23cedfa3e6c7e7f3c931d",
dev_utils="0x3e0b46bad8e374e4a110c12b832cb120dbe4a479",
),
NetworkId.RINKEBY: ContractAddresses( # nosec
exchange="0xbff9493f92a3df4b0429b6d00743b3cfb4c85831",
erc20_proxy="0x2f5ae4f6106e89b4147651688a92256885c5f410",
erc721_proxy="0x7656d773e11ff7383a14dcf09a9c50990481cd10",
zrx_token="0x8080c7e4b81ecf23aa6f877cfbfd9b0c228c6ffa",
ether_token="0xc778417e063141139fce010982780140aa0cd5ab",
asset_proxy_owner="0xe1703da878afcebff5b7624a826902af475b9c03",
forwarder="0x1ebdc9758e85c1c6a85af06cc96cf89000a31913",
order_validator="0x0c5173a51e26b29d6126c686756fb9fbef71f762",
coordinator_registry="0x1084b6a398e47907bae43fec3ff4b677db6e4fee",
coordinator="0x2ba02e03ee0029311e0f43715307870a3e701b53",
erc1155_proxy="0x19bb6caa3bc34d39e5a23cedfa3e6c7e7f3c931d",
dev_utils="0x2d4a9abda7b8b3605c8dbd34e3550a7467c78287'",
),
NetworkId.KOVAN: ContractAddresses( # nosec
erc20_proxy="0xf1ec01d6236d3cd881a0bf0130ea25fe4234003e",
erc721_proxy="0x2a9127c745688a165106c11cd4d647d2220af821",
zrx_token="0x2002d3812f58e35f0ea1ffbf80a75a38c32175fa",
ether_token="0xd0a1e359811322d97991e03f863a0c30c2cf029c",
exchange="0x30589010550762d2f0d06f650d8e8b6ade6dbf4b",
asset_proxy_owner="0x2c824d2882baa668e0d5202b1e7f2922278703f8",
forwarder="0x1ebdc9758e85c1c6a85af06cc96cf89000a31913",
order_validator="0xb389da3d204b412df2f75c6afb3d0a7ce0bc283d",
coordinator_registry="0x09fb99968c016a3ff537bf58fb3d9fe55a7975d5",
coordinator="0x2ba02e03ee0029311e0f43715307870a3e701b53",
erc1155_proxy="0x64517fa2b480ba3678a2a3c0cf08ef7fd4fad36f",
dev_utils="0x1e3616bc5144362f95d72de41874395567697e93",
),
NetworkId.GANACHE: ContractAddresses( # nosec
exchange="0x48bacb9266a570d521063ef5dd96e61686dbe788",
erc20_proxy="0x1dc4c1cefef38a777b15aa20260a54e584b16c48",
erc721_proxy="0x1d7022f5b17d2f8b695918fb48fa1089c9f85401",
erc1155_proxy="0x6a4a62e5a7ed13c361b176a5f62c2ee620ac0df8",
zrx_token="0x871dd7c2b4b25e1aa18728e9d5f2af4c4e431f5c",
ether_token="0x0b1ba0af832d7c05fd64161e0db78e85978e8082",
asset_proxy_owner="0x8d42e38980ce74736c21c059b2240df09958d3c8",
forwarder="0xaa86dda78e9434aca114b6676fc742a18d15a1cc",
order_validator="0x4d3d5c850dd5bd9d6f4adda3dd039a3c8054ca29",
coordinator_registry="0x1941ff73d1154774d87521d2d0aaad5d19c8df60",
coordinator="0x0d8b0dd11f5d34ed41d556def5f841900d5b1c6b",
dev_utils="0x38ef19fdf8e8415f18c307ed71967e19aac28ba1",
),
}
"""A mapping from instances of NetworkId to instances of ContractAddresses.
class _AddressCache:
"""A cache to facilitate lazy & singular loading of contract addresses."""
Addresses under NetworkId.Ganache are from our Ganache snapshot generated from
npm package @0x/migrations.
# pylint: disable=too-few-public-methods
>>> NETWORK_TO_ADDRESSES[NetworkId.MAINNET].exchange
0x4f833a24e1f95d70f028921e27040ca56e09ab0b
"""
# class data, not instance:
_network_to_addresses: Dict[str, ContractAddresses] = {}
@classmethod
def network_to_addresses(cls, network_id: NetworkId):
"""Return the addresses for the given network ID.
First tries to get data from the class level storage
`_network_to_addresses`. If it's not there, loads it from disk, stores
it in the class data (for the next caller), and then returns it.
"""
try:
return cls._network_to_addresses[str(network_id.value)]
except KeyError:
cls._network_to_addresses = json.loads(
resource_string("zero_ex.contract_addresses", "addresses.json")
)
return cls._network_to_addresses[str(network_id.value)]
def network_to_addresses(network_id: NetworkId) -> ContractAddresses:
"""Map a NetworkId to an instance of ContractAddresses.
Addresses under NetworkId.Ganache are from our Ganache snapshot generated
from npm package @0x/migrations.
>>> network_to_addresses(NetworkId.MAINNET).exchange
'0x...'
"""
addresses = _AddressCache.network_to_addresses(network_id)
return ContractAddresses(
erc20_proxy=addresses["erc20Proxy"],
erc721_proxy=addresses["erc721Proxy"],
zrx_token=addresses["zrxToken"],
ether_token=addresses["etherToken"],
exchange_v2=addresses["exchangeV2"],
exchange=addresses["exchange"],
asset_proxy_owner=addresses["assetProxyOwner"],
zero_ex_governor=addresses["zeroExGovernor"],
forwarder=addresses["forwarder"],
order_validator=addresses["orderValidator"],
dutch_auction=addresses["dutchAuction"],
coordinator_registry=addresses["coordinatorRegistry"],
coordinator=addresses["coordinator"],
multi_asset_proxy=addresses["multiAssetProxy"],
static_call_proxy=addresses["staticCallProxy"],
erc1155_proxy=addresses["erc1155Proxy"],
dev_utils=addresses["devUtils"],
zrx_vault=addresses["zrxVault"],
staking=addresses["staking"],
staking_proxy=addresses["stakingProxy"],
erc20_bridge_proxy=addresses["erc20BridgeProxy"],
)

View File

@ -10,3 +10,9 @@ envlist = py37
commands =
pip install -e .[dev]
python setup.py test
[testenv:run_tests_against_deployment]
setenv = PY_IGNORE_IMPORTMISMATCH = 1
commands=
pip install 0x-contract-addresses[dev]
pytest --doctest-modules src

View File

@ -1,9 +1,13 @@
# Changelog
## 2.0.0 - 2019-01-09
## 3.0.0 - TBD
- Initial release.
- Updated with artifacts for version 3 of the protocol.
## 2.0.1 - 2019-04-30
- Expanded documentation.
## 2.0.0 - 2019-01-09
- Initial release.

View File

@ -2,10 +2,14 @@
"""setuptools module for contract_artifacts 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 copytree, rmtree
from os import environ, 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
@ -148,7 +152,7 @@ with open("README.md", "r") as file_handle:
setup(
name="0x-contract-artifacts",
version="2.0.1",
version="3.0.0",
description="0x smart contract compilation artifacts",
long_description=README_MD,
long_description_content_type="text/markdown",

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

@ -1,59 +0,0 @@
{
"schemaVersion": "2.0.0",
"contractName": "EthBalanceChecker",
"compilerOutput": {
"abi": [
{
"constant": true,
"inputs": [{ "internalType": "address[]", "name": "addresses", "type": "address[]" }],
"name": "getEthBalances",
"outputs": [{ "internalType": "uint256[]", "name": "", "type": "uint256[]" }],
"payable": false,
"stateMutability": "view",
"type": "function"
}
],
"devdoc": {
"methods": {
"getEthBalances(address[])": {
"details": "Batch fetches ETH balances",
"params": { "addresses": "Array of addresses." },
"return": "Array of ETH balances."
}
}
},
"evm": {
"bytecode": {
"object": "0x608060405234801561001057600080fd5b506101e5806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063a0901e5114610030575b600080fd5b6100d36004803603602081101561004657600080fd5b81019060208101813564010000000081111561006157600080fd5b82018360208201111561007357600080fd5b8035906020019184602083028401116401000000008311171561009557600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550610123945050505050565b60408051602080825283518183015283519192839290830191858101910280838360005b8381101561010f5781810151838201526020016100f7565b505050509050019250505060405180910390f35b6060808251604051908082528060200260200182016040528015610151578160200160208202803883390190505b50905060005b835181146101a95783818151811061016b57fe5b602002602001015173ffffffffffffffffffffffffffffffffffffffff163182828151811061019657fe5b6020908102919091010152600101610157565b509291505056fea265627a7a7231582094309783f0b63086d85d9cb4f6e5be253699056ac1580a863367c5076ecb5c1864736f6c634300050b0032"
},
"deployedBytecode": {
"object": "0x608060405234801561001057600080fd5b506004361061002b5760003560e01c8063a0901e5114610030575b600080fd5b6100d36004803603602081101561004657600080fd5b81019060208101813564010000000081111561006157600080fd5b82018360208201111561007357600080fd5b8035906020019184602083028401116401000000008311171561009557600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550610123945050505050565b60408051602080825283518183015283519192839290830191858101910280838360005b8381101561010f5781810151838201526020016100f7565b505050509050019250505060405180910390f35b6060808251604051908082528060200260200182016040528015610151578160200160208202803883390190505b50905060005b835181146101a95783818151811061016b57fe5b602002602001015173ffffffffffffffffffffffffffffffffffffffff163182828151811061019657fe5b6020908102919091010152600101610157565b509291505056fea265627a7a7231582094309783f0b63086d85d9cb4f6e5be253699056ac1580a863367c5076ecb5c1864736f6c634300050b0032"
}
}
},
"compiler": {
"name": "solc",
"version": "soljson-v0.5.11+commit.c082d0b4.js",
"settings": {
"optimizer": {
"enabled": true,
"runs": 10000,
"details": { "yul": true, "deduplicate": true, "cse": true, "constantOptimizer": true }
},
"outputSelection": {
"*": {
"*": [
"abi",
"devdoc",
"evm.bytecode.object",
"evm.bytecode.sourceMap",
"evm.deployedBytecode.object",
"evm.deployedBytecode.sourceMap"
]
}
},
"evmVersion": "constantinople"
}
},
"networks": {}
}

View File

@ -10,3 +10,9 @@ envlist = py37
commands =
pip install -e .[dev]
python setup.py test
[testenv:run_tests_against_deployment]
setenv = PY_IGNORE_IMPORTMISMATCH = 1
commands=
pip install 0x-contract-artifacts[dev]
pytest --doctest-modules src

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

View File

@ -1,5 +1,9 @@
# Changelog
## 1.1.1 - TBD
- Removed dev dependency on package `0x-contract-wrappers`
## 1.1.0 - 2019-08-09
- Added `verifyingContractAddress` to `/zeroExTransactionSchema`.

View File

@ -2,12 +2,16 @@
"""setuptools module for json_schemas 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 distutils.command.build_py
from distutils.command.clean import clean
import subprocess # nosec
from shutil import copytree, rmtree
from os import environ, path
from sys import argv
from sys import argv, exit # pylint: disable=redefined-builtin
from setuptools import find_packages, setup
from setuptools.command.test import test as TestCommand
@ -139,7 +143,7 @@ with open("README.md", "r") as file_handle:
setup(
name="0x-json-schemas",
version="1.1.0",
version="1.1.1",
description="JSON schemas for 0x applications",
long_description=README_MD,
long_description_content_type="text/markdown",
@ -162,11 +166,11 @@ setup(
extras_require={
"dev": [
"0x-contract-addresses",
"0x-contract-wrappers",
"bandit",
"black",
"coverage",
"coveralls",
"eth_utils",
"mypy",
"mypy_extensions",
"pycodestyle",

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

@ -60,36 +60,38 @@ def assert_valid(data: Mapping, schema_id: str) -> None:
Raises an exception if validation fails.
>>> from zero_ex.json_schemas import assert_valid
>>> from zero_ex.contract_addresses import NETWORK_TO_ADDRESSES, NetworkId
>>> from zero_ex.contract_wrappers.exchange.types import (
... Order, order_to_jsdict
... )
>>> from zero_ex.order_utils import asset_data_utils
>>> from zero_ex.contract_addresses import network_to_addresses, NetworkId
>>> from eth_utils import remove_0x_prefix
>>> import random
>>> from datetime import datetime, timedelta
>>> example_order = Order(
... makerAddress='0x5409ed021d9299bf6814279a6a1411a7e866a631',
... takerAddress='0x0000000000000000000000000000000000000000',
... senderAddress='0x0000000000000000000000000000000000000000',
... exchangeAddress='0x4f833a24e1f95d70f028921e27040ca56e09ab0b',
... feeRecipientAddress='0x0000000000000000000000000000000000000000',
... makerAssetData=asset_data_utils.encode_erc20(
... NETWORK_TO_ADDRESSES[NetworkId.MAINNET].zrx_token
... ),
... takerAssetData=asset_data_utils.encode_erc20(
... NETWORK_TO_ADDRESSES[NetworkId.MAINNET].ether_token
... ),
... salt=random.randint(1, 100000000000000000),
... makerFee=0,
... takerFee=0,
... makerAssetAmount=1000000000000000000,
... takerAssetAmount=500000000000000000000,
... expirationTimeSeconds=round(
... (datetime.utcnow() + timedelta(days=1)).timestamp()
... )
>>> assert_valid(
... {'makerAddress': '0x5409ed021d9299bf6814279a6a1411a7e866a631',
... 'takerAddress': '0x0000000000000000000000000000000000000000',
... 'senderAddress': '0x0000000000000000000000000000000000000000',
... 'exchangeAddress': '0x4f833a24e1f95d70f028921e27040ca56e09ab0b',
... 'feeRecipientAddress': (
... '0x0000000000000000000000000000000000000000'
... ),
... 'makerAssetData': (
... network_to_addresses(NetworkId.MAINNET).zrx_token
... ),
... 'takerAssetData': (
... network_to_addresses(NetworkId.MAINNET).ether_token
... ),
... 'salt': random.randint(1, 100000000000000000),
... 'makerFee': 0,
... 'makerFeeAssetData': '0x' + '00'*20,
... 'takerFee': 0,
... 'takerFeeAssetData': '0x' + '00'*20,
... 'makerAssetAmount': 1000000000000000000,
... 'takerAssetAmount': 500000000000000000000,
... 'expirationTimeSeconds': round(
... (datetime.utcnow() + timedelta(days=1)).timestamp()
... ),
... 'chainId': 50
... },
... "/orderSchema"
... )
>>> assert_valid(order_to_jsdict(example_order), "/orderSchema")
"""
_, schema = _LOCAL_RESOLVER.resolve(schema_id)
jsonschema.validate(data, schema, resolver=_LOCAL_RESOLVER)

View File

@ -15,11 +15,14 @@ EMPTY_ORDER = {
"takerAssetData": NULL_ADDRESS,
"salt": "0",
"makerFee": "0",
"makerFeeAssetData": NULL_ADDRESS,
"takerFee": "0",
"takerFeeAssetData": NULL_ADDRESS,
"makerAssetAmount": "0",
"takerAssetAmount": "0",
"expirationTimeSeconds": "0",
"exchangeAddress": NULL_ADDRESS,
"chainId": 50,
}

View File

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

View File

@ -1,5 +1,5 @@
# Changelog
## 1.0.0 - 2019-04-30
## 1.0.0 - TBD
- Initial release.

View File

@ -2,10 +2,14 @@
"""setuptools module for middlewares 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
from sys import argv
from sys import argv, exit # pylint: disable=redefined-builtin
from distutils.command.clean import clean
import distutils.command.build_py

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

@ -3,25 +3,21 @@
from eth_utils import to_checksum_address
from web3 import Web3, HTTPProvider
from zero_ex.contract_addresses import NETWORK_TO_ADDRESSES, NetworkId
from zero_ex.contract_addresses import network_to_addresses, NetworkId
from zero_ex.middlewares.local_message_signer import (
construct_local_message_signer,
)
from zero_ex.order_utils import (
generate_order_hash_hex,
is_valid_signature,
sign_hash,
)
from zero_ex.order_utils import generate_order_hash_hex, sign_hash
def test_local_message_signer__sign_order():
"""Test signing order with the local_message_signer middleware"""
expected_signature = (
"0x1cd17d75b891accf16030c572a64cf9e7955de63bcafa5b084439cec630ade2d7"
"c00f47a2f4d5b6a4508267bf4b8527100bd97cf1af9984c0a58e42d25b13f4f0a03"
"0x1c8bdfbb3ce3ed0f38c5a358a7f49ad5f21ea9857224c2fe98c458f2fa25551d4"
"d6db0157d9dfe9f9fadb8dedabb7786352843357f4ec8d0fbcbeeb619b1091f5803"
)
address = "0x5409ED021D9299bf6814279A6A1411A7e866A631"
exchange = NETWORK_TO_ADDRESSES[NetworkId.GANACHE].exchange
exchange = network_to_addresses(NetworkId.GANACHE).exchange
private_key = (
"f2f48ee19680706196e2e339e5da3491186e0c4c5030670656b0e0164837257d"
)
@ -36,7 +32,9 @@ def test_local_message_signer__sign_order():
"senderAddress": "0x0000000000000000000000000000000000000000",
"feeRecipientAddress": "0x0000000000000000000000000000000000000000",
"makerAssetData": (b"\x00") * 20,
"makerFeeAssetData": (b"\x00") * 20,
"takerAssetData": (b"\x00") * 20,
"takerFeeAssetData": (b"\x00") * 20,
"salt": 0,
"makerFee": 0,
"takerFee": 0,
@ -44,8 +42,11 @@ def test_local_message_signer__sign_order():
"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
is_valid = is_valid_signature(ganache, order_hash, signature, address)[0]
assert is_valid is True
assert (
sign_hash(
web3_instance,
to_checksum_address(address),
generate_order_hash_hex(order, exchange, chain_id=1337),
)
== expected_signature
)

View File

@ -1,5 +1,11 @@
# Changelog
## 4.0.0 - TBD
- Upgraded to protocol version 3.
- `is_valid_signature()` now returns just a boolean. (Formerly, it returned a tuple consisting of the boolean and a reason string.)
- Allow `sign_hash()` to be called with EITHER a Web3.py `BaseProvider` OR an already-instantiated `Web3` client object.
## 3.0.1 - 2019-08-09
- Fixed dependencies: changed `deprecated` from being an extras_require["dev"] dependency to being an install_requires dependency, since it's required not just for doc generation but also just to import the package.

View File

@ -2,11 +2,15 @@
"""setuptools module for order_utils 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
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
@ -152,7 +156,7 @@ with open("README.md", "r") as file_handle:
setup(
name="0x-order-utils",
version="3.0.1",
version="4.0.0",
description="Order utilities for 0x applications",
long_description=README_MD,
long_description_content_type="text/markdown",

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

@ -18,9 +18,9 @@ just this purpose. To start it:
from enum import auto, Enum
import json
from typing import Tuple
from pkg_resources import resource_string
from typing import cast, Tuple, Union
from pkg_resources import resource_string
from mypy_extensions import TypedDict
from eth_utils import keccak, remove_0x_prefix, to_bytes, to_checksum_address
@ -29,9 +29,11 @@ import web3.exceptions
from web3.providers.base import BaseProvider
from web3.contract import Contract
from zero_ex.contract_addresses import NETWORK_TO_ADDRESSES, NetworkId
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.contract_wrappers.exchange import Exchange
from zero_ex.contract_wrappers.exchange.types import Order
from zero_ex.contract_wrappers.order_conversions import order_to_jsdict
from zero_ex.dev_utils.type_assertions import (
assert_is_address,
assert_is_hex_string,
@ -48,13 +50,18 @@ class _Constants:
eip191_header = b"\x19\x01"
eip712_domain_separator_schema_hash = keccak(
b"EIP712Domain(string name,string version,address verifyingContract)"
b"EIP712Domain("
+ b"string name,"
+ b"string version,"
+ b"uint256 chainId,"
+ b"address verifyingContract"
+ b")"
)
eip712_domain_struct_header = (
eip712_domain_separator_schema_hash
+ keccak(b"0x Protocol")
+ keccak(b"2")
+ keccak(b"3.0.0")
)
eip712_order_schema_hash = keccak(
@ -70,7 +77,9 @@ class _Constants:
+ b"uint256 expirationTimeSeconds,"
+ b"uint256 salt,"
+ b"bytes makerAssetData,"
+ b"bytes takerAssetData"
+ b"bytes takerAssetData,"
+ b"bytes makerFeeAssetData,"
+ b"bytes takerFeeAssetData"
+ b")"
)
@ -87,7 +96,9 @@ class _Constants:
N_SIGNATURE_TYPES = auto()
def generate_order_hash_hex(order: Order, exchange_address: str) -> str:
def generate_order_hash_hex(
order: Order, exchange_address: str, chain_id: int
) -> str:
"""Calculate the hash of the given order as a hexadecimal string.
:param order: The order to be hashed. Must conform to `the 0x order JSON schema <https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/order_schema.json>`_.
@ -95,27 +106,35 @@ def generate_order_hash_hex(order: Order, exchange_address: str) -> str:
contract has been deployed.
:returns: A string, of ASCII hex digits, representing the order hash.
Inputs and expected result below were copied from
@0x/order-utils/test/order_hash_test.ts
>>> generate_order_hash_hex(
... Order(
... makerAddress="0x0000000000000000000000000000000000000000",
... takerAddress="0x0000000000000000000000000000000000000000",
... feeRecipientAddress="0x0000000000000000000000000000000000000000",
... senderAddress="0x0000000000000000000000000000000000000000",
... makerAssetAmount="1000000000000000000",
... takerAssetAmount="1000000000000000000",
... makerAssetAmount="0",
... takerAssetAmount="0",
... makerFee="0",
... takerFee="0",
... expirationTimeSeconds="12345",
... salt="12345",
... expirationTimeSeconds="0",
... salt="0",
... 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),
... ),
... exchange_address="0x0000000000000000000000000000000000000000",
... exchange_address="0x1dc4c1cefef38a777b15aa20260a54e584b16c48",
... chain_id=50
... )
'55eaa6ec02f3224d30873577e9ddd069a288c16d6fb407210eecbc501fa76692'
'331cb7e07a757bae130702da6646c26531798c92bcfaf671817268fd2c188531'
""" # noqa: E501 (line too long)
assert_is_address(exchange_address, "exchange_address")
assert_valid(order_to_jsdict(order, exchange_address), "/orderSchema")
assert_valid(
order_to_jsdict(order, chain_id, exchange_address), "/orderSchema"
)
def pad_20_bytes_to_32(twenty_bytes: bytes):
return bytes(12) + twenty_bytes
@ -125,9 +144,17 @@ def generate_order_hash_hex(order: Order, exchange_address: str) -> str:
eip712_domain_struct_hash = keccak(
_Constants.eip712_domain_struct_header
+ int_to_32_big_endian_bytes(int(chain_id))
+ pad_20_bytes_to_32(to_bytes(hexstr=exchange_address))
)
def ensure_bytes(str_or_bytes: Union[str, bytes]) -> bytes:
return (
to_bytes(hexstr=cast(bytes, str_or_bytes))
if isinstance(str_or_bytes, str)
else str_or_bytes
)
eip712_order_struct_hash = keccak(
_Constants.eip712_order_schema_hash
+ pad_20_bytes_to_32(to_bytes(hexstr=order["makerAddress"]))
@ -140,8 +167,10 @@ def generate_order_hash_hex(order: Order, exchange_address: str) -> str:
+ int_to_32_big_endian_bytes(int(order["takerFee"]))
+ int_to_32_big_endian_bytes(int(order["expirationTimeSeconds"]))
+ int_to_32_big_endian_bytes(int(order["salt"]))
+ keccak(to_bytes(hexstr=order["makerAssetData"].hex()))
+ keccak(to_bytes(hexstr=order["takerAssetData"].hex()))
+ keccak(ensure_bytes(order["makerAssetData"]))
+ keccak(ensure_bytes(order["takerAssetData"]))
+ keccak(ensure_bytes(order["makerFeeAssetData"]))
+ keccak(ensure_bytes(order["takerFeeAssetData"]))
)
return keccak(
@ -153,7 +182,7 @@ def generate_order_hash_hex(order: Order, exchange_address: str) -> str:
def is_valid_signature(
provider: BaseProvider, data: str, signature: str, signer_address: str
) -> Tuple[bool, str]:
) -> bool:
"""Check the validity of the supplied signature.
Check if the supplied `signature`:code: corresponds to signing `data`:code:
@ -173,42 +202,25 @@ def is_valid_signature(
... '0x1B61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc3340349190569279751135161d22529dc25add4f6069af05be04cacbda2ace225403',
... '0x5409ed021d9299bf6814279a6a1411a7e866a631',
... )
(True, '')
True
""" # noqa: E501 (line too long)
assert_is_provider(provider, "provider")
assert_is_hex_string(data, "data")
assert_is_hex_string(signature, "signature")
assert_is_address(signer_address, "signer_address")
web3_instance = Web3(provider)
# false positive from pylint: disable=no-member
contract_address = NETWORK_TO_ADDRESSES[
NetworkId(int(web3_instance.net.version))
].exchange
# false positive from pylint: disable=no-member
contract: Contract = web3_instance.eth.contract(
address=to_checksum_address(contract_address),
abi=zero_ex.contract_artifacts.abi_by_name("Exchange"),
return Exchange(
provider,
network_to_addresses(
NetworkId(
int(Web3(provider).net.version) # pylint: disable=no-member
)
).exchange,
).is_valid_hash_signature.call(
bytes.fromhex(remove_0x_prefix(data)),
to_checksum_address(signer_address),
bytes.fromhex(remove_0x_prefix(signature)),
)
try:
return (
contract.functions.isValidSignature(
data, to_checksum_address(signer_address), signature
).call(),
"",
)
except web3.exceptions.BadFunctionCallOutput as exception:
known_revert_reasons = [
"LENGTH_GREATER_THAN_0_REQUIRED",
"SIGNATURE_ILLEGAL",
"SIGNATURE_UNSUPPORTED",
"LENGTH_0_REQUIRED",
"LENGTH_65_REQUIRED",
]
for known_revert_reason in known_revert_reasons:
if known_revert_reason in str(exception):
return (False, known_revert_reason)
return (False, f"Unknown: {exception}")
class ECSignature(TypedDict):
@ -272,11 +284,14 @@ def _convert_ec_signature_to_vrs_hex(signature: ECSignature) -> str:
def sign_hash(
provider: BaseProvider, signer_address: str, hash_hex: str
web3_or_provider: Union[Web3, BaseProvider],
signer_address: str,
hash_hex: str,
) -> str:
"""Sign a message with the given hash, and return the signature.
:param provider: A Web3 provider.
:param web3_or_provider: Either an instance of `web3.Web3`:code: or
`web3.providers.base.BaseProvider`:code:
:param signer_address: The address of the signing account.
:param hash_hex: A hex string representing the hash, like that returned
from `generate_order_hash_hex()`:code:.
@ -290,11 +305,20 @@ def sign_hash(
... )
'0x1b117902c86dfb95fe0d1badd983ee166ad259b27acb220174cbb4460d872871137feabdfe76e05924b484789f79af4ee7fa29ec006cedce1bbf369320d034e10b03'
""" # noqa: E501 (line too long)
assert_is_provider(provider, "provider")
web3_instance = None
if isinstance(web3_or_provider, BaseProvider):
web3_instance = Web3(web3_or_provider)
elif isinstance(web3_or_provider, Web3):
web3_instance = web3_or_provider
else:
raise TypeError(
"Expected parameter 'web3_or_provider' to be an instance of either"
+ " Web3 or BaseProvider"
)
assert_is_address(signer_address, "signer_address")
assert_is_hex_string(hash_hex, "hash_hex")
web3_instance = Web3(provider)
# false positive from pylint: disable=no-member
signature = web3_instance.eth.sign( # type: ignore
signer_address, hexstr=hash_hex.replace("0x", "")
@ -319,8 +343,11 @@ def sign_hash(
).hex()
)
(valid, _) = is_valid_signature(
provider, hash_hex, signature_as_vrst_hex, signer_address
valid = is_valid_signature(
web3_instance.provider,
hash_hex,
signature_as_vrst_hex,
signer_address,
)
if valid is True:
@ -334,21 +361,26 @@ def sign_hash(
1, byteorder="big"
).hex()
)
(valid, _) = is_valid_signature(
provider, hash_hex, signature_as_vrst_hex, signer_address
valid = is_valid_signature(
web3_instance.provider,
hash_hex,
signature_as_vrst_hex,
signer_address,
)
if valid is True:
return signature_as_vrst_hex
raise RuntimeError(
"Signature returned from web3 provider is in an unknown format."
+ " Attempted to parse as RSV and as VRS."
"Signature returned from web3 provider is in an unknown format. "
+ "Signature was: {signature}"
)
def sign_hash_to_bytes(
provider: BaseProvider, signer_address: str, hash_hex: str
web3_or_provider: Union[Web3, BaseProvider],
signer_address: str,
hash_hex: str,
) -> bytes:
"""Sign a message with the given hash, and return the signature.
@ -361,5 +393,5 @@ def sign_hash_to_bytes(
'1b117902c86dfb95fe0d1badd983ee166ad259b27acb220174cbb4460d872871137feabdfe76e05924b484789f79af4ee7fa29ec006cedce1bbf369320d034e10b03'
""" # noqa: E501 (line too long)
return remove_0x_prefix(
sign_hash(provider, signer_address, hash_hex)
sign_hash(web3_or_provider, signer_address, hash_hex)
).encode(encoding="utf_8")

View File

@ -34,4 +34,6 @@ class Web3:
...
...
provider: BaseProvider
...

View File

@ -6,7 +6,7 @@ from zero_ex.order_utils import generate_order_hash_hex
def test_get_order_hash_hex__empty_order():
"""Test the hashing of an uninitialized order."""
expected_hash_hex = (
"faa49b35faeb9197e9c3ba7a52075e6dad19739549f153b77dfcf59408a4b422"
"331cb7e07a757bae130702da6646c26531798c92bcfaf671817268fd2c188531"
)
actual_hash_hex = generate_order_hash_hex(
{
@ -18,6 +18,8 @@ def test_get_order_hash_hex__empty_order():
),
"makerAssetData": (b"\x00") * 20,
"takerAssetData": (b"\x00") * 20,
"makerFeeAssetData": (b"\x00") * 20,
"takerFeeAssetData": (b"\x00") * 20,
"salt": 0,
"makerFee": 0,
"takerFee": 0,
@ -25,6 +27,7 @@ def test_get_order_hash_hex__empty_order():
"takerAssetAmount": 0,
"expirationTimeSeconds": 0,
},
"0x0000000000000000000000000000000000000000",
"0x1dc4c1cefef38a777b15aa20260a54e584b16c48",
50,
)
assert actual_hash_hex == expected_hash_hex

View File

@ -3,6 +3,10 @@
import pytest
from web3 import Web3
from zero_ex.contract_wrappers.exchange.exceptions import (
SignatureError,
SignatureErrorCodes,
)
from zero_ex.order_utils import is_valid_signature, sign_hash_to_bytes
@ -117,28 +121,49 @@ def test_is_valid_signature__unsupported_sig_types():
To induce this error, the last byte of the signature is tweaked from 03 to
ff."""
(is_valid, reason) = is_valid_signature(
Web3.HTTPProvider("http://127.0.0.1:8545"),
"0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222b0",
"0x1B61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc334"
+ "0349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254ff",
"0x5409ed021d9299bf6814279a6a1411a7e866a631",
)
assert is_valid is False
assert reason == "SIGNATURE_UNSUPPORTED"
try:
is_valid_signature(
Web3.HTTPProvider("http://127.0.0.1:8545"),
"0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222"
+ "b0",
"0x1B61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351b"
+ "c3340349190569279751135161d22529dc25add4f6069af05be04cacbda2ace"
+ "225401",
"0x5409ed021d9299bf6814279a6a1411a7e866a631",
)
except SignatureError as signature_error:
assert (
signature_error.errorCode
== SignatureErrorCodes.INVALID_LENGTH.value
)
else:
pytest.fail("Expected exception")
def test_sign_hash_to_bytes__golden_path():
def test_sign_hash_to_bytes_and_validate__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( # pylint: disable=no-member
provider
).geth.personal.listAccounts()[0],
"0x34decbedc118904df65f379a175bb39ca18209d6ce41d5ed549d54e6e0a95004",
signing_address = Web3( # pylint: disable=no-member
provider
).geth.personal.listAccounts()[0]
order_hash_hex = (
"0x34decbedc118904df65f379a175bb39ca18209d6ce41d5ed549d54e6e0a95004"
)
signature = sign_hash_to_bytes(provider, signing_address, order_hash_hex)
assert (
signature
== b"1b117902c86dfb95fe0d1badd983ee166ad259b27acb220174cbb4460d872871137feabdfe76e05924b484789f79af4ee7fa29ec006cedce1bbf369320d034e10b03" # noqa: E501 (line too long)
)
is_valid = is_valid_signature(
Web3.HTTPProvider("http://127.0.0.1:8545"),
order_hash_hex,
signature.decode("utf-8"),
signing_address,
)
assert is_valid is True

View File

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

View File

@ -22,7 +22,7 @@ $ ./parallel pip uninstall $(basename $(pwd))
from concurrent.futures import ProcessPoolExecutor, wait
from os import chdir
from subprocess import CalledProcessError, check_output
from subprocess import CalledProcessError, check_output, STDOUT
from sys import argv
PACKAGES = [
@ -38,11 +38,14 @@ PACKAGES = [
def run_cmd_on_package(package: str):
"""cd to the package dir, ./setup.py lint, cd .."""
chdir(package)
command = f"{' '.join(argv[1:])}"
try:
check_output(f"{' '.join(argv[1:])}".split())
check_output(command.split(), stderr=STDOUT)
except CalledProcessError as error:
print(f"standard output from command:\n{error.output.decode('utf-8')}")
raise RuntimeError(f"Above exception raised in {package}, ") from error
raise RuntimeError(
f"Failure return code received from command `{command}` in package"
+ f" {package}, which produced the following output:\n"
+ f"{error.output.decode('utf-8')}") from error
finally:
chdir("..")

View File

@ -1,54 +0,0 @@
#!/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, wait
from os import chdir
from subprocess import CalledProcessError, check_output
from sys import argv
PACKAGES = [
"contract_addresses",
"contract_artifacts",
"contract_wrappers",
"json_schemas",
"order_utils",
"middlewares",
]
def run_cmd_on_package(package: str):
"""cd to the package dir, ./setup.py lint, cd .."""
chdir(package)
try:
check_output(f"{' '.join(argv[1:])}".split())
except CalledProcessError as error:
print(f"standard output from command:\n{error.output.decode('utf-8')}")
raise RuntimeError(f"Above exception raised in {package}, ") from error
finally:
chdir("..")
with ProcessPoolExecutor() as executor:
for future in executor.map(run_cmd_on_package, PACKAGES):
# iterate over map()'s return value, to resolve the futures.
# but we don't actually care what the return values are, so just `pass`.
# if any exceptions were raised by the underlying task, they'll be
# raised as the iteration encounters them.
pass

View File

@ -12,6 +12,7 @@ from sys import argv
PACKAGES = [
"contract_wrappers",
"contract_addresses",
"contract_artifacts",
"json_schemas",
]

View File

@ -1,5 +1,9 @@
# Changelog
## 4.0.0 - TBD
- Migrated from v2 to v3 of the 0x protocol.
## 3.0.0 - 2019-08-08
- Migrated from v4 to v5 of Web3.py.

View File

@ -3,10 +3,15 @@
"""setuptools module for sra_client 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
import distutils.command.build_py
from distutils.command.clean import clean
from shutil import rmtree
from sys import exit # pylint: disable=redefined-builtin
from urllib.request import urlopen
from urllib.error import URLError
@ -14,7 +19,7 @@ from setuptools import setup, find_packages # noqa: H301
from setuptools.command.test import test as TestCommand
NAME = "0x-sra-client"
VERSION = "3.0.0"
VERSION = "4.0.0"
# To install the library, run the following
#
# python setup.py install
@ -41,6 +46,12 @@ class CleanCommandExtension(clean):
rmtree("0x_sra_client.egg-info", ignore_errors=True)
rmtree("build", ignore_errors=True)
rmtree("dist", ignore_errors=True)
subprocess.check_call( # nosec
("docker-compose -f test/relayer/docker-compose.yml down").split()
)
subprocess.check_call( # nosec
("docker-compose -f test/relayer/docker-compose.yml rm").split()
)
class TestCommandExtension(TestCommand):
@ -85,12 +96,15 @@ class StartTestRelayerCommand(distutils.command.build_py.build_py):
("docker-compose -f test/relayer/docker-compose.yml up -d").split()
)
launch_kit_ready = False
print("Waiting for relayer to start accepting connections...", end="")
print(
"Waiting for Launch Kit Backend to start accepting connections...",
flush=True,
)
while not launch_kit_ready:
try:
launch_kit_ready = (
urlopen( # nosec
"http://localhost:3000/v2/asset_pairs"
"http://localhost:3000/v3/asset_pairs"
).getcode()
== 200
)

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

@ -19,7 +19,7 @@ Install the package with pip::
pip install 0x-sra-client
To interact with a 0x Relayer, you need the HTTP endpoint of the Relayer you'd
like to connect to (eg https://api.radarrelay.com/0x/v2).
like to connect to (eg https://api.radarrelay.com/0x/v3).
For testing one can use the `0x-launch-kit-backend
<https://github.com/0xProject/0x-launch-kit-backend#table-of-contents/>`_ to host
@ -83,8 +83,8 @@ for this account, so the example orders below have the maker trading away ZRX.
Before such an order can be valid, though, the maker must give the 0x contracts
permission to trade their ZRX tokens:
>>> from zero_ex.contract_addresses import NETWORK_TO_ADDRESSES
>>> contract_addresses = NETWORK_TO_ADDRESSES[network_id]
>>> from zero_ex.contract_addresses import network_to_addresses
>>> contract_addresses = network_to_addresses(network_id)
>>>
>>> from zero_ex.contract_artifacts import abi_by_name
>>> zrx_token_contract = Web3(eth_node).eth.contract(
@ -105,7 +105,8 @@ 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.contract_wrappers.exchange.types import Order
>>> from zero_ex.contract_wrappers.order_conversions import order_to_jsdict
>>> from zero_ex.order_utils import (
... asset_data_utils,
... sign_hash)
@ -120,9 +121,11 @@ Post an order for our Maker to trade ZRX for WETH:
... makerAssetData=asset_data_utils.encode_erc20(
... contract_addresses.zrx_token
... ),
... makerFeeAssetData=asset_data_utils.encode_erc20('0x'+'00'*20),
... takerAssetData=asset_data_utils.encode_erc20(
... contract_addresses.ether_token
... ),
... takerFeeAssetData=asset_data_utils.encode_erc20('0x'+'00'*20),
... salt=random.randint(1, 100000000000000000),
... makerFee=0,
... takerFee=0,
@ -135,7 +138,7 @@ Post an order for our Maker to trade ZRX for WETH:
>>> from zero_ex.order_utils import generate_order_hash_hex
>>> order_hash_hex = generate_order_hash_hex(
... order, contract_addresses.exchange
... order, contract_addresses.exchange, Web3(eth_node).eth.chainId
... )
>>> relayer.post_order_with_http_info(
... network_id=network_id.value,
@ -144,7 +147,8 @@ Post an order for our Maker to trade ZRX for WETH:
... exchange_address=contract_addresses.exchange,
... signature=sign_hash(
... eth_node, Web3.toChecksumAddress(maker_address), order_hash_hex
... )
... ),
... chain_id=Web3(eth_node).eth.chainId,
... )
... )[1]
200
@ -152,24 +156,35 @@ Post an order for our Maker to trade ZRX for WETH:
Get Order
---------
(But first sleep for a moment, to give the test relayer a chance to start up.
>>> from time import sleep
>>> sleep(0.2)
This is necessary for automated verification of these examples.)
Retrieve the order we just posted:
>>> relayer.get_order("0x" + order_hash_hex)
{'meta_data': {},
'order': {'exchangeAddress': '0x...',
{'meta_data': {'orderHash': '0x...',
'remainingFillableTakerAssetAmount': '2'},
'order': {'chainId': 50,
'exchangeAddress': '0x...',
'expirationTimeSeconds': '...',
'feeRecipientAddress': '0x0000000000000000000000000000000000000000',
'makerAddress': '0x...',
'makerAssetAmount': '2',
'makerAssetData': '0xf47261b0000000000000000000000000...',
'makerFee': '0',
'makerFeeAssetData': '0xf47261b0000000000000000000000000...',
'salt': '...',
'senderAddress': '0x0000000000000000000000000000000000000000',
'signature': '0x...',
'takerAddress': '0x0000000000000000000000000000000000000000',
'takerAssetAmount': '2',
'takerAssetData': '0xf47261b0000000000000000000000000...',
'takerFee': '0'}}
'takerFee': '0',
'takerFeeAssetData': '0xf47261b0000000000000000000000000...'}}
Get Orders
-----------
@ -178,21 +193,25 @@ Retrieve all of the Relayer's orders, a set which at this point consists solely
of the one we just posted:
>>> relayer.get_orders()
{'records': [{'meta_data': {},
'order': {'exchangeAddress': '0x...',
{'records': [{'meta_data': {'orderHash': '0x...',
'remainingFillableTakerAssetAmount': '2'},
'order': {'chainId': 50,
'exchangeAddress': '0x...',
'expirationTimeSeconds': '...',
'feeRecipientAddress': '0x0000000000000000000000000000000000000000',
'makerAddress': '0x...',
'makerAssetAmount': '2',
'makerAssetData': '0xf47261b000000000000000000000000...',
'makerFee': '0',
'makerFeeAssetData': '0xf47261b000000000000000000000000...',
'salt': '...',
'senderAddress': '0x0000000000000000000000000000000000000000',
'signature': '0x...',
'takerAddress': '0x0000000000000000000000000000000000000000',
'takerAssetAmount': '2',
'takerAssetData': '0xf47261b0000000000000000000000000...',
'takerFee': '0'}}]}
'takerFee': '0',
'takerFeeAssetData': '0xf47261b0000000000000000000000000...'}}...]}
Get Asset Pairs
---------------
@ -233,43 +252,50 @@ consists just of our order):
... ).hex(),
... )
>>> orderbook
{'asks': {'records': []},
'bids': {'records': [{'meta_data': {},
'order': {'exchangeAddress': '0x...',
{'asks': {'records': [...]},
'bids': {'records': [{'meta_data': {'orderHash': '0x...',
'remainingFillableTakerAssetAmount': '2'},
'order': {'chainId': 50,
'exchangeAddress': '0x...',
'expirationTimeSeconds': '...',
'feeRecipientAddress': '0x0000000000000000000000000000000000000000',
'makerAddress': '0x...',
'makerAssetAmount': '2',
'makerAssetData': '0xf47261b0000000000000000000000000...',
'makerFee': '0',
'makerFeeAssetData': '0xf47261b0000000000000000000000000...',
'salt': '...',
'senderAddress': '0x0000000000000000000000000000000000000000',
'signature': '0x...',
'takerAddress': '0x0000000000000000000000000000000000000000',
'takerAssetAmount': '2',
'takerAssetData': '0xf47261b0000000000000000000000000...',
'takerFee': '0'}}]}}
'takerFee': '0',
'takerFeeAssetData': '0xf47261b0000000000000000000000000...'}}...]}}
Select an order from the orderbook
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>>> from zero_ex.contract_wrappers.exchange.types import jsdict_to_order
>>> from zero_ex.contract_wrappers.order_conversions import jsdict_to_order
>>> order = jsdict_to_order(orderbook.bids.records[0].order)
>>> from pprint import pprint
>>> pprint(order)
{'expirationTimeSeconds': ...,
{'chainId': 50,
'expirationTimeSeconds': ...,
'feeRecipientAddress': '0x0000000000000000000000000000000000000000',
'makerAddress': '0x...',
'makerAssetAmount': 2,
'makerAssetData': b...
'makerFee': 0,
'makerFeeAssetData': b...
'salt': ...,
'senderAddress': '0x0000000000000000000000000000000000000000',
'signature': '0x...',
'takerAddress': '0x0000000000000000000000000000000000000000',
'takerAssetAmount': 2,
'takerAssetData': b...
'takerFee': 0}
'takerAssetData': b...,
'takerFee': 0,
'takerFeeAssetData': b...}
Filling or Cancelling an Order
------------------------------
@ -319,8 +345,8 @@ book. Now let's have the taker fill it:
>>> from zero_ex.contract_wrappers.exchange import Exchange
>>> from zero_ex.order_utils import Order
>>> exchange = Exchange(
... provider=eth_node,
... contract_address=NETWORK_TO_ADDRESSES[NetworkId.GANACHE].exchange
... web3_or_provider=eth_node,
... contract_address=network_to_addresses(NetworkId.GANACHE).exchange
... )
(Due to `an Issue with the Launch Kit Backend
@ -331,7 +357,7 @@ checksum the address in the order before filling it.)
>>> exchange.fill_order.send_transaction(
... order=order,
... taker_asset_fill_amount=order['makerAssetAmount']/2, # note the half fill
... signature=order['signature'].replace('0x', '').encode('utf-8'),
... signature=bytes.fromhex(order['signature'].replace('0x', '')),
... tx_params=TxParams(from_=taker_address)
... )
HexBytes('0x...')

View File

@ -139,7 +139,7 @@ class DefaultApi(object):
auth_settings = []
return self.api_client.call_api(
"/v2/asset_pairs",
"/v3/asset_pairs",
"GET",
path_params,
query_params,
@ -250,7 +250,7 @@ class DefaultApi(object):
auth_settings = []
return self.api_client.call_api(
"/v2/fee_recipients",
"/v3/fee_recipients",
"GET",
path_params,
query_params,
@ -363,7 +363,7 @@ class DefaultApi(object):
auth_settings = []
return self.api_client.call_api(
"/v2/order/{orderHash}",
"/v3/order/{orderHash}",
"GET",
path_params,
query_params,
@ -497,7 +497,7 @@ class DefaultApi(object):
auth_settings = []
return self.api_client.call_api(
"/v2/order_config",
"/v3/order_config",
"POST",
path_params,
query_params,
@ -680,7 +680,7 @@ class DefaultApi(object):
auth_settings = []
return self.api_client.call_api(
"/v2/orderbook",
"/v3/orderbook",
"GET",
path_params,
query_params,
@ -718,50 +718,48 @@ class DefaultApi(object):
:param bool async_req: Whether request should be asynchronous.
:param str maker_asset_proxy_id: The maker
`asset proxy id
<https://0x.org/docs/tools/0x.js#types-AssetProxyId>`__
<https://0x.org/docs/tools/0x.js#enumeration-assetproxyid>`__
(example: "0xf47261b0" for ERC20, "0x02571792" for ERC721).
:param str taker_asset_proxy_id: The taker asset
`asset proxy id
<https://0x.org/docs/tools/0x.js#types-AssetProxyId>`__
<https://0x.org/docs/tools/0x.js#enumeration-assetproxyid>`__
(example: "0xf47261b0" for ERC20, "0x02571792" for ERC721).
:param str maker_asset_address: The contract address for the maker asset.
:param str taker_asset_address: The contract address for the taker asset.
:param str exchange_address: Same as exchangeAddress in the
`0x Protocol v2 Specification
<https://github.com/0xProject/0x-protocol-specification/blob/
master/v2/v2-specification.md#order-message-format>`__
:param str exchange_address: Contract address for the exchange
contract.
:param str sender_address: Same as senderAddress in the
`0x Protocol v2 Specification
`0x Protocol v3 Specification
<https://github.com/0xProject/0x-protocol-specification/blob/
master/v2/v2-specification.md#order-message-format>`__
master/v3/v3-specification.md#order-message-format>`__
:param str maker_asset_data: Same as makerAssetData in the
`0x Protocol v2 Specification
`0x Protocol v3 Specification
<https://github.com/0xProject/0x-protocol-specification/blob/
master/v2/v2-specification.md#order-message-format>`__
master/v3/v3-specification.md#order-message-format>`__
:param str taker_asset_data: Same as takerAssetData in the
`0x Protocol v2 Specification
`0x Protocol v3 Specification
<https://github.com/0xProject/0x-protocol-specification/blob/
master/v2/v2-specification.md#order-message-format>`__
master/v3/v3-specification.md#order-message-format>`__
:param str trader_asset_data: Same as traderAssetData in the [0x
`0x Protocol v2 Specification
`0x Protocol v3 Specification
<https://github.com/0xProject/0x-protocol-specification/blob/
master/v2/v2-specification.md#order-message-format>`__
master/v3/v3-specification.md#order-message-format>`__
:param str maker_address: Same as makerAddress in the
`0x Protocol v2 Specification
`0x Protocol v3 Specification
<https://github.com/0xProject/0x-protocol-specification/blob/
master/v2/v2-specification.md#order-message-format>`__
master/v3/v3-specification.md#order-message-format>`__
:param str taker_address: Same as takerAddress in the
`0x Protocol v2 Specification
`0x Protocol v3 Specification
<https://github.com/0xProject/0x-protocol-specification/blob/
master/v2/v2-specification.md#order-message-format>`__
master/v3/v3-specification.md#order-message-format>`__
:param str trader_address: Same as traderAddress in the
`0x Protocol v2 Specification
`0x Protocol v3 Specification
<https://github.com/0xProject/0x-protocol-specification/blob/
master/v2/v2-specification.md#order-message-format>`__
master/v3/v3-specification.md#order-message-format>`__
:param str fee_recipient_address: Same as feeRecipientAddress in the
`0x Protocol v2 Specification
`0x Protocol v3 Specification
<https://github.com/0xProject/0x-protocol-specification/blob/
master/v2/v2-specification.md#order-message-format>`__
master/v3/v3-specification.md#order-message-format>`__
:param int network_id: The id of the Ethereum network
:param int page: The number of the page to request in the collection.
:param int per_page: The number of records to return per page.
@ -795,50 +793,50 @@ class DefaultApi(object):
:param bool async_req: Whether request should be asynchronous.
:param str maker_asset_proxy_id: The maker
`asset proxy id
<https://0x.org/docs/tools/0x.js#types-AssetProxyId>`__
<https://0x.org/docs/tools/0x.js#enumeration-assetproxyid>`__
(example: "0xf47261b0" for ERC20, "0x02571792" for ERC721).
:param str taker_asset_proxy_id: The taker asset
`asset proxy id
<https://0x.org/docs/tools/0x.js#types-AssetProxyId>`__
<https://0x.org/docs/tools/0x.js#enumeration-assetproxyid>`__
(example: "0xf47261b0" for ERC20, "0x02571792" for ERC721).
:param str maker_asset_address: The contract address for the maker asset.
:param str taker_asset_address: The contract address for the taker asset.
:param str exchange_address: Same as exchangeAddress in the [0x
`0x Protocol v2 Specification
`0x Protocol v3 Specification
<https://github.com/0xProject/0x-protocol-specification/blob/
master/v2/v2-specification.md#order-message-format>`__
master/v3/v3-specification.md#order-message-format>`__
:param str sender_address: Same as senderAddress in the
`0x Protocol v2 Specification
`0x Protocol v3 Specification
<https://github.com/0xProject/0x-protocol-specification/blob/
master/v2/v2-specification.md#order-message-format>`__
master/v3/v3-specification.md#order-message-format>`__
:param str maker_asset_data: Same as makerAssetData in the
`0x Protocol v2 Specification
`0x Protocol v3 Specification
<https://github.com/0xProject/0x-protocol-specification/blob/
master/v2/v2-specification.md#order-message-format>`__
master/v3/v3-specification.md#order-message-format>`__
:param str taker_asset_data: Same as takerAssetData in the
`0x Protocol v2 Specification
`0x Protocol v3 Specification
<https://github.com/0xProject/0x-protocol-specification/blob/
master/v2/v2-specification.md#order-message-format>`__
master/v3/v3-specification.md#order-message-format>`__
:param str trader_asset_data: Same as traderAssetData in the
`0x Protocol v2 Specification
`0x Protocol v3 Specification
<https://github.com/0xProject/0x-protocol-specification/blob/
master/v2/v2-specification.md#order-message-format>`__
master/v3/v3-specification.md#order-message-format>`__
:param str maker_address: Same as makerAddress in the
`0x Protocol v2 Specification
`0x Protocol v3 Specification
<https://github.com/0xProject/0x-protocol-specification/blob/
master/v2/v2-specification.md#order-message-format>`__
master/v3/v3-specification.md#order-message-format>`__
:param str taker_address: Same as takerAddress in the
`0x Protocol v2 Specification
`0x Protocol v3 Specification
<https://github.com/0xProject/0x-protocol-specification/blob/
master/v2/v2-specification.md#order-message-format>`__
master/v3/v3-specification.md#order-message-format>`__
:param str trader_address: Same as traderAddress in the
`0x Protocol v2 Specification
`0x Protocol v3 Specification
<https://github.com/0xProject/0x-protocol-specification/blob/
master/v2/v2-specification.md#order-message-format>`__
master/v3/v3-specification.md#order-message-format>`__
:param str fee_recipient_address: Same as feeRecipientAddress in the
`0x Protocol v2 Specification
`0x Protocol v3 Specification
<https://github.com/0xProject/0x-protocol-specification/blob/
master/v2/v2-specification.md#order-message-format>`__
master/v3/v3-specification.md#order-message-format>`__
:param int network_id: The id of the Ethereum network
:param int page: The number of the page to request in the collection.
:param int per_page: The number of records to return per page.
@ -965,7 +963,7 @@ class DefaultApi(object):
auth_settings = []
return self.api_client.call_api(
"/v2/orders",
"/v3/orders",
"GET",
path_params,
query_params,
@ -1077,7 +1075,7 @@ class DefaultApi(object):
auth_settings = []
return self.api_client.call_api(
"/v2/order",
"/v3/order",
"POST",
path_params,
query_params,

View File

@ -1,14 +1,36 @@
# Run Launch Kit with Ganache as the backing node
# Run Launch Kit Backend with Ganache and Mesh instances backing it.
version: '3'
services:
ganache:
image: "0xorg/ganache-cli:2.2.2"
image: "0xorg/ganache-cli:4.4.0-beta.1"
ports:
- "8545:8545"
launchkit:
image: "0xorg/launch-kit-backend:74bcc39"
environment:
- VERSION=4.4.0-beta.1
- SNAPSHOT_NAME=0x_ganache_snapshot-v3-beta
mesh:
image: 0xorg/mesh:6.0.0-beta-0xv3
depends_on:
- ganache
environment:
ETHEREUM_RPC_URL: 'http://localhost:8545'
ETHEREUM_NETWORK_ID: '50'
ETHEREUM_CHAIN_ID: '1337'
USE_BOOTSTRAP_LIST: 'true'
VERBOSITY: 3
PRIVATE_KEY_PATH: ''
BLOCK_POLLING_INTERVAL: '5s'
P2P_LISTEN_PORT: '60557'
ports:
- '60557:60557'
network_mode: "host" # to connect to ganache
command: |
sh -c "waitForGanache () { until printf 'POST /\r\nContent-Length: 26\r\n\r\n{\"method\":\"net_listening\"}' | nc localhost 8545 | grep true; do continue; done }; waitForGanache && ./mesh"
launch-kit-backend:
image: "0xorg/launch-kit-backend:v3"
depends_on:
- ganache
- mesh
ports:
- "3000:3000"
network_mode: "host" # to connect to ganache
@ -16,5 +38,9 @@ services:
- NETWORK_ID=50
- RPC_URL=http://localhost:8545
- WHITELIST_ALL_TOKENS=True
- FEE_RECIPIENT=0x0000000000000000000000000000000000000001
- MAKER_FEE_UNIT_AMOUNT=0
- TAKER_FEE_UNIT_AMOUNT=0
- MESH_ENDPOINT=ws://localhost:60557
command: |
sh -c "until printf 'POST /\r\nContent-Length: 26\r\n\r\n{\"method\":\"net_listening\"}' | nc localhost 8545 | grep true; do continue; done; node_modules/.bin/forever ts/lib/index.js"
sh -c "waitForMesh () { sleep 3; }; waitForMesh && sleep 5 && node_modules/.bin/forever ts/lib/index.js"

View File

@ -18,7 +18,7 @@ commands =
pytest test
[testenv:run_tests_against_deployment]
deps=pytest
setenv = PY_IGNORE_IMPORTMISMATCH = 1
commands =
pip install 0x-sra-client
pytest test
pip install 0x-sra-client[dev]
pytest --doctest-modules src test