diff options
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 |