Merge branch 'development' of https://github.com/0xProject/0x-monorepo into feature/instant/prevent-css-leakage

This commit is contained in:
fragosti
2018-11-07 10:58:39 -08:00
22 changed files with 333 additions and 34 deletions

View File

@@ -162,6 +162,9 @@ jobs:
working_directory: ~/repo working_directory: ~/repo
docker: docker:
- image: circleci/python - image: circleci/python
- image: 0xorg/ganache-cli
command: |
ganache-cli --gasLimit 10000000 --noVMErrorsOnRPCResponse --db /snapshot --noVMErrorsOnRPCResponse -p 8545 --networkId 50 -m "concert load couple harbor equip island argue ramp clarify fence smart topic"
steps: steps:
- checkout - checkout
- run: sudo chown -R circleci:circleci /usr/local/bin - run: sudo chown -R circleci:circleci /usr/local/bin

1
.gitignore vendored
View File

@@ -99,6 +99,7 @@ packages/*/scripts/
.mypy_cache .mypy_cache
.tox .tox
python-packages/*/build python-packages/*/build
python-packages/*/dist
__pycache__ __pycache__
python-packages/*/src/*.egg-info python-packages/*/src/*.egg-info
python-packages/*/.coverage python-packages/*/.coverage

View File

@@ -4,6 +4,7 @@ lib
/packages/contracts/generated-artifacts /packages/contracts/generated-artifacts
/packages/abi-gen-wrappers/src/generated-wrappers /packages/abi-gen-wrappers/src/generated-wrappers
/packages/contract-artifacts/artifacts /packages/contract-artifacts/artifacts
/python-packages/order_utils/src/zero_ex/contract_artifacts/artifacts
/packages/json-schemas/schemas /packages/json-schemas/schemas
/packages/metacoin/src/contract_wrappers /packages/metacoin/src/contract_wrappers
/packages/metacoin/artifacts /packages/metacoin/artifacts

View File

@@ -24,6 +24,10 @@
"note": "note":
"Fix bug where default values for `AssetBuyer` public facing methods could get overriden by `undefined` values", "Fix bug where default values for `AssetBuyer` public facing methods could get overriden by `undefined` values",
"pr": 1207 "pr": 1207
},
{
"note": "Lower default expiry buffer from 5 minutes to 2 minutes",
"pr": 1217
} }
] ]
}, },

View File

@@ -9,7 +9,7 @@ const MAINNET_NETWORK_ID = 1;
const DEFAULT_ASSET_BUYER_OPTS: AssetBuyerOpts = { const DEFAULT_ASSET_BUYER_OPTS: AssetBuyerOpts = {
networkId: MAINNET_NETWORK_ID, networkId: MAINNET_NETWORK_ID,
orderRefreshIntervalMs: 10000, // 10 seconds orderRefreshIntervalMs: 10000, // 10 seconds
expiryBufferSeconds: 300, // 5 minutes expiryBufferSeconds: 120, // 2 minutes
}; };
const DEFAULT_BUY_QUOTE_REQUEST_OPTS: BuyQuoteRequestOpts = { const DEFAULT_BUY_QUOTE_REQUEST_OPTS: BuyQuoteRequestOpts = {

View File

@@ -18,7 +18,7 @@ Please read our [contribution guidelines](../../CONTRIBUTING.md) before getting
### Install Code and Dependencies ### Install Code and Dependencies
Ensure that you have Python >=3.6 installed, then: Ensure that you have installed Python >=3.6 and Docker. Then:
```bash ```bash
pip install -e .[dev] pip install -e .[dev]
@@ -26,7 +26,7 @@ pip install -e .[dev]
### Test ### Test
`./setup.py 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 ### Clean

View File

@@ -5,11 +5,12 @@
import subprocess # nosec import subprocess # nosec
from shutil import rmtree from shutil import rmtree
from os import environ, path from os import environ, path
from pathlib import Path
from sys import argv from sys import argv
from distutils.command.clean import clean from distutils.command.clean import clean
import distutils.command.build_py import distutils.command.build_py
from setuptools import setup from setuptools import find_packages, setup
from setuptools.command.test import test as TestCommand from setuptools.command.test import test as TestCommand
@@ -59,8 +60,15 @@ class LintCommand(distutils.command.build_py.build_py):
import eth_abi import eth_abi
eth_abi_dir = path.dirname(path.realpath(eth_abi.__file__)) eth_abi_dir = path.dirname(path.realpath(eth_abi.__file__))
with open(path.join(eth_abi_dir, "py.typed"), "a"): Path(path.join(eth_abi_dir, "py.typed")).touch()
pass
# 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: for lint_command in lint_commands:
print( print(
@@ -79,7 +87,7 @@ class CleanCommandExtension(clean):
rmtree(".mypy_cache", ignore_errors=True) rmtree(".mypy_cache", ignore_errors=True)
rmtree(".tox", ignore_errors=True) rmtree(".tox", ignore_errors=True)
rmtree(".pytest_cache", ignore_errors=True) rmtree(".pytest_cache", ignore_errors=True)
rmtree("src/order_utils.egg-info", ignore_errors=True) rmtree("src/0x_order_utils.egg-info", ignore_errors=True)
# pylint: disable=too-many-ancestors # pylint: disable=too-many-ancestors
@@ -111,6 +119,26 @@ class PublishCommand(distutils.command.build_py.build_py):
subprocess.check_call("twine upload dist/*".split()) # nosec subprocess.check_call("twine upload dist/*".split()) # nosec
# pylint: disable=too-many-ancestors
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 --gasLimit"
+ " 10000000 --db /snapshot --noVMErrorsOnRPCResponse -p 8545"
+ " --networkId 50 -m"
).split()
cmd_line.append(
"concert load couple harbor equip island argue ramp clarify fence"
+ " smart topic"
)
subprocess.call(cmd_line) # nosec
with open("README.md", "r") as file_handle: with open("README.md", "r") as file_handle:
README_MD = file_handle.read() README_MD = file_handle.read()
@@ -130,9 +158,9 @@ setup(
"test": TestCommandExtension, "test": TestCommandExtension,
"test_publish": TestPublishCommand, "test_publish": TestPublishCommand,
"publish": PublishCommand, "publish": PublishCommand,
"ganache": GanacheCommand,
}, },
include_package_data=True, install_requires=["eth-abi", "eth_utils", "mypy_extensions", "web3"],
install_requires=["eth-abi", "mypy_extensions", "web3"],
extras_require={ extras_require={
"dev": [ "dev": [
"bandit", "bandit",
@@ -151,14 +179,17 @@ setup(
] ]
}, },
python_requires=">=3.6, <4", python_requires=">=3.6, <4",
package_data={"zero_ex.order_utils": ["py.typed"]}, package_data={
"zero_ex.order_utils": ["py.typed"],
"zero_ex.contract_artifacts": ["artifacts/*"],
},
package_dir={"": "src"}, package_dir={"": "src"},
license="Apache 2.0", license="Apache 2.0",
keywords=( keywords=(
"ethereum cryptocurrency 0x decentralized blockchain dex exchange" "ethereum cryptocurrency 0x decentralized blockchain dex exchange"
), ),
namespace_packages=["zero_ex"], namespace_packages=["zero_ex"],
packages=["zero_ex.order_utils", "zero_ex.dev_utils"], packages=find_packages("src"),
classifiers=[ classifiers=[
"Development Status :: 2 - Pre-Alpha", "Development Status :: 2 - Pre-Alpha",
"Intended Audience :: Developers", "Intended Audience :: Developers",

View File

@@ -3,6 +3,7 @@
# Reference: http://www.sphinx-doc.org/en/master/config # Reference: http://www.sphinx-doc.org/en/master/config
from typing import List from typing import List
import pkg_resources
# pylint: disable=invalid-name # pylint: disable=invalid-name
@@ -12,7 +13,7 @@ project = "0x-order-utils"
# pylint: disable=redefined-builtin # pylint: disable=redefined-builtin
copyright = "2018, ZeroEx, Intl." copyright = "2018, ZeroEx, Intl."
author = "F. Eugene Aumson" author = "F. Eugene Aumson"
version = "0.1.0" # The short X.Y version version = pkg_resources.get_distribution("0x-order-utils").version
release = "" # The full version, including alpha/beta/rc tags release = "" # The full version, including alpha/beta/rc tags
extensions = [ extensions = [

View File

@@ -19,6 +19,9 @@ Python zero_ex.order_utils
See source for class properties. Sphinx does not easily generate class property docs; pull requests welcome. See source for class properties. Sphinx does not easily generate class property docs; pull requests welcome.
.. automodule:: zero_ex.order_utils.signature_utils
:members:
Indices and tables Indices and tables
================== ==================

View File

@@ -0,0 +1 @@
"""Solc-generated artifacts for 0x smart contracts."""

View File

@@ -0,0 +1 @@
../../../../../packages/contract-artifacts/artifacts

View File

@@ -46,3 +46,13 @@ def assert_is_int(value: Any, name: str) -> None:
f"expected variable '{name}', with value {str(value)}, to have" f"expected variable '{name}', with value {str(value)}, to have"
+ f" type 'int', not '{type(value).__name__}'" + f" type 'int', not '{type(value).__name__}'"
) )
def assert_is_hex_string(value: Any, name: str) -> None:
"""Assert that :param value: is a string of hex chars.
If :param value: isn't a str, raise a TypeError. If it is a string but
contains non-hex characters ("0x" prefix permitted), raise a ValueError.
"""
assert_is_string(value, name)
int(value, 16) # raises a ValueError if value isn't a base-16 str

View File

@@ -1 +1,11 @@
"""Order utilities for 0x applications.""" """Order utilities for 0x applications.
Some methods require the caller to pass in a `Web3.HTTPProvider` object. For
local testing one may construct such a provider pointing at an instance of
`ganache-cli <https://www.npmjs.com/package/ganache-cli>`_ which has the 0x
contracts deployed on it. For convenience, a docker container is provided for
just this purpose. To start it: ``docker run -d -p 8545:8545 0xorg/ganache-cli
--gasLimit 10000000 --db /snapshot --noVMErrorsOnRPCResponse -p 8545
--networkId 50 -m "concert load couple harbor equip island argue ramp clarify
fence smart topic"``.
"""

View File

@@ -0,0 +1,88 @@
"""Signature utilities."""
from typing import Dict, Tuple
import json
from pkg_resources import resource_string
from eth_utils import is_address, to_checksum_address
from web3 import Web3
import web3.exceptions
from web3.utils import datatypes
from zero_ex.dev_utils.type_assertions import assert_is_hex_string
# prefer `black` formatting. pylint: disable=C0330
EXCHANGE_ABI = json.loads(
resource_string("zero_ex.contract_artifacts", "artifacts/Exchange.json")
)["compilerOutput"]["abi"]
network_to_exchange_addr: Dict[str, str] = {
"1": "0x4f833a24e1f95d70f028921e27040ca56e09ab0b",
"3": "0x4530c0483a1633c7a1c97d2c53721caff2caaaaf",
"42": "0x35dd2932454449b14cee11a94d3674a936d5d7b2",
"50": "0x48bacb9266a570d521063ef5dd96e61686dbe788",
}
# prefer `black` formatting. pylint: disable=C0330
def is_valid_signature(
provider: Web3.HTTPProvider, data: str, signature: str, signer_address: str
) -> Tuple[bool, str]:
# docstring considered all one line by pylint: disable=line-too-long
"""Check the validity of the supplied signature.
Check if the supplied ``signature`` corresponds to signing ``data`` with
the private key corresponding to ``signer_address``.
:param provider: A Web3 provider able to access the 0x Exchange contract.
:param data: The hex encoded data signed by the supplied signature.
:param signature: The hex encoded signature.
:param signer_address: The hex encoded address that signed the data to
produce the supplied signature.
:rtype: Boolean indicating whether the given signature is valid.
>>> is_valid_signature(
... Web3.HTTPProvider("http://127.0.0.1:8545"),
... '0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222b0',
... '0x1B61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc3340349190569279751135161d22529dc25add4f6069af05be04cacbda2ace225403',
... '0x5409ed021d9299bf6814279a6a1411a7e866a631',
... )
(True, '')
""" # noqa: E501 (line too long)
# TODO: make this provider check more flexible. pylint: disable=fixme
# https://app.asana.com/0/684263176955174/901300863045491/f
if not isinstance(provider, Web3.HTTPProvider):
raise TypeError("provider is not a Web3.HTTPProvider")
assert_is_hex_string(data, "data")
assert_is_hex_string(signature, "signature")
assert_is_hex_string(signer_address, "signer_address")
if not is_address(signer_address):
raise ValueError("signer_address is not a valid address")
web3_instance = Web3(provider)
# false positive from pylint: disable=no-member
network_id = web3_instance.net.version
contract_address = network_to_exchange_addr[network_id]
# false positive from pylint: disable=no-member
contract: datatypes.Contract = web3_instance.eth.contract(
address=to_checksum_address(contract_address), abi=EXCHANGE_ABI
)
try:
return (
contract.call().isValidSignature(
data, to_checksum_address(signer_address), signature
),
"",
)
except web3.exceptions.BadFunctionCallOutput as exception:
known_revert_reasons = [
"LENGTH_GREATER_THAN_0_REQUIRED",
"SIGNATURE_UNSUPPORTED",
"LENGTH_0_REQUIRED",
"LENGTH_65_REQUIRED",
]
for known_revert_reason in known_revert_reasons:
if known_revert_reason in str(exception):
return (False, known_revert_reason)
return (False, f"Unknown: {exception}")

View File

@@ -1,6 +1,8 @@
from distutils.dist import Distribution from distutils.dist import Distribution
from typing import Any from typing import Any, List
def setup(**attrs: Any) -> Distribution: ... def setup(**attrs: Any) -> Distribution: ...
class Command: ... class Command: ...
def find_packages(where: str) -> List[str]: ...

View File

@@ -1,10 +1,26 @@
from typing import Optional, Union from typing import Dict, Optional, Union
from web3.utils import datatypes
class Web3: class Web3:
class HTTPProvider: ...
def __init__(self, provider: HTTPProvider) -> None: ...
@staticmethod @staticmethod
def sha3( def sha3(
primitive: Optional[Union[bytes, int, None]] = None, primitive: Optional[Union[bytes, int, None]] = None,
text: Optional[str] = None, text: Optional[str] = None,
hexstr: Optional[str] = None hexstr: Optional[str] = None
) -> bytes: ... ) -> bytes: ...
class net:
version: str
...
class eth:
@staticmethod
def contract(address: str, abi: Dict) -> datatypes.Contract: ...
...
... ...

View File

@@ -0,0 +1,2 @@
class BadFunctionCallOutput(Exception):
...

View File

@@ -0,0 +1,3 @@
class Contract:
def call(self): ...
...

View File

@@ -1,24 +1,18 @@
"""Exercise doctests for order_utils module.""" """Exercise doctests for all of our modules."""
from doctest import testmod from doctest import testmod
import pkgutil
from zero_ex.dev_utils import abi_utils, type_assertions import zero_ex
from zero_ex.order_utils import asset_data_utils
def test_doctest_asset_data_utils(): def test_all_doctests():
"""Invoke doctest on the asset_data_utils module.""" """Gather zero_ex.* modules and doctest them."""
(failure_count, _) = testmod(asset_data_utils) # prefer `black` formatting. pylint: disable=bad-continuation
assert failure_count == 0 for (importer, modname, _) in pkgutil.walk_packages(
path=zero_ex.__path__, prefix="zero_ex."
):
def test_doctest_abi_utils(): module = importer.find_module(modname).load_module(modname)
"""Invoke doctest on the abi_utils module.""" print(module)
(failure_count, _) = testmod(abi_utils) (failure_count, _) = testmod(module)
assert failure_count == 0 assert failure_count == 0
def test_doctest_type_assertions():
"""Invoke doctest on the type_assertions module."""
(failure_count, _) = testmod(type_assertions)
assert failure_count == 0

View File

@@ -0,0 +1,128 @@
"""Tests of zero_ex.order_utils.signature_utils."""
import pytest
from web3 import Web3
from zero_ex.order_utils.signature_utils import is_valid_signature
def test_is_valid_signature__provider_wrong_type():
"""Test that giving a non-HTTPProvider raises a TypeError."""
with pytest.raises(TypeError):
is_valid_signature(
123,
"0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222b"
+ "0",
"0x1B61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351b"
+ "c3340349190569279751135161d22529dc25add4f6069af05be04cacbda2ace"
+ "225403",
"0x5409ed021d9299bf6814279a6a1411a7e866a631",
)
def test_is_valid_signature__data_not_string():
"""Test that giving non-string `data` raises a TypeError."""
with pytest.raises(TypeError):
is_valid_signature(
Web3.HTTPProvider("http://127.0.0.1:8545"),
123,
"0x1B61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351b"
+ "c3340349190569279751135161d22529dc25add4f6069af05be04cacbda2ace"
+ "225403",
"0x5409ed021d9299bf6814279a6a1411a7e866a631",
)
def test_is_valid_signature__data_not_hex_string():
"""Test that giving non-hex-string `data` raises a ValueError."""
with pytest.raises(ValueError):
is_valid_signature(
Web3.HTTPProvider("http://127.0.0.1:8545"),
"jjj",
"0x1B61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351b"
+ "c3340349190569279751135161d22529dc25add4f6069af05be04cacbda2ace"
+ "225403",
"0x5409ed021d9299bf6814279a6a1411a7e866a631",
)
def test_is_valid_signature__signature_not_string():
"""Test that passng a non-string signature raises a TypeError."""
with pytest.raises(TypeError):
is_valid_signature(
Web3.HTTPProvider("http://127.0.0.1:8545"),
"0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222b"
+ "0",
123,
"0x5409ed021d9299bf6814279a6a1411a7e866a631",
)
def test_is_valid_signature__signature_not_hex_string():
"""Test that passing a non-hex-string signature raises a ValueError."""
with pytest.raises(ValueError):
is_valid_signature(
Web3.HTTPProvider("http://127.0.0.1:8545"),
"0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222b"
+ "0",
"jjj",
"0x5409ed021d9299bf6814279a6a1411a7e866a631",
)
def test_is_valid_signature__signer_address_not_string():
"""Test that giving a non-address `signer_address` raises a ValueError."""
with pytest.raises(TypeError):
is_valid_signature(
Web3.HTTPProvider("http://127.0.0.1:8545"),
"0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222b"
+ "0",
"0x1B61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351b"
+ "c3340349190569279751135161d22529dc25add4f6069af05be04cacbda2ace"
+ "225403",
123,
)
def test_is_valid_signature__signer_address_not_hex_string():
"""Test that giving a non-hex-str `signer_address` raises a ValueError."""
with pytest.raises(ValueError):
is_valid_signature(
Web3.HTTPProvider("http://127.0.0.1:8545"),
"0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222b"
+ "0",
"0x1B61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351b"
+ "c3340349190569279751135161d22529dc25add4f6069af05be04cacbda2ace"
+ "225403",
"jjj",
)
def test_is_valid_signature__signer_address_not_valid_address():
"""Test that giving a non-address for `signer_address` raises an error."""
with pytest.raises(ValueError):
is_valid_signature(
Web3.HTTPProvider("http://127.0.0.1:8545"),
"0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222b"
+ "0",
"0x1B61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351b"
+ "c3340349190569279751135161d22529dc25add4f6069af05be04cacbda2ace"
+ "225403",
"0xff",
)
def test_is_valid_signature__unsupported_sig_types():
"""Test that passing in a sig w/invalid type raises error.
To induce this error, the last byte of the signature is tweaked from 03 to
ff."""
(is_valid, reason) = is_valid_signature(
Web3.HTTPProvider("http://127.0.0.1:8545"),
"0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222b0",
"0x1B61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc334"
+ "0349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254ff",
"0x5409ed021d9299bf6814279a6a1411a7e866a631",
)
assert is_valid is False
assert reason == "SIGNATURE_UNSUPPORTED"