diff --git a/.circleci/config.yml b/.circleci/config.yml index 421c1d9a9d..61e62d9eab 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -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 diff --git a/.gitignore b/.gitignore index 934fdf50d0..7313764d8c 100644 --- a/.gitignore +++ b/.gitignore @@ -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* diff --git a/packages/abi-gen/CHANGELOG.json b/packages/abi-gen/CHANGELOG.json index 73b8d81211..079e1f902b 100644 --- a/packages/abi-gen/CHANGELOG.json +++ b/packages/abi-gen/CHANGELOG.json @@ -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": [ diff --git a/packages/abi-gen/src/utils.ts b/packages/abi-gen/src/utils.ts index bb5a2d7812..8828ce009b 100644 --- a/packages/abi-gen/src/utils.ts +++ b/packages/abi-gen/src/utils.ts @@ -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; diff --git a/packages/abi-gen/templates/Python/contract.handlebars b/packages/abi-gen/templates/Python/contract.handlebars index 6aec525d3f..ab6e9682da 100644 --- a/packages/abi-gen/templates/Python/contract.handlebars +++ b/packages/abi-gen/templates/Python/contract.handlebars @@ -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}} diff --git a/packages/abi-gen/templates/Python/partials/method_class.handlebars b/packages/abi-gen/templates/Python/partials/method_class.handlebars index 1817eac591..2586142662 100644 --- a/packages/abi-gen/templates/Python/partials/method_class.handlebars +++ b/packages/abi-gen/templates/Python/partials/method_class.handlebars @@ -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 }}) diff --git a/packages/abi-gen/test-cli/output/python/abi_gen_dummy/__init__.py b/packages/abi-gen/test-cli/output/python/abi_gen_dummy/__init__.py index e215b5f7cc..73d062aefc 100644 --- a/packages/abi-gen/test-cli/output/python/abi_gen_dummy/__init__.py +++ b/packages/abi-gen/test-cli/output/python/abi_gen_dummy/__init__.py @@ -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( diff --git a/packages/abi-gen/test-cli/output/python/lib_dummy/__init__.py b/packages/abi-gen/test-cli/output/python/lib_dummy/__init__.py index 1181817fdc..6c1375710e 100644 --- a/packages/abi-gen/test-cli/output/python/lib_dummy/__init__.py +++ b/packages/abi-gen/test-cli/output/python/lib_dummy/__init__.py @@ -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(): diff --git a/packages/abi-gen/test-cli/output/python/test_lib_dummy/__init__.py b/packages/abi-gen/test-cli/output/python/test_lib_dummy/__init__.py index ebdd8c6c6a..b49c62b7f2 100644 --- a/packages/abi-gen/test-cli/output/python/test_lib_dummy/__init__.py +++ b/packages/abi-gen/test-cli/output/python/test_lib_dummy/__init__.py @@ -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 diff --git a/packages/contract-addresses/addresses.json b/packages/contract-addresses/addresses.json new file mode 100644 index 0000000000..9eeb90f921 --- /dev/null +++ b/packages/contract-addresses/addresses.json @@ -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" + } +} diff --git a/packages/contract-addresses/src/index.ts b/packages/contract-addresses/src/index.ts index 1687c0d311..f2c12e6458 100644 --- a/packages/contract-addresses/src/index.ts +++ b/packages/contract-addresses/src/index.ts @@ -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.`); } diff --git a/packages/contract-addresses/tsconfig.json b/packages/contract-addresses/tsconfig.json index 233008d61c..3d191a3c01 100644 --- a/packages/contract-addresses/tsconfig.json +++ b/packages/contract-addresses/tsconfig.json @@ -2,7 +2,10 @@ "extends": "../../tsconfig", "compilerOptions": { "outDir": "lib", - "rootDir": "." + "rootDir": ".", + "resolveJsonModule": true, + "esModuleInterop": true }, - "include": ["./src/**/*"] + "include": ["./src/**/*"], + "files": ["./addresses.json"] } diff --git a/python-packages/contract_addresses/setup.py b/python-packages/contract_addresses/setup.py index 1e1d811f6e..0d4dee62c3 100755 --- a/python-packages/contract_addresses/setup.py +++ b/python-packages/contract_addresses/setup.py @@ -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=( diff --git a/python-packages/contract_addresses/src/index.rst b/python-packages/contract_addresses/src/index.rst index 5dd096b16a..62a2f4bf0e 100644 --- a/python-packages/contract_addresses/src/index.rst +++ b/python-packages/contract_addresses/src/index.rst @@ -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 diff --git a/python-packages/contract_addresses/src/zero_ex/__init__.py b/python-packages/contract_addresses/src/zero_ex/__init__.py index e90d833db6..3fe1300cfb 100644 --- a/python-packages/contract_addresses/src/zero_ex/__init__.py +++ b/python-packages/contract_addresses/src/zero_ex/__init__.py @@ -1,2 +1,2 @@ """0x Python API.""" -__import__("pkg_resources").declare_namespace(__name__) +__import__("pkg_resources").declare_namespace(__name__) # type: ignore diff --git a/python-packages/contract_addresses/src/zero_ex/contract_addresses/__init__.py b/python-packages/contract_addresses/src/zero_ex/contract_addresses/__init__.py index 1a252bfbd2..7e267485d0 100644 --- a/python-packages/contract_addresses/src/zero_ex/contract_addresses/__init__.py +++ b/python-packages/contract_addresses/src/zero_ex/contract_addresses/__init__.py @@ -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"], + ) diff --git a/python-packages/contract_addresses/tox.ini b/python-packages/contract_addresses/tox.ini index 1cce32b5f0..944049af60 100644 --- a/python-packages/contract_addresses/tox.ini +++ b/python-packages/contract_addresses/tox.ini @@ -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 diff --git a/python-packages/contract_artifacts/CHANGELOG.md b/python-packages/contract_artifacts/CHANGELOG.md index 3a4a5cca80..0a67e1b4fe 100644 --- a/python-packages/contract_artifacts/CHANGELOG.md +++ b/python-packages/contract_artifacts/CHANGELOG.md @@ -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. diff --git a/python-packages/contract_artifacts/setup.py b/python-packages/contract_artifacts/setup.py index 101fb2fc41..d4ea9b5577 100755 --- a/python-packages/contract_artifacts/setup.py +++ b/python-packages/contract_artifacts/setup.py @@ -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", diff --git a/python-packages/contract_artifacts/src/zero_ex/__init__.py b/python-packages/contract_artifacts/src/zero_ex/__init__.py index e90d833db6..3fe1300cfb 100644 --- a/python-packages/contract_artifacts/src/zero_ex/__init__.py +++ b/python-packages/contract_artifacts/src/zero_ex/__init__.py @@ -1,2 +1,2 @@ """0x Python API.""" -__import__("pkg_resources").declare_namespace(__name__) +__import__("pkg_resources").declare_namespace(__name__) # type: ignore diff --git a/python-packages/contract_artifacts/src/zero_ex/contract_artifacts/artifacts/EthBalanceChecker.json b/python-packages/contract_artifacts/src/zero_ex/contract_artifacts/artifacts/EthBalanceChecker.json deleted file mode 100644 index 37d20e9223..0000000000 --- a/python-packages/contract_artifacts/src/zero_ex/contract_artifacts/artifacts/EthBalanceChecker.json +++ /dev/null @@ -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": {} -} diff --git a/python-packages/contract_artifacts/tox.ini b/python-packages/contract_artifacts/tox.ini index 1cce32b5f0..4fd5f65639 100644 --- a/python-packages/contract_artifacts/tox.ini +++ b/python-packages/contract_artifacts/tox.ini @@ -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 diff --git a/python-packages/contract_wrappers/CHANGELOG.md b/python-packages/contract_wrappers/CHANGELOG.md index 69ed9b8caa..24161a8b93 100644 --- a/python-packages/contract_wrappers/CHANGELOG.md +++ b/python-packages/contract_wrappers/CHANGELOG.md @@ -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. diff --git a/python-packages/contract_wrappers/setup.py b/python-packages/contract_wrappers/setup.py index 58ad0aba81..301fb6f514 100755 --- a/python-packages/contract_wrappers/setup.py +++ b/python-packages/contract_wrappers/setup.py @@ -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", diff --git a/python-packages/contract_wrappers/src/index.rst b/python-packages/contract_wrappers/src/index.rst index 2cb98cea20..7a20f701e2 100644 --- a/python-packages/contract_wrappers/src/index.rst +++ b/python-packages/contract_wrappers/src/index.rst @@ -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 `_. -.. autoclass:: zero_ex.contract_wrappers.exchange.Tuple0xbb41e5b3 +.. autoclass:: zero_ex.contract_wrappers.exchange.Tuple0x735c43e3 This is the generated class representing `the FillResults struct `_. -.. autoclass:: zero_ex.contract_wrappers.exchange.Tuple0x054ca44e +.. autoclass:: zero_ex.contract_wrappers.exchange.Tuple0x4c5ca29b This is the generated class representing `the MatchedFillResults struct `_. @@ -199,6 +225,10 @@ zero_ex.contract_wrappers.exchange: Generated Tuples This is the generated class representing `the OrderInfo struct `_. +.. autoclass:: zero_ex.contract_wrappers.exchange.Tuple0xdabc15fe + + This is the generated class representing `the ZeroExTransaction struct `_. + Indices and tables ================== diff --git a/python-packages/contract_wrappers/src/zero_ex/__init__.py b/python-packages/contract_wrappers/src/zero_ex/__init__.py index e90d833db6..3fe1300cfb 100644 --- a/python-packages/contract_wrappers/src/zero_ex/__init__.py +++ b/python-packages/contract_wrappers/src/zero_ex/__init__.py @@ -1,2 +1,2 @@ """0x Python API.""" -__import__("pkg_resources").declare_namespace(__name__) +__import__("pkg_resources").declare_namespace(__name__) # type: ignore diff --git a/python-packages/contract_wrappers/src/zero_ex/contract_wrappers/__init__.py b/python-packages/contract_wrappers/src/zero_ex/contract_wrappers/__init__.py index 3ffd80b6bc..538d378689 100644 --- a/python-packages/contract_wrappers/src/zero_ex/contract_wrappers/__init__.py +++ b/python-packages/contract_wrappers/src/zero_ex/contract_wrappers/__init__.py @@ -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]) + + +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 diff --git a/python-packages/contract_wrappers/src/zero_ex/contract_wrappers/bases.py b/python-packages/contract_wrappers/src/zero_ex/contract_wrappers/bases.py index 39280f4efe..75878c77e0 100644 --- a/python-packages/contract_wrappers/src/zero_ex/contract_wrappers/bases.py +++ b/python-packages/contract_wrappers/src/zero_ex/contract_wrappers/bases.py @@ -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 diff --git a/python-packages/contract_wrappers/src/zero_ex/contract_wrappers/exceptions.py b/python-packages/contract_wrappers/src/zero_ex/contract_wrappers/exceptions.py new file mode 100644 index 0000000000..32cf19f25c --- /dev/null +++ b/python-packages/contract_wrappers/src/zero_ex/contract_wrappers/exceptions.py @@ -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) diff --git a/python-packages/contract_wrappers/src/zero_ex/contract_wrappers/exchange/exceptions.py b/python-packages/contract_wrappers/src/zero_ex/contract_wrappers/exchange/exceptions.py new file mode 100644 index 0000000000..ab60b845ac --- /dev/null +++ b/python-packages/contract_wrappers/src/zero_ex/contract_wrappers/exchange/exceptions.py @@ -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" diff --git a/python-packages/contract_wrappers/src/zero_ex/contract_wrappers/exchange/middleware.py b/python-packages/contract_wrappers/src/zero_ex/contract_wrappers/exchange/middleware.py new file mode 100644 index 0000000000..4b509fdd8c --- /dev/null +++ b/python-packages/contract_wrappers/src/zero_ex/contract_wrappers/exchange/middleware.py @@ -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}] diff --git a/python-packages/contract_wrappers/src/zero_ex/contract_wrappers/exchange/types.py b/python-packages/contract_wrappers/src/zero_ex/contract_wrappers/exchange/types.py index 3bdb3e4e75..500732d6bd 100644 --- a/python-packages/contract_wrappers/src/zero_ex/contract_wrappers/exchange/types.py +++ b/python-packages/contract_wrappers/src/zero_ex/contract_wrappers/exchange/types.py @@ -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() diff --git a/python-packages/contract_wrappers/src/zero_ex/contract_wrappers/exchange/validator.py b/python-packages/contract_wrappers/src/zero_ex/contract_wrappers/exchange/validator.py index 2479ee8179..bf9ec40a3b 100644 --- a/python-packages/contract_wrappers/src/zero_ex/contract_wrappers/exchange/validator.py +++ b/python-packages/contract_wrappers/src/zero_ex/contract_wrappers/exchange/validator.py @@ -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", ) diff --git a/python-packages/contract_wrappers/src/zero_ex/contract_wrappers/order_conversions.py b/python-packages/contract_wrappers/src/zero_ex/contract_wrappers/order_conversions.py new file mode 100644 index 0000000000..138e125eeb --- /dev/null +++ b/python-packages/contract_wrappers/src/zero_ex/contract_wrappers/order_conversions.py @@ -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 diff --git a/python-packages/contract_wrappers/src/zero_ex/contract_wrappers/tx_params.py b/python-packages/contract_wrappers/src/zero_ex/contract_wrappers/tx_params.py index 4b864898ec..226a1f908e 100644 --- a/python-packages/contract_wrappers/src/zero_ex/contract_wrappers/tx_params.py +++ b/python-packages/contract_wrappers/src/zero_ex/contract_wrappers/tx_params.py @@ -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 diff --git a/python-packages/contract_wrappers/stubs/web3/__init__.pyi b/python-packages/contract_wrappers/stubs/web3/__init__.pyi index ab531c1e73..1dfa4bb5f5 100644 --- a/python-packages/contract_wrappers/stubs/web3/__init__.pyi +++ b/python-packages/contract_wrappers/stubs/web3/__init__.pyi @@ -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 + ... diff --git a/python-packages/contract_wrappers/test/conftest.py b/python-packages/contract_wrappers/test/conftest.py index 9c0fb47097..040593a373 100644 --- a/python-packages/contract_wrappers/test/conftest.py +++ b/python-packages/contract_wrappers/test/conftest.py @@ -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") diff --git a/python-packages/contract_wrappers/test/test_base_contract_method.py b/python-packages/contract_wrappers/test/test_base_contract_method.py index 6b558b9fad..e437f55aec 100644 --- a/python-packages/contract_wrappers/test/test_base_contract_method.py +++ b/python-packages/contract_wrappers/test/test_base_contract_method.py @@ -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, ) diff --git a/python-packages/contract_wrappers/test/test_erc20_wrapper.py b/python-packages/contract_wrappers/test/test_erc20_wrapper.py index da80356ed0..8e13528e50 100644 --- a/python-packages/contract_wrappers/test/test_erc20_wrapper.py +++ b/python-packages/contract_wrappers/test/test_erc20_wrapper.py @@ -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 ) diff --git a/python-packages/contract_wrappers/test/test_exchange_wrapper.py b/python-packages/contract_wrappers/test/test_exchange_wrapper.py index 8290756a9a..c299eeac9d 100644 --- a/python-packages/contract_wrappers/test/test_exchange_wrapper.py +++ b/python-packages/contract_wrappers/test/test_exchange_wrapper.py @@ -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 + ) diff --git a/python-packages/contract_wrappers/tox.ini b/python-packages/contract_wrappers/tox.ini index cd847a5bb8..c3e7029329 100644 --- a/python-packages/contract_wrappers/tox.ini +++ b/python-packages/contract_wrappers/tox.ini @@ -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 diff --git a/python-packages/json_schemas/CHANGELOG.md b/python-packages/json_schemas/CHANGELOG.md index 88d9babcd2..b06c98ec8c 100644 --- a/python-packages/json_schemas/CHANGELOG.md +++ b/python-packages/json_schemas/CHANGELOG.md @@ -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`. diff --git a/python-packages/json_schemas/setup.py b/python-packages/json_schemas/setup.py index 650e2d8469..1a2d5d6836 100755 --- a/python-packages/json_schemas/setup.py +++ b/python-packages/json_schemas/setup.py @@ -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", diff --git a/python-packages/json_schemas/src/zero_ex/__init__.py b/python-packages/json_schemas/src/zero_ex/__init__.py index e90d833db6..3fe1300cfb 100644 --- a/python-packages/json_schemas/src/zero_ex/__init__.py +++ b/python-packages/json_schemas/src/zero_ex/__init__.py @@ -1,2 +1,2 @@ """0x Python API.""" -__import__("pkg_resources").declare_namespace(__name__) +__import__("pkg_resources").declare_namespace(__name__) # type: ignore diff --git a/python-packages/json_schemas/src/zero_ex/json_schemas/__init__.py b/python-packages/json_schemas/src/zero_ex/json_schemas/__init__.py index e74717d6ea..5036b6fa38 100644 --- a/python-packages/json_schemas/src/zero_ex/json_schemas/__init__.py +++ b/python-packages/json_schemas/src/zero_ex/json_schemas/__init__.py @@ -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) diff --git a/python-packages/json_schemas/test/test_json_schemas.py b/python-packages/json_schemas/test/test_json_schemas.py index cfd2787ee6..bba37f333a 100644 --- a/python-packages/json_schemas/test/test_json_schemas.py +++ b/python-packages/json_schemas/test/test_json_schemas.py @@ -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, } diff --git a/python-packages/json_schemas/tox.ini b/python-packages/json_schemas/tox.ini index 1d5de646ef..87ec5e8473 100644 --- a/python-packages/json_schemas/tox.ini +++ b/python-packages/json_schemas/tox.ini @@ -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 diff --git a/python-packages/middlewares/CHANGELOG.md b/python-packages/middlewares/CHANGELOG.md index 1639a8f546..c1ebd2f6ed 100644 --- a/python-packages/middlewares/CHANGELOG.md +++ b/python-packages/middlewares/CHANGELOG.md @@ -1,5 +1,5 @@ # Changelog -## 1.0.0 - 2019-04-30 +## 1.0.0 - TBD - Initial release. diff --git a/python-packages/middlewares/setup.py b/python-packages/middlewares/setup.py index 3a02febcf0..53b78a8023 100755 --- a/python-packages/middlewares/setup.py +++ b/python-packages/middlewares/setup.py @@ -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 diff --git a/python-packages/middlewares/src/zero_ex/__init__.py b/python-packages/middlewares/src/zero_ex/__init__.py index e90d833db6..3fe1300cfb 100644 --- a/python-packages/middlewares/src/zero_ex/__init__.py +++ b/python-packages/middlewares/src/zero_ex/__init__.py @@ -1,2 +1,2 @@ """0x Python API.""" -__import__("pkg_resources").declare_namespace(__name__) +__import__("pkg_resources").declare_namespace(__name__) # type: ignore diff --git a/python-packages/middlewares/test/test_local_message_signer.py b/python-packages/middlewares/test/test_local_message_signer.py index ef1b36f595..2eb87997a8 100644 --- a/python-packages/middlewares/test/test_local_message_signer.py +++ b/python-packages/middlewares/test/test_local_message_signer.py @@ -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 + ) diff --git a/python-packages/order_utils/CHANGELOG.md b/python-packages/order_utils/CHANGELOG.md index 8a329b289f..afa979ae92 100644 --- a/python-packages/order_utils/CHANGELOG.md +++ b/python-packages/order_utils/CHANGELOG.md @@ -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. diff --git a/python-packages/order_utils/setup.py b/python-packages/order_utils/setup.py index e7a4b2e2fd..f54c1defde 100755 --- a/python-packages/order_utils/setup.py +++ b/python-packages/order_utils/setup.py @@ -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", diff --git a/python-packages/order_utils/src/zero_ex/__init__.py b/python-packages/order_utils/src/zero_ex/__init__.py index e90d833db6..3fe1300cfb 100644 --- a/python-packages/order_utils/src/zero_ex/__init__.py +++ b/python-packages/order_utils/src/zero_ex/__init__.py @@ -1,2 +1,2 @@ """0x Python API.""" -__import__("pkg_resources").declare_namespace(__name__) +__import__("pkg_resources").declare_namespace(__name__) # type: ignore diff --git a/python-packages/order_utils/src/zero_ex/order_utils/__init__.py b/python-packages/order_utils/src/zero_ex/order_utils/__init__.py index 62306ce057..7cc7ef277e 100644 --- a/python-packages/order_utils/src/zero_ex/order_utils/__init__.py +++ b/python-packages/order_utils/src/zero_ex/order_utils/__init__.py @@ -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 `_. @@ -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") diff --git a/python-packages/order_utils/stubs/web3/__init__.pyi b/python-packages/order_utils/stubs/web3/__init__.pyi index a1dd77adb4..a23a5141a8 100644 --- a/python-packages/order_utils/stubs/web3/__init__.pyi +++ b/python-packages/order_utils/stubs/web3/__init__.pyi @@ -34,4 +34,6 @@ class Web3: ... ... + provider: BaseProvider + ... diff --git a/python-packages/order_utils/test/test_generate_order_hash_hex.py b/python-packages/order_utils/test/test_generate_order_hash_hex.py index 21b8dda2db..cb6dca35c7 100644 --- a/python-packages/order_utils/test/test_generate_order_hash_hex.py +++ b/python-packages/order_utils/test/test_generate_order_hash_hex.py @@ -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 diff --git a/python-packages/order_utils/test/test_signature_utils.py b/python-packages/order_utils/test/test_signature_utils.py index a542f8eba3..7bcd090169 100644 --- a/python-packages/order_utils/test/test_signature_utils.py +++ b/python-packages/order_utils/test/test_signature_utils.py @@ -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 diff --git a/python-packages/order_utils/tox.ini b/python-packages/order_utils/tox.ini index ba7d55b569..c51a7e14f0 100644 --- a/python-packages/order_utils/tox.ini +++ b/python-packages/order_utils/tox.ini @@ -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 diff --git a/python-packages/parallel b/python-packages/parallel index c127d3cf5f..0f374bb950 100755 --- a/python-packages/parallel +++ b/python-packages/parallel @@ -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("..") diff --git a/python-packages/parallel_without_sra_client b/python-packages/parallel_without_sra_client deleted file mode 100755 index b0744824bc..0000000000 --- a/python-packages/parallel_without_sra_client +++ /dev/null @@ -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 diff --git a/python-packages/pre_install b/python-packages/pre_install index 42e561ca88..24ed40a633 100755 --- a/python-packages/pre_install +++ b/python-packages/pre_install @@ -12,6 +12,7 @@ from sys import argv PACKAGES = [ "contract_wrappers", + "contract_addresses", "contract_artifacts", "json_schemas", ] diff --git a/python-packages/sra_client/CHANGELOG.md b/python-packages/sra_client/CHANGELOG.md index 1220c1a349..2b68b91b91 100644 --- a/python-packages/sra_client/CHANGELOG.md +++ b/python-packages/sra_client/CHANGELOG.md @@ -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. diff --git a/python-packages/sra_client/setup.py b/python-packages/sra_client/setup.py index a79d148c47..4e2d6a78a8 100755 --- a/python-packages/sra_client/setup.py +++ b/python-packages/sra_client/setup.py @@ -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 ) diff --git a/python-packages/sra_client/src/zero_ex/__init__.py b/python-packages/sra_client/src/zero_ex/__init__.py index e90d833db6..3fe1300cfb 100644 --- a/python-packages/sra_client/src/zero_ex/__init__.py +++ b/python-packages/sra_client/src/zero_ex/__init__.py @@ -1,2 +1,2 @@ """0x Python API.""" -__import__("pkg_resources").declare_namespace(__name__) +__import__("pkg_resources").declare_namespace(__name__) # type: ignore diff --git a/python-packages/sra_client/src/zero_ex/sra_client/__init__.py b/python-packages/sra_client/src/zero_ex/sra_client/__init__.py index a1ccdd1ff8..dd15ab7ffb 100644 --- a/python-packages/sra_client/src/zero_ex/sra_client/__init__.py +++ b/python-packages/sra_client/src/zero_ex/sra_client/__init__.py @@ -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 `_ 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...') diff --git a/python-packages/sra_client/src/zero_ex/sra_client/api/default_api.py b/python-packages/sra_client/src/zero_ex/sra_client/api/default_api.py index e097f6c7c9..03e0316979 100644 --- a/python-packages/sra_client/src/zero_ex/sra_client/api/default_api.py +++ b/python-packages/sra_client/src/zero_ex/sra_client/api/default_api.py @@ -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 - `__ + `__ (example: "0xf47261b0" for ERC20, "0x02571792" for ERC721). :param str taker_asset_proxy_id: The taker asset `asset proxy id - `__ + `__ (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 - `__ + :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 `__ + 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 `__ + 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 `__ + 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 `__ + 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 `__ + 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 `__ + 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 `__ + 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 `__ + 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 - `__ + `__ (example: "0xf47261b0" for ERC20, "0x02571792" for ERC721). :param str taker_asset_proxy_id: The taker asset `asset proxy id - `__ + `__ (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 `__ + 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 `__ + 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 `__ + 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 `__ + 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 `__ + 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 `__ + 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 `__ + 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 `__ + 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 `__ + 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, diff --git a/python-packages/sra_client/test/relayer/docker-compose.yml b/python-packages/sra_client/test/relayer/docker-compose.yml index 6a67dcea71..e5b6657ab4 100644 --- a/python-packages/sra_client/test/relayer/docker-compose.yml +++ b/python-packages/sra_client/test/relayer/docker-compose.yml @@ -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" diff --git a/python-packages/sra_client/tox.ini b/python-packages/sra_client/tox.ini index 0d776c7b07..2f40a07c89 100644 --- a/python-packages/sra_client/tox.ini +++ b/python-packages/sra_client/tox.ini @@ -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