diff options
author | F. Eugene Aumson <feuGeneA@users.noreply.github.com> | 2019-01-09 22:58:29 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-01-09 22:58:29 +0800 |
commit | aa5af04447dfae24731557c6beead55bd8ff99a9 (patch) | |
tree | 1ffcc631ab078c88f85e2ab2b708f5d91b731cea /python-packages/contract_demo | |
parent | 5b7eff217e9c8d09d64ff8721d7a16e1df8a7c58 (diff) | |
download | dexon-sol-tools-aa5af04447dfae24731557c6beead55bd8ff99a9.tar dexon-sol-tools-aa5af04447dfae24731557c6beead55bd8ff99a9.tar.gz dexon-sol-tools-aa5af04447dfae24731557c6beead55bd8ff99a9.tar.bz2 dexon-sol-tools-aa5af04447dfae24731557c6beead55bd8ff99a9.tar.lz dexon-sol-tools-aa5af04447dfae24731557c6beead55bd8ff99a9.tar.xz dexon-sol-tools-aa5af04447dfae24731557c6beead55bd8ff99a9.tar.zst dexon-sol-tools-aa5af04447dfae24731557c6beead55bd8ff99a9.zip |
Python contract demo, with lots of refactoring (#1485)
* Refine Order for Web3 compat. & add conversions
Changed some of the fields in the Order class so that it can be passed
to our contracts via Web3.
Added conversion utilities so that an Order can be easily converted to
and from a JSON-compatible dict (specifically by encoding/decoding the
`bytes` fields), to facilitate validation against the JSON schema.
Also modified JSON order schema to accept integers in addition to
stringified integers.
* Fixes for json_schemas
Has-types indicator file, py.typed, was not being included in package.
Schemas were not being properly gathered into package installation.
* Add test/demo of Exchange.getOrderInfo()
* web3 bug workaround
* Fix problem packaging contract artifacts
* Move contract addresses to their own package
* Move contract artifacts to their own package
* Add scripts to install, test & lint all components
* prettierignore files in local python dev env
* Correct missing coverage analysis for sra_client
* CI cache lint: don't save, re-use from test-python
* tag hacks as hacks
* correct merge mistake
* remove local strip_0x() in favor of eth_utils
* remove json schemas from old order_utils location
* correct merge mistake
* doctest json schemas via command-line, not code
Diffstat (limited to 'python-packages/contract_demo')
22 files changed, 370 insertions, 0 deletions
diff --git a/python-packages/contract_demo/.discharge.json b/python-packages/contract_demo/.discharge.json new file mode 100644 index 000000000..9d811ae3e --- /dev/null +++ b/python-packages/contract_demo/.discharge.json @@ -0,0 +1,13 @@ +{ + "domain": "0x-contract-demo-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 +} diff --git a/python-packages/contract_demo/README.md b/python-packages/contract_demo/README.md new file mode 100644 index 000000000..3a0f964e3 --- /dev/null +++ b/python-packages/contract_demo/README.md @@ -0,0 +1,39 @@ +## 0x-contract-demo + +A demonstration of calling 0x smart contracts from Python. + +Read the [documentation](http://0x-contract-demo-py.s3-website-us-east-1.amazonaws.com/) + +## 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 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. diff --git a/python-packages/contract_demo/setup.py b/python-packages/contract_demo/setup.py new file mode 100755 index 000000000..a7afbd30c --- /dev/null +++ b/python-packages/contract_demo/setup.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python + +"""setuptools module for 0x-contract-demo package.""" + +import distutils.command.build_py +from distutils.command.clean import clean +import subprocess # nosec +from shutil import rmtree +from os import environ, path +from sys import argv + +from setuptools import 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()) + + +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 test setup.py".split(), + # style guide checker (formerly pep8): + "pycodestyle test setup.py".split(), + # docstring style checker: + "pydocstyle test setup.py".split(), + # static type checker: + "mypy test setup.py".split(), + # security issue checker: + "bandit -r ./setup.py".split(), + # general linter: + "pylint 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" + ) + + 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(".mypy_cache", ignore_errors=True) + rmtree(".tox", ignore_errors=True) + rmtree(".pytest_cache", ignore_errors=True) + + +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 + + +class PublishDocsCommand(distutils.command.build_py.build_py): + """Custom command to publish docs to S3.""" + + description = ( + "Publish docs to " + + "http://0x-contract-addresses-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 + + +setup( + name="0x-contract-demo", + version="1.0.0", + description="Demonstration of calling 0x contracts", + url=( + "https://github.com/0xProject/0x-monorepo/tree/development" + + "/python-packages/contract_demo" + ), + author="F. Eugene Aumson", + author_email="feuGeneA@users.noreply.github.com", + cmdclass={ + "clean": CleanCommandExtension, + "lint": LintCommand, + "test": TestCommandExtension, + "ganache": GanacheCommand, + "publish_docs": PublishDocsCommand, + }, + install_requires=[ + "0x-contract-addresses", + "0x-contract-artifacts", + "0x-order-utils", + "0x-web3", # TEMPORARY! pending resolution of our web3.py PR#1147 + "mypy_extensions", + ], + extras_require={ + "dev": [ + "bandit", + "black", + "coverage", + "coveralls", + "mypy", + "mypy_extensions", + "pycodestyle", + "pydocstyle", + "pylint", + "pytest", + "sphinx", + "tox", + ] + }, + python_requires=">=3.6, <4", + license="Apache 2.0", + zip_safe=False, # required per mypy + command_options={ + "build_sphinx": { + "source_dir": ("setup.py", "test"), + "build_dir": ("setup.py", "build/docs"), + } + }, +) diff --git a/python-packages/contract_demo/stubs/__init__.pyi b/python-packages/contract_demo/stubs/__init__.pyi new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/python-packages/contract_demo/stubs/__init__.pyi diff --git a/python-packages/contract_demo/stubs/command/__init__.pyi b/python-packages/contract_demo/stubs/command/__init__.pyi new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/python-packages/contract_demo/stubs/command/__init__.pyi diff --git a/python-packages/contract_demo/stubs/command/clean.pyi b/python-packages/contract_demo/stubs/command/clean.pyi new file mode 100644 index 000000000..46a42ddb1 --- /dev/null +++ b/python-packages/contract_demo/stubs/command/clean.pyi @@ -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: ... + ... diff --git a/python-packages/contract_demo/stubs/distutils/__init__.pyi b/python-packages/contract_demo/stubs/distutils/__init__.pyi new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/python-packages/contract_demo/stubs/distutils/__init__.pyi diff --git a/python-packages/contract_demo/stubs/distutils/command/__init__.pyi b/python-packages/contract_demo/stubs/distutils/command/__init__.pyi new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/python-packages/contract_demo/stubs/distutils/command/__init__.pyi diff --git a/python-packages/contract_demo/stubs/distutils/command/clean.pyi b/python-packages/contract_demo/stubs/distutils/command/clean.pyi new file mode 100644 index 000000000..46a42ddb1 --- /dev/null +++ b/python-packages/contract_demo/stubs/distutils/command/clean.pyi @@ -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: ... + ... diff --git a/python-packages/contract_demo/stubs/eth_utils/__init__.pyi b/python-packages/contract_demo/stubs/eth_utils/__init__.pyi new file mode 100644 index 000000000..4a83338ca --- /dev/null +++ b/python-packages/contract_demo/stubs/eth_utils/__init__.pyi @@ -0,0 +1,4 @@ +from typing import Union + +def to_checksum_address(value: Union[str, bytes]) -> str: + ... diff --git a/python-packages/contract_demo/stubs/pytest/__init__.pyi b/python-packages/contract_demo/stubs/pytest/__init__.pyi new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/python-packages/contract_demo/stubs/pytest/__init__.pyi diff --git a/python-packages/contract_demo/stubs/setuptools/__init__.pyi b/python-packages/contract_demo/stubs/setuptools/__init__.pyi new file mode 100644 index 000000000..8ea8d32b7 --- /dev/null +++ b/python-packages/contract_demo/stubs/setuptools/__init__.pyi @@ -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]: ... diff --git a/python-packages/contract_demo/stubs/setuptools/command/__init__.pyi b/python-packages/contract_demo/stubs/setuptools/command/__init__.pyi new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/python-packages/contract_demo/stubs/setuptools/command/__init__.pyi diff --git a/python-packages/contract_demo/stubs/setuptools/command/test.pyi b/python-packages/contract_demo/stubs/setuptools/command/test.pyi new file mode 100644 index 000000000..c5ec770ad --- /dev/null +++ b/python-packages/contract_demo/stubs/setuptools/command/test.pyi @@ -0,0 +1,3 @@ +from setuptools import Command + +class test(Command): ... diff --git a/python-packages/contract_demo/stubs/web3/__init__.pyi b/python-packages/contract_demo/stubs/web3/__init__.pyi new file mode 100644 index 000000000..21482d598 --- /dev/null +++ b/python-packages/contract_demo/stubs/web3/__init__.pyi @@ -0,0 +1,2 @@ +class Web3: + ... diff --git a/python-packages/contract_demo/stubs/web3/utils/__init__.pyi b/python-packages/contract_demo/stubs/web3/utils/__init__.pyi new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/python-packages/contract_demo/stubs/web3/utils/__init__.pyi diff --git a/python-packages/contract_demo/stubs/web3/utils/datatypes.pyi b/python-packages/contract_demo/stubs/web3/utils/datatypes.pyi new file mode 100644 index 000000000..70baff372 --- /dev/null +++ b/python-packages/contract_demo/stubs/web3/utils/datatypes.pyi @@ -0,0 +1,3 @@ +class Contract: + def call(self): ... + ... diff --git a/python-packages/contract_demo/test/__init__.py b/python-packages/contract_demo/test/__init__.py new file mode 100644 index 000000000..600f143bf --- /dev/null +++ b/python-packages/contract_demo/test/__init__.py @@ -0,0 +1 @@ +"""Demonstrations of calling 0x smart contracts.""" diff --git a/python-packages/contract_demo/test/conf.py b/python-packages/contract_demo/test/conf.py new file mode 100644 index 000000000..45ed4b2a5 --- /dev/null +++ b/python-packages/contract_demo/test/conf.py @@ -0,0 +1,54 @@ +"""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-demo" +# pylint: disable=redefined-builtin +copyright = "2018, ZeroEx, Intl." +author = "F. Eugene Aumson" +version = pkg_resources.get_distribution("0x-contract-demo").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", +] + +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_demopydoc" + +# -- Extension configuration: + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = {"https://docs.python.org/": None} diff --git a/python-packages/contract_demo/test/doc_static/.gitkeep b/python-packages/contract_demo/test/doc_static/.gitkeep new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/python-packages/contract_demo/test/doc_static/.gitkeep diff --git a/python-packages/contract_demo/test/index.rst b/python-packages/contract_demo/test/index.rst new file mode 100644 index 000000000..c546eccfa --- /dev/null +++ b/python-packages/contract_demo/test/index.rst @@ -0,0 +1,18 @@ +.. source for the sphinx-generated build/docs/web/index.html + +Python demo of 0x Smart Contracts +================================= + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + +.. automodule:: test.test_exchange + :members: + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/python-packages/contract_demo/test/test_exchange.py b/python-packages/contract_demo/test/test_exchange.py new file mode 100644 index 000000000..07492a403 --- /dev/null +++ b/python-packages/contract_demo/test/test_exchange.py @@ -0,0 +1,65 @@ +"""Test calling methods on the Exchange contract.""" + +from eth_utils import to_checksum_address +from web3 import Web3 +from web3.utils import datatypes + +from zero_ex.contract_addresses import NETWORK_TO_ADDRESSES, NetworkId +import zero_ex.contract_artifacts +from zero_ex.json_schemas import assert_valid +from zero_ex.order_utils import ( + Order, + OrderInfo, + order_to_jsdict, + generate_order_hash_hex, +) + + +def test_get_order_info(): + """Demonstrate Exchange.getOrderInfo().""" + order: Order = { + "makerAddress": "0x0000000000000000000000000000000000000000", + "takerAddress": "0x0000000000000000000000000000000000000000", + "feeRecipientAddress": "0x0000000000000000000000000000000000000000", + "senderAddress": "0x0000000000000000000000000000000000000000", + "makerAssetAmount": 1000000000000000000, + "takerAssetAmount": 1000000000000000000, + "makerFee": 0, + "takerFee": 0, + "expirationTimeSeconds": 12345, + "salt": 12345, + "makerAssetData": (b"\x00") * 20, + "takerAssetData": (b"\x00") * 20, + } + + web3_instance = Web3(Web3.HTTPProvider("http://127.0.0.1:8545")) + + # false positive from pylint: disable=no-member + contract_address = NETWORK_TO_ADDRESSES[ + NetworkId(int(web3_instance.net.version)) + ].exchange + + assert_valid( + order_to_jsdict(order, exchange_address=contract_address), + "/orderSchema", + ) + + # false positive from pylint: disable=no-member + exchange: datatypes.Contract = web3_instance.eth.contract( + address=to_checksum_address(contract_address), + abi=zero_ex.contract_artifacts.abi_by_name("Exchange"), + ) + + order_info = OrderInfo(*exchange.call().getOrderInfo(order)) + + assert isinstance(order_info.order_status, int) + assert order_info.order_status == 4 + + assert isinstance(order_info.order_hash, bytes) + assert order_info.order_hash.hex() == generate_order_hash_hex( + order, + exchange_address=NETWORK_TO_ADDRESSES[NetworkId.GANACHE].exchange, + ) + + assert isinstance(order_info.order_taker_asset_filled_amount, int) + assert order_info.order_taker_asset_filled_amount == 0 |