Python contract wrappers (#1721)
This commit is contained in:
		
				
					committed by
					
						
						F. Eugene Aumson
					
				
			
			
				
	
			
			
			
						parent
						
							e043735362
						
					
				
				
					commit
					a256494ec8
				
			@@ -12,6 +12,7 @@ PACKAGE_DEPENDENCY_LIST = [
 | 
			
		||||
    # independent first) in order for them to resolve properly.
 | 
			
		||||
    "contract_addresses",
 | 
			
		||||
    "contract_artifacts",
 | 
			
		||||
    "contract_wrappers",
 | 
			
		||||
    "json_schemas",
 | 
			
		||||
    "sra_client",
 | 
			
		||||
    "order_utils",
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										13
									
								
								python-packages/contract_wrappers/.discharge.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								python-packages/contract_wrappers/.discharge.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
{
 | 
			
		||||
    "domain": "0x-contract-wrappers-py",
 | 
			
		||||
    "build_command": "python setup.py build_sphinx",
 | 
			
		||||
    "upload_directory": "build/docs/html",
 | 
			
		||||
    "index_key": "index.html",
 | 
			
		||||
    "error_key": "index.html",
 | 
			
		||||
    "trailing_slashes": true,
 | 
			
		||||
    "cache": 3600,
 | 
			
		||||
    "aws_profile": "default",
 | 
			
		||||
    "aws_region": "us-east-1",
 | 
			
		||||
    "cdn": false,
 | 
			
		||||
    "dns_configured": true
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										4
									
								
								python-packages/contract_wrappers/.pylintrc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								python-packages/contract_wrappers/.pylintrc
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
			
		||||
[MESSAGES CONTROL]
 | 
			
		||||
disable=C0330,line-too-long,fixme,too-few-public-methods,too-many-ancestors,too-many-arguments
 | 
			
		||||
# C0330 is "bad hanging indent". we use indents per `black`.
 | 
			
		||||
min-similarity-lines=10
 | 
			
		||||
							
								
								
									
										45
									
								
								python-packages/contract_wrappers/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								python-packages/contract_wrappers/README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,45 @@
 | 
			
		||||
## 0x-contract-wrappers
 | 
			
		||||
 | 
			
		||||
0x contract wrappers for those developing on top of 0x protocol.
 | 
			
		||||
 | 
			
		||||
Read the [documentation](http://0x-contract-wrappers-py.s3-website-us-east-1.amazonaws.com/)
 | 
			
		||||
 | 
			
		||||
## Installing
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
pip install 0x-contract-wrappers
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Contributing
 | 
			
		||||
 | 
			
		||||
We welcome improvements and fixes from the wider community! To report bugs within this package, please create an issue in this repository.
 | 
			
		||||
 | 
			
		||||
Please read our [contribution guidelines](../../CONTRIBUTING.md) before getting started.
 | 
			
		||||
 | 
			
		||||
### Install Code and Dependencies
 | 
			
		||||
 | 
			
		||||
Ensure that you have installed Python >=3.6 and Docker. Then:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
pip install -e .[dev]
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Test
 | 
			
		||||
 | 
			
		||||
Tests depend on a running ganache instance and with the 0x contracts deployed in it. For convenience, a docker container is provided that has ganache-cli and a snapshot containing the necessary contracts. A shortcut is provided to run that docker container: `./setup.py ganache`. With that running, the tests can be run with `./setup.py test`.
 | 
			
		||||
 | 
			
		||||
### Clean
 | 
			
		||||
 | 
			
		||||
`./setup.py clean --all`
 | 
			
		||||
 | 
			
		||||
### Lint
 | 
			
		||||
 | 
			
		||||
`./setup.py lint`
 | 
			
		||||
 | 
			
		||||
### Build Documentation
 | 
			
		||||
 | 
			
		||||
`./setup.py build_sphinx`
 | 
			
		||||
 | 
			
		||||
### More
 | 
			
		||||
 | 
			
		||||
See `./setup.py --help-commands` for more info.
 | 
			
		||||
							
								
								
									
										226
									
								
								python-packages/contract_wrappers/setup.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										226
									
								
								python-packages/contract_wrappers/setup.py
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,226 @@
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
 | 
			
		||||
"""setuptools module for contract_wrappers package."""
 | 
			
		||||
 | 
			
		||||
import subprocess  # nosec
 | 
			
		||||
from shutil import rmtree
 | 
			
		||||
from os import environ, path
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
from sys import argv
 | 
			
		||||
 | 
			
		||||
from distutils.command.clean import clean
 | 
			
		||||
import distutils.command.build_py
 | 
			
		||||
from setuptools import find_packages, setup
 | 
			
		||||
from setuptools.command.test import test as TestCommand
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestCommandExtension(TestCommand):
 | 
			
		||||
    """Run pytest tests."""
 | 
			
		||||
 | 
			
		||||
    def run_tests(self):
 | 
			
		||||
        """Invoke pytest."""
 | 
			
		||||
        import pytest
 | 
			
		||||
 | 
			
		||||
        exit(pytest.main(["--doctest-modules"]))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LintCommand(distutils.command.build_py.build_py):
 | 
			
		||||
    """Custom setuptools command class for running linters."""
 | 
			
		||||
 | 
			
		||||
    description = "Run linters"
 | 
			
		||||
 | 
			
		||||
    def run(self):
 | 
			
		||||
        """Run linter shell commands."""
 | 
			
		||||
        lint_commands = [
 | 
			
		||||
            # formatter:
 | 
			
		||||
            "black --line-length 79 --check --diff src test setup.py".split(),
 | 
			
		||||
            # style guide checker (formerly pep8):
 | 
			
		||||
            "pycodestyle src test setup.py".split(),
 | 
			
		||||
            # docstring style checker:
 | 
			
		||||
            "pydocstyle src test setup.py".split(),
 | 
			
		||||
            # static type checker:
 | 
			
		||||
            "mypy src test setup.py".split(),
 | 
			
		||||
            # security issue checker:
 | 
			
		||||
            "bandit -r src ./setup.py".split(),
 | 
			
		||||
            # general linter:
 | 
			
		||||
            "pylint src test setup.py".split(),
 | 
			
		||||
            # pylint takes relatively long to run, so it runs last, to enable
 | 
			
		||||
            # fast failures.
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
        # tell mypy where to find interface stubs for 3rd party libs
 | 
			
		||||
        environ["MYPYPATH"] = path.join(
 | 
			
		||||
            path.dirname(path.realpath(argv[0])), "stubs"
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        # HACK(gene): until eth_utils fixes
 | 
			
		||||
        # https://github.com/ethereum/eth-utils/issues/140 , we need to simply
 | 
			
		||||
        # create an empty file `py.typed` in the eth_abi package directory.
 | 
			
		||||
        import eth_utils
 | 
			
		||||
 | 
			
		||||
        eth_utils_dir = path.dirname(path.realpath(eth_utils.__file__))
 | 
			
		||||
        Path(path.join(eth_utils_dir, "py.typed")).touch()
 | 
			
		||||
 | 
			
		||||
        for lint_command in lint_commands:
 | 
			
		||||
            print(
 | 
			
		||||
                "Running lint command `", " ".join(lint_command).strip(), "`"
 | 
			
		||||
            )
 | 
			
		||||
            subprocess.check_call(lint_command)  # nosec
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CleanCommandExtension(clean):
 | 
			
		||||
    """Custom command to do custom cleanup."""
 | 
			
		||||
 | 
			
		||||
    def run(self):
 | 
			
		||||
        """Run the regular clean, followed by our custom commands."""
 | 
			
		||||
        super().run()
 | 
			
		||||
        rmtree("dist", ignore_errors=True)
 | 
			
		||||
        rmtree(".mypy_cache", ignore_errors=True)
 | 
			
		||||
        rmtree(".tox", ignore_errors=True)
 | 
			
		||||
        rmtree(".pytest_cache", ignore_errors=True)
 | 
			
		||||
        rmtree("src/0x_contract_wrappers.egg-info", ignore_errors=True)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestPublishCommand(distutils.command.build_py.build_py):
 | 
			
		||||
    """Custom command to publish to test.pypi.org."""
 | 
			
		||||
 | 
			
		||||
    description = (
 | 
			
		||||
        "Publish dist/* to test.pypi.org. Run sdist & bdist_wheel first."
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    def run(self):
 | 
			
		||||
        """Run twine to upload to test.pypi.org."""
 | 
			
		||||
        subprocess.check_call(  # nosec
 | 
			
		||||
            (
 | 
			
		||||
                "twine upload --repository-url https://test.pypi.org/legacy/"
 | 
			
		||||
                + " --verbose dist/*"
 | 
			
		||||
            ).split()
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PublishCommand(distutils.command.build_py.build_py):
 | 
			
		||||
    """Custom command to publish to pypi.org."""
 | 
			
		||||
 | 
			
		||||
    description = "Publish dist/* to pypi.org. Run sdist & bdist_wheel first."
 | 
			
		||||
 | 
			
		||||
    def run(self):
 | 
			
		||||
        """Run twine to upload to pypi.org."""
 | 
			
		||||
        subprocess.check_call("twine upload dist/*".split())  # nosec
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PublishDocsCommand(distutils.command.build_py.build_py):
 | 
			
		||||
    """Custom command to publish docs to S3."""
 | 
			
		||||
 | 
			
		||||
    description = (
 | 
			
		||||
        "Publish docs to "
 | 
			
		||||
        + "http://0x-contract-wrappers-py.s3-website-us-east-1.amazonaws.com/"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    def run(self):
 | 
			
		||||
        """Run npm package `discharge` to build & upload docs."""
 | 
			
		||||
        subprocess.check_call("discharge deploy".split())  # nosec
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class GanacheCommand(distutils.command.build_py.build_py):
 | 
			
		||||
    """Custom command to publish to pypi.org."""
 | 
			
		||||
 | 
			
		||||
    description = "Run ganache daemon to support tests."
 | 
			
		||||
 | 
			
		||||
    def run(self):
 | 
			
		||||
        """Run ganache."""
 | 
			
		||||
        cmd_line = (
 | 
			
		||||
            "docker run -d -p 8545:8545 0xorg/ganache-cli:2.2.2"
 | 
			
		||||
        ).split()
 | 
			
		||||
        subprocess.call(cmd_line)  # nosec
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
with open("README.md", "r") as file_handle:
 | 
			
		||||
    README_MD = file_handle.read()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
setup(
 | 
			
		||||
    name="0x-contract-wrappers",
 | 
			
		||||
    version="1.0.1",
 | 
			
		||||
    description="Python wrappers for 0x smart contracts",
 | 
			
		||||
    long_description=README_MD,
 | 
			
		||||
    long_description_content_type="text/markdown",
 | 
			
		||||
    url=(
 | 
			
		||||
        "https://github.com/0xproject/0x-monorepo/tree/development"
 | 
			
		||||
        + "/python-packages/contract_wrappers"
 | 
			
		||||
    ),
 | 
			
		||||
    author="F. Eugene Aumson",
 | 
			
		||||
    author_email="feuGeneA@users.noreply.github.com",
 | 
			
		||||
    cmdclass={
 | 
			
		||||
        "clean": CleanCommandExtension,
 | 
			
		||||
        "lint": LintCommand,
 | 
			
		||||
        "test": TestCommandExtension,
 | 
			
		||||
        "test_publish": TestPublishCommand,
 | 
			
		||||
        "publish": PublishCommand,
 | 
			
		||||
        "publish_docs": PublishDocsCommand,
 | 
			
		||||
        "ganache": GanacheCommand,
 | 
			
		||||
    },
 | 
			
		||||
    install_requires=[
 | 
			
		||||
        "0x-contract-addresses",
 | 
			
		||||
        "0x-contract-artifacts",
 | 
			
		||||
        "0x-json-schemas",
 | 
			
		||||
        "0x-order-utils",
 | 
			
		||||
        "0x-web3",
 | 
			
		||||
        "attrs",
 | 
			
		||||
        "eth_utils",
 | 
			
		||||
        "hypothesis>=3.31.2",  # HACK! this is web3's dependency!
 | 
			
		||||
        # above works around https://github.com/ethereum/web3.py/issues/1179
 | 
			
		||||
        "mypy_extensions",
 | 
			
		||||
    ],
 | 
			
		||||
    extras_require={
 | 
			
		||||
        "dev": [
 | 
			
		||||
            "bandit",
 | 
			
		||||
            "black",
 | 
			
		||||
            "coverage",
 | 
			
		||||
            "coveralls",
 | 
			
		||||
            "mypy",
 | 
			
		||||
            "mypy_extensions",
 | 
			
		||||
            "pycodestyle",
 | 
			
		||||
            "pydocstyle",
 | 
			
		||||
            "pylint",
 | 
			
		||||
            "pytest",
 | 
			
		||||
            "sphinx",
 | 
			
		||||
            "sphinx-autodoc-typehints",
 | 
			
		||||
            "tox",
 | 
			
		||||
            "twine",
 | 
			
		||||
        ]
 | 
			
		||||
    },
 | 
			
		||||
    python_requires=">=3.6, <4",
 | 
			
		||||
    package_data={"zero_ex.contract_wrappers": ["py.typed"]},
 | 
			
		||||
    package_dir={"": "src"},
 | 
			
		||||
    license="Apache 2.0",
 | 
			
		||||
    keywords=(
 | 
			
		||||
        "ethereum cryptocurrency 0x decentralized blockchain dex exchange"
 | 
			
		||||
    ),
 | 
			
		||||
    namespace_packages=["zero_ex"],
 | 
			
		||||
    packages=find_packages("src"),
 | 
			
		||||
    classifiers=[
 | 
			
		||||
        "Development Status :: 2 - Pre-Alpha",
 | 
			
		||||
        "Intended Audience :: Developers",
 | 
			
		||||
        "Intended Audience :: Financial and Insurance Industry",
 | 
			
		||||
        "License :: OSI Approved :: Apache Software License",
 | 
			
		||||
        "Natural Language :: English",
 | 
			
		||||
        "Operating System :: OS Independent",
 | 
			
		||||
        "Programming Language :: Python",
 | 
			
		||||
        "Programming Language :: Python :: 3 :: Only",
 | 
			
		||||
        "Programming Language :: Python :: 3.6",
 | 
			
		||||
        "Programming Language :: Python :: 3.7",
 | 
			
		||||
        "Topic :: Internet :: WWW/HTTP",
 | 
			
		||||
        "Topic :: Office/Business :: Financial",
 | 
			
		||||
        "Topic :: Other/Nonlisted Topic",
 | 
			
		||||
        "Topic :: Security :: Cryptography",
 | 
			
		||||
        "Topic :: Software Development :: Libraries",
 | 
			
		||||
        "Topic :: Utilities",
 | 
			
		||||
    ],
 | 
			
		||||
    zip_safe=False,  # required per mypy
 | 
			
		||||
    command_options={
 | 
			
		||||
        "build_sphinx": {
 | 
			
		||||
            "source_dir": ("setup.py", "src"),
 | 
			
		||||
            "build_dir": ("setup.py", "build/docs"),
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										56
									
								
								python-packages/contract_wrappers/src/conf.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								python-packages/contract_wrappers/src/conf.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,56 @@
 | 
			
		||||
"""Configuration file for the Sphinx documentation builder."""
 | 
			
		||||
 | 
			
		||||
# Reference: http://www.sphinx-doc.org/en/master/config
 | 
			
		||||
 | 
			
		||||
from typing import List
 | 
			
		||||
 | 
			
		||||
import pkg_resources
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# pylint: disable=invalid-name
 | 
			
		||||
# because these variables are not named in upper case, as globals should be.
 | 
			
		||||
 | 
			
		||||
project = "0x-contract-wrappers"
 | 
			
		||||
# pylint: disable=redefined-builtin
 | 
			
		||||
copyright = "2019, ZeroEx, Intl."
 | 
			
		||||
author = "Michael Huang"
 | 
			
		||||
version = pkg_resources.get_distribution("0x-contract-wrappers").version
 | 
			
		||||
release = ""  # The full version, including alpha/beta/rc tags
 | 
			
		||||
 | 
			
		||||
extensions = [
 | 
			
		||||
    "sphinx.ext.autodoc",
 | 
			
		||||
    "sphinx.ext.doctest",
 | 
			
		||||
    "sphinx.ext.intersphinx",
 | 
			
		||||
    "sphinx.ext.coverage",
 | 
			
		||||
    "sphinx.ext.viewcode",
 | 
			
		||||
    "sphinx_autodoc_typehints",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
templates_path = ["doc_templates"]
 | 
			
		||||
 | 
			
		||||
source_suffix = ".rst"
 | 
			
		||||
# eg: source_suffix = [".rst", ".md"]
 | 
			
		||||
 | 
			
		||||
master_doc = "index"  # The master toctree document.
 | 
			
		||||
 | 
			
		||||
language = None
 | 
			
		||||
 | 
			
		||||
exclude_patterns: List[str] = []
 | 
			
		||||
 | 
			
		||||
# The name of the Pygments (syntax highlighting) style to use.
 | 
			
		||||
pygments_style = None
 | 
			
		||||
 | 
			
		||||
html_theme = "alabaster"
 | 
			
		||||
 | 
			
		||||
html_static_path = ["doc_static"]
 | 
			
		||||
# Add any paths that contain custom static files (such as style sheets) here,
 | 
			
		||||
# relative to this directory. They are copied after the builtin static files,
 | 
			
		||||
# so a file named "default.css" will overwrite the builtin "default.css".
 | 
			
		||||
 | 
			
		||||
# Output file base name for HTML help builder.
 | 
			
		||||
htmlhelp_basename = "contract_wrapperspydoc"
 | 
			
		||||
 | 
			
		||||
# -- Extension configuration:
 | 
			
		||||
 | 
			
		||||
# Example configuration for intersphinx: refer to the Python standard library.
 | 
			
		||||
intersphinx_mapping = {"https://docs.python.org/": None}
 | 
			
		||||
							
								
								
									
										40
									
								
								python-packages/contract_wrappers/src/index.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								python-packages/contract_wrappers/src/index.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,40 @@
 | 
			
		||||
Python zero_ex.contract_wrappers
 | 
			
		||||
================================================
 | 
			
		||||
 | 
			
		||||
.. toctree::
 | 
			
		||||
   :maxdepth: 2
 | 
			
		||||
   :caption: Contents:
 | 
			
		||||
 | 
			
		||||
.. automodule:: zero_ex.contract_wrappers
 | 
			
		||||
   :members:
 | 
			
		||||
   :inherited-members:
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
zero_ex.contract_wrappers.Exchange
 | 
			
		||||
----------------------------------
 | 
			
		||||
 | 
			
		||||
.. autoclass:: zero_ex.contract_wrappers.Exchange
 | 
			
		||||
   :members:
 | 
			
		||||
   :inherited-members:
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
zero_ex.contract_wrappers.ERC20Token
 | 
			
		||||
-------------------------------------
 | 
			
		||||
 | 
			
		||||
.. autoclass:: zero_ex.contract_wrappers.ERC20Token
 | 
			
		||||
   :members:
 | 
			
		||||
   :inherited-members:
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
zero_ex.contract_wrappers.TxParams
 | 
			
		||||
----------------------------------
 | 
			
		||||
 | 
			
		||||
.. autoclass:: zero_ex.contract_wrappers.TxParams
 | 
			
		||||
   :members:
 | 
			
		||||
 | 
			
		||||
Indices and tables
 | 
			
		||||
==================
 | 
			
		||||
 | 
			
		||||
* :ref:`genindex`
 | 
			
		||||
* :ref:`modindex`
 | 
			
		||||
* :ref:`search`
 | 
			
		||||
@@ -0,0 +1,2 @@
 | 
			
		||||
"""0x Python API."""
 | 
			
		||||
__import__("pkg_resources").declare_namespace(__name__)
 | 
			
		||||
@@ -0,0 +1,352 @@
 | 
			
		||||
"""Python wrappers for interacting with 0x smart contracts.
 | 
			
		||||
 | 
			
		||||
The smart contract wrappers have simplified interfaces,
 | 
			
		||||
and perform client-side validation on transactions and throw
 | 
			
		||||
helpful error messages.
 | 
			
		||||
 | 
			
		||||
Installing
 | 
			
		||||
==========
 | 
			
		||||
Install the 0x-contract-wrappers with pip:
 | 
			
		||||
 | 
			
		||||
``pip install 0x-contract-wrappers``
 | 
			
		||||
 | 
			
		||||
Demo
 | 
			
		||||
====
 | 
			
		||||
We will demonstrate some basic steps to help you get started trading on 0x.
 | 
			
		||||
 | 
			
		||||
Importing packages
 | 
			
		||||
------------------
 | 
			
		||||
 | 
			
		||||
The first step to interact with the 0x smart contract is to import
 | 
			
		||||
the following relevant packages:
 | 
			
		||||
 | 
			
		||||
>>> import random
 | 
			
		||||
>>> from eth_utils import to_checksum_address
 | 
			
		||||
>>> from zero_ex.contract_addresses import NETWORK_TO_ADDRESSES, NetworkId
 | 
			
		||||
>>> from zero_ex.contract_wrappers import (
 | 
			
		||||
...     ERC20Token, Exchange, TxParams
 | 
			
		||||
... )
 | 
			
		||||
>>> from zero_ex.order_utils import(
 | 
			
		||||
...     sign_hash, generate_order_hash_hex)
 | 
			
		||||
 | 
			
		||||
Provider
 | 
			
		||||
--------
 | 
			
		||||
 | 
			
		||||
We need a web3 provider to allow us to talk to the blockchain. You can
 | 
			
		||||
read more about providers
 | 
			
		||||
`here <https://web3py.readthedocs.io/en/stable/providers.htm>`__.  In our
 | 
			
		||||
case, we are using our local node (ganache), we will connect to our provider
 | 
			
		||||
at http://localhost:8545.
 | 
			
		||||
 | 
			
		||||
>>> from web3 import HTTPProvider, Web3
 | 
			
		||||
>>> provider = HTTPProvider("http://localhost:8545")
 | 
			
		||||
>>> # Create a web3 instance from the provider
 | 
			
		||||
>>> web3_instance = Web3(provider)
 | 
			
		||||
 | 
			
		||||
Declaring Decimals and Addresses
 | 
			
		||||
---------------------------------
 | 
			
		||||
 | 
			
		||||
Since we are dealing with a few contracts, we will specify them now to
 | 
			
		||||
reduce the syntax load. Fortunately for us, the 0x python packages comes
 | 
			
		||||
with a couple of contract addresses that can be useful to have on hand.
 | 
			
		||||
One thing that is important to remember is that there are no decimals in
 | 
			
		||||
the Ethereum virtual machine (EVM), which means you always need to keep
 | 
			
		||||
track of how many "decimals" each token possesses. Since we will sell some
 | 
			
		||||
ZRX for some ETH and since they both have 18 decimals, we can use a shared
 | 
			
		||||
constant. Let us first get the addresses of the WETH and ZRX tokens on
 | 
			
		||||
the test network Ganache:
 | 
			
		||||
 | 
			
		||||
>>> weth_address = NETWORK_TO_ADDRESSES[NetworkId.GANACHE].ether_token
 | 
			
		||||
>>> zrx_address = NETWORK_TO_ADDRESSES[NetworkId.GANACHE].zrx_token
 | 
			
		||||
 | 
			
		||||
Approvals and WETH Balance
 | 
			
		||||
--------------------------
 | 
			
		||||
 | 
			
		||||
To trade on 0x, the participants (maker and taker) require a small
 | 
			
		||||
amount of initial set up. They need to approve the 0x smart contracts
 | 
			
		||||
to move funds on their behalf. In order to give 0x protocol smart contract
 | 
			
		||||
access to funds, we need to set allowances (you can read about allowances
 | 
			
		||||
`here <https://tokenallowance.io/>`__).
 | 
			
		||||
In our demo the taker asset is WETH (or Wrapped ETH, you can read about WETH
 | 
			
		||||
`here <https://weth.io/>`__).,
 | 
			
		||||
as ETH is not an ERC20 token it must first be converted into WETH to be
 | 
			
		||||
used by 0x. Concretely, "converting" ETH to WETH means that we will deposit
 | 
			
		||||
some ETH in a smart contract acting as a ERC20 wrapper. In exchange of
 | 
			
		||||
depositing ETH, we will get some ERC20 compliant tokens called WETH at a
 | 
			
		||||
1:1 conversion rate. For example, depositing 10 ETH will give us back 10 WETH
 | 
			
		||||
and we can revert the process at any time.
 | 
			
		||||
 | 
			
		||||
In this demo, we will use test accounts on Ganache, which are accessible
 | 
			
		||||
through the Web3 instance. The first account will be the maker, and the second
 | 
			
		||||
account will be the taker.
 | 
			
		||||
 | 
			
		||||
>>> import pprint
 | 
			
		||||
>>> # Instantiate an instance of the erc20_wrapper with the provider
 | 
			
		||||
>>> erc20_wrapper = ERC20Token(provider)
 | 
			
		||||
>>> # Get accounts from the web 3 instance
 | 
			
		||||
>>> accounts = web3_instance.eth.accounts
 | 
			
		||||
>>> pprint.pprint(accounts)
 | 
			
		||||
['0x5409ED021D9299bf6814279A6A1411A7e866A631',
 | 
			
		||||
 '0x6Ecbe1DB9EF729CBe972C83Fb886247691Fb6beb',
 | 
			
		||||
 '0xE36Ea790bc9d7AB70C55260C66D52b1eca985f84',
 | 
			
		||||
 '0xE834EC434DABA538cd1b9Fe1582052B880BD7e63',
 | 
			
		||||
 '0x78dc5D2D739606d31509C31d654056A45185ECb6',
 | 
			
		||||
 '0xA8dDa8d7F5310E4A9E24F8eBA77E091Ac264f872',
 | 
			
		||||
 '0x06cEf8E666768cC40Cc78CF93d9611019dDcB628',
 | 
			
		||||
 '0x4404ac8bd8F9618D27Ad2f1485AA1B2cFD82482D',
 | 
			
		||||
 '0x7457d5E02197480Db681D3fdF256c7acA21bDc12',
 | 
			
		||||
 '0x91c987bf62D25945dB517BDAa840A6c661374402']
 | 
			
		||||
 | 
			
		||||
>>> maker = accounts[0]
 | 
			
		||||
>>> taker = accounts[1]
 | 
			
		||||
 | 
			
		||||
Now we need to allow the 0x ERC20 Proxy to move WETH on behalf of our
 | 
			
		||||
maker and taker accounts. Let's let our maker and taker here approve
 | 
			
		||||
the 0x ERC20 Proxy an allowance of 100 WETH.
 | 
			
		||||
 | 
			
		||||
>>> # Multiplying by 10 ** 18 to account for decimals
 | 
			
		||||
>>> ALLOWANCE = (100) * 10 ** 18
 | 
			
		||||
>>> erc20_proxy = NETWORK_TO_ADDRESSES[NetworkId.GANACHE].erc20_proxy
 | 
			
		||||
 | 
			
		||||
>>> # Set allowance to the erc20_proxy from maker account
 | 
			
		||||
>>> tx = erc20_wrapper.approve(
 | 
			
		||||
...     weth_address,
 | 
			
		||||
...     erc20_proxy,
 | 
			
		||||
...     ALLOWANCE,
 | 
			
		||||
...     tx_params=TxParams(from_=maker),
 | 
			
		||||
... )
 | 
			
		||||
>>> # Check the allowance given to the 0x ERC20 Proxy
 | 
			
		||||
>>> maker_allowance = erc20_wrapper.allowance(
 | 
			
		||||
...     weth_address,
 | 
			
		||||
...     maker,
 | 
			
		||||
...     erc20_proxy,
 | 
			
		||||
... )
 | 
			
		||||
>>> (maker_allowance) // 10 ** 18
 | 
			
		||||
100
 | 
			
		||||
 | 
			
		||||
>>> # Set allowance to the erc20_proxy from taker account
 | 
			
		||||
>>> tx = erc20_wrapper.approve(
 | 
			
		||||
...     weth_address,
 | 
			
		||||
...     erc20_proxy,
 | 
			
		||||
...     ALLOWANCE,
 | 
			
		||||
...     tx_params=TxParams(from_=taker),
 | 
			
		||||
... )
 | 
			
		||||
>>> # Check the allowance given to the 0x ERC20 Proxy
 | 
			
		||||
>>> taker_allowance = erc20_wrapper.allowance(
 | 
			
		||||
...     weth_address,
 | 
			
		||||
...     taker,
 | 
			
		||||
...     erc20_proxy,
 | 
			
		||||
... )
 | 
			
		||||
>>> (taker_allowance) // 10 ** 18
 | 
			
		||||
100
 | 
			
		||||
 | 
			
		||||
To give our accounts some initial WETH balance, we'll need
 | 
			
		||||
to *wrap* some ETH to get WETH. The WETH token contract
 | 
			
		||||
contains two extra methods, not included in the ERC20 token
 | 
			
		||||
standard, so we will grab the ABI for the WETH Token contract
 | 
			
		||||
and call the deposit method to wrap our ETH. Here is how we do so.
 | 
			
		||||
 | 
			
		||||
>>> from zero_ex.contract_artifacts import abi_by_name
 | 
			
		||||
>>> # Converting 0.5 ETH to base unit wei
 | 
			
		||||
>>> deposit_amount = int(0.5 * 10 ** 18)
 | 
			
		||||
 | 
			
		||||
>>> # Let's have our maker wrap 1 ETH for 1 WETH
 | 
			
		||||
>>> tx = erc20_wrapper.execute_method(
 | 
			
		||||
... address=weth_address,
 | 
			
		||||
... abi=abi_by_name("WETH9"),
 | 
			
		||||
... method="deposit",
 | 
			
		||||
... tx_params=TxParams(from_=maker, value=deposit_amount))
 | 
			
		||||
>>> # Checking our maker's WETH balance
 | 
			
		||||
>>> maker_balance = erc20_wrapper.balance_of(
 | 
			
		||||
...     token_address=weth_address, owner_address=maker)
 | 
			
		||||
>>> (maker_balance) / 10 ** 18  # doctest: +SKIP
 | 
			
		||||
0.5
 | 
			
		||||
 | 
			
		||||
>>> # Let's have our taker wrap 0.5 ETH as well
 | 
			
		||||
>>> tx = erc20_wrapper.execute_method(
 | 
			
		||||
... address=weth_address,
 | 
			
		||||
... abi=abi_by_name("WETH9"),
 | 
			
		||||
... method="deposit",
 | 
			
		||||
... tx_params=TxParams(from_=taker, value=deposit_amount))
 | 
			
		||||
>>> # Checking our taker's WETH balance
 | 
			
		||||
>>> taker_balance = erc20_wrapper.balance_of(
 | 
			
		||||
...     token_address=weth_address, owner_address=taker)
 | 
			
		||||
>>> (taker_balance) / 10 ** 18  # doctest: +SKIP
 | 
			
		||||
0.5
 | 
			
		||||
 | 
			
		||||
Now we can trade our WETH tokens on 0x!
 | 
			
		||||
 | 
			
		||||
Signing an order
 | 
			
		||||
----------------
 | 
			
		||||
 | 
			
		||||
Here is an example of a JSON order previously generated by our maker
 | 
			
		||||
to sell 0.1 WETH. To confirm his intent to sell and recieve the described
 | 
			
		||||
token amounts in this order, our maker must first sign the order by
 | 
			
		||||
creating a signature with the given order data.
 | 
			
		||||
 | 
			
		||||
>>> maker
 | 
			
		||||
'0x5409ED021D9299bf6814279A6A1411A7e866A631'
 | 
			
		||||
 | 
			
		||||
>>> example_order = {
 | 
			
		||||
... 'makerAddress': '0x5409ed021d9299bf6814279a6a1411a7e866a631',
 | 
			
		||||
... 'takerAddress': '0x0000000000000000000000000000000000000000',
 | 
			
		||||
... 'senderAddress': '0x0000000000000000000000000000000000000000',
 | 
			
		||||
... 'exchangeAddress': '0x48bacb9266a570d521063ef5dd96e61686dbe788',
 | 
			
		||||
... 'feeRecipientAddress': '0x0000000000000000000000000000000000000000',
 | 
			
		||||
... 'makerAssetData': bytes.fromhex(
 | 
			
		||||
...     'f47261b0000000000000000000000000'
 | 
			
		||||
...     'c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'),
 | 
			
		||||
... 'takerAssetData': bytes.fromhex(
 | 
			
		||||
...     'f47261b0000000000000000000000000'
 | 
			
		||||
...     'c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'),
 | 
			
		||||
... 'salt': random.randint(1, 100000000000000000),
 | 
			
		||||
... 'makerFee': 0,
 | 
			
		||||
... 'takerFee': 0,
 | 
			
		||||
... 'makerAssetAmount': 100000000000000000,
 | 
			
		||||
... 'takerAssetAmount': 100000000000000000,
 | 
			
		||||
... 'expirationTimeSeconds': 999999999999999999999}
 | 
			
		||||
 | 
			
		||||
Please checkout our demo `here
 | 
			
		||||
<http://0x-demos-py.s3-website-us-east-1.amazonaws.com/>`__
 | 
			
		||||
if you would like to see how you can create an 0x order
 | 
			
		||||
with our python packages.
 | 
			
		||||
 | 
			
		||||
To sign this order, we first need to generate the order hash.
 | 
			
		||||
 | 
			
		||||
>>> order_hash = generate_order_hash_hex(
 | 
			
		||||
...     example_order, example_order["exchangeAddress"])
 | 
			
		||||
 | 
			
		||||
Now our maker can sign this order hash with our web3 provider and
 | 
			
		||||
the `sign_hash` function from the order utils package.
 | 
			
		||||
 | 
			
		||||
>>> maker_signature = sign_hash(
 | 
			
		||||
...     provider, to_checksum_address(maker), order_hash)
 | 
			
		||||
 | 
			
		||||
Now our maker can either deliver his signature and example order
 | 
			
		||||
directly to the taker, or he can choose to broadcast the order
 | 
			
		||||
with his signature to a 0x-relayer.
 | 
			
		||||
 | 
			
		||||
Filling an order
 | 
			
		||||
----------------
 | 
			
		||||
 | 
			
		||||
We finally have a valid order! We can now have our taker try
 | 
			
		||||
to fill the example order. The *takerAssetAmount* is simply the
 | 
			
		||||
amount of tokens (in our case WETH) the taker wants to fill.
 | 
			
		||||
For this demonstration, we will be completely filling the order.
 | 
			
		||||
Orders may also be partially filled.
 | 
			
		||||
 | 
			
		||||
Now let's fill the example order:
 | 
			
		||||
 | 
			
		||||
>>> # Instantiate an instance of the exchange_wrapper with
 | 
			
		||||
>>> # the provider
 | 
			
		||||
>>> zero_ex_exchange = Exchange(provider)
 | 
			
		||||
>>> tx_hash = zero_ex_exchange.fill_order(
 | 
			
		||||
...     order=example_order,
 | 
			
		||||
...     taker_amount=example_order["takerAssetAmount"],
 | 
			
		||||
...     signature=maker_signature,
 | 
			
		||||
...     tx_params=TxParams(from_=taker))
 | 
			
		||||
 | 
			
		||||
Once the transaction is mined, we can get the details of
 | 
			
		||||
our exchange through the exchange wrapper.
 | 
			
		||||
 | 
			
		||||
>>> fill_event = zero_ex_exchange.get_fill_event(tx_hash)
 | 
			
		||||
>>> taker_filled_amount = fill_event[0].args.takerAssetFilledAmount
 | 
			
		||||
>>> taker_filled_amount / 10 ** 18
 | 
			
		||||
0.1
 | 
			
		||||
 | 
			
		||||
Cancelling an order
 | 
			
		||||
--------------------
 | 
			
		||||
 | 
			
		||||
Now we will show how to cancel an order if the maker no
 | 
			
		||||
long wishes to exchange his WETH tokens. We will use a second example
 | 
			
		||||
order to demonstrate.
 | 
			
		||||
 | 
			
		||||
>>> example_order_2 = {
 | 
			
		||||
... 'makerAddress': '0x5409ed021d9299bf6814279a6a1411a7e866a631',
 | 
			
		||||
... 'takerAddress': '0x0000000000000000000000000000000000000000',
 | 
			
		||||
... 'exchangeAddress': '0x4f833a24e1f95d70f028921e27040ca56e09ab0b',
 | 
			
		||||
... 'senderAddress': '0x0000000000000000000000000000000000000000',
 | 
			
		||||
... 'feeRecipientAddress': '0x0000000000000000000000000000000000000000',
 | 
			
		||||
... 'makerAssetData': bytes.fromhex(
 | 
			
		||||
...     'f47261b0000000000000000000000000'
 | 
			
		||||
...     'c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'),
 | 
			
		||||
... 'takerAssetData': bytes.fromhex(
 | 
			
		||||
...     'f47261b0000000000000000000000000'
 | 
			
		||||
...     'e41d2489571d322189246dafa5ebde1f4699f498'),
 | 
			
		||||
... 'salt': random.randint(1, 100000000000000000),
 | 
			
		||||
... 'makerFee': 0,
 | 
			
		||||
... 'takerFee': 0,
 | 
			
		||||
... 'makerAssetAmount': 1000000000000000000,
 | 
			
		||||
... 'takerAssetAmount': 500000000000000000000,
 | 
			
		||||
... 'expirationTimeSeconds': 999999999999999999999}
 | 
			
		||||
>>> tx_hash = zero_ex_exchange.cancel_order(
 | 
			
		||||
...     order=example_order_2, tx_params=TxParams(from_=maker))
 | 
			
		||||
 | 
			
		||||
Once the transaction is mined, we can get the details of
 | 
			
		||||
our cancellation through the exchange wrapper.
 | 
			
		||||
 | 
			
		||||
>>> cancel_event = zero_ex_exchange.get_cancel_event(tx_hash);
 | 
			
		||||
>>> cancelled_order_hash = cancel_event[0].args.orderHash.hex()
 | 
			
		||||
 | 
			
		||||
Batching orders
 | 
			
		||||
----------------
 | 
			
		||||
 | 
			
		||||
The 0x exchange contract can also process multiple orders at
 | 
			
		||||
the same time. Here is an example where the taker fills
 | 
			
		||||
two orders in one transaction.
 | 
			
		||||
 | 
			
		||||
>>> order_1 = {
 | 
			
		||||
... 'makerAddress': '0x5409ed021d9299bf6814279a6a1411a7e866a631',
 | 
			
		||||
... 'takerAddress': '0x0000000000000000000000000000000000000000',
 | 
			
		||||
... 'senderAddress': '0x0000000000000000000000000000000000000000',
 | 
			
		||||
... 'feeRecipientAddress': '0x0000000000000000000000000000000000000000',
 | 
			
		||||
... 'makerAssetData': bytes.fromhex(
 | 
			
		||||
...     'f47261b0000000000000000000000000'
 | 
			
		||||
...     'c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'),
 | 
			
		||||
... 'takerAssetData': bytes.fromhex(
 | 
			
		||||
...     'f47261b0000000000000000000000000'
 | 
			
		||||
...     'c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'),
 | 
			
		||||
... 'salt': random.randint(1, 100000000000000000),
 | 
			
		||||
... 'makerFee': 0,
 | 
			
		||||
... 'takerFee': 0,
 | 
			
		||||
... 'makerAssetAmount': 100,
 | 
			
		||||
... 'takerAssetAmount': 100,
 | 
			
		||||
... 'expirationTimeSeconds': 1000000000000000000}
 | 
			
		||||
>>> order_hash_1 = generate_order_hash_hex(
 | 
			
		||||
...     order_1, zero_ex_exchange.address)
 | 
			
		||||
>>> signature_1 = sign_hash(
 | 
			
		||||
...     provider, to_checksum_address(maker), order_hash_1)
 | 
			
		||||
>>> order_2 = {
 | 
			
		||||
... 'makerAddress': '0x5409ed021d9299bf6814279a6a1411a7e866a631',
 | 
			
		||||
... 'takerAddress': '0x0000000000000000000000000000000000000000',
 | 
			
		||||
... 'senderAddress': '0x0000000000000000000000000000000000000000',
 | 
			
		||||
... 'feeRecipientAddress': '0x0000000000000000000000000000000000000000',
 | 
			
		||||
... 'makerAssetData': bytes.fromhex(
 | 
			
		||||
...     'f47261b0000000000000000000000000'
 | 
			
		||||
...     'c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'),
 | 
			
		||||
... 'takerAssetData': bytes.fromhex(
 | 
			
		||||
...     'f47261b0000000000000000000000000'
 | 
			
		||||
...     'c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'),
 | 
			
		||||
... 'salt': random.randint(1, 100000000000000000),
 | 
			
		||||
... 'makerFee': 0,
 | 
			
		||||
... 'takerFee': 0,
 | 
			
		||||
... 'makerAssetAmount': 200,
 | 
			
		||||
... 'takerAssetAmount': 200,
 | 
			
		||||
... 'expirationTimeSeconds': 2000000000000000000}
 | 
			
		||||
>>> order_hash_2 = generate_order_hash_hex(
 | 
			
		||||
...     order_2, zero_ex_exchange.address)
 | 
			
		||||
>>> signature_2 = sign_hash(
 | 
			
		||||
...     provider, to_checksum_address(maker), order_hash_2)
 | 
			
		||||
 | 
			
		||||
Fill order_1 and order_2 together:
 | 
			
		||||
 | 
			
		||||
>>> tx_hash = zero_ex_exchange.batch_fill_orders(
 | 
			
		||||
...     orders=[order_1, order_2],
 | 
			
		||||
...     taker_amounts=[1, 2],
 | 
			
		||||
...     signatures=[signature_1, signature_2],
 | 
			
		||||
...     tx_params=TxParams(from_=taker))
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from .tx_params import TxParams
 | 
			
		||||
from .erc20_wrapper import ERC20Token
 | 
			
		||||
from .exchange_wrapper import Exchange
 | 
			
		||||
@@ -0,0 +1,129 @@
 | 
			
		||||
"""Base wrapper class for accessing ethereum smart contracts."""
 | 
			
		||||
 | 
			
		||||
from typing import Optional, Union
 | 
			
		||||
 | 
			
		||||
from eth_utils import to_checksum_address
 | 
			
		||||
from web3 import Web3
 | 
			
		||||
from web3.providers.base import BaseProvider
 | 
			
		||||
 | 
			
		||||
from .tx_params import TxParams
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BaseContractWrapper:
 | 
			
		||||
    """Base class for wrapping ethereum smart contracts.
 | 
			
		||||
 | 
			
		||||
    It provides functionality for instantiating a contract instance,
 | 
			
		||||
    calling view functions, and calling functions which require
 | 
			
		||||
    transactions.
 | 
			
		||||
 | 
			
		||||
    :param provider: instance of :class:`web3.providers.base.BaseProvider`
 | 
			
		||||
    :param account_address: default None, str of account address
 | 
			
		||||
    :param private_key: default None, str of private_key
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(
 | 
			
		||||
        self,
 | 
			
		||||
        provider: BaseProvider,
 | 
			
		||||
        account_address: str = None,
 | 
			
		||||
        private_key: str = None,
 | 
			
		||||
    ):
 | 
			
		||||
        """Create an instance of BaseContractWrapper."""
 | 
			
		||||
        self._provider = provider
 | 
			
		||||
        self._account_address = account_address
 | 
			
		||||
        self._private_key = private_key
 | 
			
		||||
        self._web3 = Web3(provider)
 | 
			
		||||
        self._web3_eth = self._web3.eth  # pylint: disable=no-member
 | 
			
		||||
 | 
			
		||||
        self._can_send_tx = False
 | 
			
		||||
        if self._web3_eth.defaultAccount or self._web3_eth.accounts:
 | 
			
		||||
            self._can_send_tx = True
 | 
			
		||||
        else:
 | 
			
		||||
            middleware_stack = getattr(self._web3, "middleware_stack")
 | 
			
		||||
            if middleware_stack.get("sign_and_send_raw_middleware"):
 | 
			
		||||
                self._can_send_tx = True
 | 
			
		||||
            elif private_key:
 | 
			
		||||
                self._private_key = private_key
 | 
			
		||||
                self._web3_eth.defaultAccount = to_checksum_address(
 | 
			
		||||
                    self._web3_eth.account.privateKeyToAccount(
 | 
			
		||||
                        private_key
 | 
			
		||||
                    ).address
 | 
			
		||||
                )
 | 
			
		||||
                self._can_send_tx = True
 | 
			
		||||
 | 
			
		||||
    def _contract_instance(self, address: str, abi: dict):
 | 
			
		||||
        """Get a contract instance.
 | 
			
		||||
 | 
			
		||||
        :param address: string address of contract
 | 
			
		||||
        :param abi: dict contract ABI
 | 
			
		||||
 | 
			
		||||
        :returns: instance of contract
 | 
			
		||||
        """
 | 
			
		||||
        return self._web3_eth.contract(
 | 
			
		||||
            address=to_checksum_address(address), abi=abi
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def _validate_and_checksum_address(self, address: str):
 | 
			
		||||
        if not self._web3.isAddress(address):
 | 
			
		||||
            raise TypeError("Invalid address provided: {}".format(address))
 | 
			
		||||
        return to_checksum_address(address)
 | 
			
		||||
 | 
			
		||||
    def _invoke_function_call(self, func, tx_params, view_only):
 | 
			
		||||
        if view_only:
 | 
			
		||||
            return func.call()
 | 
			
		||||
        if not self._can_send_tx:
 | 
			
		||||
            raise Exception(
 | 
			
		||||
                "Cannot send transaction because no local private_key"
 | 
			
		||||
                " or account found."
 | 
			
		||||
            )
 | 
			
		||||
        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._validate_and_checksum_address(tx_params.from_)
 | 
			
		||||
        if self._private_key:
 | 
			
		||||
            res = self._sign_and_send_raw_direct(func, tx_params)
 | 
			
		||||
        else:
 | 
			
		||||
            res = func.transact(tx_params.as_dict())
 | 
			
		||||
        return res
 | 
			
		||||
 | 
			
		||||
    def _sign_and_send_raw_direct(self, func, tx_params):
 | 
			
		||||
        transaction = func.buildTransaction(tx_params.as_dict())
 | 
			
		||||
        signed_tx = self._web3_eth.account.signTransaction(
 | 
			
		||||
            transaction, private_key=self._private_key
 | 
			
		||||
        )
 | 
			
		||||
        return self._web3_eth.sendRawTransaction(signed_tx.rawTransaction)
 | 
			
		||||
 | 
			
		||||
    def execute_method(
 | 
			
		||||
        self,
 | 
			
		||||
        address: str,
 | 
			
		||||
        abi: dict,
 | 
			
		||||
        method: str,
 | 
			
		||||
        args: Optional[Union[list, tuple]] = None,
 | 
			
		||||
        tx_params: Optional[TxParams] = None,
 | 
			
		||||
        view_only: bool = False,
 | 
			
		||||
    ) -> str:
 | 
			
		||||
        """Execute the method on a contract instance.
 | 
			
		||||
 | 
			
		||||
        :param address: string of contract address
 | 
			
		||||
        :param abi: dict of contract ABI
 | 
			
		||||
        :param method: string name of method to call
 | 
			
		||||
        :param args: default None, list or tuple of arguments for the method
 | 
			
		||||
        :param tx_params: default None, :class:`TxParams` transaction params
 | 
			
		||||
        :param view_only: default False, boolean of whether the transaction
 | 
			
		||||
            should only be validated.
 | 
			
		||||
 | 
			
		||||
        :returns: str of transaction hash
 | 
			
		||||
        """
 | 
			
		||||
        contract_instance = self._contract_instance(address=address, abi=abi)
 | 
			
		||||
        if args is None:
 | 
			
		||||
            args = []
 | 
			
		||||
        if hasattr(contract_instance.functions, method):
 | 
			
		||||
            func = getattr(contract_instance.functions, method)(*args)
 | 
			
		||||
            return self._invoke_function_call(
 | 
			
		||||
                func=func, tx_params=tx_params, view_only=view_only
 | 
			
		||||
            )
 | 
			
		||||
        raise Exception(
 | 
			
		||||
            "No method {} found on contract {}.".format(address, method)
 | 
			
		||||
        )
 | 
			
		||||
@@ -0,0 +1,224 @@
 | 
			
		||||
"""Wrapper for Ethereum ERC20 Token smart contract."""
 | 
			
		||||
 | 
			
		||||
from typing import Optional, Tuple, Union
 | 
			
		||||
 | 
			
		||||
from hexbytes import HexBytes
 | 
			
		||||
from web3.datastructures import AttributeDict
 | 
			
		||||
from web3.providers.base import BaseProvider
 | 
			
		||||
 | 
			
		||||
from zero_ex.contract_artifacts import abi_by_name
 | 
			
		||||
 | 
			
		||||
from ._base_contract_wrapper import BaseContractWrapper
 | 
			
		||||
from .tx_params import TxParams
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ERC20Token(BaseContractWrapper):
 | 
			
		||||
    """Wrapper class for Ethereum ERC20 smart contract.
 | 
			
		||||
 | 
			
		||||
    :param provider: instance of :class:`web3.providers.base.BaseProvider`
 | 
			
		||||
    :param account_address: default None, str of account address
 | 
			
		||||
    :param private_key: default None, str of private_key
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(
 | 
			
		||||
        self,
 | 
			
		||||
        provider: BaseProvider,
 | 
			
		||||
        account_address: str = None,
 | 
			
		||||
        private_key: str = None,
 | 
			
		||||
    ):
 | 
			
		||||
        """Get an instance of wrapper for ERC20 smart contract."""
 | 
			
		||||
        super(ERC20Token, self).__init__(
 | 
			
		||||
            provider=provider,
 | 
			
		||||
            account_address=account_address,
 | 
			
		||||
            private_key=private_key,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def _erc20(self, token_address):
 | 
			
		||||
        """Get an instance of the ERC20 smart contract at a specific address.
 | 
			
		||||
 | 
			
		||||
        :param token_address: string address of token smart contract
 | 
			
		||||
 | 
			
		||||
        :returns: ERC20 contract object
 | 
			
		||||
        """
 | 
			
		||||
        return self._contract_instance(
 | 
			
		||||
            address=token_address, abi=abi_by_name("ERC20Token")
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def transfer(
 | 
			
		||||
        self,
 | 
			
		||||
        token_address: str,
 | 
			
		||||
        to_address: str,
 | 
			
		||||
        value: int,
 | 
			
		||||
        tx_params: Optional[TxParams] = None,
 | 
			
		||||
        view_only: bool = False,
 | 
			
		||||
    ) -> Union[HexBytes, bytes]:
 | 
			
		||||
        """Transfer the balance from owner's account to another account.
 | 
			
		||||
 | 
			
		||||
        :param token_address: string address of token smart contract
 | 
			
		||||
        :param to_address: string address of receiver
 | 
			
		||||
        :param value: integer amount to send in Wei base unit
 | 
			
		||||
        :param tx_params: default None, dict of transaction options
 | 
			
		||||
        :param view_only: default False, boolean of whether to transact or
 | 
			
		||||
            view only
 | 
			
		||||
 | 
			
		||||
        :returns: transaction hash
 | 
			
		||||
        """
 | 
			
		||||
        token_address = self._validate_and_checksum_address(token_address)
 | 
			
		||||
        to_address = self._validate_and_checksum_address(to_address)
 | 
			
		||||
        # safeguard against fractional inputs
 | 
			
		||||
        value = int(value)
 | 
			
		||||
        func = self._erc20(token_address).functions.transfer(to_address, value)
 | 
			
		||||
        return self._invoke_function_call(
 | 
			
		||||
            func=func, tx_params=tx_params, view_only=view_only
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def approve(
 | 
			
		||||
        self,
 | 
			
		||||
        token_address: str,
 | 
			
		||||
        spender_address: str,
 | 
			
		||||
        value: int,
 | 
			
		||||
        tx_params: Optional[TxParams] = None,
 | 
			
		||||
        view_only: bool = False,
 | 
			
		||||
    ) -> Union[HexBytes, bytes]:
 | 
			
		||||
        """Approve a `spender_address` to spend up to `value` your account.
 | 
			
		||||
 | 
			
		||||
        :param token_address: string address of token smart contract
 | 
			
		||||
        :param spender_address: string address of receiver
 | 
			
		||||
        :param value: integer amount of allowance in Wei base unit
 | 
			
		||||
        :param tx_params: default None, dict of transaction options
 | 
			
		||||
        :param view_only: default False, boolean of whether to transact or
 | 
			
		||||
            view only
 | 
			
		||||
 | 
			
		||||
        :returns: transaction hash
 | 
			
		||||
        """
 | 
			
		||||
        token_address = self._validate_and_checksum_address(token_address)
 | 
			
		||||
        spender_address = self._validate_and_checksum_address(spender_address)
 | 
			
		||||
        # safeguard against fractional inputs
 | 
			
		||||
        value = int(value)
 | 
			
		||||
        func = self._erc20(token_address).functions.approve(
 | 
			
		||||
            spender_address, value
 | 
			
		||||
        )
 | 
			
		||||
        return self._invoke_function_call(
 | 
			
		||||
            func=func, tx_params=tx_params, view_only=view_only
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def transfer_from(
 | 
			
		||||
        self,
 | 
			
		||||
        token_address: str,
 | 
			
		||||
        authorized_address: str,
 | 
			
		||||
        to_address: str,
 | 
			
		||||
        value: int,
 | 
			
		||||
        tx_params: Optional[TxParams] = None,
 | 
			
		||||
        view_only: bool = False,
 | 
			
		||||
    ) -> Union[HexBytes, bytes]:
 | 
			
		||||
        """Transfer tokens from `authorized_address` to another address.
 | 
			
		||||
 | 
			
		||||
        Note that the `authorized_address` must have called with `approve`
 | 
			
		||||
        with your address as the `spender_address`.
 | 
			
		||||
 | 
			
		||||
        :param token_address: string address of token smart contract
 | 
			
		||||
        :param authorized_address: string address you have been authorized to
 | 
			
		||||
            to transfer tokens from
 | 
			
		||||
        :param to_address: string address of receiver
 | 
			
		||||
        :param value: integer amount to send in Wei base unit
 | 
			
		||||
        :param tx_params: default None, dict of transaction options
 | 
			
		||||
        :param view_only: default False, boolean of whether to transact or
 | 
			
		||||
            view only
 | 
			
		||||
 | 
			
		||||
        :returns: transaction hash
 | 
			
		||||
        """
 | 
			
		||||
        token_address = self._validate_and_checksum_address(token_address)
 | 
			
		||||
        authorized_address = self._validate_and_checksum_address(
 | 
			
		||||
            authorized_address
 | 
			
		||||
        )
 | 
			
		||||
        to_address = self._validate_and_checksum_address(to_address)
 | 
			
		||||
        # safeguard against fractional inputs
 | 
			
		||||
        value = int(value)
 | 
			
		||||
        func = self._erc20(token_address).functions.transferFrom(
 | 
			
		||||
            authorized_address, to_address, value
 | 
			
		||||
        )
 | 
			
		||||
        return self._invoke_function_call(
 | 
			
		||||
            func=func, tx_params=tx_params, view_only=view_only
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def total_supply(self, token_address: str) -> int:
 | 
			
		||||
        """Get total supply of a given ERC20 Token.
 | 
			
		||||
 | 
			
		||||
        :param token_address: string address of token smart contract
 | 
			
		||||
 | 
			
		||||
        :returns: integer amount of tokens in Wei
 | 
			
		||||
        """
 | 
			
		||||
        token_address = self._validate_and_checksum_address(token_address)
 | 
			
		||||
        func = self._erc20(token_address).functions.totalSupply()
 | 
			
		||||
        return self._invoke_function_call(
 | 
			
		||||
            func=func, tx_params=None, view_only=True
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def balance_of(self, token_address: str, owner_address: str) -> int:
 | 
			
		||||
        """Get token balance of a given owner address.
 | 
			
		||||
 | 
			
		||||
        :param token_address: string address of token smart contract
 | 
			
		||||
        :param owner_address: string address of owner to check balance for
 | 
			
		||||
 | 
			
		||||
        :returns: integer amount of tokens in Wei the owner has
 | 
			
		||||
        """
 | 
			
		||||
        token_address = self._validate_and_checksum_address(token_address)
 | 
			
		||||
        owner_address = self._validate_and_checksum_address(owner_address)
 | 
			
		||||
        func = self._erc20(token_address).functions.balanceOf(owner_address)
 | 
			
		||||
        return self._invoke_function_call(
 | 
			
		||||
            func=func, tx_params=None, view_only=True
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def allowance(
 | 
			
		||||
        self, token_address: str, owner_address: str, spender_address: str
 | 
			
		||||
    ) -> Union[HexBytes, bytes]:
 | 
			
		||||
        """Get the amount of tokens approved for a spender.
 | 
			
		||||
 | 
			
		||||
        :param token_address: string address of token smart contract
 | 
			
		||||
        :param owner_address: string address of owner of the tokens
 | 
			
		||||
        :param spender_address: string address of spender to be checked
 | 
			
		||||
 | 
			
		||||
        :returns: integer amount of tokens in Wei spender is authorized
 | 
			
		||||
            to spend
 | 
			
		||||
        """
 | 
			
		||||
        token_address = self._validate_and_checksum_address(token_address)
 | 
			
		||||
        owner_address = self._validate_and_checksum_address(owner_address)
 | 
			
		||||
        spender_address = self._validate_and_checksum_address(spender_address)
 | 
			
		||||
        func = self._erc20(token_address).functions.allowance(
 | 
			
		||||
            owner_address, spender_address
 | 
			
		||||
        )
 | 
			
		||||
        return self._invoke_function_call(
 | 
			
		||||
            func=func, tx_params=None, view_only=True
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def get_transfer_event(
 | 
			
		||||
        self, token_address: str, tx_hash: Union[HexBytes, bytes]
 | 
			
		||||
    ) -> Tuple[AttributeDict]:
 | 
			
		||||
        """Get the result of a transfer from its transaction hash.
 | 
			
		||||
 | 
			
		||||
        :param token_address: string address of token smart contract
 | 
			
		||||
        :param tx_hash: `HexBytes` hash of transfer transaction
 | 
			
		||||
        """
 | 
			
		||||
        tx_receipt = self._web3_eth.getTransactionReceipt(tx_hash)
 | 
			
		||||
        token_address = self._validate_and_checksum_address(token_address)
 | 
			
		||||
        return (
 | 
			
		||||
            self._erc20(token_address)
 | 
			
		||||
            .events.Transfer()
 | 
			
		||||
            .processReceipt(tx_receipt)
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def get_approval_event(
 | 
			
		||||
        self, token_address: str, tx_hash: Union[HexBytes, bytes]
 | 
			
		||||
    ) -> Tuple[AttributeDict]:
 | 
			
		||||
        """Get the result of an approval event from its transaction hash.
 | 
			
		||||
 | 
			
		||||
        :param token_address: string address of token smart contract
 | 
			
		||||
        :param tx_hash: `HexBytes` hash of approval transaction
 | 
			
		||||
        """
 | 
			
		||||
        tx_receipt = self._web3_eth.getTransactionReceipt(tx_hash)
 | 
			
		||||
        token_address = self._validate_and_checksum_address(token_address)
 | 
			
		||||
        return (
 | 
			
		||||
            self._erc20(token_address)
 | 
			
		||||
            .events.Approval()
 | 
			
		||||
            .processReceipt(tx_receipt)
 | 
			
		||||
        )
 | 
			
		||||
@@ -0,0 +1,324 @@
 | 
			
		||||
"""Wrapper for 0x Exchange smart contract."""
 | 
			
		||||
 | 
			
		||||
from typing import List, Optional, Tuple, Union
 | 
			
		||||
from itertools import repeat
 | 
			
		||||
 | 
			
		||||
from eth_utils import remove_0x_prefix
 | 
			
		||||
from hexbytes import HexBytes
 | 
			
		||||
from web3.providers.base import BaseProvider
 | 
			
		||||
from web3.datastructures import AttributeDict
 | 
			
		||||
 | 
			
		||||
from zero_ex.contract_addresses import NETWORK_TO_ADDRESSES, NetworkId
 | 
			
		||||
from zero_ex.contract_artifacts import abi_by_name
 | 
			
		||||
from zero_ex.json_schemas import assert_valid
 | 
			
		||||
from zero_ex.order_utils import (
 | 
			
		||||
    Order,
 | 
			
		||||
    generate_order_hash_hex,
 | 
			
		||||
    is_valid_signature,
 | 
			
		||||
    order_to_jsdict,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
from ._base_contract_wrapper import BaseContractWrapper
 | 
			
		||||
from .tx_params import TxParams
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CancelDisallowedError(Exception):
 | 
			
		||||
    """Exception for when Cancel is not allowed."""
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Exchange(BaseContractWrapper):
 | 
			
		||||
    """Wrapper class for 0x Exchange smart contract.
 | 
			
		||||
 | 
			
		||||
    :param provider: instance of :class:`web3.providers.base.BaseProvider`
 | 
			
		||||
    :param account_address: default None, str of account address
 | 
			
		||||
    :param private_key: default None, str of private_key
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(
 | 
			
		||||
        self,
 | 
			
		||||
        provider: BaseProvider,
 | 
			
		||||
        account_address: str = None,
 | 
			
		||||
        private_key: str = None,
 | 
			
		||||
    ):
 | 
			
		||||
        """Get an instance of the 0x Exchange smart contract wrapper."""
 | 
			
		||||
        super(Exchange, self).__init__(
 | 
			
		||||
            provider=provider,
 | 
			
		||||
            account_address=account_address,
 | 
			
		||||
            private_key=private_key,
 | 
			
		||||
        )
 | 
			
		||||
        self._web3_net = self._web3.net  # pylint: disable=no-member
 | 
			
		||||
        self.address = NETWORK_TO_ADDRESSES[
 | 
			
		||||
            NetworkId(int(self._web3_net.version))
 | 
			
		||||
        ].exchange
 | 
			
		||||
        self._exchange = self._contract_instance(
 | 
			
		||||
            address=self.address, abi=abi_by_name("Exchange")
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def fill_order(
 | 
			
		||||
        self,
 | 
			
		||||
        order: Order,
 | 
			
		||||
        taker_amount: int,
 | 
			
		||||
        signature: str,
 | 
			
		||||
        tx_params: Optional[TxParams] = None,
 | 
			
		||||
        view_only: bool = False,
 | 
			
		||||
    ) -> Union[HexBytes, bytes]:
 | 
			
		||||
        """Fill a signed order with given amount of taker asset.
 | 
			
		||||
 | 
			
		||||
        This is the most basic way to fill an order. All of the other methods
 | 
			
		||||
        call fillOrder under the hood with additional logic. This function
 | 
			
		||||
        will attempt to fill the amount specified by the caller. However, if
 | 
			
		||||
        the remaining fillable amount is less than the amount specified, the
 | 
			
		||||
        remaining amount will be filled. Partial fills are allowed when
 | 
			
		||||
        filling orders.
 | 
			
		||||
 | 
			
		||||
        See the specification docs for `fillOrder
 | 
			
		||||
        <https://github.com/0xProject/0x-protocol-specification/blob/master
 | 
			
		||||
        /v2/v2-specification.md#fillorder>`_.
 | 
			
		||||
 | 
			
		||||
        :param order: instance of :class:`zero_ex.order_utils.Order`
 | 
			
		||||
        :param taker_amount: integer taker amount in Wei (1 Wei is 10e-18 ETH)
 | 
			
		||||
        :param signature: str or hexstr or bytes of order hash signature
 | 
			
		||||
        :param tx_params: default None, :class:`TxParams` transaction params
 | 
			
		||||
        :param view_only: default False, boolean of whether to transact or
 | 
			
		||||
            view only
 | 
			
		||||
 | 
			
		||||
        :returns: `HexBytes` transaction hash
 | 
			
		||||
        """
 | 
			
		||||
        assert_valid(order_to_jsdict(order, self.address), "/orderSchema")
 | 
			
		||||
        is_valid_signature(
 | 
			
		||||
            self._provider,
 | 
			
		||||
            generate_order_hash_hex(order, self.address),
 | 
			
		||||
            signature,
 | 
			
		||||
            order["makerAddress"],
 | 
			
		||||
        )
 | 
			
		||||
        # safeguard against fractional inputs
 | 
			
		||||
        taker_fill_amount = int(taker_amount)
 | 
			
		||||
        normalized_signature = bytes.fromhex(remove_0x_prefix(signature))
 | 
			
		||||
        func = self._exchange.functions.fillOrder(
 | 
			
		||||
            order, taker_fill_amount, normalized_signature
 | 
			
		||||
        )
 | 
			
		||||
        return self._invoke_function_call(
 | 
			
		||||
            func=func, tx_params=tx_params, view_only=view_only
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def batch_fill_orders(
 | 
			
		||||
        self,
 | 
			
		||||
        orders: List[Order],
 | 
			
		||||
        taker_amounts: List[int],
 | 
			
		||||
        signatures: List[str],
 | 
			
		||||
        tx_params: Optional[TxParams] = None,
 | 
			
		||||
        view_only: bool = False,
 | 
			
		||||
    ) -> Union[HexBytes, bytes]:
 | 
			
		||||
        """Call `fillOrder` sequentially for orders, amounts and signatures.
 | 
			
		||||
 | 
			
		||||
        :param orders: list of instances of :class:`zero_ex.order_utils.Order`
 | 
			
		||||
        :param taker_amounts: list of integer taker amounts in Wei
 | 
			
		||||
        :param signatures: list of str|hexstr|bytes of order hash signature
 | 
			
		||||
        :param tx_params: default None, :class:`TxParams` transaction params
 | 
			
		||||
        :param view_only: default False, boolean of whether to transact or
 | 
			
		||||
            view only
 | 
			
		||||
 | 
			
		||||
        :returns: `HexBytes` transaction hash
 | 
			
		||||
        """
 | 
			
		||||
        order_jsdicts = [
 | 
			
		||||
            order_to_jsdict(order, self.address) for order in orders
 | 
			
		||||
        ]
 | 
			
		||||
        map(assert_valid, order_jsdicts, repeat("/orderSchema"))
 | 
			
		||||
        # safeguard against fractional inputs
 | 
			
		||||
        normalized_fill_amounts = [
 | 
			
		||||
            int(taker_fill_amount) for taker_fill_amount in taker_amounts
 | 
			
		||||
        ]
 | 
			
		||||
        normalized_signatures = [
 | 
			
		||||
            bytes.fromhex(remove_0x_prefix(signature))
 | 
			
		||||
            for signature in signatures
 | 
			
		||||
        ]
 | 
			
		||||
        func = self._exchange.functions.batchFillOrders(
 | 
			
		||||
            orders, normalized_fill_amounts, normalized_signatures
 | 
			
		||||
        )
 | 
			
		||||
        return self._invoke_function_call(
 | 
			
		||||
            func=func, tx_params=tx_params, view_only=view_only
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def fill_or_kill_order(
 | 
			
		||||
        self,
 | 
			
		||||
        order: Order,
 | 
			
		||||
        taker_amount: int,
 | 
			
		||||
        signature: str,
 | 
			
		||||
        tx_params: Optional[TxParams] = None,
 | 
			
		||||
        view_only: bool = False,
 | 
			
		||||
    ) -> Union[HexBytes, bytes]:
 | 
			
		||||
        """Attemp to `fillOrder`, revert if fill is not exact amount.
 | 
			
		||||
 | 
			
		||||
        :param order: instance of :class:`zero_ex.order_utils.Order`
 | 
			
		||||
        :param taker_amount: integer taker amount in Wei (1 Wei is 10e-18 ETH)
 | 
			
		||||
        :param signature: str or hexstr or bytes of order hash signature
 | 
			
		||||
        :param tx_params: default None, :class:`TxParams` transaction params
 | 
			
		||||
        :param view_only: default False, boolean of whether to transact or
 | 
			
		||||
            view only
 | 
			
		||||
 | 
			
		||||
        :returns: `HexBytes` transaction hash
 | 
			
		||||
        """
 | 
			
		||||
        assert_valid(order_to_jsdict(order, self.address), "/orderSchema")
 | 
			
		||||
        is_valid_signature(
 | 
			
		||||
            self._provider,
 | 
			
		||||
            generate_order_hash_hex(order, self.address),
 | 
			
		||||
            signature,
 | 
			
		||||
            order["makerAddress"],
 | 
			
		||||
        )
 | 
			
		||||
        # safeguard against fractional inputs
 | 
			
		||||
        taker_fill_amount = int(taker_amount)
 | 
			
		||||
        normalized_signature = bytes.fromhex(remove_0x_prefix(signature))
 | 
			
		||||
        func = self._exchange.functions.fillOrKillOrder(
 | 
			
		||||
            order, taker_fill_amount, normalized_signature
 | 
			
		||||
        )
 | 
			
		||||
        return self._invoke_function_call(
 | 
			
		||||
            func=func, tx_params=tx_params, view_only=view_only
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def batch_fill_or_kill_orders(
 | 
			
		||||
        self,
 | 
			
		||||
        orders: List[Order],
 | 
			
		||||
        taker_amounts: List[int],
 | 
			
		||||
        signatures: List[str],
 | 
			
		||||
        tx_params: Optional[TxParams] = None,
 | 
			
		||||
        view_only: bool = False,
 | 
			
		||||
    ) -> Union[HexBytes, bytes]:
 | 
			
		||||
        """Call `fillOrKillOrder` sequentially for orders.
 | 
			
		||||
 | 
			
		||||
        :param orders: list of instances of :class:`zero_ex.order_utils.Order`
 | 
			
		||||
        :param taker_amounts: list of integer taker amounts in Wei
 | 
			
		||||
        :param signatures: list of str|hexstr|bytes of order hash signature
 | 
			
		||||
        :param tx_params: default None, :class:`TxParams` transaction params
 | 
			
		||||
        :param view_only: default False, boolean of whether to transact or
 | 
			
		||||
            view only
 | 
			
		||||
 | 
			
		||||
        :returns: `HexBytes` transaction hash
 | 
			
		||||
        """
 | 
			
		||||
        order_jsdicts = [
 | 
			
		||||
            order_to_jsdict(order, self.address) for order in orders
 | 
			
		||||
        ]
 | 
			
		||||
        map(assert_valid, order_jsdicts, repeat("/orderSchema"))
 | 
			
		||||
        # safeguard against fractional inputs
 | 
			
		||||
        normalized_fill_amounts = [
 | 
			
		||||
            int(taker_fill_amount) for taker_fill_amount in taker_amounts
 | 
			
		||||
        ]
 | 
			
		||||
        normalized_signatures = [
 | 
			
		||||
            bytes.fromhex(remove_0x_prefix(signature))
 | 
			
		||||
            for signature in signatures
 | 
			
		||||
        ]
 | 
			
		||||
        func = self._exchange.functions.batchFillOrKillOrders(
 | 
			
		||||
            orders, normalized_fill_amounts, normalized_signatures
 | 
			
		||||
        )
 | 
			
		||||
        return self._invoke_function_call(
 | 
			
		||||
            func=func, tx_params=tx_params, view_only=view_only
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def cancel_order(
 | 
			
		||||
        self,
 | 
			
		||||
        order: Order,
 | 
			
		||||
        tx_params: Optional[TxParams] = None,
 | 
			
		||||
        view_only: bool = False,
 | 
			
		||||
    ) -> Union[HexBytes, bytes]:
 | 
			
		||||
        """Cancel an order.
 | 
			
		||||
 | 
			
		||||
        See the specification docs for `cancelOrder
 | 
			
		||||
        <https://github.com/0xProject/0x-protocol-specification/blob/master
 | 
			
		||||
        /v2/v2-specification.md#cancelorder>`_.
 | 
			
		||||
 | 
			
		||||
        :param order: instance of :class:`zero_ex.order_utils.Order`
 | 
			
		||||
        :param tx_params: default None, :class:`TxParams` transaction params
 | 
			
		||||
        :param view_only: default False, boolean of whether to transact or
 | 
			
		||||
            view only
 | 
			
		||||
 | 
			
		||||
        :returns: `HexBytes` transaction hash
 | 
			
		||||
        """
 | 
			
		||||
        assert_valid(order_to_jsdict(order, self.address), "/orderSchema")
 | 
			
		||||
        maker_address = self._validate_and_checksum_address(
 | 
			
		||||
            order["makerAddress"]
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        if tx_params and tx_params.from_:
 | 
			
		||||
            self._raise_if_maker_not_canceller(
 | 
			
		||||
                maker_address,
 | 
			
		||||
                self._validate_and_checksum_address(tx_params.from_),
 | 
			
		||||
            )
 | 
			
		||||
        elif self._web3_eth.defaultAccount:
 | 
			
		||||
            self._raise_if_maker_not_canceller(
 | 
			
		||||
                maker_address, self._web3_eth.defaultAccount
 | 
			
		||||
            )
 | 
			
		||||
        func = self._exchange.functions.cancelOrder(order)
 | 
			
		||||
        return self._invoke_function_call(
 | 
			
		||||
            func=func, tx_params=tx_params, view_only=view_only
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def batch_cancel_orders(
 | 
			
		||||
        self,
 | 
			
		||||
        orders: List[Order],
 | 
			
		||||
        tx_params: Optional[TxParams] = None,
 | 
			
		||||
        view_only: bool = False,
 | 
			
		||||
    ) -> Union[HexBytes, bytes]:
 | 
			
		||||
        """Call `cancelOrder` sequentially for provided orders.
 | 
			
		||||
 | 
			
		||||
        :param orders: list of instance of :class:`zero_ex.order_utils.Order`
 | 
			
		||||
        :param tx_params: default None, :class:`TxParams` transaction params
 | 
			
		||||
        :param view_only: default False, boolean of whether to transact or
 | 
			
		||||
            view only
 | 
			
		||||
 | 
			
		||||
        :returns: `HexBytes` transaction hash
 | 
			
		||||
        """
 | 
			
		||||
        order_jsdicts = [
 | 
			
		||||
            order_to_jsdict(order, self.address) for order in orders
 | 
			
		||||
        ]
 | 
			
		||||
        map(assert_valid, order_jsdicts, repeat("/orderSchema"))
 | 
			
		||||
        maker_addresses = [
 | 
			
		||||
            self._validate_and_checksum_address(order["makerAddress"])
 | 
			
		||||
            for order in orders
 | 
			
		||||
        ]
 | 
			
		||||
        if tx_params and tx_params.from_:
 | 
			
		||||
            map(
 | 
			
		||||
                self._raise_if_maker_not_canceller,
 | 
			
		||||
                maker_addresses,
 | 
			
		||||
                repeat(tx_params.from_),
 | 
			
		||||
            )
 | 
			
		||||
        elif self._web3_eth.defaultAccount:
 | 
			
		||||
            map(
 | 
			
		||||
                self._raise_if_maker_not_canceller,
 | 
			
		||||
                maker_addresses,
 | 
			
		||||
                repeat(self._web3_eth.defaultAccount),
 | 
			
		||||
            )
 | 
			
		||||
        func = self._exchange.functions.batchCancelOrders(orders)
 | 
			
		||||
        return self._invoke_function_call(
 | 
			
		||||
            func=func, tx_params=tx_params, view_only=view_only
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def get_fill_event(
 | 
			
		||||
        self, tx_hash: Union[HexBytes, bytes]
 | 
			
		||||
    ) -> Tuple[AttributeDict]:
 | 
			
		||||
        """Get fill event for a fill transaction.
 | 
			
		||||
 | 
			
		||||
        :param tx_hash: `HexBytes` hash of fill transaction
 | 
			
		||||
 | 
			
		||||
        :returns: tuple of `FillResults`.
 | 
			
		||||
        """
 | 
			
		||||
        tx_receipt = self._web3_eth.getTransactionReceipt(tx_hash)
 | 
			
		||||
        return self._exchange.events.Fill().processReceipt(tx_receipt)
 | 
			
		||||
 | 
			
		||||
    def get_cancel_event(
 | 
			
		||||
        self, tx_hash: Union[HexBytes, bytes]
 | 
			
		||||
    ) -> Tuple[AttributeDict]:
 | 
			
		||||
        """Get cancel event for cancel transaction.
 | 
			
		||||
 | 
			
		||||
        :param tx_hash: `HexBytes` hash of cancel transaction
 | 
			
		||||
        """
 | 
			
		||||
        tx_receipt = self._web3_eth.getTransactionReceipt(tx_hash)
 | 
			
		||||
        return self._exchange.events.Cancel().processReceipt(tx_receipt)
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def _raise_if_maker_not_canceller(maker_address, canceller_address):
 | 
			
		||||
        """Raise exception is maker is not same as canceller."""
 | 
			
		||||
        if maker_address != canceller_address:
 | 
			
		||||
            raise CancelDisallowedError(
 | 
			
		||||
                "Order with makerAddress {} can not be cancelled by {}".format(
 | 
			
		||||
                    maker_address, canceller_address
 | 
			
		||||
                )
 | 
			
		||||
            )
 | 
			
		||||
@@ -0,0 +1,39 @@
 | 
			
		||||
"""Transaction parameters for use with contract wrappers."""
 | 
			
		||||
 | 
			
		||||
from typing import Optional
 | 
			
		||||
 | 
			
		||||
import attr
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@attr.s(kw_only=True)
 | 
			
		||||
class TxParams:
 | 
			
		||||
    """Transaction parameters for use with contract wrappers.
 | 
			
		||||
 | 
			
		||||
    :param from_: default None, string of account address to initiate tx from
 | 
			
		||||
    :param value: default None, integer of amount of ETH in Wei for transfer
 | 
			
		||||
    :param gas: default None, integer maximum amount of ETH in Wei for gas
 | 
			
		||||
    :param grasPrice: default None, integer price of unit of gas
 | 
			
		||||
    :param nonce: default None, integer nonce for account
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    from_: Optional[str] = attr.ib(default=None)
 | 
			
		||||
    value: Optional[int] = attr.ib(
 | 
			
		||||
        default=None, converter=attr.converters.optional(int)
 | 
			
		||||
    )
 | 
			
		||||
    gas: Optional[int] = attr.ib(
 | 
			
		||||
        default=None, converter=attr.converters.optional(int)
 | 
			
		||||
    )
 | 
			
		||||
    gasPrice: Optional[int] = attr.ib(
 | 
			
		||||
        default=None, converter=attr.converters.optional(int)
 | 
			
		||||
    )
 | 
			
		||||
    nonce: Optional[int] = attr.ib(
 | 
			
		||||
        default=None, converter=attr.converters.optional(int)
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    def as_dict(self):
 | 
			
		||||
        """Get transaction params as dict appropriate for web3."""
 | 
			
		||||
        res = {k: v for k, v in attr.asdict(self).items() if v is not None}
 | 
			
		||||
        if "from_" in res:
 | 
			
		||||
            res["from"] = res["from_"]
 | 
			
		||||
            del res["from_"]
 | 
			
		||||
        return res
 | 
			
		||||
@@ -0,0 +1,7 @@
 | 
			
		||||
from distutils.core import Command
 | 
			
		||||
 | 
			
		||||
class clean(Command):
 | 
			
		||||
    def initialize_options(self: clean) -> None: ...
 | 
			
		||||
    def finalize_options(self: clean) -> None: ...
 | 
			
		||||
    def run(self: clean) -> None: ...
 | 
			
		||||
    ...
 | 
			
		||||
@@ -0,0 +1 @@
 | 
			
		||||
class Account: ...
 | 
			
		||||
@@ -0,0 +1,3 @@
 | 
			
		||||
class LocalAccount: 
 | 
			
		||||
    address: str
 | 
			
		||||
    ...
 | 
			
		||||
@@ -0,0 +1,3 @@
 | 
			
		||||
def to_checksum_address(address: str) -> str: ...
 | 
			
		||||
 | 
			
		||||
def remove_0x_prefix(hex_string: str) -> str: ...
 | 
			
		||||
@@ -0,0 +1 @@
 | 
			
		||||
class HexBytes: ...
 | 
			
		||||
@@ -0,0 +1,9 @@
 | 
			
		||||
from typing import Callable
 | 
			
		||||
 | 
			
		||||
def fixture(scope: str) -> Callable:
 | 
			
		||||
    ...
 | 
			
		||||
 | 
			
		||||
class ExceptionInfo:
 | 
			
		||||
    ...
 | 
			
		||||
 | 
			
		||||
def raises(exception: Exception) -> ExceptionInfo: ...
 | 
			
		||||
@@ -0,0 +1,8 @@
 | 
			
		||||
from distutils.dist import Distribution
 | 
			
		||||
from typing import Any, List
 | 
			
		||||
 | 
			
		||||
def setup(**attrs: Any) -> Distribution: ...
 | 
			
		||||
 | 
			
		||||
class Command: ...
 | 
			
		||||
 | 
			
		||||
def find_packages(where: str) -> List[str]: ...
 | 
			
		||||
@@ -0,0 +1,3 @@
 | 
			
		||||
from setuptools import Command
 | 
			
		||||
 | 
			
		||||
class test(Command): ...
 | 
			
		||||
							
								
								
									
										56
									
								
								python-packages/contract_wrappers/stubs/web3/__init__.pyi
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								python-packages/contract_wrappers/stubs/web3/__init__.pyi
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,56 @@
 | 
			
		||||
from typing import Any, Callable, Dict, List, Optional, Union
 | 
			
		||||
 | 
			
		||||
from hexbytes import HexBytes
 | 
			
		||||
from eth_account.local import LocalAccount
 | 
			
		||||
from web3 import datastructures
 | 
			
		||||
from web3.utils import datatypes
 | 
			
		||||
from web3.providers.base import BaseProvider
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Web3:
 | 
			
		||||
    class HTTPProvider(BaseProvider):
 | 
			
		||||
        ...
 | 
			
		||||
 | 
			
		||||
    def __init__(self, provider: BaseProvider) -> None: ...
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def sha3(
 | 
			
		||||
        primitive: Optional[Union[bytes, int, None]] = None,
 | 
			
		||||
        text: Optional[str] = None,
 | 
			
		||||
        hexstr: Optional[str] = None
 | 
			
		||||
    ) -> bytes: ...
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def isAddress(address: str) -> bool: ...
 | 
			
		||||
 | 
			
		||||
    class middleware_stack:
 | 
			
		||||
        @staticmethod
 | 
			
		||||
        def get(key: str) -> Callable: ...
 | 
			
		||||
        ...
 | 
			
		||||
 | 
			
		||||
    class net:
 | 
			
		||||
        version: str
 | 
			
		||||
        ...
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    class eth:
 | 
			
		||||
        defaultAccount: str
 | 
			
		||||
        accounts: List[str]
 | 
			
		||||
        ...
 | 
			
		||||
 | 
			
		||||
        class account:
 | 
			
		||||
            @staticmethod
 | 
			
		||||
            def privateKeyToAccount(private_key: str) -> LocalAccount: ...
 | 
			
		||||
            ...
 | 
			
		||||
 | 
			
		||||
        @staticmethod
 | 
			
		||||
        def getTransactionReceipt(tx_hash: Union[HexBytes, bytes]) -> Any: ...
 | 
			
		||||
        
 | 
			
		||||
        @staticmethod
 | 
			
		||||
        def contract(address: str, abi: Dict) -> datatypes.Contract: ...
 | 
			
		||||
        ...
 | 
			
		||||
 | 
			
		||||
        @staticmethod
 | 
			
		||||
        def isAddress(address: str) -> bool: ...
 | 
			
		||||
        ...
 | 
			
		||||
    ...
 | 
			
		||||
@@ -0,0 +1,5 @@
 | 
			
		||||
class NamedElementOnion:
 | 
			
		||||
    ...
 | 
			
		||||
 | 
			
		||||
class AttributeDict:
 | 
			
		||||
    ...
 | 
			
		||||
@@ -0,0 +1,2 @@
 | 
			
		||||
class BadFunctionCallOutput(Exception):
 | 
			
		||||
    ...
 | 
			
		||||
@@ -0,0 +1,2 @@
 | 
			
		||||
class BaseProvider:
 | 
			
		||||
    ...
 | 
			
		||||
@@ -0,0 +1,3 @@
 | 
			
		||||
class Contract:
 | 
			
		||||
    def call(self): ...
 | 
			
		||||
    ...
 | 
			
		||||
							
								
								
									
										1
									
								
								python-packages/contract_wrappers/test/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								python-packages/contract_wrappers/test/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
"""Tests of zero_ex.contract_wrappers."""
 | 
			
		||||
							
								
								
									
										79
									
								
								python-packages/contract_wrappers/test/conftest.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								python-packages/contract_wrappers/test/conftest.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,79 @@
 | 
			
		||||
"""Fixtures for pytest."""
 | 
			
		||||
 | 
			
		||||
import pytest
 | 
			
		||||
from eth_utils import remove_0x_prefix, 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_artifacts import abi_by_name
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture(scope="module")
 | 
			
		||||
def ganache_provider():
 | 
			
		||||
    """Get a ganache web3 provider."""
 | 
			
		||||
    return Web3.HTTPProvider(endpoint_uri="http://127.0.0.1:8545")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture(scope="module")
 | 
			
		||||
def web3_instance(ganache_provider):  # pylint: disable=redefined-outer-name
 | 
			
		||||
    """Get a web3 instance."""
 | 
			
		||||
    return Web3(ganache_provider)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture(scope="module")
 | 
			
		||||
def web3_eth(web3_instance):  # pylint: disable=redefined-outer-name
 | 
			
		||||
    """Get web3 instance's eth member."""
 | 
			
		||||
    return web3_instance.eth  # pylint: disable=no-member
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture(scope="module")
 | 
			
		||||
def accounts(web3_eth):  # pylint: disable=redefined-outer-name
 | 
			
		||||
    """Get the accounts associated with the test web3_eth instance."""
 | 
			
		||||
    return web3_eth.accounts
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture(scope="module")
 | 
			
		||||
def erc20_proxy_address():
 | 
			
		||||
    """Get the 0x ERC20 Proxy address."""
 | 
			
		||||
    return NETWORK_TO_ADDRESSES[NetworkId.GANACHE].erc20_proxy
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture(scope="module")
 | 
			
		||||
def weth_address():
 | 
			
		||||
    """Get address of Wrapped Ether (WETH) token for the Ganache network."""
 | 
			
		||||
    return NETWORK_TO_ADDRESSES[NetworkId.GANACHE].ether_token
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture(scope="module")
 | 
			
		||||
def weth_asset_data(weth_address):  # pylint: disable=redefined-outer-name
 | 
			
		||||
    """Get 0x asset data for Wrapped Ether (WETH) token."""
 | 
			
		||||
    return bytes.fromhex(
 | 
			
		||||
        remove_0x_prefix(
 | 
			
		||||
            asset_data_utils.encode_erc20_asset_data(weth_address)
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture(scope="module")
 | 
			
		||||
def weth_instance(
 | 
			
		||||
    web3_eth, weth_address
 | 
			
		||||
):  # pylint: disable=redefined-outer-name
 | 
			
		||||
    """Get an instance of the WrapperEther contract."""
 | 
			
		||||
    return web3_eth.contract(
 | 
			
		||||
        address=to_checksum_address(weth_address), abi=abi_by_name("WETH9")
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture(scope="module")
 | 
			
		||||
def zrx_address():
 | 
			
		||||
    """Get address of ZRX token for Ganache network."""
 | 
			
		||||
    return NETWORK_TO_ADDRESSES[NetworkId.GANACHE].zrx_token
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture(scope="module")
 | 
			
		||||
def zrx_asset_data(zrx_address):  # pylint: disable=redefined-outer-name
 | 
			
		||||
    """Get 0x asset data for ZRX token."""
 | 
			
		||||
    return bytes.fromhex(
 | 
			
		||||
        remove_0x_prefix(asset_data_utils.encode_erc20_asset_data(zrx_address))
 | 
			
		||||
    )
 | 
			
		||||
@@ -0,0 +1,47 @@
 | 
			
		||||
"""Tests for :class:`BaseContractWrapper`."""
 | 
			
		||||
 | 
			
		||||
import pytest
 | 
			
		||||
from eth_utils import to_checksum_address
 | 
			
		||||
 | 
			
		||||
from zero_ex.contract_artifacts import abi_by_name
 | 
			
		||||
from zero_ex.contract_wrappers._base_contract_wrapper import (
 | 
			
		||||
    BaseContractWrapper,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture(scope="module")
 | 
			
		||||
def contract_wrapper(ganache_provider):
 | 
			
		||||
    """Get a BaseContractWrapper instance for testing."""
 | 
			
		||||
    return BaseContractWrapper(provider=ganache_provider)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_contract_wrapper__execute_method(
 | 
			
		||||
    accounts,
 | 
			
		||||
    contract_wrapper,  # pylint: disable=redefined-outer-name
 | 
			
		||||
    erc20_proxy_address,
 | 
			
		||||
    weth_address,  # pylint: disable=redefined-outer-name
 | 
			
		||||
):
 | 
			
		||||
    """Test :function:`BaseContractWrapper.execute` method."""
 | 
			
		||||
    acc1_allowance = contract_wrapper.execute_method(
 | 
			
		||||
        address=weth_address,
 | 
			
		||||
        abi=abi_by_name("WETH9"),
 | 
			
		||||
        method="allowance",
 | 
			
		||||
        view_only=True,
 | 
			
		||||
        args=(
 | 
			
		||||
            to_checksum_address(accounts[3]),
 | 
			
		||||
            to_checksum_address(erc20_proxy_address),
 | 
			
		||||
        ),
 | 
			
		||||
    )
 | 
			
		||||
    assert acc1_allowance == 0
 | 
			
		||||
 | 
			
		||||
    with pytest.raises(Exception):
 | 
			
		||||
        contract_wrapper.execute_method(
 | 
			
		||||
            address=weth_address,
 | 
			
		||||
            abi=abi_by_name("WETH9"),
 | 
			
		||||
            method="send",
 | 
			
		||||
            view_only=True,
 | 
			
		||||
            args=[
 | 
			
		||||
                to_checksum_address(accounts[3]),
 | 
			
		||||
                to_checksum_address(erc20_proxy_address),
 | 
			
		||||
            ],
 | 
			
		||||
        )
 | 
			
		||||
							
								
								
									
										80
									
								
								python-packages/contract_wrappers/test/test_erc20_wrapper.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								python-packages/contract_wrappers/test/test_erc20_wrapper.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,80 @@
 | 
			
		||||
"""Tests for ERC20Token wrapper."""
 | 
			
		||||
 | 
			
		||||
from decimal import Decimal
 | 
			
		||||
 | 
			
		||||
import pytest
 | 
			
		||||
 | 
			
		||||
from zero_ex.contract_wrappers import ERC20Token, TxParams
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
MAX_ALLOWANCE = int("{:.0f}".format(Decimal(2) ** 256 - 1))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture(scope="module")
 | 
			
		||||
def erc20_wrapper(ganache_provider):
 | 
			
		||||
    """Get an instance of ERC20Token wrapper class for testing."""
 | 
			
		||||
    return ERC20Token(ganache_provider)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_erc20_wrapper__balance_of(
 | 
			
		||||
    accounts,
 | 
			
		||||
    erc20_wrapper,  # pylint: disable=redefined-outer-name
 | 
			
		||||
    weth_address,
 | 
			
		||||
    weth_instance,  # pylint: disable=redefined-outer-name
 | 
			
		||||
):
 | 
			
		||||
    """Test getting baance of an account for an ERC20 token."""
 | 
			
		||||
    acc1_original_weth_balance = erc20_wrapper.balance_of(
 | 
			
		||||
        weth_address, accounts[0]
 | 
			
		||||
    )
 | 
			
		||||
    acc2_original_weth_balance = erc20_wrapper.balance_of(
 | 
			
		||||
        weth_address, accounts[1]
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    expected_difference = 1 * 10 ** 18
 | 
			
		||||
 | 
			
		||||
    weth_instance.functions.deposit().transact(
 | 
			
		||||
        {"from": accounts[0], "value": expected_difference}
 | 
			
		||||
    )
 | 
			
		||||
    weth_instance.functions.deposit().transact(
 | 
			
		||||
        {"from": accounts[1], "value": expected_difference}
 | 
			
		||||
    )
 | 
			
		||||
    acc1_weth_balance = erc20_wrapper.balance_of(weth_address, accounts[0])
 | 
			
		||||
    acc2_weth_balance = erc20_wrapper.balance_of(weth_address, accounts[1])
 | 
			
		||||
 | 
			
		||||
    assert (
 | 
			
		||||
        acc1_weth_balance - acc1_original_weth_balance == expected_difference
 | 
			
		||||
    )
 | 
			
		||||
    assert (
 | 
			
		||||
        acc2_weth_balance - acc2_original_weth_balance == expected_difference
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_erc20_wrapper__approve(
 | 
			
		||||
    accounts,
 | 
			
		||||
    erc20_proxy_address,
 | 
			
		||||
    erc20_wrapper,  # pylint: disable=redefined-outer-name
 | 
			
		||||
    weth_address,  # pylint: disable=redefined-outer-name
 | 
			
		||||
):
 | 
			
		||||
    """Test approving one account to spend balance from another account."""
 | 
			
		||||
    erc20_wrapper.approve(
 | 
			
		||||
        weth_address,
 | 
			
		||||
        erc20_proxy_address,
 | 
			
		||||
        MAX_ALLOWANCE,
 | 
			
		||||
        tx_params=TxParams(from_=accounts[0]),
 | 
			
		||||
    )
 | 
			
		||||
    erc20_wrapper.approve(
 | 
			
		||||
        weth_address,
 | 
			
		||||
        erc20_proxy_address,
 | 
			
		||||
        MAX_ALLOWANCE,
 | 
			
		||||
        tx_params=TxParams(from_=accounts[1]),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    acc_1_weth_allowance = erc20_wrapper.allowance(
 | 
			
		||||
        weth_address, accounts[0], erc20_proxy_address
 | 
			
		||||
    )
 | 
			
		||||
    acc_2_weth_allowance = erc20_wrapper.allowance(
 | 
			
		||||
        weth_address, accounts[1], erc20_proxy_address
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    assert acc_1_weth_allowance == MAX_ALLOWANCE
 | 
			
		||||
    assert acc_2_weth_allowance == MAX_ALLOWANCE
 | 
			
		||||
							
								
								
									
										124
									
								
								python-packages/contract_wrappers/test/test_exchange_wrapper.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								python-packages/contract_wrappers/test/test_exchange_wrapper.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,124 @@
 | 
			
		||||
"""Test 0x Exchnage wrapper."""
 | 
			
		||||
 | 
			
		||||
import random
 | 
			
		||||
 | 
			
		||||
import pytest
 | 
			
		||||
from eth_utils import remove_0x_prefix
 | 
			
		||||
 | 
			
		||||
from zero_ex.contract_wrappers import Exchange, TxParams
 | 
			
		||||
from zero_ex.json_schemas import assert_valid
 | 
			
		||||
from zero_ex.order_utils import generate_order_hash_hex, Order, sign_hash
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture(scope="module")
 | 
			
		||||
def exchange_wrapper(ganache_provider):
 | 
			
		||||
    """Get an Exchange wrapper instance."""
 | 
			
		||||
    return Exchange(provider=ganache_provider)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def create_test_order(
 | 
			
		||||
    maker_address,
 | 
			
		||||
    maker_asset_amount,
 | 
			
		||||
    maker_asset_data,
 | 
			
		||||
    taker_asset_amount,
 | 
			
		||||
    taker_asset_data,
 | 
			
		||||
):
 | 
			
		||||
    """Create a test order."""
 | 
			
		||||
    order: Order = {
 | 
			
		||||
        "makerAddress": maker_address.lower(),
 | 
			
		||||
        "takerAddress": "0x0000000000000000000000000000000000000000",
 | 
			
		||||
        "feeRecipientAddress": "0x0000000000000000000000000000000000000000",
 | 
			
		||||
        "senderAddress": "0x0000000000000000000000000000000000000000",
 | 
			
		||||
        "makerAssetAmount": maker_asset_amount,
 | 
			
		||||
        "takerAssetAmount": taker_asset_amount,
 | 
			
		||||
        "makerFee": 0,
 | 
			
		||||
        "takerFee": 0,
 | 
			
		||||
        "expirationTimeSeconds": 100000000000000,
 | 
			
		||||
        "salt": random.randint(1, 1000000000),
 | 
			
		||||
        "makerAssetData": maker_asset_data,
 | 
			
		||||
        "takerAssetData": taker_asset_data,
 | 
			
		||||
    }
 | 
			
		||||
    return order
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def assert_fill_log(fill_log, maker, taker, order, order_hash):
 | 
			
		||||
    """assert that the fill log matches the order details"""
 | 
			
		||||
    assert fill_log.makerAddress == maker
 | 
			
		||||
    assert fill_log.takerAddress == taker
 | 
			
		||||
    assert fill_log.feeRecipientAddress == order["feeRecipientAddress"]
 | 
			
		||||
    assert fill_log.senderAddress == taker
 | 
			
		||||
    assert fill_log.orderHash == bytes.fromhex(remove_0x_prefix(order_hash))
 | 
			
		||||
    assert fill_log.makerAssetFilledAmount == order["makerAssetAmount"]
 | 
			
		||||
    assert fill_log.takerAssetFilledAmount == order["takerAssetAmount"]
 | 
			
		||||
    assert fill_log.makerFeePaid == order["makerFee"]
 | 
			
		||||
    assert fill_log.takerFeePaid == order["takerFee"]
 | 
			
		||||
    assert fill_log.makerAssetData == order["makerAssetData"]
 | 
			
		||||
    assert fill_log.takerAssetData == order["takerAssetData"]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_exchange_wrapper__fill_order(
 | 
			
		||||
    accounts,
 | 
			
		||||
    exchange_wrapper,  # pylint: disable=redefined-outer-name
 | 
			
		||||
    ganache_provider,
 | 
			
		||||
    weth_asset_data,
 | 
			
		||||
):
 | 
			
		||||
    """Test filling an order."""
 | 
			
		||||
    taker = accounts[0]
 | 
			
		||||
    maker = accounts[1]
 | 
			
		||||
    exchange_address = exchange_wrapper.address
 | 
			
		||||
    order = create_test_order(maker, 1, weth_asset_data, 1, weth_asset_data)
 | 
			
		||||
    order_hash = generate_order_hash_hex(
 | 
			
		||||
        order=order, exchange_address=exchange_address
 | 
			
		||||
    )
 | 
			
		||||
    order_signature = sign_hash(ganache_provider, maker, order_hash)
 | 
			
		||||
 | 
			
		||||
    tx_hash = exchange_wrapper.fill_order(
 | 
			
		||||
        order=order,
 | 
			
		||||
        taker_amount=order["takerAssetAmount"],
 | 
			
		||||
        signature=order_signature,
 | 
			
		||||
        tx_params=TxParams(from_=taker),
 | 
			
		||||
    )
 | 
			
		||||
    assert_valid(tx_hash.hex(), "/hexSchema")
 | 
			
		||||
 | 
			
		||||
    fill_event = exchange_wrapper.get_fill_event(tx_hash)
 | 
			
		||||
    assert_fill_log(fill_event[0].args, maker, taker, order, order_hash)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# pylint: disable=too-many-locals
 | 
			
		||||
def test_exchange_wrapper__batch_fill_orders(
 | 
			
		||||
    accounts,
 | 
			
		||||
    exchange_wrapper,  # pylint: disable=redefined-outer-name
 | 
			
		||||
    ganache_provider,
 | 
			
		||||
    weth_asset_data,
 | 
			
		||||
):
 | 
			
		||||
    """Test filling a batch of orders."""
 | 
			
		||||
    taker = accounts[0]
 | 
			
		||||
    maker = accounts[1]
 | 
			
		||||
    exchange_address = exchange_wrapper.address
 | 
			
		||||
    orders = []
 | 
			
		||||
    order_1 = create_test_order(maker, 1, weth_asset_data, 1, weth_asset_data)
 | 
			
		||||
    order_2 = create_test_order(maker, 1, weth_asset_data, 1, weth_asset_data)
 | 
			
		||||
    orders.append(order_1)
 | 
			
		||||
    orders.append(order_2)
 | 
			
		||||
    order_hashes = [
 | 
			
		||||
        generate_order_hash_hex(order=order, exchange_address=exchange_address)
 | 
			
		||||
        for order in orders
 | 
			
		||||
    ]
 | 
			
		||||
    order_signatures = [
 | 
			
		||||
        sign_hash(ganache_provider, maker, order_hash)
 | 
			
		||||
        for order_hash in order_hashes
 | 
			
		||||
    ]
 | 
			
		||||
    taker_amounts = [order["takerAssetAmount"] for order in orders]
 | 
			
		||||
    tx_hash = exchange_wrapper.batch_fill_orders(
 | 
			
		||||
        orders=orders,
 | 
			
		||||
        taker_amounts=taker_amounts,
 | 
			
		||||
        signatures=order_signatures,
 | 
			
		||||
        tx_params=TxParams(from_=taker),
 | 
			
		||||
    )
 | 
			
		||||
    assert_valid(tx_hash.hex(), "/hexSchema")
 | 
			
		||||
 | 
			
		||||
    fill_events = exchange_wrapper.get_fill_event(tx_hash)
 | 
			
		||||
    for index, order in enumerate(orders):
 | 
			
		||||
        assert_fill_log(
 | 
			
		||||
            fill_events[index].args, maker, taker, order, order_hashes[index]
 | 
			
		||||
        )
 | 
			
		||||
							
								
								
									
										25
									
								
								python-packages/contract_wrappers/tox.ini
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								python-packages/contract_wrappers/tox.ini
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
			
		||||
# tox (https://tox.readthedocs.io/) is a tool for running tests
 | 
			
		||||
# in multiple virtualenvs. This configuration file will run the
 | 
			
		||||
# test suite on all supported python versions. To use it, "pip install tox"
 | 
			
		||||
# and then run "tox" from this directory.
 | 
			
		||||
 | 
			
		||||
[tox]
 | 
			
		||||
envlist = py37
 | 
			
		||||
 | 
			
		||||
[testenv]
 | 
			
		||||
commands =
 | 
			
		||||
    pip install -e .[dev]
 | 
			
		||||
    python setup.py test
 | 
			
		||||
 | 
			
		||||
[testenv:run_tests_against_test_deployment]
 | 
			
		||||
commands =
 | 
			
		||||
    # install dependencies from real PyPI
 | 
			
		||||
    pip install 0x-contract-addresses 0x-contract-artifacts 0x-json-schemas 0x-order-utils 0x-web3 attrs eth_utils hypothesis>=3.31.2 mypy_extensions pytest
 | 
			
		||||
    # install package-under-test from test PyPI
 | 
			
		||||
    pip install --index-url https://test.pypi.org/legacy/ 0x-contract-wrappers
 | 
			
		||||
    pytest test
 | 
			
		||||
 | 
			
		||||
[testenv:run_tests_against_deployment]
 | 
			
		||||
commands =
 | 
			
		||||
    pip install 0x-contract-wrappers
 | 
			
		||||
    pytest test
 | 
			
		||||
		Reference in New Issue
	
	Block a user