From 3284d912decec2d96d88fb47ef985e88ad139f0e Mon Sep 17 00:00:00 2001 From: user Date: Thu, 7 Jun 2018 16:20:38 +0100 Subject: [PATCH 01/49] Reintroduced Makefile/build stuff from v0.1 (HarryR), see notes: * `solc` now uses `solcjs` installed via npm from `./node_modules` * un-broke `Makefile` * added back PyInstaller build (to produce `dist/ion`) * added back Docker build (via alpine-glibc with `dist/ion`, and alpine-python) * added back shebang line where somebody did stupid copy-pasta of license header duhhhh... * split requirements into dev & normal (lint, pyinstaller in dev) * removed unnecessary python dependencies --- .docker-build-requirements.txt | 4 ++ .docker-entrypoint.sh | 103 ++++++++++++++++++++++++++++ .dockerignore | 12 ++++ .gitignore | 4 ++ Dockerfile.alpine-glibc | 7 ++ Dockerfile.alpine-python | 19 +++++ Makefile | 122 ++++++++++++++++++++++++++------- abi/IonLock.abi | 2 +- contracts/ECVerify.sol | 106 ---------------------------- contracts/ERC223.sol | 4 ++ contracts/ERC223Compatible.sol | 9 --- contracts/IonLock.sol | 2 +- contracts/Token.sol | 4 +- ion.spec | 23 +++++++ ion/Ion.py | 2 +- ion/args.py | 2 +- ion/crypto.py | 2 +- ion/ethrpc.py | 7 +- ion/merkle.py | 2 +- requirements-dev.txt | 5 ++ requirements.txt | 7 -- utils/extract-imports.sh | 2 + 22 files changed, 295 insertions(+), 155 deletions(-) create mode 100644 .docker-build-requirements.txt create mode 100755 .docker-entrypoint.sh create mode 100644 .dockerignore create mode 100644 Dockerfile.alpine-glibc create mode 100644 Dockerfile.alpine-python delete mode 100644 contracts/ECVerify.sol delete mode 100644 contracts/ERC223Compatible.sol create mode 100644 ion.spec create mode 100644 requirements-dev.txt create mode 100755 utils/extract-imports.sh diff --git a/.docker-build-requirements.txt b/.docker-build-requirements.txt new file mode 100644 index 0000000..eea828c --- /dev/null +++ b/.docker-build-requirements.txt @@ -0,0 +1,4 @@ +py-cffi +libffi-dev +openssl-dev +gmp-dev diff --git a/.docker-entrypoint.sh b/.docker-entrypoint.sh new file mode 100755 index 0000000..c8c419c --- /dev/null +++ b/.docker-entrypoint.sh @@ -0,0 +1,103 @@ +#!/usr/bin/dumb-init /bin/bash +set -e + +APK_REQUIREMENTS=() +BUILD_REQUIREMENTS=() +PIP_REQUIREMENTS=() +APKFILE='/apk-requirements.txt' +BUILDFILE='/build-requirements.txt' +REQFILE='/requirements.txt' + +function usage () { + echo <<"EOF" +Usage: $0 [-a -b -p -A -B -P -r] [--] + -a : APK requirement. Can be specified multiple times. + -b : APK build requirement. These will be removed at the end to save space. + -p : Pip requirement. Can be specified multiple times. + + -A : apk-requirements.txt file location, default: /apk-requirements.txt + -B : build-requirements.txt file location, default: /build-requirements.txt + -P : requirements.txt file location, default: /requirements.txt + -r : same as above, just to match Pip's -r flag. + + -- : Separator for flags and your command + + Whatever you provide after your arguments is run at the end. +EOF + exit 1 +} + +# Get and process arguments +while getopts ":a:b:p:A:B:P:r:" opt; do + case $opt in + a) APK_REQUIREMENTS+=("$OPTARG") ;; + b) BUILD_REQUIREMENTS+=("$OPTARG") ;; + p) PIP_REQUIREMENTS+=("$OPTARG") ;; + A) APKFILE="$OPTARG" ;; + B) BUILDFILE="$OPTARG" ;; + P) REQFILE="$OPTARG" ;; + r) REQFILE="$OPTARG" ;; + \?) + echo "Invalid option: -$OPTARG" >&2 + usage + ;; + :) + echo "Option -$OPTARG requires an argument." >&2 + usage + ;; + esac +done + +# Bad arguments +if [ $? -ne 0 ]; +then + usage +fi + +# Strip out all the arguments that have been processed +shift $((OPTIND-1)) + +# If there's a double dash at the end, get that off +[[ $1 = "--" ]] && shift + +# Don't do anything if we've already done this. +if [[ ! -f /requirements.installed ]]; then + + # Install any APK requirements + if [[ -f "$APKFILE" ]]; then + APK_REQUIREMENTS+=($( cat "$APKFILE" )) + fi + + if [[ -f "$BUILDFILE" ]]; then + BUILD_REQUIREMENTS+=($( cat "$BUILDFILE" )) + fi + + # Unfortunately the Alpine repositories are in a slightly inconsistent state for now-- python2 only exists in 'edge', not main. + # if [[ "$PYTHON_VERSION" == '2' ]]; then BUILD_PACKAGES="$(echo $BUILD_PACKAGES | sed -e 's/python2/python/g')"; fi \ + + apk add --no-cache $BUILD_PACKAGES "${APK_REQUIREMENTS[@]}" "${BUILD_REQUIREMENTS[@]}" + + # Install any Pip requirements + if [[ -f "$REQFILE" ]]; then + PIP_REQUIREMENTS+=($( cat "$REQFILE" )) + fi + + if [[ ${#PIP_REQUIREMENTS[@]} -gt 0 ]]; then + pip install --upgrade pip + pip install "${PIP_REQUIREMENTS[@]}" + fi + + # Remove packages that were only required for build. + apk del $BUILD_PACKAGES "${BUILD_REQUIREMENTS[@]}" + + touch /requirements.installed +fi + + +if [[ ! -z "$@" ]]; then + # If the user has given us a command, run it. + $@ +else + # Otherwise, default to running 'python'. + python +fi diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..2fea842 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,12 @@ +node_modules +*.egg-info +build +dist +chaindata +contracts +docs +migrations +test +*.log +*.swp +*.swo diff --git a/.gitignore b/.gitignore index 1e1b7f3..70aa61b 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,7 @@ docs/deps-modules.dot *.log *.pdf *.egg-info/ +lextab.py +yacctab.py +*.swp +*.swo diff --git a/Dockerfile.alpine-glibc b/Dockerfile.alpine-glibc new file mode 100644 index 0000000..d915a79 --- /dev/null +++ b/Dockerfile.alpine-glibc @@ -0,0 +1,7 @@ +FROM frolvlad/alpine-glibc + +COPY dist/ion /app/ +COPY abi /app/abi/ +WORKDIR /app + +ENTRYPOINT ["/app/ion"] diff --git a/Dockerfile.alpine-python b/Dockerfile.alpine-python new file mode 100644 index 0000000..92b6d79 --- /dev/null +++ b/Dockerfile.alpine-python @@ -0,0 +1,19 @@ +FROM jfloff/alpine-python:2.7 + +COPY . /app/ +WORKDIR /app + +# Copy in the entrypoint script -- this installs prerequisites on container start. +COPY .docker-entrypoint.sh /entrypoint.sh + +# install requirements +# this way when you build you won't need to install again +# and since COPY is cached we don't need to wait +COPY requirements.txt /tmp/requirements.txt +COPY .docker-build-requirements.txt /tmp/build-requirements.txt + +# Run the dependencies installer and then allow it to be run again if needed. +RUN /entrypoint.sh -B /tmp/build-requirements.txt -r /tmp/requirements.txt +RUN rm -f /requirements.installed + +ENTRYPOINT ["/usr/bin/python", "-mion"] diff --git a/Makefile b/Makefile index 67adfd7..2a9dded 100644 --- a/Makefile +++ b/Makefile @@ -1,36 +1,111 @@ -SOLC=solc --optimize +ROOT_DIR := $(shell dirname $(realpath $(MAKEFILE_LIST))) + +SOLC=$(ROOT_DIR)/node_modules/.bin/solcjs --optimize PYTHON=python -GANACHE=./node_modules/.bin/ganache-cli -TRUFFLE=./node_modules/.bin/truffle +NPM=npm +GANACHE=$(ROOT_DIR)/node_modules/.bin/ganache-cli +TRUFFLE=$(ROOT_DIR)/node_modules/.bin/truffle -build: - mkdir -p build - npm install - $(PYTHON) -mpip install -r requirements.txt +DOCKER_TAG_NAME=clearmatics/ion:latest + +UTIL_IMPORTS=$(ROOT_DIR)/utils/extract-imports.sh + +CONTRACTS=IonLock IonLink ERC223 Token +CONTRACTS_BIN=$(addprefix build/,$(addsuffix .bin,$(CONTRACTS))) +CONTRACTS_ABI=$(addprefix abi/,$(addsuffix .abi,$(CONTRACTS))) + +all: contracts python-pyflakes test truffle-test pylint + +clean: + rm -rf build chaindata dist + find . -name '*.pyc' -exec rm '{}' ';' + rm -rf *.pyc *.pdf *.egg-info + + +####################################################################### +# +# Packaging and distribution docker-build: dist/ion - docker build --rm=true -t clearmatics/ion:latest -f Dockerfile.alpine-glibc . + docker build --rm=true -t $(DOCKER_TAG_NAME) -f Dockerfile.alpine-glibc . docker-run: - docker run --rm=true -ti clearmatics/ion:latest shell + docker run --rm=true -ti $(DOCKER_TAG_NAME) --help + +bdist: + $(PYTHON) setup.py bdist_egg --exclude-source-files + $(PYTHON) setup.py bdist_wheel --universal + +dist: + mkdir -p $@ + +dist/ion: dist + $(PYTHON) -mPyInstaller ion.spec + + +####################################################################### +# +# Linting and anti-retardery measures + +python-pyflakes: + $(PYTHON) -mpyflakes ion + +python-pylint: + $(PYTHON) -mpylint ion -python-lint: - $(PYTHON) -mpylint ion/ +python-lint: python-pyflakes python-pylint solidity-lint: - npm run lint + $(NPM) run lint -requirements: requirements.txt - $(PYTHON) -mpip install -r requirements.txt + +####################################################################### +# +# Install dependencies / requirements etc. for Python and NodeJS +# + +nodejs-requirements: + $(NPM) install + +python-requirements: requirements.txt + $(PYTHON) -mpip install --user -r $< + +python-dev-requirements: requirements-dev.txt + $(PYTHON) -mpip install --user -r $< + +requirements-dev: nodejs-requirements python-dev-requirements + +requirements: python-requirements + +fedora-dev: + # use `nvm` to manage nodejs versions, rather than relying on system node + curl https://raw.githubusercontent.com/creationix/nvm/master/install.sh | bash + nvm install --lts + + +####################################################################### +# +# Builds Solidity contracts and ABI files +# + +contracts: $(CONTRACTS_BIN) $(CONTRACTS_ABI) abi: mkdir -p abi -abi/%.abi: build/%.abi abi +abi/%.abi: build/%.abi abi contracts/%.sol cp $< $@ -build/%.bin: contracts/%.sol - $(SOLC) -o build --asm --bin --overwrite --abi $< +build: + mkdir -p build + +build/%.abi: build/%.bin + +build/%.bin: contracts/%.sol build + $(eval contract_name := $(shell echo $(shell basename $<) | cut -f 1 -d .)) + cd $(shell dirname $<) && $(SOLC) -o ../build --asm --bin --overwrite --abi $(shell basename $<) $(shell $(UTIL_IMPORTS) $<) + cp build/$(contract_name)_sol_$(contract_name).bin build/$(contract_name).bin + cp build/$(contract_name)_sol_$(contract_name).abi build/$(contract_name).abi build/%.combined.bin: build/%.combined.sol $(SOLC) -o build --asm --bin --overwrite --abi $< @@ -38,16 +113,17 @@ build/%.combined.bin: build/%.combined.sol build/%.combined.sol: contracts/%.sol build cat $< | sed -e 's/\bimport\(\b.*\);/#include \1/g' | cpp -Icontracts | sed -e 's/^#.*$$//g' > $@ -clean: - rm -rf build chaindata dist - find . -name '*.pyc' -exec rm '{}' ';' - rm -rf *.pyc *.pdf *.egg-info + +####################################################################### +# +# Testing and unit test harnesses +# testrpc: - npm run testrpca + $(NPM) run testrpca test-js: - npm run test + $(NPM) run test test-unit: $(PYTHON) -m unittest discover test/ diff --git a/abi/IonLock.abi b/abi/IonLock.abi index 9295f9f..542cec0 100644 --- a/abi/IonLock.abi +++ b/abi/IonLock.abi @@ -1 +1 @@ -[{"constant":true,"inputs":[],"name":"LatestBlock","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"currency","type":"address"},{"name":"ion","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_recipient","type":"address"},{"indexed":false,"name":"_currency","type":"address"},{"indexed":true,"name":"value","type":"uint256"},{"indexed":true,"name":"ref","type":"bytes32"},{"indexed":false,"name":"data","type":"bytes"}],"name":"IonTransfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_recipient","type":"address"},{"indexed":false,"name":"_currency","type":"address"},{"indexed":true,"name":"value","type":"uint256"},{"indexed":true,"name":"ref","type":"bytes32"}],"name":"IonWithdraw","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"value","type":"uint256"},{"indexed":true,"name":"ref","type":"bytes32"}],"name":"IonMint","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"value","type":"uint256"},{"indexed":true,"name":"ref","type":"bytes32"}],"name":"IonBurn","type":"event"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_value","type":"uint256"},{"name":"_data","type":"bytes"}],"name":"tokenFallback","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_value","type":"uint256"},{"name":"_ref","type":"bytes32"},{"name":"_block_id","type":"uint256"},{"name":"_proof","type":"uint256[]"}],"name":"Withdraw","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file +[{"constant":false,"inputs":[{"name":"_value","type":"uint256"},{"name":"_ref","type":"bytes32"},{"name":"_block_id","type":"uint256"},{"name":"_proof","type":"uint256[]"}],"name":"Withdraw","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"LatestBlock","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_value","type":"uint256"},{"name":"_data","type":"bytes"}],"name":"tokenFallback","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"currency","type":"address"},{"name":"ion","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_recipient","type":"address"},{"indexed":false,"name":"_currency","type":"address"},{"indexed":true,"name":"value","type":"uint256"},{"indexed":true,"name":"ref","type":"bytes32"},{"indexed":false,"name":"data","type":"bytes"}],"name":"IonTransfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_recipient","type":"address"},{"indexed":false,"name":"_currency","type":"address"},{"indexed":true,"name":"value","type":"uint256"},{"indexed":true,"name":"ref","type":"bytes32"}],"name":"IonWithdraw","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"value","type":"uint256"},{"indexed":true,"name":"ref","type":"bytes32"}],"name":"IonMint","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"value","type":"uint256"},{"indexed":true,"name":"ref","type":"bytes32"}],"name":"IonBurn","type":"event"}] \ No newline at end of file diff --git a/contracts/ECVerify.sol b/contracts/ECVerify.sol deleted file mode 100644 index 622dd6b..0000000 --- a/contracts/ECVerify.sol +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright (c) 2016-2018 Clearmatics Technologies Ltd -// SPDX-License-Identifier: LGPL-3.0+ -pragma solidity ^0.4.18; - -// -// The new assembly support in Solidity makes writing helpers easy. -// Many have complained how complex it is to use `ecrecover`, especially in conjunction -// with the `eth_sign` RPC call. Here is a helper, which makes that a matter of a single call. -// -// Sample input parameters: -// (with v=0) -// "0x47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad", -// "0xaca7da997ad177f040240cdccf6905b71ab16b74434388c3a72f34fd25d6439346b2bac274ff29b48b3ea6e2d04c1336eaceafda3c53ab483fc3ff12fac3ebf200", -// "0x0e5cb767cce09a7f3ca594df118aa519be5e2b5a" -// -// (with v=1) -// "0x47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad", -// "0xdebaaa0cddb321b2dcaaf846d39605de7b97e77ba6106587855b9106cb10421561a22d94fa8b8a687ff9c911c844d1c016d1a685a9166858f9c7c1bc85128aca01", -// "0x8743523d96a1b2cbe0c6909653a56da18ed484af" -// -// (The hash is a hash of "hello world".) -// -// Written by Alex Beregszaszi (@axic), use it under the terms of the MIT license. -// - -library ECVerify { - // Duplicate Solidity's ecrecover, but catching the CALL return value - function safer_ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) - constant - internal - returns (address) - { - // We do our own memory management here. Solidity uses memory offset - // 0x40 to store the current end of memory. We write past it (as - // writes are memory extensions), but don't update the offset so - // Solidity will reuse it. The memory used here is only needed for - // this context. - - // FIXME: inline assembly can't access return values - bool ret; - address addr; - - assembly { - let size := mload(0x40) - mstore(size, hash) - mstore(add(size, 32), v) - mstore(add(size, 64), r) - mstore(add(size, 96), s) - - // NOTE: we can reuse the request memory because we deal with - // the return code - ret := call(3000, 1, 0, size, 128, size, 32) - addr := mload(size) - } - - require( ret == true ); - - return addr; - } - - function ecrecovery(bytes32 hash, bytes sig) internal constant returns (address) { - bytes32 r; - bytes32 s; - uint8 v; - - require (sig.length == 65); - - // The signature format is a compact form of: - // {bytes32 r}{bytes32 s}{uint8 v} - // Compact means, uint8 is not padded to 32 bytes. - assembly { - r := mload(add(sig, 32)) - s := mload(add(sig, 64)) - - // Here we are loading the last 32 bytes. We exploit the fact that - // 'mload' will pad with zeroes if we overread. - // There is no 'mload8' to do this, but that would be nicer. - /* v := byte(0, mload(add(sig, 96))) */ - - // Alternative solution: - // 'byte' is not working due to the Solidity parser, so lets - // use the second best option, 'and' - v := and(mload(add(sig, 65)), 255) - } - - // albeit non-transactional signatures are not specified by the YP, one would expect it - // to match the YP range of [27, 28] - // - // geth uses [0, 1] and some clients have followed. This might change, see: - // https://github.com/ethereum/go-ethereum/issues/2053 - if (v < 27) - v += 27; - - require (v == 27 || v == 28); - - /* prefix might be needed for geth only - * https://github.com/ethereum/go-ethereum/issues/3731 - */ - bytes memory prefix = "\x19Ethereum Signed Message:\n32"; - hash = keccak256(prefix, hash); - /* hash = sha3(prefix, hash); */ - - return safer_ecrecover(hash, v, r, s); - /* return ecrecover(hash, v, r, s); */ - } -} diff --git a/contracts/ERC223.sol b/contracts/ERC223.sol index d93f9a3..b38aec1 100644 --- a/contracts/ERC223.sol +++ b/contracts/ERC223.sol @@ -2,6 +2,10 @@ // SPDX-License-Identifier: LGPL-3.0+ pragma solidity ^0.4.18; +contract ERC223ReceivingContract { + function tokenFallback(address _from, uint _value, bytes _data) public; +} + contract ERC223 { uint public totalSupply; diff --git a/contracts/ERC223Compatible.sol b/contracts/ERC223Compatible.sol deleted file mode 100644 index 1ccfac2..0000000 --- a/contracts/ERC223Compatible.sol +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c) 2016-2018 Clearmatics Technologies Ltd -// SPDX-License-Identifier: LGPL-3.0+ -pragma solidity ^0.4.18; - -import "./ERC223.sol"; - -contract ERC223ReceivingContract { - function tokenFallback(address _from, uint _value, bytes _data) public; -} diff --git a/contracts/IonLock.sol b/contracts/IonLock.sol index b82eb69..c6b4ddd 100644 --- a/contracts/IonLock.sol +++ b/contracts/IonLock.sol @@ -2,7 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0+ pragma solidity ^0.4.18; -import "./ERC223Compatible.sol"; +import "./ERC223.sol"; import "./IonCompatible.sol"; diff --git a/contracts/Token.sol b/contracts/Token.sol index 516a945..23309ab 100644 --- a/contracts/Token.sol +++ b/contracts/Token.sol @@ -2,8 +2,8 @@ // SPDX-License-Identifier: LGPL-3.0+ pragma solidity ^0.4.18; -import "./ERC223Compatible.sol"; -import './SafeMath.sol'; +import "./ERC223.sol"; +import "./SafeMath.sol"; /** * @title Reference implementation of the ERC223 standard token. diff --git a/ion.spec b/ion.spec new file mode 100644 index 0000000..f9f04b6 --- /dev/null +++ b/ion.spec @@ -0,0 +1,23 @@ +# Run `pyinstaller ion.spec` to generate an executable + +import subprocess, sys + +def get_crypto_path(): + import Crypto + return Crypto.__path__[0] + +dict_tree = Tree(get_crypto_path(), prefix='Crypto', excludes=["*.pyc"]) + +# Generate filename +#suffix = {'linux2': '-linux', 'win32': '-win', 'darwin': '-osx'} +#output = 'ion-' + subprocess.check_output(['git', 'describe', '--always', '--tags']).decode('ascii').strip() + suffix.get(sys.platform, '') +output = 'ion' + +# Analyze files +a = Analysis(['__main__.py'], excludes=[], datas=[]) +a.binaries = filter(lambda x: 'Crypto' not in x[0], a.binaries) +a.datas += dict_tree + +# Generate executable +pyz = PYZ(a.pure, a.zipped_data) +exe = EXE(pyz, [('', '__main__.py', 'PYSOURCE')], a.binaries, a.zipfiles, a.datas, name=output) diff --git a/ion/Ion.py b/ion/Ion.py index c8ee7db..befe5a1 100644 --- a/ion/Ion.py +++ b/ion/Ion.py @@ -12,7 +12,7 @@ from ion.merkle import merkle_hash from ethereum.utils import keccak from .ethrpc import BadStatusCodeError, BadJsonError, BadResponseError, ConnectionError -from .args import arg_ethrpc, arg_bytes20, arg_lithium_api +from .args import arg_ethrpc, arg_bytes20 #, arg_lithium_api PRIMITIVE = (int, long, float, str, bool) def rpc_call_with_exceptions(function, *args): diff --git a/ion/args.py b/ion/args.py index 64066ed..5b22c8e 100644 --- a/ion/args.py +++ b/ion/args.py @@ -1,7 +1,7 @@ +#!/usr/bin/env python ## Copyright (c) 2016-2018 Clearmatics Technologies Ltd ## SPDX-License-Identifier: LGPL-3.0+ -#!/usr/bin/env python """ Provides a set of useful arguements for interacting with ethrpc """ diff --git a/ion/crypto.py b/ion/crypto.py index 4f5307d..6c152e9 100644 --- a/ion/crypto.py +++ b/ion/crypto.py @@ -1,7 +1,7 @@ +#!/usr/bin/env python ## Copyright (c) 2016-2018 Clearmatics Technologies Ltd ## SPDX-License-Identifier: LGPL-3.0+ -#!/usr/bin/env python """ Crypto: Has a load of useful crypto stuff """ diff --git a/ion/ethrpc.py b/ion/ethrpc.py index 497b61a..ce3cb95 100644 --- a/ion/ethrpc.py +++ b/ion/ethrpc.py @@ -188,13 +188,16 @@ def _solproxy_bind(self, method, address, account): if len(outs) > 1 else self.call(address, sig, args, outs, **kwa)[0]) if account is None: - # Without account, cannot call non-constant methods - return None + raise RuntimeError("Without account, cannot call non-constant methods") return lambda *args, **kwa: (self.call_with_transaction(account, address, sig, args, **kwa) if len(outs) > 1 else self.call_with_transaction(account, address, sig, args, **kwa)[0]) def proxy(self, abi, address, account=None): + """ + Provides a Python proxy object which exposes the contract ABI as + callable methods, allowing for seamless use of contracts from Python... + """ # XXX: specific to Ethereum addresses, 20 octets if len(address) == 20: address = address.encode('hex') diff --git a/ion/merkle.py b/ion/merkle.py index b96b905..8492acd 100644 --- a/ion/merkle.py +++ b/ion/merkle.py @@ -1,7 +1,7 @@ +#!/usr/bin/env python ## Copyright (c) 2016-2018 Clearmatics Technologies Ltd ## SPDX-License-Identifier: LGPL-3.0+ -#!/usr/bin/env python """ Merkle: Provides an interface to produce merkle trees, proofs, etc. diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..091041a --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,5 @@ +coverage +pylint +pyflakes +wheel +pyinstaller diff --git a/requirements.txt b/requirements.txt index b6de249..e905ceb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,12 +8,5 @@ flask==1.0.2 flask_restful==0.3.6 gevent==1.2.2 rlp==0.6.0 -python-jsonrpc==0.10.0 -graphviz==0.8.3 coincurve==7.1.0 -msgpack-python==0.5.6 -coverage==4.5.1 click==6.7 -prompt_toolkit==1.0.15 -pylint==1.8.4 -simplejson diff --git a/utils/extract-imports.sh b/utils/extract-imports.sh new file mode 100755 index 0000000..34b04fc --- /dev/null +++ b/utils/extract-imports.sh @@ -0,0 +1,2 @@ +#!/bin/sh +cat $1 | grep import | sed -e 's/import "\.\//import "/g' | cut -f 2 -d '"' | xargs echo From 9e983d3fec8e3115bbce28ea93d728ffd06e578d Mon Sep 17 00:00:00 2001 From: user Date: Thu, 7 Jun 2018 17:02:09 +0100 Subject: [PATCH 02/49] Remove `dist` from `.dockerignore` - causes Docker alpine-glibc to build because there's no `dist/ion` --- .dockerignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.dockerignore b/.dockerignore index 2fea842..4b29d0d 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,7 +1,6 @@ node_modules *.egg-info build -dist chaindata contracts docs From 1dc3fa43a31bd7509e065b1818702ba167aab725 Mon Sep 17 00:00:00 2001 From: user Date: Thu, 7 Jun 2018 21:29:39 +0100 Subject: [PATCH 03/49] Added `check-prereqs` make target, will tell you to RTFM if `node_modules/.../solcjs` no exist --- Makefile | 12 +++++++++--- README.md | 4 ++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 2a9dded..d3cee60 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ ROOT_DIR := $(shell dirname $(realpath $(MAKEFILE_LIST))) -SOLC=$(ROOT_DIR)/node_modules/.bin/solcjs --optimize +SOLC=$(ROOT_DIR)/node_modules/.bin/solcjs PYTHON=python NPM=npm GANACHE=$(ROOT_DIR)/node_modules/.bin/ganache-cli @@ -14,7 +14,13 @@ CONTRACTS=IonLock IonLink ERC223 Token CONTRACTS_BIN=$(addprefix build/,$(addsuffix .bin,$(CONTRACTS))) CONTRACTS_ABI=$(addprefix abi/,$(addsuffix .abi,$(CONTRACTS))) -all: contracts python-pyflakes test truffle-test pylint +all: check-prereqs contracts python-pyflakes test python-pylint + +check-prereqs: + @if [ ! -f "$(SOLC)" ]; then \ + echo -e "Dependencies not found!\nInstall prerequisites first! See README.md"; \ + false; \ + fi clean: rm -rf build chaindata dist @@ -103,7 +109,7 @@ build/%.abi: build/%.bin build/%.bin: contracts/%.sol build $(eval contract_name := $(shell echo $(shell basename $<) | cut -f 1 -d .)) - cd $(shell dirname $<) && $(SOLC) -o ../build --asm --bin --overwrite --abi $(shell basename $<) $(shell $(UTIL_IMPORTS) $<) + cd $(shell dirname $<) && $(SOLC) --optimize -o ../build --asm --bin --overwrite --abi $(shell basename $<) $(shell $(UTIL_IMPORTS) $<) cp build/$(contract_name)_sol_$(contract_name).bin build/$(contract_name).bin cp build/$(contract_name)_sol_$(contract_name).abi build/$(contract_name).abi diff --git a/README.md b/README.md index 55c4297..350b3d3 100644 --- a/README.md +++ b/README.md @@ -38,10 +38,10 @@ Currently notable flaws in the design: ## Install and Test -Install all the dependencies which need Node v9.0.0, NPM, and Python 2.7. Furthermore it is recommended to use a isolated Python environment with a tool such as `virtualenv`. +Install all the dependencies which need Node v8+, NPM, and Python 2.7. Furthermore it is recommended to use a isolated Python environment with a tool such as `virtualenv`, Vagrant or Docker. ``` -$ make build +$ make ``` ### Testing From b72eb328ec4829264061ea953e1e422c7c5fbbb6 Mon Sep 17 00:00:00 2001 From: user Date: Thu, 7 Jun 2018 23:57:56 +0100 Subject: [PATCH 04/49] Converted absolute imports into relative imports --- ion/Ion.py | 2 +- ion/__main__.py | 4 ++-- ion/crypto.py | 2 +- ion/lithium/__init__.py | 1 + ion/lithium/api.py | 2 +- ion/lithium/lithium.py | 6 +++--- 6 files changed, 9 insertions(+), 8 deletions(-) diff --git a/ion/Ion.py b/ion/Ion.py index befe5a1..edc8a10 100644 --- a/ion/Ion.py +++ b/ion/Ion.py @@ -9,7 +9,7 @@ import requests import simplejson -from ion.merkle import merkle_hash +from .merkle import merkle_hash from ethereum.utils import keccak from .ethrpc import BadStatusCodeError, BadJsonError, BadResponseError, ConnectionError from .args import arg_ethrpc, arg_bytes20 #, arg_lithium_api diff --git a/ion/__main__.py b/ion/__main__.py index ad5d030..8e24804 100644 --- a/ion/__main__.py +++ b/ion/__main__.py @@ -1,7 +1,7 @@ import click -from Ion import commands as ion_commands -from ion.lithium.lithium import etheventrelay as lithium +from .Ion import commands as ion_commands +from .lithium.lithium import etheventrelay as lithium commands = click.Group('commands') commands.add_command(ion_commands, "ion") diff --git a/ion/crypto.py b/ion/crypto.py index 6c152e9..031095d 100644 --- a/ion/crypto.py +++ b/ion/crypto.py @@ -12,7 +12,7 @@ from sha3 import keccak_256 -from utils import Marshalled, u256be, safe_ord +from .utils import Marshalled, u256be, safe_ord try: import coincurve diff --git a/ion/lithium/__init__.py b/ion/lithium/__init__.py index e69de29..8819ca2 100644 --- a/ion/lithium/__init__.py +++ b/ion/lithium/__init__.py @@ -0,0 +1 @@ +from .lithium import etheventrelay \ No newline at end of file diff --git a/ion/lithium/api.py b/ion/lithium/api.py index 5abfab2..607741a 100644 --- a/ion/lithium/api.py +++ b/ion/lithium/api.py @@ -12,7 +12,7 @@ from flask import Flask, request, jsonify # from flask import Flask, url_for -from ion.merkle import merkle_tree, merkle_path, merkle_proof +from ..merkle import merkle_tree, merkle_path, merkle_proof app = Flask(__name__) diff --git a/ion/lithium/lithium.py b/ion/lithium/lithium.py index 162efaa..2afa4e9 100644 --- a/ion/lithium/lithium.py +++ b/ion/lithium/lithium.py @@ -15,10 +15,10 @@ import click from ethereum.utils import scan_bin, sha3, keccak -from ion.args import arg_bytes20, arg_ethrpc -from ion.merkle import merkle_tree, merkle_hash +from ..args import arg_bytes20, arg_ethrpc +from ..merkle import merkle_tree, merkle_hash -from ion.lithium.api import app +from .api import app TRANSFER_SIGNATURE = keccak.new(digest_bits=256) \ .update('IonTransfer(address,address,uint256,bytes32,bytes)') \ From c002a40deb7d3526568779bbba20b591432fb9fe Mon Sep 17 00:00:00 2001 From: user Date: Fri, 8 Jun 2018 20:02:37 +0100 Subject: [PATCH 05/49] Initial sketch of HTLC contract, TODO: CLI api + cleanups to API / CLI --- Makefile | 2 +- contracts/HTLC.sol | 76 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 contracts/HTLC.sol diff --git a/Makefile b/Makefile index d3cee60..afb48ed 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ DOCKER_TAG_NAME=clearmatics/ion:latest UTIL_IMPORTS=$(ROOT_DIR)/utils/extract-imports.sh -CONTRACTS=IonLock IonLink ERC223 Token +CONTRACTS=IonLock IonLink ERC223 Token HTLC CONTRACTS_BIN=$(addprefix build/,$(addsuffix .bin,$(CONTRACTS))) CONTRACTS_ABI=$(addprefix abi/,$(addsuffix .abi,$(CONTRACTS))) diff --git a/contracts/HTLC.sol b/contracts/HTLC.sol new file mode 100644 index 0000000..e3bc955 --- /dev/null +++ b/contracts/HTLC.sol @@ -0,0 +1,76 @@ +// Copyright (c) 2018 Harry Roberts. All Rights Reserved. +// SPDX-License-Identifier: LGPL-3.0+ + +pragma solidity ^0.4.23; + +contract HTLC +{ + event OnDeposit( address indexed receiver, bytes32 image, uint256 expiry ); + + event OnRefund( bytes32 indexed exchange_id ); + + event OnWithdraw( bytes32 indexed exchange_id ); + + struct Exchange + { + address sender; + address receiver; + uint256 amount; + bytes32 image; + uint expiry; + } + + mapping (bytes32 => Exchange) public exchanges; + + function Deposit ( address receiver, bytes32 image, uint256 expiry ) + public payable + { + bytes32 exch_id = sha256( abi.encodePacked(receiver, image, expiry) ); + + require( exchanges[exch_id].sender == address(0x0), "Duplicate exchange" ); + require( receiver != address(0x0), "Invalid receiver address" ); + require( expiry > block.timestamp, "Expiry not in future" ); + + exchanges[exch_id] = Exchange( + msg.sender, + receiver, + msg.value, + image, + expiry + ); + + emit OnDeposit( receiver, image, expiry ); + } + + + function Withdraw ( bytes32 in_id, bytes32 in_preimage ) + public + { + Exchange storage exch = exchanges[in_id]; + require( exch.sender != 0x0, "Non existant exchange" ); + require( exch.receiver == msg.sender, "Only receiver can Withdraw" ); + require( block.timestamp < exch.expiry, "Exchange expired" ); + require( sha256(abi.encodePacked(in_preimage)) == exch.image, "Bad preimage" ); + + msg.sender.transfer( exch.amount ); + + delete exchanges[in_id]; + + emit OnWithdraw( in_id ); + } + + + function Refund ( bytes32 in_id ) + public + { + Exchange storage exch = exchanges[in_id]; + require( exch.sender == msg.sender, "Only depositor can refund" ); + require( block.timestamp > exch.expiry, "Exchange not expired, cannot refund" ); + + exch.sender.transfer( exch.amount ); + + delete exchanges[in_id]; + + emit OnRefund( in_id ); + } +} \ No newline at end of file From 623178c7549dbf008e78b8bf02cd7c82934c6ea0 Mon Sep 17 00:00:00 2001 From: user Date: Fri, 8 Jun 2018 22:30:01 +0100 Subject: [PATCH 06/49] Added 'htlc' contract, handles arguments --- contracts/HTLC.sol | 81 ++++++++++++++++++++++++++++++++------------ ion/__main__.py | 2 ++ ion/htlc.py | 84 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 145 insertions(+), 22 deletions(-) create mode 100644 ion/htlc.py diff --git a/contracts/HTLC.sol b/contracts/HTLC.sol index e3bc955..59c1b11 100644 --- a/contracts/HTLC.sol +++ b/contracts/HTLC.sol @@ -7,70 +7,107 @@ contract HTLC { event OnDeposit( address indexed receiver, bytes32 image, uint256 expiry ); - event OnRefund( bytes32 indexed exchange_id ); + event OnRefund( bytes32 indexed image ); - event OnWithdraw( bytes32 indexed exchange_id ); + event OnWithdraw( bytes32 indexed image, bytes32 preimage ); + + enum ExchangeState + { + Invalid, // Default state, invalid + Deposited, + Withdrawn, + Refunded, + Expired + } struct Exchange { address sender; address receiver; uint256 amount; - bytes32 image; - uint expiry; + uint256 expiry; + ExchangeState state; } mapping (bytes32 => Exchange) public exchanges; + + function GetState ( bytes32 in_image ) + public view returns (ExchangeState) + { + Exchange storage exch = exchanges[in_image]; + + if( exch.state == ExchangeState.Invalid ) + { + return ExchangeState.Invalid; + } + + if( exch.expiry < block.timestamp ) + { + return ExchangeState.Expired; + } + + return exch.state; + } + + function Deposit ( address receiver, bytes32 image, uint256 expiry ) public payable { - bytes32 exch_id = sha256( abi.encodePacked(receiver, image, expiry) ); + require( exchanges[image].state == ExchangeState.Invalid, "Duplicate exchange" ); - require( exchanges[exch_id].sender == address(0x0), "Duplicate exchange" ); require( receiver != address(0x0), "Invalid receiver address" ); + require( expiry > block.timestamp, "Expiry not in future" ); - exchanges[exch_id] = Exchange( + exchanges[image] = Exchange( msg.sender, receiver, msg.value, - image, - expiry + expiry, + ExchangeState.Deposited ); emit OnDeposit( receiver, image, expiry ); } - function Withdraw ( bytes32 in_id, bytes32 in_preimage ) + function Withdraw ( bytes32 in_image, bytes32 in_preimage ) public { - Exchange storage exch = exchanges[in_id]; - require( exch.sender != 0x0, "Non existant exchange" ); + Exchange storage exch = exchanges[in_image]; + + require( exch.state == ExchangeState.Deposited, "Unknown exchange, or invalid state" ); + require( exch.receiver == msg.sender, "Only receiver can Withdraw" ); - require( block.timestamp < exch.expiry, "Exchange expired" ); - require( sha256(abi.encodePacked(in_preimage)) == exch.image, "Bad preimage" ); - msg.sender.transfer( exch.amount ); + require( block.timestamp <= exch.expiry, "Exchange expired" ); - delete exchanges[in_id]; + require( sha256(abi.encodePacked(in_preimage)) == in_image, "Bad preimage" ); + + exch.state = ExchangeState.Withdrawn; + + msg.sender.transfer( exch.amount ); - emit OnWithdraw( in_id ); + emit OnWithdraw( in_image, in_preimage ); } - function Refund ( bytes32 in_id ) + function Refund ( bytes32 in_image ) public { - Exchange storage exch = exchanges[in_id]; + Exchange storage exch = exchanges[in_image]; + require( exch.sender == msg.sender, "Only depositor can refund" ); + require( block.timestamp > exch.expiry, "Exchange not expired, cannot refund" ); - exch.sender.transfer( exch.amount ); + require( exch.state == ExchangeState.Deposited, "Unknown exchange, or invalid state" ); - delete exchanges[in_id]; + exch.state = ExchangeState.Refunded; + + exch.sender.transfer( exch.amount ); - emit OnRefund( in_id ); + emit OnRefund( in_image ); } } \ No newline at end of file diff --git a/ion/__main__.py b/ion/__main__.py index 8e24804..bfbd01d 100644 --- a/ion/__main__.py +++ b/ion/__main__.py @@ -2,10 +2,12 @@ from .Ion import commands as ion_commands from .lithium.lithium import etheventrelay as lithium +from .htlc import commands as htlc_commands commands = click.Group('commands') commands.add_command(ion_commands, "ion") commands.add_command(lithium, "lithium") +commands.add_command(htlc_commands, "htlc") if __name__ == "__main__": commands.main() diff --git a/ion/htlc.py b/ion/htlc.py new file mode 100644 index 0000000..ac38e12 --- /dev/null +++ b/ion/htlc.py @@ -0,0 +1,84 @@ +# Copyright (c) 2018 Harry Roberts. All Rights Reserved. +# SPDX-License-Identifier: LGPL-3.0+ +from __future__ import print_function +import time +import os +from os import urandom +from hashlib import sha256 + +import click + +from .args import arg_ethrpc, arg_bytes20, arg_bytes32 + +ONE_MINUTE = 60 +ONE_HOUR = ONE_MINUTE * 60 +ONE_DAY = ONE_HOUR * 24 +ONE_YEAR = ONE_DAY * 365 + +DEFAULT_EXPIRY_DURATION = 10 * ONE_MINUTE +DURATION_OR_EPOCH_SPLIT = ONE_YEAR + + +def make_htlc_proxy(rpc, contract, account): + return rpc.proxy('abi/HTLC.abi', contract, account) + + +def get_default_expiry(): + return int(time.time()) + DEFAULT_EXPIRY_DURATION + + +def arg_expiry(ctx, param, value): + """ + Accepts either a duration, or an absolute UNIX epoch time + Returns absolute UNIX epoch time + """ + value = int(value) + if value < DURATION_OR_EPOCH_SPLIT: + return int(time.time()) + value + return value + + +def get_random_secret_32(): + return '0x' + os.urandom(32).encode('hex') + + +@click.command(help="Deposit into Hash-Time-Lock contract") +@click.option('--rpc', callback=arg_ethrpc, metavar="ip:port", default='127.0.0.1:8545', help="Ethereum JSON-RPC server") +@click.option('--account', callback=arg_bytes20, metavar="0x...20", required=True, help="Account to transfer from.") +@click.option('--contract', callback=arg_bytes20, metavar="0x...20", required=True, help="HTLC contract address") +@click.option('--receiver', callback=arg_bytes20, metavar="0x...20", required=True, help="Receiver address") +@click.option('--secret', callback=arg_bytes32, metavar="0x...32", default=get_random_secret_32, help="Secret to be supplied upon withdraw") +@click.option('--expires', metavar="seconds|unixtime", callback=arg_expiry, type=int, default=get_default_expiry, help="Expiry time, as duration (seconds), or UNIX epoch") +def deposit(rpc, account, contract, receiver, secret, expires): + now = int(time.time()) + print("Expires in", expires - now, "seconds") + api = make_htlc_proxy(rpc, contract, account) + image = sha256(secret).digest() # the hash pre-image is the 'secret' + api.Deposit( receiver, image, expires ) + + +@click.command(help="Withdraw from Hash-Time-Lock contract") +@click.option('--rpc', callback=arg_ethrpc, metavar="ip:port", default='127.0.0.1:8545', help="Ethereum JSON-RPC server") +@click.option('--account', callback=arg_bytes20, metavar="0x...20", required=True, help="Account to withdraw to") +@click.option('--contract', callback=arg_bytes20, metavar="0x...20", required=True, help="HTLC contract address") +@click.option('--secret', callback=arg_bytes32, metavar="0x...32", required=True, help="Exchange ID") +def withdraw(rpc, account, contract, secret): + api = make_htlc_proxy(rpc, contract, account) + image = sha256(secret).digest() # the hash pre-image is the 'secret' + api.Withdraw( image, secret ) + + +@click.command(help="Refund a Hash-Time-Lock contract") +@click.option('--rpc', callback=arg_ethrpc, metavar="ip:port", default='127.0.0.1:8545', help="Ethereum JSON-RPC server") +@click.option('--account', callback=arg_bytes20, metavar="0x...20", required=True, help="Account to withdraw to") +@click.option('--contract', callback=arg_bytes20, metavar="0x...20", required=True, help="HTLC contract address") +@click.option('--image', callback=arg_bytes32, metavar="0x...32", required=True, help="Exchange hash image") +def refund(rpc, account, contract, image): + api = make_htlc_proxy(rpc, contract, account) + api.Refund( image ) + + +commands = click.Group("htlc", help="Hash-Time-Lock Contract Interface") +commands.add_command(deposit, "deposit") +commands.add_command(withdraw, "withdraw") +commands.add_command(refund, "refund") From 84768b9242a4c221f75c45384cc20a4e08e2a960 Mon Sep 17 00:00:00 2001 From: user Date: Sun, 10 Jun 2018 14:51:29 +0100 Subject: [PATCH 07/49] Initial work towards a coordinator server for HTLC exchanges/swaps --- contracts/HTLC.sol | 40 ++++--- ion/__main__.py | 2 +- ion/htlc/__init__.py | 0 ion/{htlc.py => htlc/cli.py} | 7 +- ion/htlc/coordinator.py | 200 +++++++++++++++++++++++++++++++++++ ion/merkle.py | 0 6 files changed, 231 insertions(+), 18 deletions(-) create mode 100644 ion/htlc/__init__.py rename ion/{htlc.py => htlc/cli.py} (97%) create mode 100644 ion/htlc/coordinator.py mode change 100644 => 100755 ion/merkle.py diff --git a/contracts/HTLC.sol b/contracts/HTLC.sol index 59c1b11..7151602 100644 --- a/contracts/HTLC.sol +++ b/contracts/HTLC.sol @@ -51,24 +51,27 @@ contract HTLC } - function Deposit ( address receiver, bytes32 image, uint256 expiry ) + function Deposit ( address in_receiver, bytes32 in_image, uint256 in_expiry ) public payable { - require( exchanges[image].state == ExchangeState.Invalid, "Duplicate exchange" ); + require( exchanges[in_image].state == ExchangeState.Invalid, + "Duplicate exchange" ); - require( receiver != address(0x0), "Invalid receiver address" ); + require( in_receiver != address(0x0), + "Invalid receiver address" ); - require( expiry > block.timestamp, "Expiry not in future" ); + require( in_expiry > block.timestamp, + "Expiry not in future" ); - exchanges[image] = Exchange( + exchanges[in_image] = Exchange( msg.sender, - receiver, + in_receiver, msg.value, - expiry, + in_expiry, ExchangeState.Deposited ); - emit OnDeposit( receiver, image, expiry ); + emit OnDeposit( in_receiver, in_image, in_expiry ); } @@ -77,13 +80,17 @@ contract HTLC { Exchange storage exch = exchanges[in_image]; - require( exch.state == ExchangeState.Deposited, "Unknown exchange, or invalid state" ); + require( exch.state == ExchangeState.Deposited, + "Unknown exchange, or invalid state" ); - require( exch.receiver == msg.sender, "Only receiver can Withdraw" ); + require( exch.receiver == msg.sender, + "Only receiver can Withdraw" ); - require( block.timestamp <= exch.expiry, "Exchange expired" ); + require( block.timestamp <= exch.expiry, + "Exchange expired" ); - require( sha256(abi.encodePacked(in_preimage)) == in_image, "Bad preimage" ); + require( sha256(abi.encodePacked(in_preimage)) == in_image, + "Bad preimage" ); exch.state = ExchangeState.Withdrawn; @@ -98,11 +105,14 @@ contract HTLC { Exchange storage exch = exchanges[in_image]; - require( exch.sender == msg.sender, "Only depositor can refund" ); + require( exch.sender == msg.sender, + "Only depositor can refund" ); - require( block.timestamp > exch.expiry, "Exchange not expired, cannot refund" ); + require( block.timestamp > exch.expiry, + "Exchange not expired, cannot refund" ); - require( exch.state == ExchangeState.Deposited, "Unknown exchange, or invalid state" ); + require( exch.state == ExchangeState.Deposited, + "Unknown exchange, or invalid state" ); exch.state = ExchangeState.Refunded; diff --git a/ion/__main__.py b/ion/__main__.py index bfbd01d..5e891d4 100644 --- a/ion/__main__.py +++ b/ion/__main__.py @@ -2,7 +2,7 @@ from .Ion import commands as ion_commands from .lithium.lithium import etheventrelay as lithium -from .htlc import commands as htlc_commands +from .htlc.cli import commands as htlc_commands commands = click.Group('commands') commands.add_command(ion_commands, "ion") diff --git a/ion/htlc/__init__.py b/ion/htlc/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ion/htlc.py b/ion/htlc/cli.py similarity index 97% rename from ion/htlc.py rename to ion/htlc/cli.py index ac38e12..15c925c 100644 --- a/ion/htlc.py +++ b/ion/htlc/cli.py @@ -3,12 +3,11 @@ from __future__ import print_function import time import os -from os import urandom from hashlib import sha256 import click -from .args import arg_ethrpc, arg_bytes20, arg_bytes32 +from ..args import arg_ethrpc, arg_bytes20, arg_bytes32 ONE_MINUTE = 60 ONE_HOUR = ONE_MINUTE * 60 @@ -82,3 +81,7 @@ def refund(rpc, account, contract, image): commands.add_command(deposit, "deposit") commands.add_command(withdraw, "withdraw") commands.add_command(refund, "refund") + + +if __name__ == "__main__": + commands.main() diff --git a/ion/htlc/coordinator.py b/ion/htlc/coordinator.py new file mode 100644 index 0000000..a970330 --- /dev/null +++ b/ion/htlc/coordinator.py @@ -0,0 +1,200 @@ +## Copyright (c) 2018 Harry Roberts. All Rights Reserved. +## SPDX-License-Identifier: LGPL-3.0+ + +import sys +import os +import time + +from flask import Flask, Blueprint, request, abort, jsonify +from werkzeug.routing import BaseConverter + +from ..args import arg_bytes32, arg_bytes20, arg_uint256 + +ONE_MINUTE = 60 +MINIMUM_EXPIRY = 2 * ONE_MINUTE + + +def param(the_dict, key): + if key not in the_dict: + return abort(400, "Parameter required: " + key) + return the_dict[key] + + +def param_filter_arg(the_dict, key, filter_fn): + """ + Applies a click argument filter from `..args` to a value from a dictionary + Does the things with HTTP errors etc. upon failure. + """ + value = param(the_dict, key) + try: + filter_fn(None, None, value) + except Exception as ex: + return abort(400, "Invalid parameter %s - %s" % (key, str(ex))) + return value + + +def param_bytes32(the_dict, key): + return param_filter_arg(the_dict, key, arg_bytes32) + + +def param_bytes20(the_dict, key): + return param_filter_arg(the_dict, key, arg_bytes20) + + +def param_uint256(the_dict, key): + return param_filter_arg(the_dict, key, arg_uint256) + + +class Bytes32Converter(BaseConverter): + """Accept 32 hex-encoded bytes as URL param""" + def __init__(self, url_map, *items): + super(Bytes32Converter, self).__init__(url_map) + self.regex = r'[a-fA-F0-9]{64}' + + +class Bytes20Converter(BaseConverter): + """Accept 20 hex-encoded bytes as URL param""" + def __init__(self, url_map, *items): + super(Bytes20Converter, self).__init__(url_map) + self.regex = r'[a-fA-F0-9]{40}' + + +class CoordinatorBlueprint(Blueprint): + """ + Provides a web API for coordinating cross-chain HTLC exchanges + """ + + def __init__(self, **kwa): + Blueprint.__init__(self, 'htlc', __name__, **kwa) + + self._exchanges = dict() + + self.url_map.converters['bytes32'] = Bytes32Converter + self.url_map.converters['bytes20'] = Bytes20Converter + + self.add_url_rule("/", 'index', self.index) + self.add_url_rule("/advertise", 'advertise', self.exch_advertise, methods=['POST']) + self.add_url_rule("/", 'get', self.exch_get, methods=['GET']) + self.add_url_rule("//", 'propose', self.exch_propose, methods=['POST']) + self.add_url_rule("///confirm", 'confirm', self.exch_confirm, methods=['POST']) + self.add_url_rule("///finish", 'finish', self.exch_finish, methods=['POST']) + + def _get_exch(self, exch_id): + if exch_id not in self._exchanges: + return abort(404) + return self._exchanges[exch_id] + + def index(self): + """ + Display list of all exchanges, and their details + """ + return jsonify(self._exchanges) + + def exch_advertise(self): + """ + Advertise a potential exchange, advertiser offers N of X, wants M of Y + The address of the 'offer' side is advertised, as only that address can withdraw. + + --- + + Bob = offerer + Bob wants, 100 of B in return for 150 of A + Bob requests exchange + """ + # Parse and validate input parameters + offer_address = param_bytes20(request.data, 'offer_address') + offer_contract = param_bytes20(request.data, 'offer_contract') + offer_amount = param_uint256(request.data, 'offer_amount') + want_contract = param_bytes20(request.data, 'want_contract') + want_amount = param_uint256(request.data, 'want_amount') + + # TODO: validate contract addresses etc. and verify on-chain stuff + + exch_id = os.urandom(20) + + # Save exchange details + # TODO: replace with class instance, `Exchange` + self._exchanges[exch_id] = dict( + offer_address=offer_address, + offer_contract=offer_contract, + offer_amount=offer_amount, + want_contract=want_contract, + want_amount=want_amount, + proposals=dict(), + chosen_proposal=None, + ) + + return jsonify(dict( + ok=1 + )) + + def exch_get(self, exch_id): + """ + Retrieve details of exchange + """ + exch = self._get_exch(exch_id) + return jsonify(self._exchanges[exch_id]) + + def exch_propose(self, exch_id, secret_hashed): + """ + Somebody who has what the offerer wants proves they have deposited it. + They post proof as a proposal for exchange, this includes a hash of the secret they chose. + They post details of this deposit as the proposal. + """ + exch = self._get_exch(exch_id) + + # Hashed secret is the 'image', pre-image can be supplied to prove knowledge of secret + if secret_hashed in exch['proposals']: + return abort(409) # Duplicate proposal secret + + expiry = param_uint256(request.data, 'expiry') + depositor = param_bytes20(request.data, 'depositor') + + # TODO: verify details on-chain, expiry, depositor and secret must match + + # Verify expiry time is acceptable + # XXX: should minimum expiry be left to the contract, or the coordinator? + now = int(time.time()) + min_expiry = now + MINIMUM_EXPIRY + if expiry < min_expiry: + return abort(400) # TODO: add descriptive error message + + # Store proposal + exch['proposals'][secret_hashed] = dict( + expiry=expiry, + depositor=depositor + ) + + return jsonify(dict( + ok=1 + )) + + def exch_confirm(self, exch_id, secret_hashed): + """ + The initial offerer (A), who wants M of Y and offers N of X + Upon seeing the proposal by B for M of Y, posts their confirmation of the specific deal + The offerer (A) must have deposited N of X, using the same hash image that (B) proposed + """ + exch = self._get_exch(exch_id) + + # TODO: verify + + def exch_finish(self): + """ + Finish the exchange, by withdrawing the original deposit + """ + pass + + +def main(): + # XXX: not suitable for 'production' + # see: http://flask.pocoo.org/docs/1.0/deploying/#deployment + bp = CoordinatorBlueprint() + app = Flask(__name__) + app.register_blueprint(bp, url_prefix='/htlc') + app.run() + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/ion/merkle.py b/ion/merkle.py old mode 100644 new mode 100755 From 3371961626b3111eb2e2086cd4b455ab3fa6dbd1 Mon Sep 17 00:00:00 2001 From: user Date: Sun, 10 Jun 2018 21:00:02 +0100 Subject: [PATCH 08/49] Cleaned up the HTLC contract CLI and added RestClient --- ion/htlc/cli.py | 64 ++++++++++++++++++++++------------- ion/htlc/coordinator.py | 57 +++++++++++++++++++++++++------ ion/restclient.py | 75 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 161 insertions(+), 35 deletions(-) create mode 100644 ion/restclient.py diff --git a/ion/htlc/cli.py b/ion/htlc/cli.py index 15c925c..b106a3c 100644 --- a/ion/htlc/cli.py +++ b/ion/htlc/cli.py @@ -41,46 +41,62 @@ def get_random_secret_32(): return '0x' + os.urandom(32).encode('hex') -@click.command(help="Deposit into Hash-Time-Lock contract") -@click.option('--rpc', callback=arg_ethrpc, metavar="ip:port", default='127.0.0.1:8545', help="Ethereum JSON-RPC server") -@click.option('--account', callback=arg_bytes20, metavar="0x...20", required=True, help="Account to transfer from.") -@click.option('--contract', callback=arg_bytes20, metavar="0x...20", required=True, help="HTLC contract address") +####################################################################### +# +# Command-line interface to the HTLC contract +# +# $ ion htlc contract [options] sub-command [sub-options] +# +# e.g. +# +# $ ion htlc contract --account X --contract Y deposit --receiver Z ... +# + + +@click.command() +@click.pass_obj @click.option('--receiver', callback=arg_bytes20, metavar="0x...20", required=True, help="Receiver address") @click.option('--secret', callback=arg_bytes32, metavar="0x...32", default=get_random_secret_32, help="Secret to be supplied upon withdraw") @click.option('--expires', metavar="seconds|unixtime", callback=arg_expiry, type=int, default=get_default_expiry, help="Expiry time, as duration (seconds), or UNIX epoch") -def deposit(rpc, account, contract, receiver, secret, expires): +def contract_deposit(contract, receiver, secret, expires): now = int(time.time()) print("Expires in", expires - now, "seconds") - api = make_htlc_proxy(rpc, contract, account) + image = sha256(secret).digest() # the hash pre-image is the 'secret' - api.Deposit( receiver, image, expires ) + contract.Deposit( receiver, image, expires ) -@click.command(help="Withdraw from Hash-Time-Lock contract") -@click.option('--rpc', callback=arg_ethrpc, metavar="ip:port", default='127.0.0.1:8545', help="Ethereum JSON-RPC server") -@click.option('--account', callback=arg_bytes20, metavar="0x...20", required=True, help="Account to withdraw to") -@click.option('--contract', callback=arg_bytes20, metavar="0x...20", required=True, help="HTLC contract address") +@click.command() +@click.pass_obj @click.option('--secret', callback=arg_bytes32, metavar="0x...32", required=True, help="Exchange ID") -def withdraw(rpc, account, contract, secret): - api = make_htlc_proxy(rpc, contract, account) +def contract_withdraw(contract, secret): image = sha256(secret).digest() # the hash pre-image is the 'secret' - api.Withdraw( image, secret ) + contract.Withdraw( image, secret ) -@click.command(help="Refund a Hash-Time-Lock contract") +@click.command() +@click.pass_obj +@click.option('--image', callback=arg_bytes32, metavar="0x...32", required=True, help="Exchange hash image") +def contract_refund(contract, image): + contract.Refund( image ) + + +@click.group('contract', help='Command-line interface to Ethereum HTLC contract') +@click.pass_context @click.option('--rpc', callback=arg_ethrpc, metavar="ip:port", default='127.0.0.1:8545', help="Ethereum JSON-RPC server") -@click.option('--account', callback=arg_bytes20, metavar="0x...20", required=True, help="Account to withdraw to") +@click.option('--account', callback=arg_bytes20, metavar="0x...20", required=True, help="Account to transfer from.") @click.option('--contract', callback=arg_bytes20, metavar="0x...20", required=True, help="HTLC contract address") -@click.option('--image', callback=arg_bytes32, metavar="0x...32", required=True, help="Exchange hash image") -def refund(rpc, account, contract, image): - api = make_htlc_proxy(rpc, contract, account) - api.Refund( image ) +def contract(ctx, rpc, account, contract): + ctx.obj = make_htlc_proxy(rpc, contract, account) + + +contract.add_command(contract_deposit, "deposit") +contract.add_command(contract_withdraw, "withdraw") +contract.add_command(contract_refund, "refund") -commands = click.Group("htlc", help="Hash-Time-Lock Contract Interface") -commands.add_command(deposit, "deposit") -commands.add_command(withdraw, "withdraw") -commands.add_command(refund, "refund") +commands = click.Group("htlc", help="Hash-Time-Lock Atomic Swap") +commands.add_command(contract, 'contract') if __name__ == "__main__": diff --git a/ion/htlc/coordinator.py b/ion/htlc/coordinator.py index a970330..5736d10 100644 --- a/ion/htlc/coordinator.py +++ b/ion/htlc/coordinator.py @@ -75,15 +75,23 @@ def __init__(self, **kwa): self.add_url_rule("/", 'index', self.index) self.add_url_rule("/advertise", 'advertise', self.exch_advertise, methods=['POST']) self.add_url_rule("/", 'get', self.exch_get, methods=['GET']) + self.add_url_rule("//", 'proposal_get', self.exch_proposal_get, methods=['GET']) self.add_url_rule("//", 'propose', self.exch_propose, methods=['POST']) self.add_url_rule("///confirm", 'confirm', self.exch_confirm, methods=['POST']) - self.add_url_rule("///finish", 'finish', self.exch_finish, methods=['POST']) + self.add_url_rule("///release", 'release', self.exch_release, methods=['POST']) def _get_exch(self, exch_id): if exch_id not in self._exchanges: return abort(404) return self._exchanges[exch_id] + def _get_proposal(self, exch_id, secret_hashed): + exch = self._get_exch(exch_id) + proposal = exch['proposals'].get(secret_hashed) + if not proposal: + return abort(404) + return exch, proposal + def index(self): """ Display list of all exchanges, and their details @@ -95,11 +103,7 @@ def exch_advertise(self): Advertise a potential exchange, advertiser offers N of X, wants M of Y The address of the 'offer' side is advertised, as only that address can withdraw. - --- - - Bob = offerer - Bob wants, 100 of B in return for 150 of A - Bob requests exchange + This is performed by Alice """ # Parse and validate input parameters offer_address = param_bytes20(request.data, 'offer_address') @@ -125,6 +129,7 @@ def exch_advertise(self): ) return jsonify(dict( + id=exch_id, ok=1 )) @@ -140,6 +145,8 @@ def exch_propose(self, exch_id, secret_hashed): Somebody who has what the offerer wants proves they have deposited it. They post proof as a proposal for exchange, this includes a hash of the secret they chose. They post details of this deposit as the proposal. + + This is performed by Bob """ exch = self._get_exch(exch_id) @@ -169,21 +176,49 @@ def exch_propose(self, exch_id, secret_hashed): ok=1 )) + def exch_proposal_get(self, exch_id, secret_hashed): + """ + Retrieve details for a specific exchange proposal + """ + exch, proposal = self._get_proposal(exch_id, secret_hashed) + return jsonify(proposal) + def exch_confirm(self, exch_id, secret_hashed): """ The initial offerer (A), who wants M of Y and offers N of X Upon seeing the proposal by B for M of Y, posts their confirmation of the specific deal The offerer (A) must have deposited N of X, using the same hash image that (B) proposed + + This is performed by Alice """ - exch = self._get_exch(exch_id) + exch, proposal = self._get_proposal(exch_id, secret_hashed) + # TODO: verify on-chain details match the proposal + + def exch_release(self, exch_id, secret_hashed): + """ + Bob releases the secret to withdraw funds that Alice deposited. + + This is performed by Bob + """ + exch, proposal = self._get_proposal(exch_id, secret_hashed) - # TODO: verify + # XXX: technically a web API call isn't necessary for this step + # the API should monitor the state of both sides of the exchange + # and update the status / information automagically - def exch_finish(self): + def exch_finish(self, exch_id, secret_hashed): """ - Finish the exchange, by withdrawing the original deposit + Alice uses the secret revealed by Bob to withdraw the funds he deposited. + + This is performed by Alice. + + This completes the exchange. """ - pass + exch, proposal = self._get_proposal(exch_id, secret_hashed) + + # XXX: technically a web API call isn't necessary for this step + # the API should monitor the state of both sides of the exchange + # and update the status / information automagically def main(): diff --git a/ion/restclient.py b/ion/restclient.py new file mode 100644 index 0000000..2a7fea7 --- /dev/null +++ b/ion/restclient.py @@ -0,0 +1,75 @@ +# Copyright (c) 2018 Harry Roberts. All Rights Reserved. +# SPDX-License-Identifier: LGPL-3.0+ + +# Adapted from https://gist.githubusercontent.com/HarryR/d2373f421c39353cd462/raw/c3f726455bbbc03fe791dda0dabbf4f73b5d2ec9/restclient.py +# Except the command-line interface has been removed (so it doesn't depend on Plugin and Host components) + +__all__ = ('RestClient',) + +import sys +try: + from urllib.parse import quote_plus +except ImportError: + from urllib import quote_plus +import requests + + +class Resource(object): + __slots__ = ('_api', '_url') + + def __init__(self, url=None, api=None): + self._url = url + self._api = api + + def __getattr__(self, name): + if name[0] == '_': + raise AttributeError + return Resource(self._url + '/' + name, self._api) + + def __call__(self, name=None): + if name is None: + return self + if name[0] == '_': + raise AttributeError + return Resource(self._url + '/' + quote_plus(name), self._api) + + def _do(self, method, kwargs): + resp = self._api._request(method=method, + url=self._url, + params=kwargs) + resp.raise_for_status() + return resp + + def GET(self, **kwargs): + return self._do('GET', kwargs) + + def POST(self, **kwargs): + return self._do('POST', kwargs) + + def PUT(self, **kwargs): + return self._do('PUT', kwargs) + + def DELETE(self, id=None, **kwargs): + url = self._url + if id is not None: + url += "/" + str(id) + resp = self._api._request(method='DELETE', + url=url, + data=kwargs) + resp.raise_for_status() + return resp + + +class RestClient(object): + __slots__ = ('_base_url', '_session') + def __init__(self, base_url): + self._base_url = base_url.rstrip('/') + self._session = requests.Session() + + def _request(self, **kwargs): + req = requests.Request(**kwargs) + # TODO: setup authentication on `req` + return self._session.send(req.prepare()) + + def __getattr__(self, name): + return Resource(self._base_url + '/' + name, self) From 573dcdf5794695a261a21f36443de6aaaf8fb285 Mon Sep 17 00:00:00 2001 From: user Date: Sun, 10 Jun 2018 21:20:40 +0100 Subject: [PATCH 09/49] Added __main__ to ion.htlc, can now run python -mion.htlc --- ion/htlc/__main__.py | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 ion/htlc/__main__.py diff --git a/ion/htlc/__main__.py b/ion/htlc/__main__.py new file mode 100644 index 0000000..71d3094 --- /dev/null +++ b/ion/htlc/__main__.py @@ -0,0 +1,4 @@ +from .cli import commands + +if __name__ == "__main__": + commands.main() From 19f0d9f19931af9be428a5d16eb3fce49580344c Mon Sep 17 00:00:00 2001 From: user Date: Sun, 10 Jun 2018 21:38:10 +0100 Subject: [PATCH 10/49] Cleaned up pylint warnings in ion.htlc --- ion/__main__.py | 4 +- ion/htlc/__main__.py | 4 +- ion/htlc/cli.py | 59 +++--- ion/htlc/coordinator.py | 388 ++++++++++++++++++++-------------------- ion/restclient.py | 1 - 5 files changed, 233 insertions(+), 223 deletions(-) diff --git a/ion/__main__.py b/ion/__main__.py index 5e891d4..ba5241b 100644 --- a/ion/__main__.py +++ b/ion/__main__.py @@ -2,12 +2,12 @@ from .Ion import commands as ion_commands from .lithium.lithium import etheventrelay as lithium -from .htlc.cli import commands as htlc_commands +from .htlc.cli import COMMANDS as HTLC_COMMANDS commands = click.Group('commands') commands.add_command(ion_commands, "ion") commands.add_command(lithium, "lithium") -commands.add_command(htlc_commands, "htlc") +commands.add_command(HTLC_COMMANDS, "htlc") if __name__ == "__main__": commands.main() diff --git a/ion/htlc/__main__.py b/ion/htlc/__main__.py index 71d3094..311d954 100644 --- a/ion/htlc/__main__.py +++ b/ion/htlc/__main__.py @@ -1,4 +1,4 @@ -from .cli import commands +from .cli import COMMANDS if __name__ == "__main__": - commands.main() + COMMANDS.main() diff --git a/ion/htlc/cli.py b/ion/htlc/cli.py index b106a3c..184ee99 100644 --- a/ion/htlc/cli.py +++ b/ion/htlc/cli.py @@ -19,26 +19,27 @@ def make_htlc_proxy(rpc, contract, account): - return rpc.proxy('abi/HTLC.abi', contract, account) + # TODO: embed 'abi/HTLC.abi' file in package resources? + return rpc.proxy('abi/HTLC.abi', contract, account) def get_default_expiry(): - return int(time.time()) + DEFAULT_EXPIRY_DURATION + return int(time.time()) + DEFAULT_EXPIRY_DURATION def arg_expiry(ctx, param, value): - """ - Accepts either a duration, or an absolute UNIX epoch time - Returns absolute UNIX epoch time - """ - value = int(value) - if value < DURATION_OR_EPOCH_SPLIT: - return int(time.time()) + value - return value + """ + Accepts either a duration, or an absolute UNIX epoch time + Returns absolute UNIX epoch time + """ + value = int(value) + if value < DURATION_OR_EPOCH_SPLIT: + return int(time.time()) + value + return value def get_random_secret_32(): - return '0x' + os.urandom(32).encode('hex') + return '0x' + os.urandom(32).encode('hex') ####################################################################### @@ -59,26 +60,26 @@ def get_random_secret_32(): @click.option('--secret', callback=arg_bytes32, metavar="0x...32", default=get_random_secret_32, help="Secret to be supplied upon withdraw") @click.option('--expires', metavar="seconds|unixtime", callback=arg_expiry, type=int, default=get_default_expiry, help="Expiry time, as duration (seconds), or UNIX epoch") def contract_deposit(contract, receiver, secret, expires): - now = int(time.time()) - print("Expires in", expires - now, "seconds") + now = int(time.time()) + print("Expires in", expires - now, "seconds") - image = sha256(secret).digest() # the hash pre-image is the 'secret' - contract.Deposit( receiver, image, expires ) + image = sha256(secret).digest() # the hash pre-image is the 'secret' + contract.Deposit(receiver, image, expires) @click.command() @click.pass_obj @click.option('--secret', callback=arg_bytes32, metavar="0x...32", required=True, help="Exchange ID") def contract_withdraw(contract, secret): - image = sha256(secret).digest() # the hash pre-image is the 'secret' - contract.Withdraw( image, secret ) + image = sha256(secret).digest() # the hash pre-image is the 'secret' + contract.Withdraw(image, secret) @click.command() @click.pass_obj @click.option('--image', callback=arg_bytes32, metavar="0x...32", required=True, help="Exchange hash image") def contract_refund(contract, image): - contract.Refund( image ) + contract.Refund(image) @click.group('contract', help='Command-line interface to Ethereum HTLC contract') @@ -86,18 +87,24 @@ def contract_refund(contract, image): @click.option('--rpc', callback=arg_ethrpc, metavar="ip:port", default='127.0.0.1:8545', help="Ethereum JSON-RPC server") @click.option('--account', callback=arg_bytes20, metavar="0x...20", required=True, help="Account to transfer from.") @click.option('--contract', callback=arg_bytes20, metavar="0x...20", required=True, help="HTLC contract address") -def contract(ctx, rpc, account, contract): - ctx.obj = make_htlc_proxy(rpc, contract, account) +def contract_multicommand(ctx, rpc, account, contract): + # Contract will get passed to sub-commands as first object when using `@click.pass_obj` + ctx.obj = make_htlc_proxy(rpc, contract, account) -contract.add_command(contract_deposit, "deposit") -contract.add_command(contract_withdraw, "withdraw") -contract.add_command(contract_refund, "refund") +contract_multicommand.add_command(contract_deposit, "deposit") +contract_multicommand.add_command(contract_withdraw, "withdraw") +contract_multicommand.add_command(contract_refund, "refund") -commands = click.Group("htlc", help="Hash-Time-Lock Atomic Swap") -commands.add_command(contract, 'contract') +####################################################################### +# +# Multi-command entry-point +# + +COMMANDS = click.Group("htlc", help="Hash-Time-Lock Atomic Swap") +COMMANDS.add_command(contract_multicommand, 'contract') if __name__ == "__main__": - commands.main() + COMMANDS.main() diff --git a/ion/htlc/coordinator.py b/ion/htlc/coordinator.py index 5736d10..0e2445b 100644 --- a/ion/htlc/coordinator.py +++ b/ion/htlc/coordinator.py @@ -11,225 +11,229 @@ from ..args import arg_bytes32, arg_bytes20, arg_uint256 ONE_MINUTE = 60 -MINIMUM_EXPIRY = 2 * ONE_MINUTE +MINIMUM_EXPIRY = 2 * ONE_MINUTE def param(the_dict, key): - if key not in the_dict: - return abort(400, "Parameter required: " + key) - return the_dict[key] + if key not in the_dict: + return abort(400, "Parameter required: " + key) + return the_dict[key] def param_filter_arg(the_dict, key, filter_fn): - """ - Applies a click argument filter from `..args` to a value from a dictionary - Does the things with HTTP errors etc. upon failure. - """ - value = param(the_dict, key) - try: - filter_fn(None, None, value) - except Exception as ex: - return abort(400, "Invalid parameter %s - %s" % (key, str(ex))) - return value + """ + Applies a click argument filter from `..args` to a value from a dictionary + Does the things with HTTP errors etc. upon failure. + """ + value = param(the_dict, key) + try: + filter_fn(None, None, value) + except Exception as ex: + return abort(400, "Invalid parameter %s - %s" % (key, str(ex))) + return value def param_bytes32(the_dict, key): - return param_filter_arg(the_dict, key, arg_bytes32) + return param_filter_arg(the_dict, key, arg_bytes32) def param_bytes20(the_dict, key): - return param_filter_arg(the_dict, key, arg_bytes20) + return param_filter_arg(the_dict, key, arg_bytes20) def param_uint256(the_dict, key): - return param_filter_arg(the_dict, key, arg_uint256) + return param_filter_arg(the_dict, key, arg_uint256) class Bytes32Converter(BaseConverter): - """Accept 32 hex-encoded bytes as URL param""" - def __init__(self, url_map, *items): - super(Bytes32Converter, self).__init__(url_map) - self.regex = r'[a-fA-F0-9]{64}' + """Accept 32 hex-encoded bytes as URL param""" + def __init__(self, url_map, *items): + super(Bytes32Converter, self).__init__(url_map) + self.regex = r'[a-fA-F0-9]{64}' class Bytes20Converter(BaseConverter): - """Accept 20 hex-encoded bytes as URL param""" - def __init__(self, url_map, *items): - super(Bytes20Converter, self).__init__(url_map) - self.regex = r'[a-fA-F0-9]{40}' + """Accept 20 hex-encoded bytes as URL param""" + def __init__(self, url_map, *items): + super(Bytes20Converter, self).__init__(url_map) + self.regex = r'[a-fA-F0-9]{40}' class CoordinatorBlueprint(Blueprint): - """ - Provides a web API for coordinating cross-chain HTLC exchanges - """ - - def __init__(self, **kwa): - Blueprint.__init__(self, 'htlc', __name__, **kwa) - - self._exchanges = dict() - - self.url_map.converters['bytes32'] = Bytes32Converter - self.url_map.converters['bytes20'] = Bytes20Converter - - self.add_url_rule("/", 'index', self.index) - self.add_url_rule("/advertise", 'advertise', self.exch_advertise, methods=['POST']) - self.add_url_rule("/", 'get', self.exch_get, methods=['GET']) - self.add_url_rule("//", 'proposal_get', self.exch_proposal_get, methods=['GET']) - self.add_url_rule("//", 'propose', self.exch_propose, methods=['POST']) - self.add_url_rule("///confirm", 'confirm', self.exch_confirm, methods=['POST']) - self.add_url_rule("///release", 'release', self.exch_release, methods=['POST']) - - def _get_exch(self, exch_id): - if exch_id not in self._exchanges: - return abort(404) - return self._exchanges[exch_id] - - def _get_proposal(self, exch_id, secret_hashed): - exch = self._get_exch(exch_id) - proposal = exch['proposals'].get(secret_hashed) - if not proposal: - return abort(404) - return exch, proposal - - def index(self): - """ - Display list of all exchanges, and their details - """ - return jsonify(self._exchanges) - - def exch_advertise(self): - """ - Advertise a potential exchange, advertiser offers N of X, wants M of Y - The address of the 'offer' side is advertised, as only that address can withdraw. - - This is performed by Alice - """ - # Parse and validate input parameters - offer_address = param_bytes20(request.data, 'offer_address') - offer_contract = param_bytes20(request.data, 'offer_contract') - offer_amount = param_uint256(request.data, 'offer_amount') - want_contract = param_bytes20(request.data, 'want_contract') - want_amount = param_uint256(request.data, 'want_amount') - - # TODO: validate contract addresses etc. and verify on-chain stuff - - exch_id = os.urandom(20) - - # Save exchange details - # TODO: replace with class instance, `Exchange` - self._exchanges[exch_id] = dict( - offer_address=offer_address, - offer_contract=offer_contract, - offer_amount=offer_amount, - want_contract=want_contract, - want_amount=want_amount, - proposals=dict(), - chosen_proposal=None, - ) - - return jsonify(dict( - id=exch_id, - ok=1 - )) - - def exch_get(self, exch_id): - """ - Retrieve details of exchange - """ - exch = self._get_exch(exch_id) - return jsonify(self._exchanges[exch_id]) - - def exch_propose(self, exch_id, secret_hashed): - """ - Somebody who has what the offerer wants proves they have deposited it. - They post proof as a proposal for exchange, this includes a hash of the secret they chose. - They post details of this deposit as the proposal. - - This is performed by Bob - """ - exch = self._get_exch(exch_id) - - # Hashed secret is the 'image', pre-image can be supplied to prove knowledge of secret - if secret_hashed in exch['proposals']: - return abort(409) # Duplicate proposal secret - - expiry = param_uint256(request.data, 'expiry') - depositor = param_bytes20(request.data, 'depositor') - - # TODO: verify details on-chain, expiry, depositor and secret must match - - # Verify expiry time is acceptable - # XXX: should minimum expiry be left to the contract, or the coordinator? - now = int(time.time()) - min_expiry = now + MINIMUM_EXPIRY - if expiry < min_expiry: - return abort(400) # TODO: add descriptive error message - - # Store proposal - exch['proposals'][secret_hashed] = dict( - expiry=expiry, - depositor=depositor - ) - - return jsonify(dict( - ok=1 - )) - - def exch_proposal_get(self, exch_id, secret_hashed): - """ - Retrieve details for a specific exchange proposal - """ - exch, proposal = self._get_proposal(exch_id, secret_hashed) - return jsonify(proposal) - - def exch_confirm(self, exch_id, secret_hashed): - """ - The initial offerer (A), who wants M of Y and offers N of X - Upon seeing the proposal by B for M of Y, posts their confirmation of the specific deal - The offerer (A) must have deposited N of X, using the same hash image that (B) proposed - - This is performed by Alice - """ - exch, proposal = self._get_proposal(exch_id, secret_hashed) - # TODO: verify on-chain details match the proposal - - def exch_release(self, exch_id, secret_hashed): - """ - Bob releases the secret to withdraw funds that Alice deposited. - - This is performed by Bob - """ - exch, proposal = self._get_proposal(exch_id, secret_hashed) - - # XXX: technically a web API call isn't necessary for this step - # the API should monitor the state of both sides of the exchange - # and update the status / information automagically - - def exch_finish(self, exch_id, secret_hashed): - """ - Alice uses the secret revealed by Bob to withdraw the funds he deposited. - - This is performed by Alice. - - This completes the exchange. - """ - exch, proposal = self._get_proposal(exch_id, secret_hashed) - - # XXX: technically a web API call isn't necessary for this step - # the API should monitor the state of both sides of the exchange - # and update the status / information automagically + """ + Provides a web API for coordinating cross-chain HTLC exchanges + """ + + def __init__(self, **kwa): + Blueprint.__init__(self, 'htlc', __name__, **kwa) + + self._exchanges = dict() + + self.url_map.converters['bytes32'] = Bytes32Converter + self.url_map.converters['bytes20'] = Bytes20Converter + + self.add_url_rule("/", 'index', self.index) + self.add_url_rule("/advertise", 'advertise', self.exch_advertise, methods=['POST']) + self.add_url_rule("/", 'get', self.exch_get, methods=['GET']) + self.add_url_rule("//", 'proposal_get', + self.exch_proposal_get, methods=['GET']) + self.add_url_rule("//", 'propose', + self.exch_propose, methods=['POST']) + self.add_url_rule("///confirm", 'confirm', + self.exch_confirm, methods=['POST']) + self.add_url_rule("///release", 'release', + self.exch_release, methods=['POST']) + + def _get_exch(self, exch_id): + if exch_id not in self._exchanges: + return abort(404) + return self._exchanges[exch_id] + + def _get_proposal(self, exch_id, secret_hashed): + exch = self._get_exch(exch_id) + proposal = exch['proposals'].get(secret_hashed) + if not proposal: + return abort(404) + return exch, proposal + + def index(self): + """ + Display list of all exchanges, and their details + """ + return jsonify(self._exchanges) + + def exch_advertise(self): + """ + Advertise a potential exchange, advertiser offers N of X, wants M of Y + The address of the 'offer' side is advertised, as only that address can withdraw. + + This is performed by Alice + """ + # Parse and validate input parameters + offer_address = param_bytes20(request.data, 'offer_address') + offer_contract = param_bytes20(request.data, 'offer_contract') + offer_amount = param_uint256(request.data, 'offer_amount') + want_contract = param_bytes20(request.data, 'want_contract') + want_amount = param_uint256(request.data, 'want_amount') + + # TODO: validate contract addresses etc. and verify on-chain stuff + + exch_id = os.urandom(20) + + # Save exchange details + # TODO: replace with class instance, `Exchange` + self._exchanges[exch_id] = dict( + offer_address=offer_address, + offer_contract=offer_contract, + offer_amount=offer_amount, + want_contract=want_contract, + want_amount=want_amount, + proposals=dict(), + chosen_proposal=None, + ) + + return jsonify(dict( + id=exch_id, + ok=1 + )) + + def exch_get(self, exch_id): + """ + Retrieve details of exchange + """ + exch = self._get_exch(exch_id) + return jsonify(exch) + + def exch_propose(self, exch_id, secret_hashed): + """ + Somebody who has what the offerer wants proves they have deposited it. + They post proof as a proposal for exchange, this includes a hash of the secret they chose. + They post details of this deposit as the proposal. + + This is performed by Bob + """ + exch = self._get_exch(exch_id) + + # Hashed secret is the 'image', pre-image can be supplied to prove knowledge of secret + if secret_hashed in exch['proposals']: + return abort(409) # Duplicate proposal secret + + expiry = param_uint256(request.data, 'expiry') + depositor = param_bytes20(request.data, 'depositor') + + # TODO: verify details on-chain, expiry, depositor and secret must match + + # Verify expiry time is acceptable + # XXX: should minimum expiry be left to the contract, or the coordinator? + now = int(time.time()) + min_expiry = now + MINIMUM_EXPIRY + if expiry < min_expiry: + return abort(400) # TODO: add descriptive error message + + # Store proposal + exch['proposals'][secret_hashed] = dict( + expiry=expiry, + depositor=depositor + ) + + return jsonify(dict( + ok=1 + )) + + def exch_proposal_get(self, exch_id, secret_hashed): + """ + Retrieve details for a specific exchange proposal + """ + exch, proposal = self._get_proposal(exch_id, secret_hashed) + return jsonify(proposal) + + def exch_confirm(self, exch_id, secret_hashed): + """ + The initial offerer (A), who wants M of Y and offers N of X + Upon seeing the proposal by B for M of Y, posts their confirmation of the specific deal + The offerer (A) must have deposited N of X, using the same hash image that (B) proposed + + This is performed by Alice + """ + exch, proposal = self._get_proposal(exch_id, secret_hashed) + # TODO: verify on-chain details match the proposal + + def exch_release(self, exch_id, secret_hashed): + """ + Bob releases the secret to withdraw funds that Alice deposited. + + This is performed by Bob + """ + exch, proposal = self._get_proposal(exch_id, secret_hashed) + + # XXX: technically a web API call isn't necessary for this step + # the API should monitor the state of both sides of the exchange + # and update the status / information automagically + + def exch_finish(self, exch_id, secret_hashed): + """ + Alice uses the secret revealed by Bob to withdraw the funds he deposited. + + This is performed by Alice. + + This completes the exchange. + """ + exch, proposal = self._get_proposal(exch_id, secret_hashed) + + # XXX: technically a web API call isn't necessary for this step + # the API should monitor the state of both sides of the exchange + # and update the status / information automagically def main(): - # XXX: not suitable for 'production' - # see: http://flask.pocoo.org/docs/1.0/deploying/#deployment - bp = CoordinatorBlueprint() - app = Flask(__name__) - app.register_blueprint(bp, url_prefix='/htlc') - app.run() - return 0 + # XXX: not suitable for 'production' + # see: http://flask.pocoo.org/docs/1.0/deploying/#deployment + coordinator = CoordinatorBlueprint() + app = Flask(__name__) + app.register_blueprint(coordinator, url_prefix='/htlc') + app.run() + return 0 if __name__ == "__main__": - sys.exit(main()) + sys.exit(main()) diff --git a/ion/restclient.py b/ion/restclient.py index 2a7fea7..29b3fa7 100644 --- a/ion/restclient.py +++ b/ion/restclient.py @@ -6,7 +6,6 @@ __all__ = ('RestClient',) -import sys try: from urllib.parse import quote_plus except ImportError: From 5eabddb084d698e6495661c495d8ebaeda4109cc Mon Sep 17 00:00:00 2001 From: user Date: Mon, 11 Jun 2018 01:17:59 +0100 Subject: [PATCH 11/49] Initial sketch of coordinator client API --- ion/htlc/coordclient.py | 75 +++++++++++++++++++++++++++++++++++++++++ ion/htlc/coordinator.py | 1 + ion/restclient.py | 28 ++++----------- 3 files changed, 83 insertions(+), 21 deletions(-) create mode 100644 ion/htlc/coordclient.py diff --git a/ion/htlc/coordclient.py b/ion/htlc/coordclient.py new file mode 100644 index 0000000..1a1e0ce --- /dev/null +++ b/ion/htlc/coordclient.py @@ -0,0 +1,75 @@ +# Copyright (c) 2018 Harry Roberts. All Rights Reserved. +# SPDX-License-Identifier: LGPL-3.0+ + +from ..restclient import RestClient + + +class Proposal(object): + def __init__(self, restapi, data): + self._restapi = restapi + self._data = data + + def confirm(self): + pass + + def release(self): + pass + + def finish(self): + pass + + +class Exchange(object): + def __init__(self, restapi, data_dict): + self._restapi = restapi + self._data = None + self.data = data_dict + + @property + def data(self): + return self._data + + @data.setter + def set_data(self, value): + # TODO: wrap proposals + self._data = value + + def refresh(self): + self._data = self._restapi.GET() + + @property + def proposals(self): + return self._data['proposals'] + + def propose(self): + # TODO: perform POST request to create proposal + # add proposal to list + # then return Proposal object + pass + + +class CoordinatorClient(object): + """ + Uses the coordinator API and the HTLC Ethereum contract to + perform a cross-chain atomic swap as easily as possible. + + essentially the glue between the coordinator and the contract. + """ + def __init__(self, ethrpc, api_url): + # TODO: needs parameter for HTLC contract address + self._restapi = RestClient(api_url) + self._ethrpc = ethrpc + #self._htlc = rpc.proxy('abi/HTLC.abi', contract, account) + + def list(self): + results = self._restapi.list.GET() + retval = list() + for exch_id, data in results.items(): + resource = self._restapi(exch_id) + exch = Exchange(resource, data) + retval.append(exch) + return retval + + def advertise(self): + # TODO: return Exchange object + pass diff --git a/ion/htlc/coordinator.py b/ion/htlc/coordinator.py index 0e2445b..06e2bc9 100644 --- a/ion/htlc/coordinator.py +++ b/ion/htlc/coordinator.py @@ -73,6 +73,7 @@ def __init__(self, **kwa): self.url_map.converters['bytes20'] = Bytes20Converter self.add_url_rule("/", 'index', self.index) + self.add_url_rule("/list", 'list', self.index) self.add_url_rule("/advertise", 'advertise', self.exch_advertise, methods=['POST']) self.add_url_rule("/", 'get', self.exch_get, methods=['GET']) self.add_url_rule("//", 'proposal_get', diff --git a/ion/restclient.py b/ion/restclient.py index 29b3fa7..4502ea0 100644 --- a/ion/restclient.py +++ b/ion/restclient.py @@ -1,7 +1,7 @@ # Copyright (c) 2018 Harry Roberts. All Rights Reserved. # SPDX-License-Identifier: LGPL-3.0+ -# Adapted from https://gist.githubusercontent.com/HarryR/d2373f421c39353cd462/raw/c3f726455bbbc03fe791dda0dabbf4f73b5d2ec9/restclient.py +# Derived from https://gist.githubusercontent.com/HarryR/d2373f421c39353cd462/raw/c3f726455bbbc03fe791dda0dabbf4f73b5d2ec9/restclient.py # Except the command-line interface has been removed (so it doesn't depend on Plugin and Host components) __all__ = ('RestClient',) @@ -10,15 +10,16 @@ from urllib.parse import quote_plus except ImportError: from urllib import quote_plus + import requests -class Resource(object): +class RestClient(object): __slots__ = ('_api', '_url') - def __init__(self, url=None, api=None): + def __init__(self, url, api=None): self._url = url - self._api = api + self._api = self if api is None else api def __getattr__(self, name): if name[0] == '_': @@ -37,7 +38,7 @@ def _do(self, method, kwargs): url=self._url, params=kwargs) resp.raise_for_status() - return resp + return resp.json() def GET(self, **kwargs): return self._do('GET', kwargs) @@ -56,19 +57,4 @@ def DELETE(self, id=None, **kwargs): url=url, data=kwargs) resp.raise_for_status() - return resp - - -class RestClient(object): - __slots__ = ('_base_url', '_session') - def __init__(self, base_url): - self._base_url = base_url.rstrip('/') - self._session = requests.Session() - - def _request(self, **kwargs): - req = requests.Request(**kwargs) - # TODO: setup authentication on `req` - return self._session.send(req.prepare()) - - def __getattr__(self, name): - return Resource(self._base_url + '/' + name, self) + return resp.json() From b7d2b608c9885fa9b80b63681c9a6205bf27edb9 Mon Sep 17 00:00:00 2001 From: user Date: Mon, 11 Jun 2018 22:08:22 +0100 Subject: [PATCH 12/49] Initial implementation of Proposal logic on coordinator client side --- ion/htlc/cli.py | 24 +-------- ion/htlc/common.py | 24 +++++++++ ion/htlc/coordclient.py | 115 ++++++++++++++++++++++++++++++++-------- ion/htlc/coordinator.py | 11 ++-- ion/restclient.py | 3 ++ 5 files changed, 126 insertions(+), 51 deletions(-) create mode 100644 ion/htlc/common.py diff --git a/ion/htlc/cli.py b/ion/htlc/cli.py index 184ee99..aefa770 100644 --- a/ion/htlc/cli.py +++ b/ion/htlc/cli.py @@ -2,30 +2,14 @@ # SPDX-License-Identifier: LGPL-3.0+ from __future__ import print_function import time -import os from hashlib import sha256 import click from ..args import arg_ethrpc, arg_bytes20, arg_bytes32 -ONE_MINUTE = 60 -ONE_HOUR = ONE_MINUTE * 60 -ONE_DAY = ONE_HOUR * 24 -ONE_YEAR = ONE_DAY * 365 - -DEFAULT_EXPIRY_DURATION = 10 * ONE_MINUTE -DURATION_OR_EPOCH_SPLIT = ONE_YEAR - - -def make_htlc_proxy(rpc, contract, account): - # TODO: embed 'abi/HTLC.abi' file in package resources? - return rpc.proxy('abi/HTLC.abi', contract, account) - - -def get_default_expiry(): - return int(time.time()) + DEFAULT_EXPIRY_DURATION - +from .common import (DURATION_OR_EPOCH_SPLIT, get_random_secret_32, + get_default_expiry, make_htlc_proxy) def arg_expiry(ctx, param, value): """ @@ -38,10 +22,6 @@ def arg_expiry(ctx, param, value): return value -def get_random_secret_32(): - return '0x' + os.urandom(32).encode('hex') - - ####################################################################### # # Command-line interface to the HTLC contract diff --git a/ion/htlc/common.py b/ion/htlc/common.py new file mode 100644 index 0000000..030d248 --- /dev/null +++ b/ion/htlc/common.py @@ -0,0 +1,24 @@ +import os +import time + +ONE_MINUTE = 60 +ONE_HOUR = ONE_MINUTE * 60 +ONE_DAY = ONE_HOUR * 24 +ONE_YEAR = ONE_DAY * 365 + +DEFAULT_EXPIRY_DURATION = 10 * ONE_MINUTE +MINIMUM_EXPIRY_DURATION = 2 * ONE_MINUTE +DURATION_OR_EPOCH_SPLIT = ONE_YEAR + + +def make_htlc_proxy(rpc, contract, account): + # TODO: embed 'abi/HTLC.abi' file in package resources? + return rpc.proxy('abi/HTLC.abi', contract, account) + + +def get_default_expiry(): + return int(time.time()) + DEFAULT_EXPIRY_DURATION + + +def get_random_secret_32(): + return '0x' + os.urandom(32).encode('hex') diff --git a/ion/htlc/coordclient.py b/ion/htlc/coordclient.py index 1a1e0ce..4c7bfcb 100644 --- a/ion/htlc/coordclient.py +++ b/ion/htlc/coordclient.py @@ -1,13 +1,31 @@ # Copyright (c) 2018 Harry Roberts. All Rights Reserved. # SPDX-License-Identifier: LGPL-3.0+ +import os +from hashlib import sha256 + +from ethereum.utils import keccak + from ..restclient import RestClient +from .common import get_default_expiry, make_htlc_proxy + + +DEPOSIT_SIGNATURE = (keccak.new(digest_bits=256) + .update('Deposit(address,bytes32,uint256)') + .hexdigest()[:8]) + class Proposal(object): - def __init__(self, restapi, data): - self._restapi = restapi + def __init__(self, client, resource, data=None): + self._client = client + self._resource = resource self._data = data + if data is None: + self.refresh() + + def refresh(self): + self._data = self._resource.GET() def confirm(self): pass @@ -20,10 +38,17 @@ def finish(self): class Exchange(object): - def __init__(self, restapi, data_dict): - self._restapi = restapi + def __init__(self, client, resource, data_dict=None): + self._client = client + self._resource = resource self._data = None - self.data = data_dict + if data_dict is None: + self.refresh() + else: + self.data = data_dict + + def _make_proposal(self, key, propdata=None): + return Proposal(self._client, self._resource(key), propdata) @property def data(self): @@ -31,21 +56,54 @@ def data(self): @data.setter def set_data(self, value): - # TODO: wrap proposals + value['proposals'] = [self._make_proposal(key, propdata) + for key, propdata in value['proposals'].items()] self._data = value def refresh(self): - self._data = self._restapi.GET() + self.data = self._resource.GET() @property def proposals(self): return self._data['proposals'] def propose(self): - # TODO: perform POST request to create proposal - # add proposal to list - # then return Proposal object - pass + """ + Submit a proposal for the exchange by depositing your tokens + into a HTLC contract. + """ + # Create a random secret + secret = os.urandom(32) + secret_hashed = sha256(secret).digest() + + # Proposal parameters + prop_receiver = self._data['offer_address'] + prop_value = self._data['want_amount'] + prop_expiry = get_default_expiry() + + + # Perform deposit + ethrpc = self._client.ethrpc + my_address = self._client.ethrpc + + htlc_address = self._data['want_htlc_contract'] + htlc_contract = make_htlc_proxy(ethrpc, htlc_address, my_address) + htlc_contract.Deposit(prop_receiver, secret_hashed, prop_expiry, value=prop_value) + + # TODO: wait for deposit to go through? + # or provide some kind of receipt... + + # Notify coordinator of proposal + proposal_resource = self._resource(secret_hashed.encode('hex')) + response = proposal_resource.POST( + expiry=prop_expiry, + depositor=my_address.encode('hex') + ) + require(response['ok'] == 1, "Proposal coordinator API error") + + proposal = Proposal(self._client, proposal_resource) + self.proposals.append(proposal) + return proposal class CoordinatorClient(object): @@ -55,21 +113,36 @@ class CoordinatorClient(object): essentially the glue between the coordinator and the contract. """ - def __init__(self, ethrpc, api_url): - # TODO: needs parameter for HTLC contract address - self._restapi = RestClient(api_url) + def __init__(self, my_address, ethrpc, api_url=None, resource=None): + self._my_address = my_address + self._resource = RestClient(api_url) if resource is None else resource self._ethrpc = ethrpc - #self._htlc = rpc.proxy('abi/HTLC.abi', contract, account) + + @property + def ethrpc(self): + return self._ethrpc + + @property + def my_address(self): + return self._my_address def list(self): - results = self._restapi.list.GET() + results = self._resource.list.GET() retval = list() for exch_id, data in results.items(): - resource = self._restapi(exch_id) - exch = Exchange(resource, data) + resource = self._resource(exch_id) + exch = Exchange(self, resource, data) retval.append(exch) return retval - def advertise(self): - # TODO: return Exchange object - pass + def get_exchange(self, exch_id, exch_data=None): + assert len(exch_id) == 40 + resource = self._resource(exch_id) + return Exchange(self, resource, exch_data) + + def advertise(self, offer_amount, want_amount): + resp = self._resource.advertise.POST( + offer_address=self._my_address, + offer_amount=offer_amount, + want_amount=want_amount) + return self.get_exchange(resp['id']) diff --git a/ion/htlc/coordinator.py b/ion/htlc/coordinator.py index 06e2bc9..9748a49 100644 --- a/ion/htlc/coordinator.py +++ b/ion/htlc/coordinator.py @@ -10,8 +10,7 @@ from ..args import arg_bytes32, arg_bytes20, arg_uint256 -ONE_MINUTE = 60 -MINIMUM_EXPIRY = 2 * ONE_MINUTE +from .common import MINIMUM_EXPIRY_DURATION def param(the_dict, key): @@ -112,22 +111,18 @@ def exch_advertise(self): """ # Parse and validate input parameters offer_address = param_bytes20(request.data, 'offer_address') - offer_contract = param_bytes20(request.data, 'offer_contract') offer_amount = param_uint256(request.data, 'offer_amount') - want_contract = param_bytes20(request.data, 'want_contract') want_amount = param_uint256(request.data, 'want_amount') # TODO: validate contract addresses etc. and verify on-chain stuff - exch_id = os.urandom(20) + exch_id = os.urandom(20).encode('hex') # Save exchange details # TODO: replace with class instance, `Exchange` self._exchanges[exch_id] = dict( offer_address=offer_address, - offer_contract=offer_contract, offer_amount=offer_amount, - want_contract=want_contract, want_amount=want_amount, proposals=dict(), chosen_proposal=None, @@ -167,7 +162,7 @@ def exch_propose(self, exch_id, secret_hashed): # Verify expiry time is acceptable # XXX: should minimum expiry be left to the contract, or the coordinator? now = int(time.time()) - min_expiry = now + MINIMUM_EXPIRY + min_expiry = now + MINIMUM_EXPIRY_DURATION if expiry < min_expiry: return abort(400) # TODO: add descriptive error message diff --git a/ion/restclient.py b/ion/restclient.py index 4502ea0..e45e29e 100644 --- a/ion/restclient.py +++ b/ion/restclient.py @@ -13,11 +13,14 @@ import requests +from .utils import require + class RestClient(object): __slots__ = ('_api', '_url') def __init__(self, url, api=None): + require(url is not None, "Must provide REST API HTTP URL") self._url = url self._api = self if api is None else api From e9f8d048c7ebb97231d16c02c3f5eb053317f32e Mon Sep 17 00:00:00 2001 From: user Date: Tue, 12 Jun 2018 00:39:43 +0100 Subject: [PATCH 13/49] Initial fleshing out of the Proposal object --- abi/HTLC.abi | 1 + ion/args.py | 15 +++++ ion/htlc/cli.py | 17 ++--- ion/htlc/coordclient.py | 144 +++++++++++++++++++++++++++++++++------- ion/htlc/coordinator.py | 13 ++-- 5 files changed, 150 insertions(+), 40 deletions(-) create mode 100644 abi/HTLC.abi diff --git a/abi/HTLC.abi b/abi/HTLC.abi new file mode 100644 index 0000000..4df1a25 --- /dev/null +++ b/abi/HTLC.abi @@ -0,0 +1 @@ +[{"constant":false,"inputs":[{"name":"receiver","type":"address"},{"name":"image","type":"bytes32"},{"name":"expiry","type":"uint256"}],"name":"Deposit","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"in_image","type":"bytes32"}],"name":"Refund","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"in_image","type":"bytes32"},{"name":"in_preimage","type":"bytes32"}],"name":"Withdraw","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"in_image","type":"bytes32"}],"name":"GetState","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"exchanges","outputs":[{"name":"sender","type":"address"},{"name":"receiver","type":"address"},{"name":"amount","type":"uint256"},{"name":"expiry","type":"uint256"},{"name":"state","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"receiver","type":"address"},{"indexed":false,"name":"image","type":"bytes32"},{"indexed":false,"name":"expiry","type":"uint256"}],"name":"OnDeposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"image","type":"bytes32"}],"name":"OnRefund","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"image","type":"bytes32"}],"name":"OnWithdraw","type":"event"}] \ No newline at end of file diff --git a/ion/args.py b/ion/args.py index 5b22c8e..48968ad 100644 --- a/ion/args.py +++ b/ion/args.py @@ -1,14 +1,29 @@ #!/usr/bin/env python ## Copyright (c) 2016-2018 Clearmatics Technologies Ltd +## Copyright (c) 2018 Harry Roberts. ## SPDX-License-Identifier: LGPL-3.0+ """ Provides a set of useful arguements for interacting with ethrpc """ + +import time + from .ethrpc import EthJsonRpc from .utils import require, scan_bin +def arg_expiry(ctx, param, value): + """ + Accepts either a duration, or an absolute UNIX epoch time + Returns absolute UNIX epoch time + """ + value = int(value) + if value < DURATION_OR_EPOCH_SPLIT: + return int(time.time()) + value + return value + + def arg_bytes(ctx, param, value): if value is None: return None diff --git a/ion/htlc/cli.py b/ion/htlc/cli.py index aefa770..aca93f7 100644 --- a/ion/htlc/cli.py +++ b/ion/htlc/cli.py @@ -6,20 +6,12 @@ import click -from ..args import arg_ethrpc, arg_bytes20, arg_bytes32 +from ..args import arg_ethrpc, arg_bytes20, arg_bytes32, arg_expiry -from .common import (DURATION_OR_EPOCH_SPLIT, get_random_secret_32, - get_default_expiry, make_htlc_proxy) +from .common import get_random_secret_32, get_default_expiry, make_htlc_proxy + +# TODO: move to ..args -def arg_expiry(ctx, param, value): - """ - Accepts either a duration, or an absolute UNIX epoch time - Returns absolute UNIX epoch time - """ - value = int(value) - if value < DURATION_OR_EPOCH_SPLIT: - return int(time.time()) + value - return value ####################################################################### @@ -34,6 +26,7 @@ def arg_expiry(ctx, param, value): # +# TODO: add value... @click.command() @click.pass_obj @click.option('--receiver', callback=arg_bytes20, metavar="0x...20", required=True, help="Receiver address") diff --git a/ion/htlc/coordclient.py b/ion/htlc/coordclient.py index 4c7bfcb..80db5e9 100644 --- a/ion/htlc/coordclient.py +++ b/ion/htlc/coordclient.py @@ -4,21 +4,19 @@ import os from hashlib import sha256 -from ethereum.utils import keccak - +from ..utils import require from ..restclient import RestClient from .common import get_default_expiry, make_htlc_proxy -DEPOSIT_SIGNATURE = (keccak.new(digest_bits=256) - .update('Deposit(address,bytes32,uint256)') - .hexdigest()[:8]) - - class Proposal(object): - def __init__(self, client, resource, data=None): - self._client = client + def __init__(self, coordapi, exch_obj, resource, data=None): + assert isinstance(coordapi, CoordinatorClient) + assert isinstance(exch_obj, Exchange) + assert isinstance(resource, RestClient) + self._coordapi = coordapi + self._exch_obj = exch_obj self._resource = resource self._data = data if data is None: @@ -28,18 +26,109 @@ def refresh(self): self._data = self._resource.GET() def confirm(self): - pass + """ + Confirm the exchange by depositing your side of the deal + You must be the original deal offerer to confirm the exchange + It must be locked for the same parameters as the proposal + """ + ethrpc = self._coordapi.ethrpc + my_address = self._coordapi.my_address + exch_data = self._exch_obj.data + + require(my_address == exch_data['offer_address'], "Only offerer can confirm") + + # XXX: one side of the expiry must be twice as long as the other to handle failure case + conf_expiry = self._data['expiry'] + conf_receiver = self._data['depositor'] + conf_secret_hashed = self._data['secret_hashed'] + + # Offerer deposits their side of the deal, locked to same hashed secret + htlc_address = exch_data['offer_htlc_address'] + htlc_contract = make_htlc_proxy(ethrpc, htlc_address, my_address) + htlc_contract.Deposit(conf_receiver, conf_secret_hashed, conf_expiry) + + # TODO: wait for Deposit to complete, or submit transaction receipt - def release(self): - pass + self._resource.confirm.POST( + xxx=123 #... what to do here? + # TODO: add transaction receipt + ) + + def release(self, secret): + """ + Reveal the secret by withdrawing from your side of the exchange + This must be performed by party B (the proposer) + """ + assert len(secret) == 32 + # TODO: make this callable by either side, determine which one we are? + ethrpc = self._coordapi.ethrpc + my_address = self._coordapi.my_address + exch_data = self._exch_obj.data + + secret_hex = secret.encode('hex') + secret_hashed = sha256(secret).digest() + secret_hashed_hex = secret_hashed.encode('hex') + + require(my_address == self._data['depositor'], "Only proposer can release") + require(self._data['secret_hashed'] == secret_hashed_hex, "Secrets don't match!") + + htlc_address = exch_data['offer_htlc_address'] + htlc_contract = make_htlc_proxy(ethrpc, htlc_address, my_address) + htlc_contract.Withdraw(secret_hashed, secret) + + # Reveal secret, posting back to server + self._resource.release.POST( + secret=secret_hex, + # TODO: add transaction receipt + ) def finish(self): - pass + """ + After the secret has been revealed by the proposer the offerer + can withdraws funds using the same secret. + """ + ethrpc = self._coordapi.ethrpc + my_address = self._coordapi.my_address + exch_data = self._exch_obj.data + + secret_hex = self._data['secret'] + secret = secret_hex.decode('hex') + secret_hashed_hex = self._data['secret_hashed'] + secret_hashed = secret_hashed_hex.decode('hex') + + # TODO: verify secret hashes to hashed image + + htlc_address = exch_data['want_htlc_address'] + htlc_contract = make_htlc_proxy(ethrpc, htlc_address, my_address) + htlc_contract.Withdraw(secret_hashed, secret) + + self._resource.finish.POST( + xxx=123, + # TODO: add transaction receipt + ) + + def refund(self): + """ + After the timeout has expired the funds can be withdrawn by the original depositor + """ + ethrpc = self._coordapi.ethrpc + my_address = self._coordapi.my_address + exch_data = self._exch_obj.data + + # TODO: detertmine which side we're on, automagically call correct one + if x: + htlc_address = self._data['depositor'] + else: + htlc_address = exch_data['want_htlc_address'] + htlc_contract = make_htlc_proxy(ethrpc, htlc_address, my_address) + htlc_contract.Refund(secret_hashed) class Exchange(object): - def __init__(self, client, resource, data_dict=None): - self._client = client + def __init__(self, coordapi, resource, data_dict=None): + assert isinstance(coordapi, CoordinatorClient) + assert isinstance(resource, RestClient) + self._coordapi = coordapi self._resource = resource self._data = None if data_dict is None: @@ -47,8 +136,8 @@ def __init__(self, client, resource, data_dict=None): else: self.data = data_dict - def _make_proposal(self, key, propdata=None): - return Proposal(self._client, self._resource(key), propdata) + def _make_proposal(self, secret_hashed_hex, propdata=None): + return Proposal(self._coordapi, self, self._resource(secret_hashed_hex), propdata) @property def data(self): @@ -75,33 +164,40 @@ def propose(self): # Create a random secret secret = os.urandom(32) secret_hashed = sha256(secret).digest() + secret_hashed_hex = secret_hashed.encode('hex') # Proposal parameters prop_receiver = self._data['offer_address'] prop_value = self._data['want_amount'] prop_expiry = get_default_expiry() - # Perform deposit - ethrpc = self._client.ethrpc - my_address = self._client.ethrpc + ethrpc = self._coordapi.ethrpc + my_address = self._coordapi.my_address + + # XXX: for testing, we can be both sides... + #require(my_address != prop_receiver, "Cannot be both sides of exchange") - htlc_address = self._data['want_htlc_contract'] + htlc_address = self._data['want_htlc_address'] htlc_contract = make_htlc_proxy(ethrpc, htlc_address, my_address) htlc_contract.Deposit(prop_receiver, secret_hashed, prop_expiry, value=prop_value) # TODO: wait for deposit to go through? # or provide some kind of receipt... + # should be able to wait for the OnDeposit event to be emitted + # TODO: add 'wait for event' method to ethrpc # Notify coordinator of proposal - proposal_resource = self._resource(secret_hashed.encode('hex')) + proposal_resource = self._resource(secret_hashed_hex) response = proposal_resource.POST( expiry=prop_expiry, - depositor=my_address.encode('hex') + depositor=my_address.encode('hex'), + # TODO: add transaction receipt ) require(response['ok'] == 1, "Proposal coordinator API error") - proposal = Proposal(self._client, proposal_resource) + # Add proposal to list, then return it + proposal = self._make_proposal(secret_hashed_hex) self.proposals.append(proposal) return proposal diff --git a/ion/htlc/coordinator.py b/ion/htlc/coordinator.py index 9748a49..b9dc48e 100644 --- a/ion/htlc/coordinator.py +++ b/ion/htlc/coordinator.py @@ -114,6 +114,9 @@ def exch_advertise(self): offer_amount = param_uint256(request.data, 'offer_amount') want_amount = param_uint256(request.data, 'want_amount') + # TODO: offer_htlc_address + # TODO: want_htlc_address + # TODO: validate contract addresses etc. and verify on-chain stuff exch_id = os.urandom(20).encode('hex') @@ -154,6 +157,8 @@ def exch_propose(self, exch_id, secret_hashed): if secret_hashed in exch['proposals']: return abort(409) # Duplicate proposal secret + # TODO: verify either side of the exchange aren't the same + expiry = param_uint256(request.data, 'expiry') depositor = param_bytes20(request.data, 'depositor') @@ -168,6 +173,7 @@ def exch_propose(self, exch_id, secret_hashed): # Store proposal exch['proposals'][secret_hashed] = dict( + secret_hashed=secret_hashed, expiry=expiry, depositor=depositor ) @@ -192,6 +198,7 @@ def exch_confirm(self, exch_id, secret_hashed): This is performed by Alice """ exch, proposal = self._get_proposal(exch_id, secret_hashed) + # XXX: one side of the expiry must be twice as long as the other to handle failure case # TODO: verify on-chain details match the proposal def exch_release(self, exch_id, secret_hashed): @@ -201,10 +208,8 @@ def exch_release(self, exch_id, secret_hashed): This is performed by Bob """ exch, proposal = self._get_proposal(exch_id, secret_hashed) - - # XXX: technically a web API call isn't necessary for this step - # the API should monitor the state of both sides of the exchange - # and update the status / information automagically + secret = param_bytes32(request.data, 'secret') + # TODO: verify secret matches secret_hashed def exch_finish(self, exch_id, secret_hashed): """ From 15dcaff6af0ccbf717024b8bcfdd03a6d527174c Mon Sep 17 00:00:00 2001 From: user Date: Tue, 12 Jun 2018 17:41:33 +0100 Subject: [PATCH 14/49] Initial work on testing + bugfixes --- Makefile | 9 +++++ abi/HTLC.abi | 2 +- ion/args.py | 4 +++ ion/htlc/common.py | 2 -- ion/htlc/coordclient.py | 7 ++++ ion/htlc/coordinator.py | 57 ++++++++++++++++++++++---------- ion/restclient.py | 33 ++++++++++-------- migrations/2_deploy_contracts.js | 3 ++ test_coordclient.py | 31 +++++++++++++++++ 9 files changed, 114 insertions(+), 34 deletions(-) create mode 100755 test_coordclient.py diff --git a/Makefile b/Makefile index afb48ed..2282d27 100644 --- a/Makefile +++ b/Makefile @@ -135,3 +135,12 @@ test-unit: $(PYTHON) -m unittest discover test/ test: test-unit test-js + + +####################################################################### +# +# Truffle utils +# + +truffle-deploy: + $(TRUFFLE) deploy diff --git a/abi/HTLC.abi b/abi/HTLC.abi index 4df1a25..bb2c328 100644 --- a/abi/HTLC.abi +++ b/abi/HTLC.abi @@ -1 +1 @@ -[{"constant":false,"inputs":[{"name":"receiver","type":"address"},{"name":"image","type":"bytes32"},{"name":"expiry","type":"uint256"}],"name":"Deposit","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"in_image","type":"bytes32"}],"name":"Refund","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"in_image","type":"bytes32"},{"name":"in_preimage","type":"bytes32"}],"name":"Withdraw","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"in_image","type":"bytes32"}],"name":"GetState","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"exchanges","outputs":[{"name":"sender","type":"address"},{"name":"receiver","type":"address"},{"name":"amount","type":"uint256"},{"name":"expiry","type":"uint256"},{"name":"state","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"receiver","type":"address"},{"indexed":false,"name":"image","type":"bytes32"},{"indexed":false,"name":"expiry","type":"uint256"}],"name":"OnDeposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"image","type":"bytes32"}],"name":"OnRefund","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"image","type":"bytes32"}],"name":"OnWithdraw","type":"event"}] \ No newline at end of file +[{"constant":false,"inputs":[{"name":"in_receiver","type":"address"},{"name":"in_image","type":"bytes32"},{"name":"in_expiry","type":"uint256"}],"name":"Deposit","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"in_image","type":"bytes32"}],"name":"Refund","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"in_image","type":"bytes32"},{"name":"in_preimage","type":"bytes32"}],"name":"Withdraw","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"in_image","type":"bytes32"}],"name":"GetState","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"exchanges","outputs":[{"name":"sender","type":"address"},{"name":"receiver","type":"address"},{"name":"amount","type":"uint256"},{"name":"expiry","type":"uint256"},{"name":"state","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"receiver","type":"address"},{"indexed":false,"name":"image","type":"bytes32"},{"indexed":false,"name":"expiry","type":"uint256"}],"name":"OnDeposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"image","type":"bytes32"}],"name":"OnRefund","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"image","type":"bytes32"},{"indexed":false,"name":"preimage","type":"bytes32"}],"name":"OnWithdraw","type":"event"}] \ No newline at end of file diff --git a/ion/args.py b/ion/args.py index 48968ad..c759531 100644 --- a/ion/args.py +++ b/ion/args.py @@ -13,6 +13,9 @@ from .utils import require, scan_bin +DURATION_OR_EPOCH_SPLIT = 60 * 60 * 24 * 365 + + def arg_expiry(ctx, param, value): """ Accepts either a duration, or an absolute UNIX epoch time @@ -68,6 +71,7 @@ def arg_ethrpc(ctx, param, value): return EthJsonRpc(ip_addr, port, True) return EthJsonRpc(ip_addr, port) + def arg_lithium_api(ctx, param, value): if value is None: return None diff --git a/ion/htlc/common.py b/ion/htlc/common.py index 030d248..ed75d94 100644 --- a/ion/htlc/common.py +++ b/ion/htlc/common.py @@ -8,8 +8,6 @@ DEFAULT_EXPIRY_DURATION = 10 * ONE_MINUTE MINIMUM_EXPIRY_DURATION = 2 * ONE_MINUTE -DURATION_OR_EPOCH_SPLIT = ONE_YEAR - def make_htlc_proxy(rpc, contract, account): # TODO: embed 'abi/HTLC.abi' file in package resources? diff --git a/ion/htlc/coordclient.py b/ion/htlc/coordclient.py index 80db5e9..ef0e573 100644 --- a/ion/htlc/coordclient.py +++ b/ion/htlc/coordclient.py @@ -5,6 +5,7 @@ from hashlib import sha256 from ..utils import require +from ..ethrpc import EthJsonRpc from ..restclient import RestClient from .common import get_default_expiry, make_htlc_proxy @@ -115,11 +116,15 @@ def refund(self): my_address = self._coordapi.my_address exch_data = self._exch_obj.data + secret_hashed_hex = self._data['secret_hashed'] + secret_hashed = secret_hashed_hex.decode('hex') + # TODO: detertmine which side we're on, automagically call correct one if x: htlc_address = self._data['depositor'] else: htlc_address = exch_data['want_htlc_address'] + htlc_contract = make_htlc_proxy(ethrpc, htlc_address, my_address) htlc_contract.Refund(secret_hashed) @@ -213,6 +218,8 @@ def __init__(self, my_address, ethrpc, api_url=None, resource=None): self._my_address = my_address self._resource = RestClient(api_url) if resource is None else resource self._ethrpc = ethrpc + assert isinstance(self._resource, RestClient) + assert isinstance(ethrpc, EthJsonRpc) @property def ethrpc(self): diff --git a/ion/htlc/coordinator.py b/ion/htlc/coordinator.py index b9dc48e..2660270 100644 --- a/ion/htlc/coordinator.py +++ b/ion/htlc/coordinator.py @@ -5,6 +5,7 @@ import os import time +from ethereum.utils import scan_bin from flask import Flask, Blueprint, request, abort, jsonify from werkzeug.routing import BaseConverter @@ -44,18 +45,33 @@ def param_uint256(the_dict, key): return param_filter_arg(the_dict, key, arg_uint256) -class Bytes32Converter(BaseConverter): - """Accept 32 hex-encoded bytes as URL param""" +class BytesConverter(BaseConverter): + """ + Accepts hex encoded bytes as an argument + Provides raw bytes to Python + Marshals between raw bytes and hex encoded + """ + BYTES_LEN = None def __init__(self, url_map, *items): - super(Bytes32Converter, self).__init__(url_map) - self.regex = r'[a-fA-F0-9]{64}' + assert self.BYTES_LEN is not None + super(BytesConverter, self).__init__(url_map) + self.regex = '(0x)?[a-fA-F0-9]{' + str(self.BYTES_LEN * 2) + '}' + def to_python(self, value): + return scan_bin(value) -class Bytes20Converter(BaseConverter): + def to_url(self, value): + return value.encode('hex') + + +class Bytes32Converter(BytesConverter): + """Accept 32 hex-encoded bytes as URL param""" + BYTES_LEN = 32 + + +class Bytes20Converter(BytesConverter): """Accept 20 hex-encoded bytes as URL param""" - def __init__(self, url_map, *items): - super(Bytes20Converter, self).__init__(url_map) - self.regex = r'[a-fA-F0-9]{40}' + BYTES_LEN = 20 class CoordinatorBlueprint(Blueprint): @@ -63,13 +79,15 @@ class CoordinatorBlueprint(Blueprint): Provides a web API for coordinating cross-chain HTLC exchanges """ - def __init__(self, **kwa): + def __init__(self, htlc_address, **kwa): Blueprint.__init__(self, 'htlc', __name__, **kwa) + self._htlc_address = htlc_address self._exchanges = dict() - self.url_map.converters['bytes32'] = Bytes32Converter - self.url_map.converters['bytes20'] = Bytes20Converter + # XXX: This sure looks hacky... assignment not allowed in lambda, callback etc. + self.record(lambda s: s.app.url_map.converters.__setitem__('bytes32', Bytes32Converter)) + self.record(lambda s: s.app.url_map.converters.__setitem__('bytes20', Bytes20Converter)) self.add_url_rule("/", 'index', self.index) self.add_url_rule("/list", 'list', self.index) @@ -110,16 +128,13 @@ def exch_advertise(self): This is performed by Alice """ # Parse and validate input parameters - offer_address = param_bytes20(request.data, 'offer_address') + offer_address = param_bytes20(request.data, 'offer_address').encode('hex') offer_amount = param_uint256(request.data, 'offer_amount') want_amount = param_uint256(request.data, 'want_amount') - # TODO: offer_htlc_address - # TODO: want_htlc_address - # TODO: validate contract addresses etc. and verify on-chain stuff - exch_id = os.urandom(20).encode('hex') + exch_id = os.urandom(20) # Save exchange details # TODO: replace with class instance, `Exchange` @@ -129,10 +144,14 @@ def exch_advertise(self): want_amount=want_amount, proposals=dict(), chosen_proposal=None, + + # Temporary placeholders + offer_htlc_address=self._htlc_address, + want_htlc_address=self._htlc_address ) return jsonify(dict( - id=exch_id, + id=exch_id.encode('hex'), ok=1 )) @@ -227,9 +246,11 @@ def exch_finish(self, exch_id, secret_hashed): def main(): + htlc_address = scan_bin(sys.argv[1]) + print("HTLC address:", htlc_address.encode('hex')) # XXX: not suitable for 'production' # see: http://flask.pocoo.org/docs/1.0/deploying/#deployment - coordinator = CoordinatorBlueprint() + coordinator = CoordinatorBlueprint(htlc_address) app = Flask(__name__) app.register_blueprint(coordinator, url_prefix='/htlc') app.run() diff --git a/ion/restclient.py b/ion/restclient.py index e45e29e..6a68e48 100644 --- a/ion/restclient.py +++ b/ion/restclient.py @@ -17,47 +17,54 @@ class RestClient(object): - __slots__ = ('_api', '_url') + __slots__ = ('_api', '_url', '_session') def __init__(self, url, api=None): require(url is not None, "Must provide REST API HTTP URL") self._url = url + self._session = None + if api is None: + self._session = requests.Session() self._api = self if api is None else api def __getattr__(self, name): if name[0] == '_': raise AttributeError - return Resource(self._url + '/' + name, self._api) + return RestClient(self._url + '/' + name, self._api) def __call__(self, name=None): if name is None: return self if name[0] == '_': raise AttributeError - return Resource(self._url + '/' + quote_plus(name), self._api) + return RestClient(self._url + '/' + quote_plus(name), self._api) - def _do(self, method, kwargs): - resp = self._api._request(method=method, - url=self._url, - params=kwargs) + def _request(self, method, kwargs): + sess = self._api._session + req = requests.Request(method=method, + url=self._url, + params=kwargs) + resp = sess.send(req.prepare()) resp.raise_for_status() return resp.json() def GET(self, **kwargs): - return self._do('GET', kwargs) + return self._request('GET', kwargs) def POST(self, **kwargs): - return self._do('POST', kwargs) + return self._request('POST', kwargs) def PUT(self, **kwargs): - return self._do('PUT', kwargs) + return self._request('PUT', kwargs) def DELETE(self, id=None, **kwargs): url = self._url if id is not None: url += "/" + str(id) - resp = self._api._request(method='DELETE', - url=url, - data=kwargs) + sess = self._api._session + req = requests.Request(method='DELETE', + url=url, + data=kwargs) + resp = sess.send(req.prepare()) resp.raise_for_status() return resp.json() diff --git a/migrations/2_deploy_contracts.js b/migrations/2_deploy_contracts.js index 5317345..bb49d0b 100644 --- a/migrations/2_deploy_contracts.js +++ b/migrations/2_deploy_contracts.js @@ -1,6 +1,7 @@ const IonLink = artifacts.require("IonLink"); const IonLock = artifacts.require("IonLock"); const Token = artifacts.require("Token"); +const HTLC = artifacts.require("HTLC"); module.exports = async (deployer) => { try { @@ -9,6 +10,8 @@ module.exports = async (deployer) => { .then(() => deployer.deploy(Token)) .then(() => Token.deployed) .then(() => deployer.deploy(IonLock, Token.address, IonLink.address)) + .then(() => deployer.deploy(HTLC)) + .then(() => HTLC.deployed) } catch(err) { console.log('ERROR on deploy:',err); diff --git a/test_coordclient.py b/test_coordclient.py new file mode 100755 index 0000000..da850a3 --- /dev/null +++ b/test_coordclient.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python +## Copyright (c) 2018 Harry Roberts. +## SPDX-License-Identifier: LGPL-3.0+ + +import sys + +from ion.ethrpc import EthJsonRpc +from ion.htlc.coordclient import CoordinatorClient + + +def main(api_url='http://127.0.0.1:5000/htlc'): + ethrpc = EthJsonRpc('127.0.0.1', 8545) + accounts = ethrpc.eth_accounts() + assert len(accounts) > 1 + + addr_A = accounts[0] + addr_B = accounts[1] + + client_A = CoordinatorClient(addr_A, ethrpc, api_url) + client_B = CoordinatorClient(addr_B, ethrpc, api_url) + + balance_A = ethrpc.eth_getBalance(addr_A) + balance_B = ethrpc.eth_getBalance(addr_B) + + print("A list", client_A.list()) + client_A.advertise(1000, 500) + + print("B list", client_B.list()) + +if __name__ == "__main__": + main(*sys.argv[1:]) From fa5a09d860f911f7c7ce99f32103b703284500f7 Mon Sep 17 00:00:00 2001 From: user Date: Tue, 12 Jun 2018 21:08:46 +0100 Subject: [PATCH 15/49] Bugfixes to rest client / coordinator client --- ion/args.py | 5 ++-- ion/htlc/cli.py | 13 ++++++++ ion/htlc/coordclient.py | 5 ++-- ion/htlc/coordinator.py | 66 +++++++++++++++++++++++------------------ ion/restclient.py | 22 +++++--------- 5 files changed, 62 insertions(+), 49 deletions(-) diff --git a/ion/args.py b/ion/args.py index c759531..04a6ec8 100644 --- a/ion/args.py +++ b/ion/args.py @@ -52,8 +52,9 @@ def arg_uint_n(ctx, param, value): if value is None: return None value = int(value) - require(value >= 0) - require(value <= (1 << (num-1))) + value_max = 1 << (num - 1) + require(value >= 0, "Must be abover 0") + require(value <= value_max, "Must be below " + str(value_max)) return value return arg_uint_n diff --git a/ion/htlc/cli.py b/ion/htlc/cli.py index aca93f7..ec34078 100644 --- a/ion/htlc/cli.py +++ b/ion/htlc/cli.py @@ -70,6 +70,18 @@ def contract_multicommand(ctx, rpc, account, contract): contract_multicommand.add_command(contract_refund, "refund") +####################################################################### +# +# Multi-command entry-point +# + +@click.command() +@click.option('--contract', callback=arg_bytes20, metavar="0x...20", required=True, help="HTLC contract address") +def coordinator(contract): + from .coordinator import main + main(contract) + + ####################################################################### # # Multi-command entry-point @@ -77,6 +89,7 @@ def contract_multicommand(ctx, rpc, account, contract): COMMANDS = click.Group("htlc", help="Hash-Time-Lock Atomic Swap") COMMANDS.add_command(contract_multicommand, 'contract') +COMMANDS.add_command(coordinator, 'coordinator') if __name__ == "__main__": diff --git a/ion/htlc/coordclient.py b/ion/htlc/coordclient.py index ef0e573..7758c67 100644 --- a/ion/htlc/coordclient.py +++ b/ion/htlc/coordclient.py @@ -139,7 +139,7 @@ def __init__(self, coordapi, resource, data_dict=None): if data_dict is None: self.refresh() else: - self.data = data_dict + self.set_data(data_dict) def _make_proposal(self, secret_hashed_hex, propdata=None): return Proposal(self._coordapi, self, self._resource(secret_hashed_hex), propdata) @@ -148,14 +148,13 @@ def _make_proposal(self, secret_hashed_hex, propdata=None): def data(self): return self._data - @data.setter def set_data(self, value): value['proposals'] = [self._make_proposal(key, propdata) for key, propdata in value['proposals'].items()] self._data = value def refresh(self): - self.data = self._resource.GET() + self.set_data(self._resource.GET()) @property def proposals(self): diff --git a/ion/htlc/coordinator.py b/ion/htlc/coordinator.py index 2660270..93d13cc 100644 --- a/ion/htlc/coordinator.py +++ b/ion/htlc/coordinator.py @@ -16,7 +16,7 @@ def param(the_dict, key): if key not in the_dict: - return abort(400, "Parameter required: " + key) + return abort(400, description="Parameter required: " + key) return the_dict[key] @@ -27,18 +27,18 @@ def param_filter_arg(the_dict, key, filter_fn): """ value = param(the_dict, key) try: - filter_fn(None, None, value) + value = filter_fn(None, None, value) except Exception as ex: - return abort(400, "Invalid parameter %s - %s" % (key, str(ex))) + return abort(400, description="Invalid parameter %s - %s" % (key, str(ex))) return value def param_bytes32(the_dict, key): - return param_filter_arg(the_dict, key, arg_bytes32) + return param_filter_arg(the_dict, key, arg_bytes32).encode('hex') def param_bytes20(the_dict, key): - return param_filter_arg(the_dict, key, arg_bytes20) + return param_filter_arg(the_dict, key, arg_bytes20).encode('hex') def param_uint256(the_dict, key): @@ -58,10 +58,8 @@ def __init__(self, url_map, *items): self.regex = '(0x)?[a-fA-F0-9]{' + str(self.BYTES_LEN * 2) + '}' def to_python(self, value): - return scan_bin(value) - - def to_url(self, value): - return value.encode('hex') + # Normalise hex encoding... + return scan_bin(value).encode('hex') class Bytes32Converter(BytesConverter): @@ -104,14 +102,14 @@ def __init__(self, htlc_address, **kwa): def _get_exch(self, exch_id): if exch_id not in self._exchanges: - return abort(404) + return abort(404, description="Unknown exchange") return self._exchanges[exch_id] def _get_proposal(self, exch_id, secret_hashed): exch = self._get_exch(exch_id) proposal = exch['proposals'].get(secret_hashed) if not proposal: - return abort(404) + return abort(404, description="Unknown proposal") return exch, proposal def index(self): @@ -128,13 +126,13 @@ def exch_advertise(self): This is performed by Alice """ # Parse and validate input parameters - offer_address = param_bytes20(request.data, 'offer_address').encode('hex') - offer_amount = param_uint256(request.data, 'offer_amount') - want_amount = param_uint256(request.data, 'want_amount') + offer_address = param_bytes20(request.form, 'offer_address') + offer_amount = param_uint256(request.form, 'offer_amount') + want_amount = param_uint256(request.form, 'want_amount') # TODO: validate contract addresses etc. and verify on-chain stuff - exch_id = os.urandom(20) + exch_id = os.urandom(20).encode('hex') # Save exchange details # TODO: replace with class instance, `Exchange` @@ -146,12 +144,12 @@ def exch_advertise(self): chosen_proposal=None, # Temporary placeholders - offer_htlc_address=self._htlc_address, - want_htlc_address=self._htlc_address + offer_htlc_address=self._htlc_address.encode('hex'), + want_htlc_address=self._htlc_address.encode('hex') ) return jsonify(dict( - id=exch_id.encode('hex'), + id=exch_id, ok=1 )) @@ -160,6 +158,7 @@ def exch_get(self, exch_id): Retrieve details of exchange """ exch = self._get_exch(exch_id) + print("Exch is", exch) return jsonify(exch) def exch_propose(self, exch_id, secret_hashed): @@ -174,12 +173,12 @@ def exch_propose(self, exch_id, secret_hashed): # Hashed secret is the 'image', pre-image can be supplied to prove knowledge of secret if secret_hashed in exch['proposals']: - return abort(409) # Duplicate proposal secret + return abort(409, description="Duplicate proposal secret") # TODO: verify either side of the exchange aren't the same - expiry = param_uint256(request.data, 'expiry') - depositor = param_bytes20(request.data, 'depositor') + expiry = param_uint256(request.form, 'expiry') + depositor = param_bytes20(request.form, 'depositor') # TODO: verify details on-chain, expiry, depositor and secret must match @@ -188,7 +187,7 @@ def exch_propose(self, exch_id, secret_hashed): now = int(time.time()) min_expiry = now + MINIMUM_EXPIRY_DURATION if expiry < min_expiry: - return abort(400) # TODO: add descriptive error message + return abort(400, description="Expiry too short") # Store proposal exch['proposals'][secret_hashed] = dict( @@ -227,7 +226,7 @@ def exch_release(self, exch_id, secret_hashed): This is performed by Bob """ exch, proposal = self._get_proposal(exch_id, secret_hashed) - secret = param_bytes32(request.data, 'secret') + secret = param_bytes32(request.form, 'secret') # TODO: verify secret matches secret_hashed def exch_finish(self, exch_id, secret_hashed): @@ -245,17 +244,26 @@ def exch_finish(self, exch_id, secret_hashed): # and update the status / information automagically -def main(): - htlc_address = scan_bin(sys.argv[1]) +def main(htlc_address): + """ + Development server for coordinator + + NOTE: not suitable for 'production' + SEE: http://flask.pocoo.org/docs/1.0/deploying/#deployment + """ + if len(htlc_address) != 20: + htlc_address = scan_bin(htlc_address) print("HTLC address:", htlc_address.encode('hex')) - # XXX: not suitable for 'production' - # see: http://flask.pocoo.org/docs/1.0/deploying/#deployment + coordinator = CoordinatorBlueprint(htlc_address) app = Flask(__name__) + # app.debug = 1 app.register_blueprint(coordinator, url_prefix='/htlc') - app.run() + + # NOTE: Flask reloader is DAF, doesn't work well with packages *shakes-fists* + app.run(use_reloader=False) return 0 if __name__ == "__main__": - sys.exit(main()) + sys.exit(main(sys.argv[1])) diff --git a/ion/restclient.py b/ion/restclient.py index 6a68e48..6eae885 100644 --- a/ion/restclient.py +++ b/ion/restclient.py @@ -39,32 +39,24 @@ def __call__(self, name=None): raise AttributeError return RestClient(self._url + '/' + quote_plus(name), self._api) - def _request(self, method, kwargs): + def _request(self, method, params=None, data=None): sess = self._api._session req = requests.Request(method=method, url=self._url, - params=kwargs) + params=params, + data=data) resp = sess.send(req.prepare()) resp.raise_for_status() return resp.json() def GET(self, **kwargs): - return self._request('GET', kwargs) + return self._request('GET', params=kwargs) def POST(self, **kwargs): - return self._request('POST', kwargs) + return self._request('POST', data=kwargs) def PUT(self, **kwargs): - return self._request('PUT', kwargs) + return self._request('PUT', data=kwargs) def DELETE(self, id=None, **kwargs): - url = self._url - if id is not None: - url += "/" + str(id) - sess = self._api._session - req = requests.Request(method='DELETE', - url=url, - data=kwargs) - resp = sess.send(req.prepare()) - resp.raise_for_status() - return resp.json() + return self._request('DELETE', data=kwargs) From f448f95b89929cf0d91863c6f267f096dde593c4 Mon Sep 17 00:00:00 2001 From: user Date: Wed, 13 Jun 2018 00:25:39 +0100 Subject: [PATCH 16/49] Additional documentation --- ion/__main__.py | 10 +++++----- ion/args.py | 2 ++ ion/htlc/cli.py | 13 +++++++------ ion/htlc/common.py | 13 ++++++++++++- ion/htlc/coordinator.py | 9 ++++++--- ion/restclient.py | 24 ++++++++++++++++++++++-- test_coordclient.py | 1 + 7 files changed, 55 insertions(+), 17 deletions(-) diff --git a/ion/__main__.py b/ion/__main__.py index ba5241b..f0ceca8 100644 --- a/ion/__main__.py +++ b/ion/__main__.py @@ -4,10 +4,10 @@ from .lithium.lithium import etheventrelay as lithium from .htlc.cli import COMMANDS as HTLC_COMMANDS -commands = click.Group('commands') -commands.add_command(ion_commands, "ion") -commands.add_command(lithium, "lithium") -commands.add_command(HTLC_COMMANDS, "htlc") +COMMANDS = click.Group('commands') +COMMANDS.add_command(ion_commands, "ion") +COMMANDS.add_command(lithium, "lithium") +COMMANDS.add_command(HTLC_COMMANDS, "htlc") if __name__ == "__main__": - commands.main() + COMMANDS.main() diff --git a/ion/args.py b/ion/args.py index 04a6ec8..316de3b 100644 --- a/ion/args.py +++ b/ion/args.py @@ -33,6 +33,7 @@ def arg_bytes(ctx, param, value): value = scan_bin(value) return value + def make_bytes_n(num_bytes): def arg_bytes_n(ctx, param, value): value = arg_bytes(ctx, param, value) @@ -44,6 +45,7 @@ def arg_bytes_n(ctx, param, value): arg_bytes20 = make_bytes_n(20) + arg_bytes32 = make_bytes_n(32) diff --git a/ion/htlc/cli.py b/ion/htlc/cli.py index ec34078..ecf2642 100644 --- a/ion/htlc/cli.py +++ b/ion/htlc/cli.py @@ -1,18 +1,16 @@ # Copyright (c) 2018 Harry Roberts. All Rights Reserved. # SPDX-License-Identifier: LGPL-3.0+ + from __future__ import print_function import time from hashlib import sha256 import click -from ..args import arg_ethrpc, arg_bytes20, arg_bytes32, arg_expiry +from ..args import arg_ethrpc, arg_bytes20, arg_bytes32, arg_expiry, arg_uint256 from .common import get_random_secret_32, get_default_expiry, make_htlc_proxy -# TODO: move to ..args - - ####################################################################### # @@ -31,13 +29,16 @@ @click.pass_obj @click.option('--receiver', callback=arg_bytes20, metavar="0x...20", required=True, help="Receiver address") @click.option('--secret', callback=arg_bytes32, metavar="0x...32", default=get_random_secret_32, help="Secret to be supplied upon withdraw") +@click.option('--amount', calback=arg_uint256, metavar='wei', help='Amount of WEI to deposit') @click.option('--expires', metavar="seconds|unixtime", callback=arg_expiry, type=int, default=get_default_expiry, help="Expiry time, as duration (seconds), or UNIX epoch") -def contract_deposit(contract, receiver, secret, expires): +def contract_deposit(contract, receiver, secret, amount, expires): now = int(time.time()) print("Expires in", expires - now, "seconds") + # TODO: verify balance for account is above or equal to `amount` + image = sha256(secret).digest() # the hash pre-image is the 'secret' - contract.Deposit(receiver, image, expires) + contract.Deposit(receiver, image, expires, value=amount) @click.command() diff --git a/ion/htlc/common.py b/ion/htlc/common.py index ed75d94..18f59fd 100644 --- a/ion/htlc/common.py +++ b/ion/htlc/common.py @@ -1,3 +1,6 @@ +# Copyright (c) 2018 Harry Roberts. All Rights Reserved. +# SPDX-License-Identifier: LGPL-3.0+ + import os import time @@ -10,13 +13,21 @@ MINIMUM_EXPIRY_DURATION = 2 * ONE_MINUTE def make_htlc_proxy(rpc, contract, account): - # TODO: embed 'abi/HTLC.abi' file in package resources? + """ + TODO: embed 'abi/HTLC.abi' file in package resources? + """ return rpc.proxy('abi/HTLC.abi', contract, account) def get_default_expiry(): + """ + Default expiry, from now, that a deal should expire + """ return int(time.time()) + DEFAULT_EXPIRY_DURATION def get_random_secret_32(): + """ + Random secret, to be used as the image (revealed later) + """ return '0x' + os.urandom(32).encode('hex') diff --git a/ion/htlc/coordinator.py b/ion/htlc/coordinator.py index 93d13cc..470cbf5 100644 --- a/ion/htlc/coordinator.py +++ b/ion/htlc/coordinator.py @@ -10,7 +10,6 @@ from werkzeug.routing import BaseConverter from ..args import arg_bytes32, arg_bytes20, arg_uint256 - from .common import MINIMUM_EXPIRY_DURATION @@ -135,7 +134,7 @@ def exch_advertise(self): exch_id = os.urandom(20).encode('hex') # Save exchange details - # TODO: replace with class instance, `Exchange` + # TODO: replace with class instance, `Exchange` ? self._exchanges[exch_id] = dict( offer_address=offer_address, offer_amount=offer_amount, @@ -144,6 +143,7 @@ def exch_advertise(self): chosen_proposal=None, # Temporary placeholders + # TODO: replace with correct contracts offer_htlc_address=self._htlc_address.encode('hex'), want_htlc_address=self._htlc_address.encode('hex') ) @@ -158,7 +158,6 @@ def exch_get(self, exch_id): Retrieve details of exchange """ exch = self._get_exch(exch_id) - print("Exch is", exch) return jsonify(exch) def exch_propose(self, exch_id, secret_hashed): @@ -171,6 +170,9 @@ def exch_propose(self, exch_id, secret_hashed): """ exch = self._get_exch(exch_id) + if exch['chosen_proposal']: + return abort(409, description="Proposal has already been chosen") + # Hashed secret is the 'image', pre-image can be supplied to prove knowledge of secret if secret_hashed in exch['proposals']: return abort(409, description="Duplicate proposal secret") @@ -195,6 +197,7 @@ def exch_propose(self, exch_id, secret_hashed): expiry=expiry, depositor=depositor ) + exch['chosen_proposal'] = secret_hashed return jsonify(dict( ok=1 diff --git a/ion/restclient.py b/ion/restclient.py index 6eae885..23a96df 100644 --- a/ion/restclient.py +++ b/ion/restclient.py @@ -1,8 +1,26 @@ # Copyright (c) 2018 Harry Roberts. All Rights Reserved. # SPDX-License-Identifier: LGPL-3.0+ -# Derived from https://gist.githubusercontent.com/HarryR/d2373f421c39353cd462/raw/c3f726455bbbc03fe791dda0dabbf4f73b5d2ec9/restclient.py -# Except the command-line interface has been removed (so it doesn't depend on Plugin and Host components) +""" +Derived from https://gist.githubusercontent.com/HarryR/d2373f421c39353cd462/raw/c3f726455bbbc03fe791dda0dabbf4f73b5d2ec9/restclient.py + +Except the command-line interface has been removed (so it doesn't depend on +Plugin and Host components). I didn't realise how broken the original Gist was, +this is less broken and much cleaner.... + +This class provides a friendly Pythonic interface to a REST-ish HTTP JSON API. +Form parameters are posted as normal form encoded data, Response is returned as +JSON. + +e.g. + + x = RestClient('http://example.com/') + x('test').POST(abc=123) # POST /test abc=123 + x.test.derp.GET() # GET /test/derp + x.test() # GET /test + +see... it's nice, and predictable, and Pythonic, and flexible, etc... +""" __all__ = ('RestClient',) @@ -26,6 +44,7 @@ def __init__(self, url, api=None): if api is None: self._session = requests.Session() self._api = self if api is None else api + require(isinstance(self._api, RestClient)) def __getattr__(self, name): if name[0] == '_': @@ -46,6 +65,7 @@ def _request(self, method, params=None, data=None): params=params, data=data) resp = sess.send(req.prepare()) + # TODO: verify if 'error' is in result, raise with error message... resp.raise_for_status() return resp.json() diff --git a/test_coordclient.py b/test_coordclient.py index da850a3..bdaa345 100755 --- a/test_coordclient.py +++ b/test_coordclient.py @@ -27,5 +27,6 @@ def main(api_url='http://127.0.0.1:5000/htlc'): print("B list", client_B.list()) + if __name__ == "__main__": main(*sys.argv[1:]) From cd6e6acaab1105489c35428efcb912c3781cbf77 Mon Sep 17 00:00:00 2001 From: user Date: Wed, 13 Jun 2018 19:56:39 +0100 Subject: [PATCH 17/49] Added error handling to Coordinator and RestClient, tests run further... --- ion/ethrpc.py | 40 ++++++++++++++++++++++++++++------------ ion/htlc/cli.py | 2 +- ion/htlc/common.py | 12 ++++++------ ion/htlc/coordclient.py | 7 +++++-- ion/htlc/coordinator.py | 40 ++++++++++++++++++++++++++++++---------- ion/restclient.py | 10 +++++++--- ion/utils.py | 15 ++++++++++++++- test_coordclient.py | 13 +++++++++++-- 8 files changed, 102 insertions(+), 37 deletions(-) diff --git a/ion/ethrpc.py b/ion/ethrpc.py index ce3cb95..f539943 100644 --- a/ion/ethrpc.py +++ b/ion/ethrpc.py @@ -1,9 +1,28 @@ """ -For licensing information, see: - -https://github.com/ConsenSys/ethjsonrpc/blob/master/LICENSE - -This file contains the public domain interface definition +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to """ import json @@ -16,7 +35,7 @@ from requests.exceptions import ConnectionError as RequestsConnectionError from .crypto import keccak_256 -from .utils import require, big_endian_to_int, zpad, encode_int +from .utils import require, big_endian_to_int, zpad, encode_int, normalise_address GETH_DEFAULT_RPC_PORT = 8545 ETH_DEFAULT_RPC_PORT = 8545 @@ -199,13 +218,10 @@ def proxy(self, abi, address, account=None): callable methods, allowing for seamless use of contracts from Python... """ # XXX: specific to Ethereum addresses, 20 octets - if len(address) == 20: - address = address.encode('hex') - require(len(address) == 40) + address = normalise_address(address) + if account is not None: - if len(account) == 20: - account = account.encode('hex') - require(len(account) == 40) + account = normalise_address(account) if isinstance(abi, file): abi = json.load(abi) diff --git a/ion/htlc/cli.py b/ion/htlc/cli.py index ecf2642..4655555 100644 --- a/ion/htlc/cli.py +++ b/ion/htlc/cli.py @@ -29,7 +29,7 @@ @click.pass_obj @click.option('--receiver', callback=arg_bytes20, metavar="0x...20", required=True, help="Receiver address") @click.option('--secret', callback=arg_bytes32, metavar="0x...32", default=get_random_secret_32, help="Secret to be supplied upon withdraw") -@click.option('--amount', calback=arg_uint256, metavar='wei', help='Amount of WEI to deposit') +@click.option('--amount', callback=arg_uint256, metavar='wei', help='Amount of WEI to deposit') @click.option('--expires', metavar="seconds|unixtime", callback=arg_expiry, type=int, default=get_default_expiry, help="Expiry time, as duration (seconds), or UNIX epoch") def contract_deposit(contract, receiver, secret, amount, expires): now = int(time.time()) diff --git a/ion/htlc/common.py b/ion/htlc/common.py index 18f59fd..05d93de 100644 --- a/ion/htlc/common.py +++ b/ion/htlc/common.py @@ -20,14 +20,14 @@ def make_htlc_proxy(rpc, contract, account): def get_default_expiry(): - """ - Default expiry, from now, that a deal should expire - """ + """ + Default expiry, from now, that a deal should expire + """ return int(time.time()) + DEFAULT_EXPIRY_DURATION def get_random_secret_32(): - """ - Random secret, to be used as the image (revealed later) - """ + """ + Random secret, to be used as the image (revealed later) + """ return '0x' + os.urandom(32).encode('hex') diff --git a/ion/htlc/coordclient.py b/ion/htlc/coordclient.py index 7758c67..706a597 100644 --- a/ion/htlc/coordclient.py +++ b/ion/htlc/coordclient.py @@ -179,12 +179,15 @@ def propose(self): ethrpc = self._coordapi.ethrpc my_address = self._coordapi.my_address + # TODO: verify adequate balance to cover the deposit + # XXX: for testing, we can be both sides... #require(my_address != prop_receiver, "Cannot be both sides of exchange") htlc_address = self._data['want_htlc_address'] htlc_contract = make_htlc_proxy(ethrpc, htlc_address, my_address) - htlc_contract.Deposit(prop_receiver, secret_hashed, prop_expiry, value=prop_value) + result = htlc_contract.Deposit(prop_receiver, secret_hashed, prop_expiry, value=prop_value) + print("Deposit result is", result) # TODO: wait for deposit to go through? # or provide some kind of receipt... @@ -195,7 +198,7 @@ def propose(self): proposal_resource = self._resource(secret_hashed_hex) response = proposal_resource.POST( expiry=prop_expiry, - depositor=my_address.encode('hex'), + depositor=my_address, # TODO: add transaction receipt ) require(response['ok'] == 1, "Proposal coordinator API error") diff --git a/ion/htlc/coordinator.py b/ion/htlc/coordinator.py index 470cbf5..66a6292 100644 --- a/ion/htlc/coordinator.py +++ b/ion/htlc/coordinator.py @@ -3,19 +3,25 @@ import sys import os +import json import time -from ethereum.utils import scan_bin -from flask import Flask, Blueprint, request, abort, jsonify +from flask import Flask, Blueprint, request, abort, jsonify, make_response from werkzeug.routing import BaseConverter from ..args import arg_bytes32, arg_bytes20, arg_uint256 +from ..utils import scan_bin + from .common import MINIMUM_EXPIRY_DURATION +def api_abort(message, code=400): + return abort(make_response(jsonify(dict(_error=message)), code)) + + def param(the_dict, key): if key not in the_dict: - return abort(400, description="Parameter required: " + key) + return api_abort("Parameter required: " + key) return the_dict[key] @@ -28,7 +34,7 @@ def param_filter_arg(the_dict, key, filter_fn): try: value = filter_fn(None, None, value) except Exception as ex: - return abort(400, description="Invalid parameter %s - %s" % (key, str(ex))) + return api_abort("Invalid parameter '%s' - %s" % (key, str(ex))) return value @@ -101,14 +107,14 @@ def __init__(self, htlc_address, **kwa): def _get_exch(self, exch_id): if exch_id not in self._exchanges: - return abort(404, description="Unknown exchange") + return api_abort("Unknown exchange", code=404) return self._exchanges[exch_id] def _get_proposal(self, exch_id, secret_hashed): exch = self._get_exch(exch_id) proposal = exch['proposals'].get(secret_hashed) if not proposal: - return abort(404, description="Unknown proposal") + return api_abort("Unknown proposal", code=404) return exch, proposal def index(self): @@ -171,11 +177,11 @@ def exch_propose(self, exch_id, secret_hashed): exch = self._get_exch(exch_id) if exch['chosen_proposal']: - return abort(409, description="Proposal has already been chosen") + return api_abort("Proposal has already been chosen", code=409) # Hashed secret is the 'image', pre-image can be supplied to prove knowledge of secret if secret_hashed in exch['proposals']: - return abort(409, description="Duplicate proposal secret") + return api_abort("Duplicate proposal secret", code=409) # TODO: verify either side of the exchange aren't the same @@ -189,7 +195,7 @@ def exch_propose(self, exch_id, secret_hashed): now = int(time.time()) min_expiry = now + MINIMUM_EXPIRY_DURATION if expiry < min_expiry: - return abort(400, description="Expiry too short") + return api_abort("Expiry too short") # Store proposal exch['proposals'][secret_hashed] = dict( @@ -197,7 +203,6 @@ def exch_propose(self, exch_id, secret_hashed): expiry=expiry, depositor=depositor ) - exch['chosen_proposal'] = secret_hashed return jsonify(dict( ok=1 @@ -219,9 +224,16 @@ def exch_confirm(self, exch_id, secret_hashed): This is performed by Alice """ exch, proposal = self._get_proposal(exch_id, secret_hashed) + # XXX: one side of the expiry must be twice as long as the other to handle failure case # TODO: verify on-chain details match the proposal + exch['chosen_proposal'] = secret_hashed + + return jsonify(dict( + ok=1 + )) + def exch_release(self, exch_id, secret_hashed): """ Bob releases the secret to withdraw funds that Alice deposited. @@ -232,6 +244,10 @@ def exch_release(self, exch_id, secret_hashed): secret = param_bytes32(request.form, 'secret') # TODO: verify secret matches secret_hashed + return jsonify(dict( + ok=1 + )) + def exch_finish(self, exch_id, secret_hashed): """ Alice uses the secret revealed by Bob to withdraw the funds he deposited. @@ -246,6 +262,10 @@ def exch_finish(self, exch_id, secret_hashed): # the API should monitor the state of both sides of the exchange # and update the status / information automagically + return jsonify(dict( + ok=1 + )) + def main(htlc_address): """ diff --git a/ion/restclient.py b/ion/restclient.py index 23a96df..bfe8eff 100644 --- a/ion/restclient.py +++ b/ion/restclient.py @@ -65,9 +65,13 @@ def _request(self, method, params=None, data=None): params=params, data=data) resp = sess.send(req.prepare()) - # TODO: verify if 'error' is in result, raise with error message... - resp.raise_for_status() - return resp.json() + try: + data = resp.json() + if '_error' in data: + raise RuntimeError(data['error']) + return data + except ValueError as ex: + resp.raise_for_status() def GET(self, **kwargs): return self._request('GET', params=kwargs) diff --git a/ion/utils.py b/ion/utils.py index f572430..ba5cd91 100644 --- a/ion/utils.py +++ b/ion/utils.py @@ -1,3 +1,7 @@ +## Copyright (c) 2016-2018 Clearmatics Technologies Ltd +## Copyright (c) 2018 Harry Roberts. +## SPDX-License-Identifier: LGPL-3.0+ + import sys from base64 import b64encode, b64decode import binascii @@ -55,7 +59,8 @@ def big_endian_to_int(x): return big_endian_int.deserialize( dict_dump = lambda diff: {c: dict(d.items()) for c, d in diff.items()} -def is_numeric(x): return isinstance(x, (int, long)) +def is_numeric(x): + return isinstance(x, (int, long)) def encode_int(v): @@ -76,6 +81,14 @@ def require(arg, msg=None): if not arg: raise RuntimeError(msg or "Requirement failed") +def normalise_address(addr): + if len(addr) == 20: + addr = addr.encode('hex') + if addr[:2] == '0x': + addr = addr[2:] + require(len(addr) == 40, "Invalid address: " + addr) + return addr + class Marshalled(object): def tojson(self): diff --git a/test_coordclient.py b/test_coordclient.py index bdaa345..4fc52d2 100755 --- a/test_coordclient.py +++ b/test_coordclient.py @@ -22,10 +22,19 @@ def main(api_url='http://127.0.0.1:5000/htlc'): balance_A = ethrpc.eth_getBalance(addr_A) balance_B = ethrpc.eth_getBalance(addr_B) - print("A list", client_A.list()) + # A advertises exchange, offers 1000 for 500 client_A.advertise(1000, 500) - print("B list", client_B.list()) + # B retrieves exchanges + exchanges_B = client_B.list() + print("B list", exchanges_B) + + exch = exchanges_B[0] + exch_data = exch.data + print("Data", exch.data) + + proposal = exch.propose() + print("Proposal is", proposal) if __name__ == "__main__": From a438a9a930bee780ab1cacc30c6943604b3510be Mon Sep 17 00:00:00 2001 From: user Date: Thu, 14 Jun 2018 04:00:01 +0100 Subject: [PATCH 18/49] Temporarily broke HTLC tests by refactoring HTLC contract, but now both sides can use the same contract on the same chain (for testing) --- .solhint.json | 7 ++ Makefile | 8 +- abi/HTLC.abi | 2 +- contracts/HTLC.sol | 160 +++++++++++++++++++--------------------- ion/htlc/coordclient.py | 42 +++++++++-- ion/htlc/coordinator.py | 26 ++++++- ion/restclient.py | 7 +- test_coordclient.py | 53 ++++++++++--- 8 files changed, 193 insertions(+), 112 deletions(-) create mode 100644 .solhint.json diff --git a/.solhint.json b/.solhint.json new file mode 100644 index 0000000..8fad2f8 --- /dev/null +++ b/.solhint.json @@ -0,0 +1,7 @@ +{ + "extends": "default", + "rules": { + "func-name-mixedcase": false, + "not-rely-on-time": false + } +} \ No newline at end of file diff --git a/Makefile b/Makefile index 2282d27..b338144 100644 --- a/Makefile +++ b/Makefile @@ -14,6 +14,9 @@ CONTRACTS=IonLock IonLink ERC223 Token HTLC CONTRACTS_BIN=$(addprefix build/,$(addsuffix .bin,$(CONTRACTS))) CONTRACTS_ABI=$(addprefix abi/,$(addsuffix .abi,$(CONTRACTS))) +PYLINT_IGNORE=C0330,invalid-name,line-too-long,missing-docstring,bad-whitespace,consider-using-ternary,wrong-import-position,wrong-import-order,trailing-whitespace + + all: check-prereqs contracts python-pyflakes test python-pylint check-prereqs: @@ -57,7 +60,7 @@ python-pyflakes: $(PYTHON) -mpyflakes ion python-pylint: - $(PYTHON) -mpylint ion + $(PYTHON) -mpylint -d $(PYLINT_IGNORE) ion python-lint: python-pyflakes python-pylint @@ -144,3 +147,6 @@ test: test-unit test-js truffle-deploy: $(TRUFFLE) deploy + +truffle-console: + $(TRUFFLE) console \ No newline at end of file diff --git a/abi/HTLC.abi b/abi/HTLC.abi index bb2c328..87a1e40 100644 --- a/abi/HTLC.abi +++ b/abi/HTLC.abi @@ -1 +1 @@ -[{"constant":false,"inputs":[{"name":"in_receiver","type":"address"},{"name":"in_image","type":"bytes32"},{"name":"in_expiry","type":"uint256"}],"name":"Deposit","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"in_image","type":"bytes32"}],"name":"Refund","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"in_image","type":"bytes32"},{"name":"in_preimage","type":"bytes32"}],"name":"Withdraw","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"in_image","type":"bytes32"}],"name":"GetState","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"exchanges","outputs":[{"name":"sender","type":"address"},{"name":"receiver","type":"address"},{"name":"amount","type":"uint256"},{"name":"expiry","type":"uint256"},{"name":"state","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"receiver","type":"address"},{"indexed":false,"name":"image","type":"bytes32"},{"indexed":false,"name":"expiry","type":"uint256"}],"name":"OnDeposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"image","type":"bytes32"}],"name":"OnRefund","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"image","type":"bytes32"},{"indexed":false,"name":"preimage","type":"bytes32"}],"name":"OnWithdraw","type":"event"}] \ No newline at end of file +[{"constant":false,"inputs":[{"name":"inReceiver","type":"address"},{"name":"inSecretHashed","type":"bytes32"},{"name":"inExpiry","type":"uint256"}],"name":"Deposit","outputs":[{"name":"","type":"bytes32"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"inImage","type":"bytes32"}],"name":"Refund","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"inExchGUID","type":"bytes32"},{"name":"inSecret","type":"bytes32"}],"name":"Withdraw","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"inExchGUID","type":"bytes32"}],"name":"GetState","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"exchanges","outputs":[{"name":"secretHashed","type":"bytes32"},{"name":"sender","type":"address"},{"name":"receiver","type":"address"},{"name":"amount","type":"uint256"},{"name":"expiry","type":"uint256"},{"name":"state","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"exchGUID","type":"bytes32"},{"indexed":true,"name":"receiver","type":"address"},{"indexed":false,"name":"secretHashed","type":"bytes32"},{"indexed":false,"name":"expiry","type":"uint256"}],"name":"OnDeposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"exchGUID","type":"bytes32"}],"name":"OnRefund","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"exchGUID","type":"bytes32"},{"indexed":false,"name":"secret","type":"bytes32"}],"name":"OnWithdraw","type":"event"}] \ No newline at end of file diff --git a/contracts/HTLC.sol b/contracts/HTLC.sol index 7151602..6c852d9 100644 --- a/contracts/HTLC.sol +++ b/contracts/HTLC.sol @@ -1,123 +1,113 @@ // Copyright (c) 2018 Harry Roberts. All Rights Reserved. // SPDX-License-Identifier: LGPL-3.0+ -pragma solidity ^0.4.23; +pragma solidity 0.4.24; -contract HTLC -{ - event OnDeposit( address indexed receiver, bytes32 image, uint256 expiry ); - event OnRefund( bytes32 indexed image ); +contract HTLC { - event OnWithdraw( bytes32 indexed image, bytes32 preimage ); + event OnDeposit( bytes32 exchGUID, address indexed receiver, bytes32 secretHashed, uint256 expiry ); - enum ExchangeState - { - Invalid, // Default state, invalid - Deposited, - Withdrawn, - Refunded, - Expired - } + event OnRefund( bytes32 indexed exchGUID ); - struct Exchange - { - address sender; - address receiver; - uint256 amount; - uint256 expiry; - ExchangeState state; - } + event OnWithdraw( bytes32 indexed exchGUID, bytes32 secret ); - mapping (bytes32 => Exchange) public exchanges; + enum ExchangeState { + Invalid, // Default state, invalid + Deposited, + Withdrawn, + Refunded, + Expired + } + struct Exchange { + bytes32 secretHashed; + address sender; + address receiver; + uint256 amount; + uint256 expiry; + ExchangeState state; + } - function GetState ( bytes32 in_image ) - public view returns (ExchangeState) - { - Exchange storage exch = exchanges[in_image]; + mapping (bytes32 => Exchange) public exchanges; - if( exch.state == ExchangeState.Invalid ) + function GetState ( bytes32 inExchGUID ) + public view returns (ExchangeState) { - return ExchangeState.Invalid; - } + Exchange storage exch = exchanges[inExchGUID]; - if( exch.expiry < block.timestamp ) - { - return ExchangeState.Expired; - } + if (exch.state == ExchangeState.Invalid) { + return ExchangeState.Invalid; + } - return exch.state; - } + if (exch.expiry < block.timestamp) { + return ExchangeState.Expired; + } + return exch.state; + } - function Deposit ( address in_receiver, bytes32 in_image, uint256 in_expiry ) - public payable - { - require( exchanges[in_image].state == ExchangeState.Invalid, - "Duplicate exchange" ); + function Deposit ( address inReceiver, bytes32 inSecretHashed, uint256 inExpiry ) + public payable returns (bytes32) + { + // GUID must be predictable + bytes32 exchGUID = sha256(abi.encodePacked(inReceiver, inSecretHashed)); - require( in_receiver != address(0x0), - "Invalid receiver address" ); + require(exchanges[exchGUID].state == ExchangeState.Invalid, "Duplicate exchange"); - require( in_expiry > block.timestamp, - "Expiry not in future" ); + require(inReceiver != address(0x0), "Invalid receiver address"); - exchanges[in_image] = Exchange( - msg.sender, - in_receiver, - msg.value, - in_expiry, - ExchangeState.Deposited - ); + require(inExpiry > block.timestamp, "Expiry not in future"); - emit OnDeposit( in_receiver, in_image, in_expiry ); - } + exchanges[exchGUID] = Exchange( + inSecretHashed, + msg.sender, + inReceiver, + msg.value, + inExpiry, + ExchangeState.Deposited + ); + emit OnDeposit( exchGUID, inReceiver, inSecretHashed, inExpiry); - function Withdraw ( bytes32 in_image, bytes32 in_preimage ) - public - { - Exchange storage exch = exchanges[in_image]; + return exchGUID; + } - require( exch.state == ExchangeState.Deposited, - "Unknown exchange, or invalid state" ); + function Withdraw ( bytes32 inExchGUID, bytes32 inSecret ) + public + { + Exchange storage exch = exchanges[inExchGUID]; - require( exch.receiver == msg.sender, - "Only receiver can Withdraw" ); + require(exch.state == ExchangeState.Deposited, "Unknown exchange, or invalid state"); - require( block.timestamp <= exch.expiry, - "Exchange expired" ); + require(exch.receiver == msg.sender, "Only receiver can Withdraw"); - require( sha256(abi.encodePacked(in_preimage)) == in_image, - "Bad preimage" ); + require(block.timestamp <= exch.expiry, "Exchange expired"); - exch.state = ExchangeState.Withdrawn; + require(sha256(abi.encodePacked(inSecret)) == exch.secretHashed, "Bad secret"); - msg.sender.transfer( exch.amount ); + exch.state = ExchangeState.Withdrawn; - emit OnWithdraw( in_image, in_preimage ); - } + msg.sender.transfer(exch.amount); + emit OnWithdraw(inExchGUID, inSecret); + } - function Refund ( bytes32 in_image ) - public - { - Exchange storage exch = exchanges[in_image]; + function Refund ( bytes32 inExchGUID ) + public + { + Exchange storage exch = exchanges[inExchGUID]; - require( exch.sender == msg.sender, - "Only depositor can refund" ); + require(exch.sender == msg.sender, "Only depositor can refund"); - require( block.timestamp > exch.expiry, - "Exchange not expired, cannot refund" ); + require(block.timestamp > exch.expiry, "Exchange not expired, cannot refund"); - require( exch.state == ExchangeState.Deposited, - "Unknown exchange, or invalid state" ); + require(exch.state == ExchangeState.Deposited, "Unknown exchange, or invalid state"); - exch.state = ExchangeState.Refunded; + exch.state = ExchangeState.Refunded; - exch.sender.transfer( exch.amount ); + exch.sender.transfer(exch.amount); - emit OnRefund( in_image ); - } -} \ No newline at end of file + emit OnRefund(inExchGUID); + } +} diff --git a/ion/htlc/coordclient.py b/ion/htlc/coordclient.py index 706a597..d92a295 100644 --- a/ion/htlc/coordclient.py +++ b/ion/htlc/coordclient.py @@ -4,7 +4,7 @@ import os from hashlib import sha256 -from ..utils import require +from ..utils import require, normalise_address from ..ethrpc import EthJsonRpc from ..restclient import RestClient @@ -26,6 +26,18 @@ def __init__(self, coordapi, exch_obj, resource, data=None): def refresh(self): self._data = self._resource.GET() + @property + def data(self): + return self._data + + @property + def secret(self): + return self._data.get('secret') + + @property + def secret_hashed(self): + return self._data['secret_hashed'] + def confirm(self): """ Confirm the exchange by depositing your side of the deal @@ -36,7 +48,8 @@ def confirm(self): my_address = self._coordapi.my_address exch_data = self._exch_obj.data - require(my_address == exch_data['offer_address'], "Only offerer can confirm") + require(my_address == exch_data['offer_address'], + ' '.join(["Only offerer can confirm", my_address, '!=', exch_data['offer_address']])) # XXX: one side of the expiry must be twice as long as the other to handle failure case conf_expiry = self._data['expiry'] @@ -46,7 +59,7 @@ def confirm(self): # Offerer deposits their side of the deal, locked to same hashed secret htlc_address = exch_data['offer_htlc_address'] htlc_contract = make_htlc_proxy(ethrpc, htlc_address, my_address) - htlc_contract.Deposit(conf_receiver, conf_secret_hashed, conf_expiry) + htlc_contract.Deposit(conf_receiver, conf_secret_hashed.decode('hex'), conf_expiry) # TODO: wait for Deposit to complete, or submit transaction receipt @@ -144,22 +157,35 @@ def __init__(self, coordapi, resource, data_dict=None): def _make_proposal(self, secret_hashed_hex, propdata=None): return Proposal(self._coordapi, self, self._resource(secret_hashed_hex), propdata) + @property + def guid(self): + return self._data['guid'] + @property def data(self): return self._data def set_data(self, value): - value['proposals'] = [self._make_proposal(key, propdata) - for key, propdata in value['proposals'].items()] + value['proposals'] = {key: self._make_proposal(key, propdata) + for key, propdata in value['proposals'].items()} self._data = value def refresh(self): self.set_data(self._resource.GET()) + @property + def chosen_proposal(self): + prop_id = self._data['chosen_proposal'] + if prop_id: + return self.proposal(prop_id) + @property def proposals(self): return self._data['proposals'] + def proposal(self, secret_hashed_hex): + return self.proposals[secret_hashed_hex] + def propose(self): """ Submit a proposal for the exchange by depositing your tokens @@ -205,8 +231,8 @@ def propose(self): # Add proposal to list, then return it proposal = self._make_proposal(secret_hashed_hex) - self.proposals.append(proposal) - return proposal + self.proposals[secret_hashed_hex] = proposal + return secret, proposal class CoordinatorClient(object): @@ -217,7 +243,7 @@ class CoordinatorClient(object): essentially the glue between the coordinator and the contract. """ def __init__(self, my_address, ethrpc, api_url=None, resource=None): - self._my_address = my_address + self._my_address = normalise_address(my_address) self._resource = RestClient(api_url) if resource is None else resource self._ethrpc = ethrpc assert isinstance(self._resource, RestClient) diff --git a/ion/htlc/coordinator.py b/ion/htlc/coordinator.py index 66a6292..35dad2f 100644 --- a/ion/htlc/coordinator.py +++ b/ion/htlc/coordinator.py @@ -3,8 +3,8 @@ import sys import os -import json import time +from hashlib import sha256 from flask import Flask, Blueprint, request, abort, jsonify, make_response from werkzeug.routing import BaseConverter @@ -15,6 +15,9 @@ from .common import MINIMUM_EXPIRY_DURATION +####################################################################### +# TODO: move to ..webutils or something + def api_abort(message, code=400): return abort(make_response(jsonify(dict(_error=message)), code)) @@ -77,6 +80,9 @@ class Bytes20Converter(BytesConverter): BYTES_LEN = 20 +####################################################################### + + class CoordinatorBlueprint(Blueprint): """ Provides a web API for coordinating cross-chain HTLC exchanges @@ -104,6 +110,8 @@ def __init__(self, htlc_address, **kwa): self.exch_confirm, methods=['POST']) self.add_url_rule("///release", 'release', self.exch_release, methods=['POST']) + self.add_url_rule("///finish", 'finish', + self.exch_finish, methods=['POST']) def _get_exch(self, exch_id): if exch_id not in self._exchanges: @@ -142,6 +150,7 @@ def exch_advertise(self): # Save exchange details # TODO: replace with class instance, `Exchange` ? self._exchanges[exch_id] = dict( + guid=exch_id, offer_address=offer_address, offer_amount=offer_amount, want_amount=want_amount, @@ -204,6 +213,7 @@ def exch_propose(self, exch_id, secret_hashed): depositor=depositor ) + # TODO: redirect to proposal URL? - or avoid another GET request... return jsonify(dict( ok=1 )) @@ -225,7 +235,7 @@ def exch_confirm(self, exch_id, secret_hashed): """ exch, proposal = self._get_proposal(exch_id, secret_hashed) - # XXX: one side of the expiry must be twice as long as the other to handle failure case + # XXX: one side of the expiry must be longer than the other to handle failure case # TODO: verify on-chain details match the proposal exch['chosen_proposal'] = secret_hashed @@ -241,8 +251,16 @@ def exch_release(self, exch_id, secret_hashed): This is performed by Bob """ exch, proposal = self._get_proposal(exch_id, secret_hashed) - secret = param_bytes32(request.form, 'secret') - # TODO: verify secret matches secret_hashed + + secret_hex = param_bytes32(request.form, 'secret') + secret = secret_hex.decode('hex') + secret_hashed_check = sha256(secret).digest() + secret_hashed_check_hex = secret_hashed_check.encode('hex') + + if secret_hashed_check_hex != secret_hashed: + return api_abort(' '.join(["Secret doesn't match! Got", secret_hashed_check_hex, 'expected', secret_hashed])) + + proposal['secret'] = secret_hex return jsonify(dict( ok=1 diff --git a/ion/restclient.py b/ion/restclient.py index bfe8eff..7170617 100644 --- a/ion/restclient.py +++ b/ion/restclient.py @@ -67,10 +67,11 @@ def _request(self, method, params=None, data=None): resp = sess.send(req.prepare()) try: data = resp.json() - if '_error' in data: - raise RuntimeError(data['error']) + error = data.get('_error') + if error: + raise RuntimeError(error) return data - except ValueError as ex: + except ValueError: resp.raise_for_status() def GET(self, **kwargs): diff --git a/test_coordclient.py b/test_coordclient.py index 4fc52d2..695e069 100755 --- a/test_coordclient.py +++ b/test_coordclient.py @@ -9,32 +9,65 @@ def main(api_url='http://127.0.0.1:5000/htlc'): + # Connect to Ethereum instance ethrpc = EthJsonRpc('127.0.0.1', 8545) accounts = ethrpc.eth_accounts() assert len(accounts) > 1 - addr_A = accounts[0] addr_B = accounts[1] + balance_A_start = ethrpc.eth_getBalance(addr_A) + balance_B_start = ethrpc.eth_getBalance(addr_B) + + offer = 1000 + want = 500 + # Setup a coordinator client for each A and B client_A = CoordinatorClient(addr_A, ethrpc, api_url) client_B = CoordinatorClient(addr_B, ethrpc, api_url) - balance_A = ethrpc.eth_getBalance(addr_A) - balance_B = ethrpc.eth_getBalance(addr_B) - # A advertises exchange, offers 1000 for 500 - client_A.advertise(1000, 500) + exch_A = client_A.advertise(offer, want) # B retrieves exchanges exchanges_B = client_B.list() print("B list", exchanges_B) - exch = exchanges_B[0] - exch_data = exch.data - print("Data", exch.data) + # Verify details of the exchange + exch_B = [_ for _ in exchanges_B if _.guid == exch_A.guid][0] + exch_B_data = exch_B.data + print("Data", exch_B.data) + + # B decides to participate in the swap + # Proposing deposits the funds + secret, prop_B = exch_B.propose() + print("Proposal is", prop_B) + balance_B_deposited = ethrpc.eth_getBalance(addr_B) + + # A then confirms the proposal, depositing their side of the deal + # This 'locks-in' the trade between A and B, denying any further proposals + exch_A.refresh() + print("Updated data:", exch_A.data) + prop_A = exch_A.proposal(prop_B.secret_hashed) + prop_A.confirm() + + # Verify proposal has been chosen + exch_A.refresh() + exch_B.refresh() + print("Chosen proposal is", exch_A.chosen_proposal) + + # B side then releases the secret, withdrawing the funds A deposited + prop_B.release(secret) + + balance_B_released = ethrpc.eth_getBalance(addr_B) + print("B Balance start", balance_B_start) + print("B Balance deposited", balance_B_deposited) + print("B Balance released", balance_B_released) + print("B Difference", balance_B_deposited - balance_B_released) - proposal = exch.propose() - print("Proposal is", proposal) + # Then A finishes, using the released secret to withdraw the funds B deposited + prop_A.refresh() + print("Prop A data", prop_A.data) + prop_A.finish() if __name__ == "__main__": From f2f23eda1fc5775e8180e3e6fb25edfe1863a9fc Mon Sep 17 00:00:00 2001 From: user Date: Thu, 14 Jun 2018 16:02:03 +0100 Subject: [PATCH 19/49] Fixed coordinator to work with updated HTLC contract, updated NPM dependencies --- ion/htlc/coordclient.py | 11 +- ion/htlc/coordinator.py | 10 +- package-lock.json | 5737 ++++++++++++++++++++++++--------------- package.json | 23 +- utils/truffle-debug.sh | 3 + 5 files changed, 3628 insertions(+), 2156 deletions(-) create mode 100755 utils/truffle-debug.sh diff --git a/ion/htlc/coordclient.py b/ion/htlc/coordclient.py index d92a295..4b8cd8c 100644 --- a/ion/htlc/coordclient.py +++ b/ion/htlc/coordclient.py @@ -55,11 +55,12 @@ def confirm(self): conf_expiry = self._data['expiry'] conf_receiver = self._data['depositor'] conf_secret_hashed = self._data['secret_hashed'] + conf_value = exch_data['offer_amount'] # Offerer deposits their side of the deal, locked to same hashed secret htlc_address = exch_data['offer_htlc_address'] htlc_contract = make_htlc_proxy(ethrpc, htlc_address, my_address) - htlc_contract.Deposit(conf_receiver, conf_secret_hashed.decode('hex'), conf_expiry) + htlc_contract.Deposit(conf_receiver, conf_secret_hashed.decode('hex'), conf_expiry, value=conf_value) # TODO: wait for Deposit to complete, or submit transaction receipt @@ -86,9 +87,11 @@ def release(self, secret): require(my_address == self._data['depositor'], "Only proposer can release") require(self._data['secret_hashed'] == secret_hashed_hex, "Secrets don't match!") + exch_guid = self._data['taker_guid'].decode('hex') + htlc_address = exch_data['offer_htlc_address'] htlc_contract = make_htlc_proxy(ethrpc, htlc_address, my_address) - htlc_contract.Withdraw(secret_hashed, secret) + htlc_contract.Withdraw(exch_guid, secret) # Reveal secret, posting back to server self._resource.release.POST( @@ -112,9 +115,11 @@ def finish(self): # TODO: verify secret hashes to hashed image + exch_guid = self._data['offer_guid'].decode('hex') + htlc_address = exch_data['want_htlc_address'] htlc_contract = make_htlc_proxy(ethrpc, htlc_address, my_address) - htlc_contract.Withdraw(secret_hashed, secret) + htlc_contract.Withdraw(exch_guid, secret) self._resource.finish.POST( xxx=123, diff --git a/ion/htlc/coordinator.py b/ion/htlc/coordinator.py index 35dad2f..25087e1 100644 --- a/ion/htlc/coordinator.py +++ b/ion/htlc/coordinator.py @@ -206,11 +206,19 @@ def exch_propose(self, exch_id, secret_hashed): if expiry < min_expiry: return api_abort("Expiry too short") + # GUID used for the exchanges + # offer_guid = Deposit() by B (the proposer) + offer_guid = sha256(exch['offer_address'].decode('hex') + secret_hashed.decode('hex')).digest() + # taker_guid = Deposit() by A (the initial offerer) + taker_guid = sha256(depositor.decode('hex') + secret_hashed.decode('hex')).digest() + # Store proposal exch['proposals'][secret_hashed] = dict( secret_hashed=secret_hashed, expiry=expiry, - depositor=depositor + depositor=depositor, + offer_guid=offer_guid.encode('hex'), + taker_guid=taker_guid.encode('hex'), ) # TODO: redirect to proposal URL? - or avoid another GET request... diff --git a/package-lock.json b/package-lock.json index d2de8a8..9102e89 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,31 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@mrmlnc/readdir-enhanced": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", + "integrity": "sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g==", + "dev": true, + "requires": { + "call-me-maybe": "^1.0.1", + "glob-to-regexp": "^0.3.0" + } + }, + "@nodelib/fs.stat": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.0.tgz", + "integrity": "sha512-LAQ1d4OPfSJ/BMbI2DuizmYrrkD9JMaTdi2hQTlI53lQ4kRQPyZQRS4CYQ7O66bnBBnP/oYdRxbk++X0xuFU6A==", + "dev": true + }, + "@samverschueren/stream-to-observable": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz", + "integrity": "sha512-MI4Xx6LHs4Webyvi6EbspgyAb4D2Q2VtnCQ1blOJcoLS6mVa8lNN2rkIy1CVxfTUpoyIbCTkXES1rLXztFD1lg==", + "dev": true, + "requires": { + "any-observable": "^0.3.0" + } + }, "@sindresorhus/is": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.7.0.tgz", @@ -22,31 +47,55 @@ "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", "dev": true, "requires": { - "mime-types": "2.1.18", + "mime-types": "~2.1.18", "negotiator": "0.6.1" } }, + "acorn": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.6.2.tgz", + "integrity": "sha1-sdode+KsG0oyf7nquFFwLFBFtOc=" + }, + "acorn-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", + "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", + "requires": { + "acorn": "^3.0.4" + }, + "dependencies": { + "acorn": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", + "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=" + } + } + }, "ajv": { "version": "5.5.2", "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", - "dev": true, "requires": { - "co": "4.6.0", - "fast-deep-equal": "1.1.0", - "fast-json-stable-stringify": "2.0.0", - "json-schema-traverse": "0.3.1" + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" } }, + "ajv-keywords": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-2.1.1.tgz", + "integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=" + }, "align-text": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", "dev": true, "requires": { - "kind-of": "3.2.2", - "longest": "1.0.1", - "repeat-string": "1.6.1" + "kind-of": "^3.0.2", + "longest": "^1.0.1", + "repeat-string": "^1.5.2" } }, "amdefine": { @@ -58,48 +107,49 @@ "ansi-escapes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.0.0.tgz", - "integrity": "sha512-O/klc27mWNUigtv0F8NJWbLF00OcegQalkqKURWdosW08YZKi4m6CnSUSvIZG1otNJbTWhN01Hhz389DW7mvDQ==", - "dev": true + "integrity": "sha512-O/klc27mWNUigtv0F8NJWbLF00OcegQalkqKURWdosW08YZKi4m6CnSUSvIZG1otNJbTWhN01Hhz389DW7mvDQ==" }, "ansi-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" }, "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, "requires": { - "color-convert": "1.9.1" + "color-convert": "^1.9.0" } }, + "antlr4": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/antlr4/-/antlr4-4.7.0.tgz", + "integrity": "sha1-KX+VbdwG+DOX/AmQ7PLgzyC/u+4=" + }, "any-observable": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/any-observable/-/any-observable-0.2.0.tgz", - "integrity": "sha1-xnhwBYADV5AJCD9UrAq6+1wz0kI=", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/any-observable/-/any-observable-0.3.0.tgz", + "integrity": "sha512-/FQM1EDkTsf63Ub2C6O7GuYFDsSXUwsaZDurV0np41ocwq0jthUAYCmhBX9f+KwlaCgIuWyr/4WlUQUBfKfZog==", "dev": true }, "anymatch": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", - "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==", + "integrity": "sha1-VT3Lj5HjyImEXf26NMd3IbkLnXo=", "dev": true, "optional": true, "requires": { - "micromatch": "2.3.11", - "normalize-path": "2.1.1" + "micromatch": "^2.1.5", + "normalize-path": "^2.0.0" } }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, + "integrity": "sha1-vNZ5HqWuCXJeF+WtmIE0zUCz2RE=", "requires": { - "sprintf-js": "1.0.3" + "sprintf-js": "~1.0.2" } }, "arr-diff": { @@ -108,13 +158,19 @@ "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", "dev": true, "requires": { - "arr-flatten": "1.1.0" + "arr-flatten": "^1.0.1" } }, "arr-flatten": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "integrity": "sha1-NgSLv/TntH4TZkQxbJlmnqWukfE=", + "dev": true + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", "dev": true }, "array-differ": { @@ -133,16 +189,14 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", - "dev": true, "requires": { - "array-uniq": "1.0.3" + "array-uniq": "^1.0.1" } }, "array-uniq": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", - "dev": true + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=" }, "array-unique": { "version": "0.2.1", @@ -153,8 +207,7 @@ "arrify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", - "dev": true + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=" }, "asn1": { "version": "0.2.3", @@ -171,13 +224,19 @@ "assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "integrity": "sha1-5gtrDo8wG9l+U3UhW9pAbIURjAs=", + "dev": true + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", "dev": true }, "ast-types": { - "version": "0.11.3", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.11.3.tgz", - "integrity": "sha512-XA5o5dsNw8MhyW0Q7MWXJWc4oOzZKbdsEJq45h7c8q/d9DwWZ5F2ugUc1PuMLPGsUnphCt/cNDHu8JeBbxf1qA==", + "version": "0.11.5", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.11.5.tgz", + "integrity": "sha512-oJjo+5e7/vEc2FBK8gUalV0pba4L3VdBIs2EKhOLHLcOd2FgQIVQN9xb0eZ9IjEWyAL7vq6fGJxOvVvdCHNyMw==", "dev": true }, "async": { @@ -196,7 +255,7 @@ "async-limiter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", - "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==", + "integrity": "sha1-ePrtjD0HSrgfIrTphdeehzj3IPg=", "dev": true }, "asynckit": { @@ -205,6 +264,12 @@ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", "dev": true }, + "atob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.1.tgz", + "integrity": "sha1-ri1acpR38onWDdf5amMUoi3Wwio=", + "dev": true + }, "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", @@ -212,9 +277,9 @@ "dev": true }, "aws4": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", - "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.7.0.tgz", + "integrity": "sha512-32NDda82rhwD9/JBCCkB+MRYDp0oSvlo2IL6rQWA10PQi7tDUM3eqMSltXmY+Oyl/7N3P3qNtAlv7X0d9bI28w==", "dev": true }, "babel-cli": { @@ -223,21 +288,21 @@ "integrity": "sha1-UCq1SHTX24itALiHoGODzgPQAvE=", "dev": true, "requires": { - "babel-core": "6.26.0", - "babel-polyfill": "6.26.0", - "babel-register": "6.26.0", - "babel-runtime": "6.26.0", - "chokidar": "1.7.0", - "commander": "2.15.1", - "convert-source-map": "1.5.1", - "fs-readdir-recursive": "1.1.0", - "glob": "7.1.2", - "lodash": "4.17.5", - "output-file-sync": "1.1.2", - "path-is-absolute": "1.0.1", - "slash": "1.0.0", - "source-map": "0.5.7", - "v8flags": "2.1.1" + "babel-core": "^6.26.0", + "babel-polyfill": "^6.26.0", + "babel-register": "^6.26.0", + "babel-runtime": "^6.26.0", + "chokidar": "^1.6.1", + "commander": "^2.11.0", + "convert-source-map": "^1.5.0", + "fs-readdir-recursive": "^1.0.0", + "glob": "^7.1.2", + "lodash": "^4.17.4", + "output-file-sync": "^1.1.2", + "path-is-absolute": "^1.0.1", + "slash": "^1.0.0", + "source-map": "^0.5.6", + "v8flags": "^2.1.1" }, "dependencies": { "commander": { @@ -259,9 +324,9 @@ "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", "requires": { - "chalk": "1.1.3", - "esutils": "2.0.2", - "js-tokens": "3.0.2" + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" }, "dependencies": { "ansi-regex": { @@ -279,11 +344,11 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" } }, "strip-ansi": { @@ -291,7 +356,7 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "requires": { - "ansi-regex": "2.1.1" + "ansi-regex": "^2.0.0" } }, "supports-color": { @@ -307,25 +372,25 @@ "integrity": "sha1-rzL3izGm/O8RnIew/Y2XU/A6C7g=", "dev": true, "requires": { - "babel-code-frame": "6.26.0", - "babel-generator": "6.26.1", - "babel-helpers": "6.24.1", - "babel-messages": "6.23.0", - "babel-register": "6.26.0", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0", - "babylon": "6.18.0", - "convert-source-map": "1.5.1", - "debug": "2.6.9", - "json5": "0.5.1", - "lodash": "4.17.5", - "minimatch": "3.0.4", - "path-is-absolute": "1.0.1", - "private": "0.1.8", - "slash": "1.0.0", - "source-map": "0.5.7" + "babel-code-frame": "^6.26.0", + "babel-generator": "^6.26.0", + "babel-helpers": "^6.24.1", + "babel-messages": "^6.23.0", + "babel-register": "^6.26.0", + "babel-runtime": "^6.26.0", + "babel-template": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "convert-source-map": "^1.5.0", + "debug": "^2.6.8", + "json5": "^0.5.1", + "lodash": "^4.17.4", + "minimatch": "^3.0.4", + "path-is-absolute": "^1.0.1", + "private": "^0.1.7", + "slash": "^1.0.0", + "source-map": "^0.5.6" }, "dependencies": { "babylon": { @@ -345,17 +410,17 @@ "babel-generator": { "version": "6.26.1", "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", - "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", + "integrity": "sha1-GERAjTuPDTWkBOp6wYDwh6YBvZA=", "dev": true, "requires": { - "babel-messages": "6.23.0", - "babel-runtime": "6.26.0", - "babel-types": "6.26.0", - "detect-indent": "4.0.0", - "jsesc": "1.3.0", - "lodash": "4.17.5", - "source-map": "0.5.7", - "trim-right": "1.0.1" + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "detect-indent": "^4.0.0", + "jsesc": "^1.3.0", + "lodash": "^4.17.4", + "source-map": "^0.5.7", + "trim-right": "^1.0.1" }, "dependencies": { "jsesc": { @@ -378,9 +443,9 @@ "integrity": "sha1-FMGeXxQte0fxmlJDHlKxzLxAozA=", "dev": true, "requires": { - "babel-runtime": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0" + "babel-runtime": "^6.22.0", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" } }, "babel-helper-builder-binary-assignment-operator-visitor": { @@ -389,9 +454,9 @@ "integrity": "sha1-zORReto1b0IgvK6KAsKzRvmlZmQ=", "dev": true, "requires": { - "babel-helper-explode-assignable-expression": "6.24.1", - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" + "babel-helper-explode-assignable-expression": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" } }, "babel-helper-call-delegate": { @@ -400,10 +465,10 @@ "integrity": "sha1-7Oaqzdx25Bw0YfiL/Fdb0Nqi340=", "dev": true, "requires": { - "babel-helper-hoist-variables": "6.24.1", - "babel-runtime": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0" + "babel-helper-hoist-variables": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" } }, "babel-helper-define-map": { @@ -412,10 +477,10 @@ "integrity": "sha1-pfVtq0GiX5fstJjH66ypgZ+Vvl8=", "dev": true, "requires": { - "babel-helper-function-name": "6.24.1", - "babel-runtime": "6.26.0", - "babel-types": "6.26.0", - "lodash": "4.17.5" + "babel-helper-function-name": "^6.24.1", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "lodash": "^4.17.4" } }, "babel-helper-explode-assignable-expression": { @@ -424,9 +489,9 @@ "integrity": "sha1-8luCz33BBDPFX3BZLVdGQArCLKo=", "dev": true, "requires": { - "babel-runtime": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0" + "babel-runtime": "^6.22.0", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" } }, "babel-helper-explode-class": { @@ -435,10 +500,10 @@ "integrity": "sha1-fcKjkQ3uAHBW4eMdZAztPVTqqes=", "dev": true, "requires": { - "babel-helper-bindify-decorators": "6.24.1", - "babel-runtime": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0" + "babel-helper-bindify-decorators": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" } }, "babel-helper-function-name": { @@ -447,11 +512,11 @@ "integrity": "sha1-00dbjAPtmCQqJbSDUasYOZ01gKk=", "dev": true, "requires": { - "babel-helper-get-function-arity": "6.24.1", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0" + "babel-helper-get-function-arity": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" } }, "babel-helper-get-function-arity": { @@ -460,8 +525,8 @@ "integrity": "sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0=", "dev": true, "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" } }, "babel-helper-hoist-variables": { @@ -470,8 +535,8 @@ "integrity": "sha1-HssnaJydJVE+rbyZFKc/VAi+enY=", "dev": true, "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" } }, "babel-helper-optimise-call-expression": { @@ -480,8 +545,8 @@ "integrity": "sha1-96E0J7qfc/j0+pk8VKl4gtEkQlc=", "dev": true, "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" } }, "babel-helper-regex": { @@ -490,9 +555,9 @@ "integrity": "sha1-MlxZ+QL4LyS3T6zu0DY5VPZJXnI=", "dev": true, "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0", - "lodash": "4.17.5" + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "lodash": "^4.17.4" } }, "babel-helper-remap-async-to-generator": { @@ -501,11 +566,11 @@ "integrity": "sha1-XsWBgnrXI/7N04HxySg5BnbkVRs=", "dev": true, "requires": { - "babel-helper-function-name": "6.24.1", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0" + "babel-helper-function-name": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" } }, "babel-helper-replace-supers": { @@ -514,12 +579,12 @@ "integrity": "sha1-v22/5Dk40XNpohPKiov3S2qQqxo=", "dev": true, "requires": { - "babel-helper-optimise-call-expression": "6.24.1", - "babel-messages": "6.23.0", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0" + "babel-helper-optimise-call-expression": "^6.24.1", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" } }, "babel-helpers": { @@ -528,8 +593,8 @@ "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=", "dev": true, "requires": { - "babel-runtime": "6.26.0", - "babel-template": "6.26.0" + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" } }, "babel-messages": { @@ -537,7 +602,7 @@ "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", "requires": { - "babel-runtime": "6.26.0" + "babel-runtime": "^6.22.0" } }, "babel-plugin-check-es2015-constants": { @@ -546,7 +611,7 @@ "integrity": "sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o=", "dev": true, "requires": { - "babel-runtime": "6.26.0" + "babel-runtime": "^6.22.0" } }, "babel-plugin-syntax-async-functions": { @@ -620,9 +685,9 @@ "integrity": "sha1-8FiQAUX9PpkHpt3yjaWfIVJYpds=", "dev": true, "requires": { - "babel-helper-remap-async-to-generator": "6.24.1", - "babel-plugin-syntax-async-generators": "6.13.0", - "babel-runtime": "6.26.0" + "babel-helper-remap-async-to-generator": "^6.24.1", + "babel-plugin-syntax-async-generators": "^6.5.0", + "babel-runtime": "^6.22.0" } }, "babel-plugin-transform-async-to-generator": { @@ -631,9 +696,9 @@ "integrity": "sha1-ZTbjeK/2yx1VF6wOQOs+n8jQh2E=", "dev": true, "requires": { - "babel-helper-remap-async-to-generator": "6.24.1", - "babel-plugin-syntax-async-functions": "6.13.0", - "babel-runtime": "6.26.0" + "babel-helper-remap-async-to-generator": "^6.24.1", + "babel-plugin-syntax-async-functions": "^6.8.0", + "babel-runtime": "^6.22.0" } }, "babel-plugin-transform-class-constructor-call": { @@ -642,9 +707,9 @@ "integrity": "sha1-gNwoVQWsBn3LjWxl4vbxGrd2Xvk=", "dev": true, "requires": { - "babel-plugin-syntax-class-constructor-call": "6.18.0", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0" + "babel-plugin-syntax-class-constructor-call": "^6.18.0", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" } }, "babel-plugin-transform-class-properties": { @@ -653,10 +718,10 @@ "integrity": "sha1-anl2PqYdM9NvN7YRqp3vgagbRqw=", "dev": true, "requires": { - "babel-helper-function-name": "6.24.1", - "babel-plugin-syntax-class-properties": "6.13.0", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0" + "babel-helper-function-name": "^6.24.1", + "babel-plugin-syntax-class-properties": "^6.8.0", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" } }, "babel-plugin-transform-decorators": { @@ -665,11 +730,11 @@ "integrity": "sha1-eIAT2PjGtSIr33s0Q5Df13Vp4k0=", "dev": true, "requires": { - "babel-helper-explode-class": "6.24.1", - "babel-plugin-syntax-decorators": "6.13.0", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0", - "babel-types": "6.26.0" + "babel-helper-explode-class": "^6.24.1", + "babel-plugin-syntax-decorators": "^6.13.0", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-types": "^6.24.1" } }, "babel-plugin-transform-es2015-arrow-functions": { @@ -678,7 +743,7 @@ "integrity": "sha1-RSaSy3EdX3ncf4XkQM5BufJE0iE=", "dev": true, "requires": { - "babel-runtime": "6.26.0" + "babel-runtime": "^6.22.0" } }, "babel-plugin-transform-es2015-block-scoped-functions": { @@ -687,7 +752,7 @@ "integrity": "sha1-u8UbSflk1wy42OC5ToICRs46YUE=", "dev": true, "requires": { - "babel-runtime": "6.26.0" + "babel-runtime": "^6.22.0" } }, "babel-plugin-transform-es2015-block-scoping": { @@ -696,11 +761,11 @@ "integrity": "sha1-1w9SmcEwjQXBL0Y4E7CgnnOxiV8=", "dev": true, "requires": { - "babel-runtime": "6.26.0", - "babel-template": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0", - "lodash": "4.17.5" + "babel-runtime": "^6.26.0", + "babel-template": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "lodash": "^4.17.4" } }, "babel-plugin-transform-es2015-classes": { @@ -709,15 +774,15 @@ "integrity": "sha1-WkxYpQyclGHlZLSyo7+ryXolhNs=", "dev": true, "requires": { - "babel-helper-define-map": "6.26.0", - "babel-helper-function-name": "6.24.1", - "babel-helper-optimise-call-expression": "6.24.1", - "babel-helper-replace-supers": "6.24.1", - "babel-messages": "6.23.0", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0" + "babel-helper-define-map": "^6.24.1", + "babel-helper-function-name": "^6.24.1", + "babel-helper-optimise-call-expression": "^6.24.1", + "babel-helper-replace-supers": "^6.24.1", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" } }, "babel-plugin-transform-es2015-computed-properties": { @@ -726,8 +791,8 @@ "integrity": "sha1-b+Ko0WiV1WNPTNmZttNICjCBWbM=", "dev": true, "requires": { - "babel-runtime": "6.26.0", - "babel-template": "6.26.0" + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" } }, "babel-plugin-transform-es2015-destructuring": { @@ -736,7 +801,7 @@ "integrity": "sha1-mXux8auWf2gtKwh2/jWNYOdlxW0=", "dev": true, "requires": { - "babel-runtime": "6.26.0" + "babel-runtime": "^6.22.0" } }, "babel-plugin-transform-es2015-duplicate-keys": { @@ -745,8 +810,8 @@ "integrity": "sha1-c+s9MQypaePvnskcU3QabxV2Qj4=", "dev": true, "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" } }, "babel-plugin-transform-es2015-for-of": { @@ -755,7 +820,7 @@ "integrity": "sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE=", "dev": true, "requires": { - "babel-runtime": "6.26.0" + "babel-runtime": "^6.22.0" } }, "babel-plugin-transform-es2015-function-name": { @@ -764,9 +829,9 @@ "integrity": "sha1-g0yJhTvDaxrw86TF26qU/Y6sqos=", "dev": true, "requires": { - "babel-helper-function-name": "6.24.1", - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" + "babel-helper-function-name": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" } }, "babel-plugin-transform-es2015-literals": { @@ -775,7 +840,7 @@ "integrity": "sha1-T1SgLWzWbPkVKAAZox0xklN3yi4=", "dev": true, "requires": { - "babel-runtime": "6.26.0" + "babel-runtime": "^6.22.0" } }, "babel-plugin-transform-es2015-modules-amd": { @@ -784,9 +849,9 @@ "integrity": "sha1-Oz5UAXI5hC1tGcMBHEvS8AoA0VQ=", "dev": true, "requires": { - "babel-plugin-transform-es2015-modules-commonjs": "6.26.0", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0" + "babel-plugin-transform-es2015-modules-commonjs": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" } }, "babel-plugin-transform-es2015-modules-commonjs": { @@ -794,10 +859,10 @@ "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.0.tgz", "integrity": "sha1-DYOUApt9xqvhqX7xgeAHWN0uXYo=", "requires": { - "babel-plugin-transform-strict-mode": "6.24.1", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0", - "babel-types": "6.26.0" + "babel-plugin-transform-strict-mode": "^6.24.1", + "babel-runtime": "^6.26.0", + "babel-template": "^6.26.0", + "babel-types": "^6.26.0" } }, "babel-plugin-transform-es2015-modules-systemjs": { @@ -806,9 +871,9 @@ "integrity": "sha1-/4mhQrkRmpBhlfXxBuzzBdlAfSM=", "dev": true, "requires": { - "babel-helper-hoist-variables": "6.24.1", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0" + "babel-helper-hoist-variables": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" } }, "babel-plugin-transform-es2015-modules-umd": { @@ -817,9 +882,9 @@ "integrity": "sha1-rJl+YoXNGO1hdq22B9YCNErThGg=", "dev": true, "requires": { - "babel-plugin-transform-es2015-modules-amd": "6.24.1", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0" + "babel-plugin-transform-es2015-modules-amd": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" } }, "babel-plugin-transform-es2015-object-super": { @@ -828,8 +893,8 @@ "integrity": "sha1-JM72muIcuDp/hgPa0CH1cusnj40=", "dev": true, "requires": { - "babel-helper-replace-supers": "6.24.1", - "babel-runtime": "6.26.0" + "babel-helper-replace-supers": "^6.24.1", + "babel-runtime": "^6.22.0" } }, "babel-plugin-transform-es2015-parameters": { @@ -838,12 +903,12 @@ "integrity": "sha1-V6w1GrScrxSpfNE7CfZv3wpiXys=", "dev": true, "requires": { - "babel-helper-call-delegate": "6.24.1", - "babel-helper-get-function-arity": "6.24.1", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0" + "babel-helper-call-delegate": "^6.24.1", + "babel-helper-get-function-arity": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" } }, "babel-plugin-transform-es2015-shorthand-properties": { @@ -852,8 +917,8 @@ "integrity": "sha1-JPh11nIch2YbvZmkYi5R8U3jiqA=", "dev": true, "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" } }, "babel-plugin-transform-es2015-spread": { @@ -862,7 +927,7 @@ "integrity": "sha1-1taKmfia7cRTbIGlQujdnxdG+NE=", "dev": true, "requires": { - "babel-runtime": "6.26.0" + "babel-runtime": "^6.22.0" } }, "babel-plugin-transform-es2015-sticky-regex": { @@ -871,9 +936,9 @@ "integrity": "sha1-AMHNsaynERLN8M9hJsLta0V8zbw=", "dev": true, "requires": { - "babel-helper-regex": "6.26.0", - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" + "babel-helper-regex": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" } }, "babel-plugin-transform-es2015-template-literals": { @@ -882,7 +947,7 @@ "integrity": "sha1-qEs0UPfp+PH2g51taH2oS7EjbY0=", "dev": true, "requires": { - "babel-runtime": "6.26.0" + "babel-runtime": "^6.22.0" } }, "babel-plugin-transform-es2015-typeof-symbol": { @@ -891,7 +956,7 @@ "integrity": "sha1-3sCfHN3/lLUqxz1QXITfWdzOs3I=", "dev": true, "requires": { - "babel-runtime": "6.26.0" + "babel-runtime": "^6.22.0" } }, "babel-plugin-transform-es2015-unicode-regex": { @@ -900,9 +965,9 @@ "integrity": "sha1-04sS9C6nMj9yk4fxinxa4frrNek=", "dev": true, "requires": { - "babel-helper-regex": "6.26.0", - "babel-runtime": "6.26.0", - "regexpu-core": "2.0.0" + "babel-helper-regex": "^6.24.1", + "babel-runtime": "^6.22.0", + "regexpu-core": "^2.0.0" } }, "babel-plugin-transform-exponentiation-operator": { @@ -911,9 +976,9 @@ "integrity": "sha1-KrDJx/MJj6SJB3cruBP+QejeOg4=", "dev": true, "requires": { - "babel-helper-builder-binary-assignment-operator-visitor": "6.24.1", - "babel-plugin-syntax-exponentiation-operator": "6.13.0", - "babel-runtime": "6.26.0" + "babel-helper-builder-binary-assignment-operator-visitor": "^6.24.1", + "babel-plugin-syntax-exponentiation-operator": "^6.8.0", + "babel-runtime": "^6.22.0" } }, "babel-plugin-transform-export-extensions": { @@ -922,8 +987,8 @@ "integrity": "sha1-U3OLR+deghhYnuqUbLvTkQm75lM=", "dev": true, "requires": { - "babel-plugin-syntax-export-extensions": "6.13.0", - "babel-runtime": "6.26.0" + "babel-plugin-syntax-export-extensions": "^6.8.0", + "babel-runtime": "^6.22.0" } }, "babel-plugin-transform-flow-strip-types": { @@ -932,8 +997,8 @@ "integrity": "sha1-hMtnKTXUNxT9wyvOhFaNh0Qc988=", "dev": true, "requires": { - "babel-plugin-syntax-flow": "6.18.0", - "babel-runtime": "6.26.0" + "babel-plugin-syntax-flow": "^6.18.0", + "babel-runtime": "^6.22.0" } }, "babel-plugin-transform-object-rest-spread": { @@ -942,8 +1007,8 @@ "integrity": "sha1-DzZpLVD+9rfi1LOsFHgTepY7ewY=", "dev": true, "requires": { - "babel-plugin-syntax-object-rest-spread": "6.13.0", - "babel-runtime": "6.26.0" + "babel-plugin-syntax-object-rest-spread": "^6.8.0", + "babel-runtime": "^6.26.0" } }, "babel-plugin-transform-regenerator": { @@ -952,7 +1017,7 @@ "integrity": "sha1-4HA2lvveJ/Cj78rPi03KL3s6jy8=", "dev": true, "requires": { - "regenerator-transform": "0.10.1" + "regenerator-transform": "^0.10.0" } }, "babel-plugin-transform-strict-mode": { @@ -960,8 +1025,8 @@ "resolved": "https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz", "integrity": "sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g=", "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" } }, "babel-polyfill": { @@ -970,9 +1035,9 @@ "integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=", "dev": true, "requires": { - "babel-runtime": "6.26.0", - "core-js": "2.5.3", - "regenerator-runtime": "0.10.5" + "babel-runtime": "^6.26.0", + "core-js": "^2.5.0", + "regenerator-runtime": "^0.10.5" }, "dependencies": { "regenerator-runtime": { @@ -984,41 +1049,41 @@ } }, "babel-preset-env": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/babel-preset-env/-/babel-preset-env-1.6.1.tgz", - "integrity": "sha512-W6VIyA6Ch9ePMI7VptNn2wBM6dbG0eSz25HEiL40nQXCsXGTGZSTZu1Iap+cj3Q0S5a7T9+529l/5Bkvd+afNA==", - "dev": true, - "requires": { - "babel-plugin-check-es2015-constants": "6.22.0", - "babel-plugin-syntax-trailing-function-commas": "6.22.0", - "babel-plugin-transform-async-to-generator": "6.24.1", - "babel-plugin-transform-es2015-arrow-functions": "6.22.0", - "babel-plugin-transform-es2015-block-scoped-functions": "6.22.0", - "babel-plugin-transform-es2015-block-scoping": "6.26.0", - "babel-plugin-transform-es2015-classes": "6.24.1", - "babel-plugin-transform-es2015-computed-properties": "6.24.1", - "babel-plugin-transform-es2015-destructuring": "6.23.0", - "babel-plugin-transform-es2015-duplicate-keys": "6.24.1", - "babel-plugin-transform-es2015-for-of": "6.23.0", - "babel-plugin-transform-es2015-function-name": "6.24.1", - "babel-plugin-transform-es2015-literals": "6.22.0", - "babel-plugin-transform-es2015-modules-amd": "6.24.1", - "babel-plugin-transform-es2015-modules-commonjs": "6.26.0", - "babel-plugin-transform-es2015-modules-systemjs": "6.24.1", - "babel-plugin-transform-es2015-modules-umd": "6.24.1", - "babel-plugin-transform-es2015-object-super": "6.24.1", - "babel-plugin-transform-es2015-parameters": "6.24.1", - "babel-plugin-transform-es2015-shorthand-properties": "6.24.1", - "babel-plugin-transform-es2015-spread": "6.22.0", - "babel-plugin-transform-es2015-sticky-regex": "6.24.1", - "babel-plugin-transform-es2015-template-literals": "6.22.0", - "babel-plugin-transform-es2015-typeof-symbol": "6.23.0", - "babel-plugin-transform-es2015-unicode-regex": "6.24.1", - "babel-plugin-transform-exponentiation-operator": "6.24.1", - "babel-plugin-transform-regenerator": "6.26.0", - "browserslist": "2.11.3", - "invariant": "2.2.4", - "semver": "5.5.0" + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/babel-preset-env/-/babel-preset-env-1.7.0.tgz", + "integrity": "sha512-9OR2afuKDneX2/q2EurSftUYM0xGu4O2D9adAhVfADDhrYDaxXV0rBbevVYoY9n6nyX1PmQW/0jtpJvUNr9CHg==", + "dev": true, + "requires": { + "babel-plugin-check-es2015-constants": "^6.22.0", + "babel-plugin-syntax-trailing-function-commas": "^6.22.0", + "babel-plugin-transform-async-to-generator": "^6.22.0", + "babel-plugin-transform-es2015-arrow-functions": "^6.22.0", + "babel-plugin-transform-es2015-block-scoped-functions": "^6.22.0", + "babel-plugin-transform-es2015-block-scoping": "^6.23.0", + "babel-plugin-transform-es2015-classes": "^6.23.0", + "babel-plugin-transform-es2015-computed-properties": "^6.22.0", + "babel-plugin-transform-es2015-destructuring": "^6.23.0", + "babel-plugin-transform-es2015-duplicate-keys": "^6.22.0", + "babel-plugin-transform-es2015-for-of": "^6.23.0", + "babel-plugin-transform-es2015-function-name": "^6.22.0", + "babel-plugin-transform-es2015-literals": "^6.22.0", + "babel-plugin-transform-es2015-modules-amd": "^6.22.0", + "babel-plugin-transform-es2015-modules-commonjs": "^6.23.0", + "babel-plugin-transform-es2015-modules-systemjs": "^6.23.0", + "babel-plugin-transform-es2015-modules-umd": "^6.23.0", + "babel-plugin-transform-es2015-object-super": "^6.22.0", + "babel-plugin-transform-es2015-parameters": "^6.23.0", + "babel-plugin-transform-es2015-shorthand-properties": "^6.22.0", + "babel-plugin-transform-es2015-spread": "^6.22.0", + "babel-plugin-transform-es2015-sticky-regex": "^6.22.0", + "babel-plugin-transform-es2015-template-literals": "^6.22.0", + "babel-plugin-transform-es2015-typeof-symbol": "^6.23.0", + "babel-plugin-transform-es2015-unicode-regex": "^6.22.0", + "babel-plugin-transform-exponentiation-operator": "^6.22.0", + "babel-plugin-transform-regenerator": "^6.22.0", + "browserslist": "^3.2.6", + "invariant": "^2.2.2", + "semver": "^5.3.0" } }, "babel-preset-es2015": { @@ -1027,30 +1092,30 @@ "integrity": "sha1-1EBQ1rwsn+6nAqrzjXJ6AhBTiTk=", "dev": true, "requires": { - "babel-plugin-check-es2015-constants": "6.22.0", - "babel-plugin-transform-es2015-arrow-functions": "6.22.0", - "babel-plugin-transform-es2015-block-scoped-functions": "6.22.0", - "babel-plugin-transform-es2015-block-scoping": "6.26.0", - "babel-plugin-transform-es2015-classes": "6.24.1", - "babel-plugin-transform-es2015-computed-properties": "6.24.1", - "babel-plugin-transform-es2015-destructuring": "6.23.0", - "babel-plugin-transform-es2015-duplicate-keys": "6.24.1", - "babel-plugin-transform-es2015-for-of": "6.23.0", - "babel-plugin-transform-es2015-function-name": "6.24.1", - "babel-plugin-transform-es2015-literals": "6.22.0", - "babel-plugin-transform-es2015-modules-amd": "6.24.1", - "babel-plugin-transform-es2015-modules-commonjs": "6.26.0", - "babel-plugin-transform-es2015-modules-systemjs": "6.24.1", - "babel-plugin-transform-es2015-modules-umd": "6.24.1", - "babel-plugin-transform-es2015-object-super": "6.24.1", - "babel-plugin-transform-es2015-parameters": "6.24.1", - "babel-plugin-transform-es2015-shorthand-properties": "6.24.1", - "babel-plugin-transform-es2015-spread": "6.22.0", - "babel-plugin-transform-es2015-sticky-regex": "6.24.1", - "babel-plugin-transform-es2015-template-literals": "6.22.0", - "babel-plugin-transform-es2015-typeof-symbol": "6.23.0", - "babel-plugin-transform-es2015-unicode-regex": "6.24.1", - "babel-plugin-transform-regenerator": "6.26.0" + "babel-plugin-check-es2015-constants": "^6.22.0", + "babel-plugin-transform-es2015-arrow-functions": "^6.22.0", + "babel-plugin-transform-es2015-block-scoped-functions": "^6.22.0", + "babel-plugin-transform-es2015-block-scoping": "^6.24.1", + "babel-plugin-transform-es2015-classes": "^6.24.1", + "babel-plugin-transform-es2015-computed-properties": "^6.24.1", + "babel-plugin-transform-es2015-destructuring": "^6.22.0", + "babel-plugin-transform-es2015-duplicate-keys": "^6.24.1", + "babel-plugin-transform-es2015-for-of": "^6.22.0", + "babel-plugin-transform-es2015-function-name": "^6.24.1", + "babel-plugin-transform-es2015-literals": "^6.22.0", + "babel-plugin-transform-es2015-modules-amd": "^6.24.1", + "babel-plugin-transform-es2015-modules-commonjs": "^6.24.1", + "babel-plugin-transform-es2015-modules-systemjs": "^6.24.1", + "babel-plugin-transform-es2015-modules-umd": "^6.24.1", + "babel-plugin-transform-es2015-object-super": "^6.24.1", + "babel-plugin-transform-es2015-parameters": "^6.24.1", + "babel-plugin-transform-es2015-shorthand-properties": "^6.24.1", + "babel-plugin-transform-es2015-spread": "^6.22.0", + "babel-plugin-transform-es2015-sticky-regex": "^6.24.1", + "babel-plugin-transform-es2015-template-literals": "^6.22.0", + "babel-plugin-transform-es2015-typeof-symbol": "^6.22.0", + "babel-plugin-transform-es2015-unicode-regex": "^6.24.1", + "babel-plugin-transform-regenerator": "^6.24.1" } }, "babel-preset-node6": { @@ -1058,8 +1123,8 @@ "resolved": "https://registry.npmjs.org/babel-preset-node6/-/babel-preset-node6-11.0.0.tgz", "integrity": "sha1-CDWZRRckiYWinRj21GXasWu4p9g=", "requires": { - "babel-plugin-syntax-trailing-function-commas": "6.22.0", - "babel-plugin-transform-es2015-modules-commonjs": "6.26.0" + "babel-plugin-syntax-trailing-function-commas": "^6.5.0", + "babel-plugin-transform-es2015-modules-commonjs": "^6.7.4" } }, "babel-preset-stage-1": { @@ -1068,9 +1133,9 @@ "integrity": "sha1-dpLNfc1oSZB+auSgqFWJz7niv7A=", "dev": true, "requires": { - "babel-plugin-transform-class-constructor-call": "6.24.1", - "babel-plugin-transform-export-extensions": "6.22.0", - "babel-preset-stage-2": "6.24.1" + "babel-plugin-transform-class-constructor-call": "^6.24.1", + "babel-plugin-transform-export-extensions": "^6.22.0", + "babel-preset-stage-2": "^6.24.1" } }, "babel-preset-stage-2": { @@ -1079,10 +1144,10 @@ "integrity": "sha1-2eKWD7PXEYfw5k7sYrwHdnIZvcE=", "dev": true, "requires": { - "babel-plugin-syntax-dynamic-import": "6.18.0", - "babel-plugin-transform-class-properties": "6.24.1", - "babel-plugin-transform-decorators": "6.24.1", - "babel-preset-stage-3": "6.24.1" + "babel-plugin-syntax-dynamic-import": "^6.18.0", + "babel-plugin-transform-class-properties": "^6.24.1", + "babel-plugin-transform-decorators": "^6.24.1", + "babel-preset-stage-3": "^6.24.1" } }, "babel-preset-stage-3": { @@ -1091,11 +1156,11 @@ "integrity": "sha1-g2raCp56f6N8sTj7kyb4eTSkg5U=", "dev": true, "requires": { - "babel-plugin-syntax-trailing-function-commas": "6.22.0", - "babel-plugin-transform-async-generator-functions": "6.24.1", - "babel-plugin-transform-async-to-generator": "6.24.1", - "babel-plugin-transform-exponentiation-operator": "6.24.1", - "babel-plugin-transform-object-rest-spread": "6.26.0" + "babel-plugin-syntax-trailing-function-commas": "^6.22.0", + "babel-plugin-transform-async-generator-functions": "^6.24.1", + "babel-plugin-transform-async-to-generator": "^6.24.1", + "babel-plugin-transform-exponentiation-operator": "^6.24.1", + "babel-plugin-transform-object-rest-spread": "^6.22.0" } }, "babel-register": { @@ -1104,13 +1169,13 @@ "integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=", "dev": true, "requires": { - "babel-core": "6.26.0", - "babel-runtime": "6.26.0", - "core-js": "2.5.3", - "home-or-tmp": "2.0.0", - "lodash": "4.17.5", - "mkdirp": "0.5.1", - "source-map-support": "0.4.18" + "babel-core": "^6.26.0", + "babel-runtime": "^6.26.0", + "core-js": "^2.5.0", + "home-or-tmp": "^2.0.0", + "lodash": "^4.17.4", + "mkdirp": "^0.5.1", + "source-map-support": "^0.4.15" }, "dependencies": { "source-map": { @@ -1122,10 +1187,10 @@ "source-map-support": { "version": "0.4.18", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", - "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", + "integrity": "sha1-Aoam3ovkJkEzhZTpfM6nXwosWF8=", "dev": true, "requires": { - "source-map": "0.5.7" + "source-map": "^0.5.6" } } } @@ -1135,8 +1200,8 @@ "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "requires": { - "core-js": "2.5.3", - "regenerator-runtime": "0.11.1" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } }, "babel-template": { @@ -1144,11 +1209,11 @@ "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", "requires": { - "babel-runtime": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0", - "babylon": "6.18.0", - "lodash": "4.17.5" + "babel-runtime": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "lodash": "^4.17.4" }, "dependencies": { "babylon": { @@ -1163,15 +1228,15 @@ "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", "requires": { - "babel-code-frame": "6.26.0", - "babel-messages": "6.23.0", - "babel-runtime": "6.26.0", - "babel-types": "6.26.0", - "babylon": "6.18.0", - "debug": "2.6.9", - "globals": "9.18.0", - "invariant": "2.2.4", - "lodash": "4.17.5" + "babel-code-frame": "^6.26.0", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "debug": "^2.6.8", + "globals": "^9.18.0", + "invariant": "^2.2.2", + "lodash": "^4.17.4" }, "dependencies": { "babylon": { @@ -1186,23 +1251,89 @@ "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", "requires": { - "babel-runtime": "6.26.0", - "esutils": "2.0.2", - "lodash": "4.17.5", - "to-fast-properties": "1.0.3" + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" } }, "babylon": { - "version": "7.0.0-beta.42", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.42.tgz", - "integrity": "sha512-h6E/OkkvcBw/JimbL0p8dIaxrcuQn3QmIYGC/GtJlRYif5LTKBYPHXYwqluJpfS/kOXoz0go+9mkmOVC0M+zWw==", + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.47.tgz", + "integrity": "sha512-+rq2cr4GDhtToEzKFD6KZZMDBXhjFAr9JjPw9pAppZACeEWqNM294j+NdBzkSHYXwzzBmVjZ3nEVJlOhbR2gOQ==", "dev": true }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } + } }, "bcrypt-pbkdf": { "version": "1.0.1", @@ -1211,7 +1342,7 @@ "dev": true, "optional": true, "requires": { - "tweetnacl": "0.14.5" + "tweetnacl": "^0.14.3" } }, "big.js": { @@ -1222,6 +1353,7 @@ }, "bignumber.js": { "version": "git+https://github.com/debris/bignumber.js.git#94d7146671b9719e00a09c29b01a691bc85048c2", + "from": "git+https://github.com/debris/bignumber.js.git#94d7146671b9719e00a09c29b01a691bc85048c2", "dev": true }, "binary-extensions": { @@ -1240,7 +1372,7 @@ "bluebird": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", - "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==", + "integrity": "sha1-2VUfnemPH82h5oPRfukaBgLuLrk=", "dev": true }, "bn.js": { @@ -1250,39 +1382,40 @@ "dev": true }, "body-parser": { - "version": "1.18.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", - "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", + "version": "1.18.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", + "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=", "dev": true, "requires": { "bytes": "3.0.0", - "content-type": "1.0.4", + "content-type": "~1.0.4", "debug": "2.6.9", - "depd": "1.1.2", - "http-errors": "1.6.2", - "iconv-lite": "0.4.19", - "on-finished": "2.3.0", - "qs": "6.5.1", - "raw-body": "2.3.2", - "type-is": "1.6.16" - } - }, - "boom": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz", - "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", - "dev": true, - "requires": { - "hoek": "4.2.1" + "depd": "~1.1.2", + "http-errors": "~1.6.3", + "iconv-lite": "0.4.23", + "on-finished": "~2.3.0", + "qs": "6.5.2", + "raw-body": "2.3.3", + "type-is": "~1.6.16" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + } } }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, + "integrity": "sha1-PH/L9SnYcibz0vUrlm/1Jx60Qd0=", "requires": { - "balanced-match": "1.0.0", + "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, @@ -1292,9 +1425,9 @@ "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", "dev": true, "requires": { - "expand-range": "1.8.2", - "preserve": "0.2.0", - "repeat-element": "1.1.2" + "expand-range": "^1.8.1", + "preserve": "^0.2.0", + "repeat-element": "^1.1.2" } }, "brorand": { @@ -1315,19 +1448,24 @@ "integrity": "sha1-P/NKMAbvFcD7NWflQbkaI0ASPRE=", "dev": true, "requires": { - "js-sha3": "0.3.1" + "js-sha3": "^0.3.1" } }, "browserslist": { - "version": "2.11.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-2.11.3.tgz", - "integrity": "sha512-yWu5cXT7Av6mVwzWc8lMsJMHWn4xyjSuGYi4IozbVTLUOEYPSagUB8kiMDUHA1fS3zjr8nkxkn9jdvug4BBRmA==", + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-3.2.8.tgz", + "integrity": "sha512-WHVocJYavUwVgVViC0ORikPHQquXwVh939TaelZ4WDqpWgTX/FsGhl/+P4qBUAGcRvtOgDgC+xftNWWp2RUTAQ==", "dev": true, "requires": { - "caniuse-lite": "1.0.30000823", - "electron-to-chromium": "1.3.42" + "caniuse-lite": "^1.0.30000844", + "electron-to-chromium": "^1.3.47" } }, + "buffer-from": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.0.tgz", + "integrity": "sha1-h/yqOimDWOCt5uRCz86EB0DRrQQ=" + }, "buffer-to-arraybuffer": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/buffer-to-arraybuffer/-/buffer-to-arraybuffer-0.0.5.tgz", @@ -1337,8 +1475,7 @@ "builtin-modules": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", - "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", - "dev": true + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=" }, "bytes": { "version": "3.0.0", @@ -1346,6 +1483,31 @@ "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", "dev": true }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, "cacheable-request": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-2.1.4.tgz", @@ -1359,8 +1521,35 @@ "lowercase-keys": "1.0.0", "normalize-url": "2.0.1", "responselike": "1.0.2" + }, + "dependencies": { + "lowercase-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz", + "integrity": "sha1-TjNms55/VFfjXxMkvfb4jQv8cwY=", + "dev": true + } + } + }, + "call-me-maybe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", + "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=", + "dev": true + }, + "caller-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", + "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", + "requires": { + "callsites": "^0.2.0" } }, + "callsites": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", + "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=" + }, "camelcase": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", @@ -1368,9 +1557,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30000823", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000823.tgz", - "integrity": "sha512-3rrhqUxwBgrwNlWVUEwIJfqdZNwLPX18eTo7MGXb3gueDpbOFW6w5OXyHscdBd6IJcu9wnKmKVd7nSl+r7fmgw==", + "version": "1.0.30000855", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000855.tgz", + "integrity": "sha512-ajORrkXa5UYk62P5PK6ZmBraYOAOr9HWy+XxLwjDg8Ys/5KiSyarg8tIA32ZVqbFhtz67wyySXnU9imkh2ZT2w==", "dev": true }, "caseless": { @@ -1386,8 +1575,8 @@ "dev": true, "optional": true, "requires": { - "align-text": "0.1.4", - "lazy-cache": "1.0.4" + "align-text": "^0.1.3", + "lazy-cache": "^1.0.3" } }, "chai": { @@ -1396,45 +1585,43 @@ "integrity": "sha1-D2RYS6ZC8PKs4oBiefTwbKI61zw=", "dev": true, "requires": { - "assertion-error": "1.1.0", - "check-error": "1.0.2", - "deep-eql": "3.0.1", - "get-func-name": "2.0.0", - "pathval": "1.1.0", - "type-detect": "4.0.8" + "assertion-error": "^1.0.1", + "check-error": "^1.0.1", + "deep-eql": "^3.0.0", + "get-func-name": "^2.0.0", + "pathval": "^1.0.0", + "type-detect": "^4.0.0" } }, "chai-as-promised": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.1.tgz", - "integrity": "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==", + "integrity": "sha1-CGRdgl3rhpbuYXJdv1kMAS6wDKA=", "dev": true, "requires": { - "check-error": "1.0.2" + "check-error": "^1.0.2" } }, "chai-bignumber": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/chai-bignumber/-/chai-bignumber-2.0.2.tgz", - "integrity": "sha512-BIdRNjRaoRj4bMsZLKbIZPMNKqmwnzNiyxqBYDSs6dFOCs9w8OHPuUE8e1bH60i1IhOzT0NjLtCD+lKEWB1KTQ==", + "integrity": "sha1-3mwhnGkLLWa2Rq1pMAlvm6IZlkM=", "dev": true }, "chalk": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", - "dev": true, "requires": { - "ansi-styles": "3.2.1", - "escape-string-regexp": "1.0.5", - "supports-color": "5.3.0" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" } }, "chardet": { "version": "0.4.2", "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", - "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", - "dev": true + "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=" }, "check-error": { "version": "1.0.2", @@ -1449,24 +1636,57 @@ "dev": true, "optional": true, "requires": { - "anymatch": "1.3.2", - "async-each": "1.0.1", - "fsevents": "1.2.3", - "glob-parent": "2.0.0", - "inherits": "2.0.3", - "is-binary-path": "1.0.1", - "is-glob": "2.0.1", - "path-is-absolute": "1.0.1", - "readdirp": "2.1.0" + "anymatch": "^1.3.0", + "async-each": "^1.0.0", + "fsevents": "^1.0.0", + "glob-parent": "^2.0.0", + "inherits": "^2.0.1", + "is-binary-path": "^1.0.0", + "is-glob": "^2.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.0.0" + } + }, + "circular-json": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", + "integrity": "sha1-gVyZ6oT2gJUp0vRXkb34JxE1LWY=" + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } } }, "cli-cursor": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", - "dev": true, "requires": { - "restore-cursor": "2.0.0" + "restore-cursor": "^2.0.0" } }, "cli-spinners": { @@ -1499,7 +1719,7 @@ "dev": true, "requires": { "slice-ansi": "0.0.4", - "string-width": "1.0.2" + "string-width": "^1.0.1" }, "dependencies": { "ansi-regex": { @@ -1514,7 +1734,7 @@ "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, "requires": { - "number-is-nan": "1.0.1" + "number-is-nan": "^1.0.0" } }, "string-width": { @@ -1523,9 +1743,9 @@ "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" } }, "strip-ansi": { @@ -1534,7 +1754,7 @@ "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { - "ansi-regex": "2.1.1" + "ansi-regex": "^2.0.0" } } } @@ -1542,24 +1762,23 @@ "cli-width": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", - "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", - "dev": true + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=" }, "cliui": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.0.0.tgz", - "integrity": "sha512-nY3W5Gu2racvdDk//ELReY+dHjb9PlIcVDFXP72nVIhq2Gy3LuVXYwJoPVudwQnv1shtohpgkdCKT2YaKY0CKw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", "dev": true, "requires": { - "string-width": "2.1.1", - "strip-ansi": "4.0.0", - "wrap-ansi": "2.1.0" + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" } }, "clone": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.3.tgz", - "integrity": "sha1-KY1+IjFmD0DAA8LtMUDezz9TCF8=", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", "dev": true }, "clone-buffer": { @@ -1574,7 +1793,7 @@ "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", "dev": true, "requires": { - "mimic-response": "1.0.0" + "mimic-response": "^1.0.0" } }, "clone-stats": { @@ -1589,42 +1808,48 @@ "integrity": "sha512-Bq6+4t+lbM8vhTs/Bef5c5AdEMtapp/iFb6+s4/Hh9MVTt8OLKH7ZOOZSCT+Ys7hsHvqv0GuMPJ1lnQJVHvxpg==", "dev": true, "requires": { - "inherits": "2.0.3", - "process-nextick-args": "2.0.0", - "readable-stream": "2.3.5" + "inherits": "^2.0.1", + "process-nextick-args": "^2.0.0", + "readable-stream": "^2.3.5" } }, "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" }, "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } }, "color-convert": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", - "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", - "dev": true, + "integrity": "sha1-wSYRB66y8pTr/+ye2eytUppgl+0=", "requires": { - "color-name": "1.1.3" + "color-name": "^1.1.1" } }, "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "colors": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.2.1.tgz", - "integrity": "sha512-s8+wktIuDSLffCywiwSxQOMqtPxML11a/dtHE17tMn4B1MSWw/C22EKf7M2KGUBcDaVFEGT+S8N02geDXeuNKg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.0.tgz", + "integrity": "sha512-EDpX3a7wHMWFA7PUHWPHNWqOxIIRSJetuwl0AS5Oi/5FMV8kWm69RTlgm00GKjBO1xFHMtBbL49yRtMMdticBw==", "dev": true }, "combined-stream": { @@ -1633,14 +1858,13 @@ "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", "dev": true, "requires": { - "delayed-stream": "1.0.0" + "delayed-stream": "~1.0.0" } }, "commander": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.3.0.tgz", - "integrity": "sha1-/UMOiJgy7DU7ms0d4hfBHLPu+HM=", - "dev": true + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", + "integrity": "sha1-FXFS/R56bI2YpbcVzzdt+SgARWM=" }, "commondir": { "version": "1.0.1", @@ -1648,11 +1872,27 @@ "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", "dev": true }, + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", + "dev": true + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha1-kEvfGUzTEi/Gdcd/xKw9T/D9GjQ=", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } }, "content-disposition": { "version": "0.5.2", @@ -1663,7 +1903,7 @@ "content-type": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "integrity": "sha1-4TjMdeBAxyexlm/l5fjJruJW/js=", "dev": true }, "convert-source-map": { @@ -1684,6 +1924,12 @@ "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", "dev": true }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true + }, "core-js": { "version": "2.5.3", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.3.tgz", @@ -1692,8 +1938,7 @@ "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, "cors": { "version": "2.8.4", @@ -1701,8 +1946,8 @@ "integrity": "sha1-K9OB8usgECAQXNUOpZ2mMJBpRoY=", "dev": true, "requires": { - "object-assign": "4.1.1", - "vary": "1.1.2" + "object-assign": "^4", + "vary": "^1" } }, "cross-spawn": { @@ -1711,31 +1956,11 @@ "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "dev": true, "requires": { - "nice-try": "1.0.4", - "path-key": "2.0.1", - "semver": "5.5.0", - "shebang-command": "1.2.0", - "which": "1.3.0" - } - }, - "cryptiles": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz", - "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=", - "dev": true, - "requires": { - "boom": "5.2.0" - }, - "dependencies": { - "boom": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz", - "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", - "dev": true, - "requires": { - "hoek": "4.2.1" - } - } + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" } }, "crypto-js": { @@ -1756,7 +1981,7 @@ "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", "dev": true, "requires": { - "assert-plus": "1.0.0" + "assert-plus": "^1.0.0" } }, "date-fns": { @@ -1780,7 +2005,7 @@ "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=", "requires": { "ms": "2.0.0" } @@ -1788,8 +2013,7 @@ "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" }, "decode-uri-component": { "version": "0.2.0", @@ -1803,29 +2027,115 @@ "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", "dev": true, "requires": { - "mimic-response": "1.0.0" + "mimic-response": "^1.0.0" } }, "deep-eql": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", - "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "integrity": "sha1-38lARACtHI/gI+faHfHBR8S0RN8=", "dev": true, "requires": { - "type-detect": "4.0.8" + "type-detect": "^4.0.0" } }, "deep-extend": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz", - "integrity": "sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8=", + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.5.1.tgz", + "integrity": "sha512-N8vBdOa+DF7zkRrDCsaOXoCs/E2fJfx9B9MrKnnSiHNh4ws7eSys6YQE4KvT1cecKmOASYQBhbKjeuDD9lT81w==", "dev": true }, "deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } + } + }, + "del": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", + "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", + "requires": { + "globby": "^5.0.0", + "is-path-cwd": "^1.0.0", + "is-path-in-cwd": "^1.0.0", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "rimraf": "^2.2.8" + }, + "dependencies": { + "globby": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", + "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", + "requires": { + "array-union": "^1.0.1", + "arrify": "^1.0.0", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + } + } }, "delayed-stream": { "version": "1.0.0", @@ -1857,7 +2167,7 @@ "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", "dev": true, "requires": { - "repeating": "2.0.1" + "repeating": "^2.0.0" } }, "diff": { @@ -1866,6 +2176,24 @@ "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", "dev": true }, + "dir-glob": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.0.0.tgz", + "integrity": "sha512-37qirFDz8cA5fimp9feo43fSuRo2gHwaIn6dXL8Ber1dGwUosDrGZeCCXq57WnIqE4aQ+u3eQZzsk1yOzhdwag==", + "dev": true, + "requires": { + "arrify": "^1.0.1", + "path-type": "^3.0.0" + } + }, + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha1-XNAfwQFiG0LEzX9dGmYkNxbT850=", + "requires": { + "esutils": "^2.0.2" + } + }, "dom-walk": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.1.tgz", @@ -1885,7 +2213,7 @@ "dev": true, "optional": true, "requires": { - "jsbn": "0.1.1" + "jsbn": "~0.1.0" } }, "editions": { @@ -1901,15 +2229,15 @@ "dev": true }, "ejs": { - "version": "2.5.7", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.5.7.tgz", - "integrity": "sha1-zIcsFoiArjxxiXYv1f/ACJbJUYo=", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.6.1.tgz", + "integrity": "sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ==", "dev": true }, "electron-to-chromium": { - "version": "1.3.42", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.42.tgz", - "integrity": "sha1-lcM78B0MxAVVauyJn+Yf1NduoPk=", + "version": "1.3.48", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.48.tgz", + "integrity": "sha1-07DYWTgUBE4JLs4hCPw6ya6kuQA=", "dev": true }, "elegant-spinner": { @@ -1924,13 +2252,13 @@ "integrity": "sha1-ysmvh2LIWDYYcAPI3+GT5eLq5d8=", "dev": true, "requires": { - "bn.js": "4.11.6", - "brorand": "1.1.0", - "hash.js": "1.1.3", - "hmac-drbg": "1.0.1", - "inherits": "2.0.3", - "minimalistic-assert": "1.0.0", - "minimalistic-crypto-utils": "1.0.1" + "bn.js": "^4.4.0", + "brorand": "^1.0.1", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.0" } }, "emojis-list": { @@ -1951,18 +2279,24 @@ "integrity": "sha512-jox/62b2GofV1qTUQTMPEJSDIGycS43evqYzD/KVtEb9OCoki9cnacUPxCrZa7JfPzZSYOCZhu9O9luaMxAX8g==", "dev": true, "requires": { - "graceful-fs": "4.1.11", - "memory-fs": "0.4.1", - "tapable": "1.0.0" + "graceful-fs": "^4.1.2", + "memory-fs": "^0.4.0", + "tapable": "^1.0.0" } }, + "envinfo": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-5.10.0.tgz", + "integrity": "sha512-rXbzXWvnQxy+TcqZlARbWVQwgGVVouVJgFZhLVN5htjLxl1thstrP2ZGi0pXC309AbK7gVOPU+ulz/tmpCI7iw==", + "dev": true + }, "errno": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", "dev": true, "requires": { - "prr": "1.0.1" + "prr": "~1.0.1" } }, "error": { @@ -1971,17 +2305,16 @@ "integrity": "sha1-pfdf/02ZJhJt2sDqXcOOaJFTywI=", "dev": true, "requires": { - "string-template": "0.2.1", - "xtend": "4.0.1" + "string-template": "~0.2.1", + "xtend": "~4.0.0" } }, "error-ex": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", - "dev": true, "requires": { - "is-arrayish": "0.2.1" + "is-arrayish": "^0.2.1" } }, "escape-html": { @@ -2001,11 +2334,11 @@ "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=", "dev": true, "requires": { - "esprima": "2.7.3", - "estraverse": "1.9.3", - "esutils": "2.0.2", - "optionator": "0.8.2", - "source-map": "0.2.0" + "esprima": "^2.7.1", + "estraverse": "^1.9.1", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.2.0" }, "dependencies": { "esprima": { @@ -2014,6 +2347,12 @@ "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", "dev": true }, + "estraverse": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", + "integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=", + "dev": true + }, "source-map": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", @@ -2021,26 +2360,154 @@ "dev": true, "optional": true, "requires": { - "amdefine": "1.0.1" + "amdefine": ">=0.0.4" } } } }, - "esprima": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", - "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", - "dev": true - }, - "estraverse": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", - "integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=", - "dev": true - }, - "esutils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "eslint": { + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.19.1.tgz", + "integrity": "sha1-MtHWU+HZBAiFS/spbwdux+GGowA=", + "requires": { + "ajv": "^5.3.0", + "babel-code-frame": "^6.22.0", + "chalk": "^2.1.0", + "concat-stream": "^1.6.0", + "cross-spawn": "^5.1.0", + "debug": "^3.1.0", + "doctrine": "^2.1.0", + "eslint-scope": "^3.7.1", + "eslint-visitor-keys": "^1.0.0", + "espree": "^3.5.4", + "esquery": "^1.0.0", + "esutils": "^2.0.2", + "file-entry-cache": "^2.0.0", + "functional-red-black-tree": "^1.0.1", + "glob": "^7.1.2", + "globals": "^11.0.1", + "ignore": "^3.3.3", + "imurmurhash": "^0.1.4", + "inquirer": "^3.0.6", + "is-resolvable": "^1.0.0", + "js-yaml": "^3.9.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.4", + "minimatch": "^3.0.2", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "path-is-inside": "^1.0.2", + "pluralize": "^7.0.0", + "progress": "^2.0.0", + "regexpp": "^1.0.1", + "require-uncached": "^1.0.3", + "semver": "^5.3.0", + "strip-ansi": "^4.0.0", + "strip-json-comments": "~2.0.1", + "table": "4.0.2", + "text-table": "~0.2.0" + }, + "dependencies": { + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", + "requires": { + "ms": "2.0.0" + } + }, + "globals": { + "version": "11.5.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.5.0.tgz", + "integrity": "sha1-a8hA3mdxFzsZHxPTqclNRB7pJkI=" + }, + "inquirer": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz", + "integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==", + "requires": { + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.0", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^2.0.4", + "figures": "^2.0.0", + "lodash": "^4.3.0", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rx-lite": "^4.0.8", + "rx-lite-aggregates": "^4.0.8", + "string-width": "^2.1.0", + "strip-ansi": "^4.0.0", + "through": "^2.3.6" + } + } + } + }, + "eslint-scope": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.1.tgz", + "integrity": "sha1-PWPD7f2gLgbgGkUq2IyqzHzctug=", + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "eslint-visitor-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", + "integrity": "sha1-PzGA+y4pEBdxastMnW1bXDSmqB0=" + }, + "espree": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", + "integrity": "sha1-sPRHGHyKi+2US4FaZgvd9d610ac=", + "requires": { + "acorn": "^5.5.0", + "acorn-jsx": "^3.0.0" + } + }, + "esprima": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", + "integrity": "sha1-RJnt3NERDgshi6zy+n9/WfVcqAQ=" + }, + "esquery": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", + "integrity": "sha1-QGxRZYsfWZGl+bYrHcJbAOPlxwg=", + "requires": { + "estraverse": "^4.0.0" + } + }, + "esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha1-AHo7n9vCs7uH5IeeoZyS/b05Qs8=", + "requires": { + "estraverse": "^4.1.0" + } + }, + "estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=" + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=" }, "etag": { @@ -2052,26 +2519,26 @@ "eth-lib": { "version": "0.1.27", "resolved": "https://registry.npmjs.org/eth-lib/-/eth-lib-0.1.27.tgz", - "integrity": "sha512-B8czsfkJYzn2UIEMwjc7Mbj+Cy72V+/OXH/tb44LV8jhrjizQJJ325xMOMyk3+ETa6r6oi0jsUY14+om8mQMWA==", + "integrity": "sha1-8LD9FE+GXS1r+CV6QABPLnXKHdY=", "dev": true, "requires": { - "bn.js": "4.11.6", - "elliptic": "6.4.0", - "keccakjs": "0.2.1", - "nano-json-stream-parser": "0.1.2", - "servify": "0.1.12", - "ws": "3.3.3", - "xhr-request-promise": "0.1.2" + "bn.js": "^4.11.6", + "elliptic": "^6.4.0", + "keccakjs": "^0.2.1", + "nano-json-stream-parser": "^0.1.2", + "servify": "^0.1.12", + "ws": "^3.0.0", + "xhr-request-promise": "^0.1.2" } }, "ethereumjs-testrpc-sc": { "version": "6.1.2", "resolved": "https://registry.npmjs.org/ethereumjs-testrpc-sc/-/ethereumjs-testrpc-sc-6.1.2.tgz", - "integrity": "sha512-dBTav4AZQ7zuajmICv1k7bEesqS+8f0u0wciXNUJZb842RTBi0lgKEDF8WgZshzv4ThI+XVQSRNV/A+seiK4aA==", + "integrity": "sha1-vR2DBquy1RSB8/AVOPpx+3/USk0=", "dev": true, "requires": { - "source-map-support": "0.5.4", - "webpack-cli": "2.0.12" + "source-map-support": "^0.5.3", + "webpack-cli": "^2.0.9" } }, "ethjs-unit": { @@ -2090,13 +2557,13 @@ "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", "dev": true, "requires": { - "cross-spawn": "5.1.0", - "get-stream": "3.0.0", - "is-stream": "1.1.0", - "npm-run-path": "2.0.2", - "p-finally": "1.0.0", - "signal-exit": "3.0.2", - "strip-eof": "1.0.0" + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" }, "dependencies": { "cross-spawn": { @@ -2105,9 +2572,9 @@ "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", "dev": true, "requires": { - "lru-cache": "4.1.2", - "shebang-command": "1.2.0", - "which": "1.3.0" + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" } } } @@ -2124,7 +2591,7 @@ "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", "dev": true, "requires": { - "is-posix-bracket": "0.1.1" + "is-posix-bracket": "^0.1.0" } }, "expand-range": { @@ -2133,7 +2600,7 @@ "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", "dev": true, "requires": { - "fill-range": "2.2.3" + "fill-range": "^2.1.0" } }, "expand-tilde": { @@ -2142,7 +2609,7 @@ "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", "dev": true, "requires": { - "homedir-polyfill": "1.0.1" + "homedir-polyfill": "^1.0.1" } }, "express": { @@ -2151,42 +2618,104 @@ "integrity": "sha1-avilAjUNsyRuzEvs9rWjTSL37VM=", "dev": true, "requires": { - "accepts": "1.3.5", + "accepts": "~1.3.5", "array-flatten": "1.1.1", "body-parser": "1.18.2", "content-disposition": "0.5.2", - "content-type": "1.0.4", + "content-type": "~1.0.4", "cookie": "0.3.1", "cookie-signature": "1.0.6", "debug": "2.6.9", - "depd": "1.1.2", - "encodeurl": "1.0.2", - "escape-html": "1.0.3", - "etag": "1.8.1", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", "finalhandler": "1.1.1", "fresh": "0.5.2", "merge-descriptors": "1.0.1", - "methods": "1.1.2", - "on-finished": "2.3.0", - "parseurl": "1.3.2", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", "path-to-regexp": "0.1.7", - "proxy-addr": "2.0.3", + "proxy-addr": "~2.0.3", "qs": "6.5.1", - "range-parser": "1.2.0", + "range-parser": "~1.2.0", "safe-buffer": "5.1.1", "send": "0.16.2", "serve-static": "1.13.2", "setprototypeof": "1.1.0", - "statuses": "1.4.0", - "type-is": "1.6.16", + "statuses": "~1.4.0", + "type-is": "~1.6.16", "utils-merge": "1.0.1", - "vary": "1.1.2" + "vary": "~1.1.2" }, "dependencies": { - "setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "body-parser": { + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", + "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", + "dev": true, + "requires": { + "bytes": "3.0.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.1", + "http-errors": "~1.6.2", + "iconv-lite": "0.4.19", + "on-finished": "~2.3.0", + "qs": "6.5.1", + "raw-body": "2.3.2", + "type-is": "~1.6.15" + } + }, + "qs": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", + "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==", + "dev": true + }, + "raw-body": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", + "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", + "dev": true, + "requires": { + "bytes": "3.0.0", + "http-errors": "1.6.2", + "iconv-lite": "0.4.19", + "unpipe": "1.0.0" + }, + "dependencies": { + "depd": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", + "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=", + "dev": true + }, + "http-errors": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", + "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", + "dev": true, + "requires": { + "depd": "1.1.1", + "inherits": "2.0.3", + "setprototypeof": "1.0.3", + "statuses": ">= 1.3.1 < 2" + } + }, + "setprototypeof": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", + "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=", + "dev": true + } + } + }, + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", "dev": true } } @@ -2197,15 +2726,35 @@ "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=", "dev": true }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, "external-editor": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.1.0.tgz", "integrity": "sha512-E44iT5QVOUJBKij4IIV3uvxuNlbKS38Tw1HiupxEIHPv9qtC2PrDYohbXV5U+1jnfIXttny8gUhj+oZvflFlzA==", - "dev": true, "requires": { - "chardet": "0.4.2", - "iconv-lite": "0.4.19", - "tmp": "0.0.33" + "chardet": "^0.4.0", + "iconv-lite": "^0.4.17", + "tmp": "^0.0.33" } }, "extglob": { @@ -2214,7 +2763,7 @@ "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", "dev": true, "requires": { - "is-extglob": "1.0.0" + "is-extglob": "^1.0.0" } }, "extsprintf": { @@ -2226,28 +2775,359 @@ "fast-deep-equal": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", - "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", - "dev": true + "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" + }, + "fast-glob": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-2.2.2.tgz", + "integrity": "sha512-TR6zxCKftDQnUAPvkrCWdBgDq/gbqx8A3ApnBrR5rMvpp6+KMJI0Igw7fkWPgeVK0uhRXTXdvO3O+YP0CaUX2g==", + "dev": true, + "requires": { + "@mrmlnc/readdir-enhanced": "^2.2.1", + "@nodelib/fs.stat": "^1.0.1", + "glob-parent": "^3.1.0", + "is-glob": "^4.0.0", + "merge2": "^1.2.1", + "micromatch": "^3.1.10" + }, + "dependencies": { + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-glob": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", + "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + } + } }, "fast-json-stable-stringify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", - "dev": true + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" }, "fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" }, "figures": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", - "dev": true, "requires": { - "escape-string-regexp": "1.0.5" + "escape-string-regexp": "^1.0.5" + } + }, + "file-entry-cache": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", + "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", + "requires": { + "flat-cache": "^1.2.1", + "object-assign": "^4.0.1" } }, "filename-regex": { @@ -2257,31 +3137,39 @@ "dev": true }, "fill-range": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz", - "integrity": "sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM=", + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", + "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", "dev": true, "requires": { - "is-number": "2.1.0", - "isobject": "2.1.0", - "randomatic": "1.1.7", - "repeat-element": "1.1.2", - "repeat-string": "1.6.1" + "is-number": "^2.1.0", + "isobject": "^2.0.0", + "randomatic": "^3.0.0", + "repeat-element": "^1.1.2", + "repeat-string": "^1.5.2" } }, "finalhandler": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", - "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", + "integrity": "sha1-7r9O2EAHnIP0JJA4ydcDAIMBsQU=", "dev": true, "requires": { "debug": "2.6.9", - "encodeurl": "1.0.2", - "escape-html": "1.0.3", - "on-finished": "2.3.0", - "parseurl": "1.3.2", - "statuses": "1.4.0", - "unpipe": "1.0.0" + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "statuses": "~1.4.0", + "unpipe": "~1.0.0" + }, + "dependencies": { + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", + "dev": true + } } }, "find-up": { @@ -2290,7 +3178,7 @@ "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", "dev": true, "requires": { - "locate-path": "2.0.0" + "locate-path": "^2.0.0" } }, "first-chunk-stream": { @@ -2299,22 +3187,33 @@ "integrity": "sha1-G97NuOCDwGZLkZRVgVd6Q6nzHXA=", "dev": true, "requires": { - "readable-stream": "2.3.5" + "readable-stream": "^2.0.2" + } + }, + "flat-cache": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.0.tgz", + "integrity": "sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE=", + "requires": { + "circular-json": "^0.3.1", + "del": "^2.0.2", + "graceful-fs": "^4.1.2", + "write": "^0.2.1" } }, "flow-parser": { - "version": "0.68.0", - "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.68.0.tgz", - "integrity": "sha1-nMlmIKEC4xajFLa81WIFzqzoYtg=", + "version": "0.74.0", + "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.74.0.tgz", + "integrity": "sha512-iQHi88aFCkPLr8cW1L9FtP9lmiT/9g20YaycW6sSWX6U9EdwN6K6OkWBlLhrfG5rbDJfJ9k0npVSfAkGNR7x2Q==", "dev": true }, "for-each": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.2.tgz", - "integrity": "sha1-LEBFC5NI6X8oEyJZO6lnBLmr1NQ=", + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", "dev": true, "requires": { - "is-function": "1.0.1" + "is-callable": "^1.1.3" } }, "for-in": { @@ -2329,7 +3228,7 @@ "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", "dev": true, "requires": { - "for-in": "1.0.2" + "for-in": "^1.0.1" } }, "forever-agent": { @@ -2344,9 +3243,9 @@ "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", "dev": true, "requires": { - "asynckit": "0.4.0", + "asynckit": "^0.4.0", "combined-stream": "1.0.6", - "mime-types": "2.1.18" + "mime-types": "^2.1.12" } }, "forwarded": { @@ -2355,6 +3254,15 @@ "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", "dev": true }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "requires": { + "map-cache": "^0.2.2" + } + }, "fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", @@ -2367,44 +3275,42 @@ "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", "dev": true, "requires": { - "inherits": "2.0.3", - "readable-stream": "2.3.5" + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" } }, "fs-extra": { "version": "0.30.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz", "integrity": "sha1-8jP/zAjU2n1DLapEl3aYnbHfk/A=", - "dev": true, "requires": { - "graceful-fs": "4.1.11", - "jsonfile": "2.4.0", - "klaw": "1.3.1", - "path-is-absolute": "1.0.1", - "rimraf": "2.2.8" + "graceful-fs": "^4.1.2", + "jsonfile": "^2.1.0", + "klaw": "^1.0.0", + "path-is-absolute": "^1.0.0", + "rimraf": "^2.2.8" } }, "fs-readdir-recursive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", - "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==", + "integrity": "sha1-4y/AMKLM7kSmtTcTCNpUvgs5fSc=", "dev": true }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "fsevents": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.3.tgz", - "integrity": "sha512-X+57O5YkDTiEQGiw8i7wYc2nQgweIekqkepI8Q3y4wVlurgBt2SuwxTeYUYMZIGpLZH3r/TsMjczCMXE5ZOt7Q==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.4.tgz", + "integrity": "sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg==", "dev": true, "optional": true, "requires": { - "nan": "2.10.0", - "node-pre-gyp": "0.9.1" + "nan": "^2.9.2", + "node-pre-gyp": "^0.10.0" }, "dependencies": { "abbrev": { @@ -2430,8 +3336,8 @@ "dev": true, "optional": true, "requires": { - "delegates": "1.0.0", - "readable-stream": "2.3.6" + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" } }, "balanced-match": { @@ -2444,7 +3350,7 @@ "bundled": true, "dev": true, "requires": { - "balanced-match": "1.0.0", + "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, @@ -2485,7 +3391,7 @@ } }, "deep-extend": { - "version": "0.4.2", + "version": "0.5.1", "bundled": true, "dev": true, "optional": true @@ -2508,7 +3414,7 @@ "dev": true, "optional": true, "requires": { - "minipass": "2.2.4" + "minipass": "^2.2.1" } }, "fs.realpath": { @@ -2523,14 +3429,14 @@ "dev": true, "optional": true, "requires": { - "aproba": "1.2.0", - "console-control-strings": "1.1.0", - "has-unicode": "2.0.1", - "object-assign": "4.1.1", - "signal-exit": "3.0.2", - "string-width": "1.0.2", - "strip-ansi": "3.0.1", - "wide-align": "1.1.2" + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" } }, "glob": { @@ -2539,12 +3445,12 @@ "dev": true, "optional": true, "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } }, "has-unicode": { @@ -2559,7 +3465,7 @@ "dev": true, "optional": true, "requires": { - "safer-buffer": "2.1.2" + "safer-buffer": "^2.1.0" } }, "ignore-walk": { @@ -2568,7 +3474,7 @@ "dev": true, "optional": true, "requires": { - "minimatch": "3.0.4" + "minimatch": "^3.0.4" } }, "inflight": { @@ -2577,8 +3483,8 @@ "dev": true, "optional": true, "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" + "once": "^1.3.0", + "wrappy": "1" } }, "inherits": { @@ -2597,7 +3503,7 @@ "bundled": true, "dev": true, "requires": { - "number-is-nan": "1.0.1" + "number-is-nan": "^1.0.0" } }, "isarray": { @@ -2611,7 +3517,7 @@ "bundled": true, "dev": true, "requires": { - "brace-expansion": "1.1.11" + "brace-expansion": "^1.1.7" } }, "minimist": { @@ -2624,8 +3530,8 @@ "bundled": true, "dev": true, "requires": { - "safe-buffer": "5.1.1", - "yallist": "3.0.2" + "safe-buffer": "^5.1.1", + "yallist": "^3.0.0" } }, "minizlib": { @@ -2634,7 +3540,7 @@ "dev": true, "optional": true, "requires": { - "minipass": "2.2.4" + "minipass": "^2.2.1" } }, "mkdirp": { @@ -2657,27 +3563,27 @@ "dev": true, "optional": true, "requires": { - "debug": "2.6.9", - "iconv-lite": "0.4.21", - "sax": "1.2.4" + "debug": "^2.1.2", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" } }, "node-pre-gyp": { - "version": "0.9.1", + "version": "0.10.0", "bundled": true, "dev": true, "optional": true, "requires": { - "detect-libc": "1.0.3", - "mkdirp": "0.5.1", - "needle": "2.2.0", - "nopt": "4.0.1", - "npm-packlist": "1.1.10", - "npmlog": "4.1.2", - "rc": "1.2.6", - "rimraf": "2.6.2", - "semver": "5.5.0", - "tar": "4.4.1" + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.0", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.1.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" } }, "nopt": { @@ -2686,8 +3592,8 @@ "dev": true, "optional": true, "requires": { - "abbrev": "1.1.1", - "osenv": "0.1.5" + "abbrev": "1", + "osenv": "^0.1.4" } }, "npm-bundled": { @@ -2702,8 +3608,8 @@ "dev": true, "optional": true, "requires": { - "ignore-walk": "3.0.1", - "npm-bundled": "1.0.3" + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" } }, "npmlog": { @@ -2712,10 +3618,10 @@ "dev": true, "optional": true, "requires": { - "are-we-there-yet": "1.1.4", - "console-control-strings": "1.1.0", - "gauge": "2.7.4", - "set-blocking": "2.0.0" + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" } }, "number-is-nan": { @@ -2734,7 +3640,7 @@ "bundled": true, "dev": true, "requires": { - "wrappy": "1.0.2" + "wrappy": "1" } }, "os-homedir": { @@ -2755,8 +3661,8 @@ "dev": true, "optional": true, "requires": { - "os-homedir": "1.0.2", - "os-tmpdir": "1.0.2" + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" } }, "path-is-absolute": { @@ -2772,15 +3678,15 @@ "optional": true }, "rc": { - "version": "1.2.6", + "version": "1.2.7", "bundled": true, "dev": true, "optional": true, "requires": { - "deep-extend": "0.4.2", - "ini": "1.3.5", - "minimist": "1.2.0", - "strip-json-comments": "2.0.1" + "deep-extend": "^0.5.1", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" }, "dependencies": { "minimist": { @@ -2797,13 +3703,13 @@ "dev": true, "optional": true, "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "2.0.0", - "safe-buffer": "5.1.1", - "string_decoder": "1.1.1", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, "rimraf": { @@ -2812,7 +3718,7 @@ "dev": true, "optional": true, "requires": { - "glob": "7.1.2" + "glob": "^7.0.5" } }, "safe-buffer": { @@ -2855,9 +3761,9 @@ "bundled": true, "dev": true, "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" } }, "string_decoder": { @@ -2866,7 +3772,7 @@ "dev": true, "optional": true, "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "~5.1.0" } }, "strip-ansi": { @@ -2874,7 +3780,7 @@ "bundled": true, "dev": true, "requires": { - "ansi-regex": "2.1.1" + "ansi-regex": "^2.0.0" } }, "strip-json-comments": { @@ -2889,13 +3795,13 @@ "dev": true, "optional": true, "requires": { - "chownr": "1.0.1", - "fs-minipass": "1.2.5", - "minipass": "2.2.4", - "minizlib": "1.1.0", - "mkdirp": "0.5.1", - "safe-buffer": "5.1.1", - "yallist": "3.0.2" + "chownr": "^1.0.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.2.4", + "minizlib": "^1.1.0", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.1", + "yallist": "^3.0.2" } }, "util-deprecate": { @@ -2910,7 +3816,7 @@ "dev": true, "optional": true, "requires": { - "string-width": "1.0.2" + "string-width": "^1.0.2" } }, "wrappy": { @@ -2925,21 +3831,25 @@ } } }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=" + }, "ganache-cli": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ganache-cli/-/ganache-cli-6.1.0.tgz", - "integrity": "sha512-FdTeyk4uLRHGeFiMe+Qnh4Hc5KiTVqvRVVvLDFJEVVKC1P1yHhEgZeh9sp1KhuvxSrxToxgJS25UapYQwH4zHw==", + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/ganache-cli/-/ganache-cli-6.1.3.tgz", + "integrity": "sha512-6g/jPkhEMBlefOUQR0AXQooTCsLFD4iOvTDmsqi/B3XOktB9KGSioDaEy5lCcgzJczzUHT1YCZXhrZcUgtNkkg==", "dev": true, "requires": { - "source-map-support": "0.5.4", - "webpack-cli": "2.0.12" + "source-map-support": "^0.5.3", + "webpack-cli": "^2.0.9" } }, "get-caller-file": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz", - "integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=", - "dev": true + "integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=" }, "get-func-name": { "version": "2.0.0", @@ -2953,13 +3863,19 @@ "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", "dev": true }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true + }, "getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", "dev": true, "requires": { - "assert-plus": "1.0.0" + "assert-plus": "^1.0.0" } }, "gh-got": { @@ -2968,8 +3884,8 @@ "integrity": "sha512-F/mS+fsWQMo1zfgG9MD8KWvTWPPzzhuVwY++fhQ5Ggd+0P+CAMHtzMZhNxG+TqGfHDChJKsbh6otfMGqO2AKBw==", "dev": true, "requires": { - "got": "7.1.0", - "is-plain-obj": "1.1.0" + "got": "^7.0.0", + "is-plain-obj": "^1.1.0" }, "dependencies": { "got": { @@ -2978,20 +3894,20 @@ "integrity": "sha512-Y5WMo7xKKq1muPsxD+KmrR8DH5auG7fBdDVueZwETwV6VytKyU9OX/ddpq2/1hp1vIPvVb4T81dKQz3BivkNLw==", "dev": true, "requires": { - "decompress-response": "3.3.0", - "duplexer3": "0.1.4", - "get-stream": "3.0.0", - "is-plain-obj": "1.1.0", - "is-retry-allowed": "1.1.0", - "is-stream": "1.1.0", - "isurl": "1.0.0", - "lowercase-keys": "1.0.0", - "p-cancelable": "0.3.0", - "p-timeout": "1.2.1", - "safe-buffer": "5.1.1", - "timed-out": "4.0.1", - "url-parse-lax": "1.0.0", - "url-to-options": "1.0.1" + "decompress-response": "^3.2.0", + "duplexer3": "^0.1.4", + "get-stream": "^3.0.0", + "is-plain-obj": "^1.1.0", + "is-retry-allowed": "^1.0.0", + "is-stream": "^1.0.0", + "isurl": "^1.0.0-alpha5", + "lowercase-keys": "^1.0.0", + "p-cancelable": "^0.3.0", + "p-timeout": "^1.1.1", + "safe-buffer": "^5.0.1", + "timed-out": "^4.0.0", + "url-parse-lax": "^1.0.0", + "url-to-options": "^1.0.1" } }, "p-cancelable": { @@ -3006,7 +3922,7 @@ "integrity": "sha1-XrOzU7f86Z8QGhA4iAuwVOu+o4Y=", "dev": true, "requires": { - "p-finally": "1.0.0" + "p-finally": "^1.0.0" } }, "prepend-http": { @@ -3021,7 +3937,7 @@ "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", "dev": true, "requires": { - "prepend-http": "1.0.4" + "prepend-http": "^1.0.1" } } } @@ -3032,21 +3948,20 @@ "integrity": "sha1-y+KABBiDIG2kISrp5LXxacML9Bc=", "dev": true, "requires": { - "gh-got": "6.0.0" + "gh-got": "^6.0.0" } }, "glob": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "dev": true, + "integrity": "sha1-wZyd+aAocC1nhhI4SmVSQExjbRU=", "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } }, "glob-all": { @@ -3055,8 +3970,8 @@ "integrity": "sha1-iRPd+17hrHgSZWJBsD1SF8ZLAqs=", "dev": true, "requires": { - "glob": "7.1.2", - "yargs": "1.2.6" + "glob": "^7.0.5", + "yargs": "~1.2.6" }, "dependencies": { "yargs": { @@ -3065,7 +3980,7 @@ "integrity": "sha1-nHtKgv1dWVsr8Xq23MQxNUMv40s=", "dev": true, "requires": { - "minimist": "0.1.0" + "minimist": "^0.1.0" } } } @@ -3076,8 +3991,8 @@ "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", "dev": true, "requires": { - "glob-parent": "2.0.0", - "is-glob": "2.0.1" + "glob-parent": "^2.0.0", + "is-glob": "^2.0.0" } }, "glob-parent": { @@ -3086,17 +4001,23 @@ "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", "dev": true, "requires": { - "is-glob": "2.0.1" + "is-glob": "^2.0.0" } }, + "glob-to-regexp": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz", + "integrity": "sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=", + "dev": true + }, "global": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/global/-/global-4.3.2.tgz", "integrity": "sha1-52mJJopsdMOJCLEwWxD8DjlOnQ8=", "dev": true, "requires": { - "min-document": "2.19.0", - "process": "0.5.2" + "min-document": "^2.19.0", + "process": "~0.5.1" } }, "global-modules": { @@ -3105,9 +4026,9 @@ "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", "dev": true, "requires": { - "global-prefix": "1.0.2", - "is-windows": "1.0.2", - "resolve-dir": "1.0.1" + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" } }, "global-prefix": { @@ -3116,75 +4037,62 @@ "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", "dev": true, "requires": { - "expand-tilde": "2.0.2", - "homedir-polyfill": "1.0.1", - "ini": "1.3.5", - "is-windows": "1.0.2", - "which": "1.3.0" + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" } }, "globals": { "version": "9.18.0", "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==" + "integrity": "sha1-qjiWs+abSH8X4x7SFD1pqOMMLYo=" }, "globby": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", - "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-8.0.1.tgz", + "integrity": "sha512-oMrYrJERnKBLXNLVTqhm3vPEdJ/b2ZE28xN4YARiix1NOIOBPEpOUnm844K1iu/BkphCaf2WNFwMszv8Soi1pw==", "dev": true, "requires": { - "array-union": "1.0.2", - "glob": "7.1.2", - "object-assign": "4.1.1", - "pify": "2.3.0", - "pinkie-promise": "2.0.1" - }, - "dependencies": { - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - } + "array-union": "^1.0.1", + "dir-glob": "^2.0.0", + "fast-glob": "^2.0.2", + "glob": "^7.1.2", + "ignore": "^3.3.5", + "pify": "^3.0.0", + "slash": "^1.0.0" } }, "got": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/got/-/got-8.3.0.tgz", - "integrity": "sha512-kBNy/S2CGwrYgDSec5KTWGKUvupwkkTVAjIsVFF2shXO13xpZdFP4d4kxa//CLX2tN/rV0aYwK8vY6UKWGn2vQ==", - "dev": true, - "requires": { - "@sindresorhus/is": "0.7.0", - "cacheable-request": "2.1.4", - "decompress-response": "3.3.0", - "duplexer3": "0.1.4", - "get-stream": "3.0.0", - "into-stream": "3.1.0", - "is-retry-allowed": "1.1.0", - "isurl": "1.0.0", - "lowercase-keys": "1.0.0", - "mimic-response": "1.0.0", - "p-cancelable": "0.4.0", - "p-timeout": "2.0.1", - "pify": "3.0.0", - "safe-buffer": "5.1.1", - "timed-out": "4.0.1", - "url-parse-lax": "3.0.0", - "url-to-options": "1.0.1" + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/got/-/got-8.3.1.tgz", + "integrity": "sha512-tiLX+bnYm5A56T5N/n9Xo89vMaO1mrS9qoDqj3u/anVooqGozvY/HbXzEpDfbNeKsHCBpK40gSbz8wGYSp3i1w==", + "dev": true, + "requires": { + "@sindresorhus/is": "^0.7.0", + "cacheable-request": "^2.1.1", + "decompress-response": "^3.3.0", + "duplexer3": "^0.1.4", + "get-stream": "^3.0.0", + "into-stream": "^3.1.0", + "is-retry-allowed": "^1.1.0", + "isurl": "^1.0.0-alpha5", + "lowercase-keys": "^1.0.0", + "mimic-response": "^1.0.0", + "p-cancelable": "^0.4.0", + "p-timeout": "^2.0.1", + "pify": "^3.0.0", + "safe-buffer": "^5.1.1", + "timed-out": "^4.0.1", + "url-parse-lax": "^3.0.0", + "url-to-options": "^1.0.1" } }, "graceful-fs": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", - "dev": true - }, - "graceful-readlink": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", - "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", - "dev": true + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" }, "grouped-queue": { "version": "0.3.3", @@ -3192,13 +4100,13 @@ "integrity": "sha1-wWfSpTGcWg4JZO9qJbfC34mWyFw=", "dev": true, "requires": { - "lodash": "4.17.5" + "lodash": "^4.17.2" } }, "growl": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", - "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=", + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.3.tgz", + "integrity": "sha512-hKlsbA5Vu3xsh1Cg3J7jSmX/WaW6A5oBeqzM88oNbCRQFz+zUaXm6yxS4RVytp1scBoJzSYl4YAEOQIt6O8V1Q==", "dev": true }, "handlebars": { @@ -3207,10 +4115,10 @@ "integrity": "sha1-Ywo13+ApS8KB7a5v/F0yn8eYLcw=", "dev": true, "requires": { - "async": "1.5.2", - "optimist": "0.6.1", - "source-map": "0.4.4", - "uglify-js": "2.8.29" + "async": "^1.4.0", + "optimist": "^0.6.1", + "source-map": "^0.4.4", + "uglify-js": "^2.6" }, "dependencies": { "source-map": { @@ -3219,7 +4127,7 @@ "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", "dev": true, "requires": { - "amdefine": "1.0.1" + "amdefine": ">=0.0.4" } } } @@ -3236,8 +4144,8 @@ "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", "dev": true, "requires": { - "ajv": "5.5.2", - "har-schema": "2.0.0" + "ajv": "^5.1.0", + "har-schema": "^2.0.0" } }, "has-ansi": { @@ -3245,7 +4153,7 @@ "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", "requires": { - "ansi-regex": "2.1.1" + "ansi-regex": "^2.0.0" }, "dependencies": { "ansi-regex": { @@ -3264,8 +4172,7 @@ "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" }, "has-symbol-support-x": { "version": "1.4.2", @@ -3279,29 +4186,77 @@ "integrity": "sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw==", "dev": true, "requires": { - "has-symbol-support-x": "1.4.2" + "has-symbol-support-x": "^1.4.1" } }, - "hash.js": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.3.tgz", - "integrity": "sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA==", + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", "dev": true, "requires": { - "inherits": "2.0.3", - "minimalistic-assert": "1.0.0" + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } } }, - "hawk": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz", - "integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==", + "hash.js": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.4.tgz", + "integrity": "sha512-A6RlQvvZEtFS5fLU43IDu0QUmBy+fDO9VMdTXvufKwIkt/rFfvICAViCax5fbDO4zdNzaC3/27ZhKUok5bAJyw==", "dev": true, "requires": { - "boom": "4.3.1", - "cryptiles": "3.1.2", - "hoek": "4.2.1", - "sntp": "2.1.0" + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.0" } }, "he": { @@ -3316,25 +4271,19 @@ "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", "dev": true, "requires": { - "hash.js": "1.1.3", - "minimalistic-assert": "1.0.0", - "minimalistic-crypto-utils": "1.0.1" + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" } }, - "hoek": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz", - "integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==", - "dev": true - }, "home-or-tmp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=", "dev": true, "requires": { - "os-homedir": "1.0.2", - "os-tmpdir": "1.0.2" + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.1" } }, "homedir-polyfill": { @@ -3343,14 +4292,13 @@ "integrity": "sha1-TCu8inWJmP7r9e1oWA921GdotLw=", "dev": true, "requires": { - "parse-passwd": "1.0.0" + "parse-passwd": "^1.0.0" } }, "hosted-git-info": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.6.0.tgz", - "integrity": "sha512-lIbgIIQA3lz5XaB6vxakj6sDHADJiZadYEJB+FgA+C4nubM1NwcuvUr9EJPmnH1skZqpqUzWborWo8EIUi0Sdw==", - "dev": true + "integrity": "sha1-IyNbKasjDFdqqw1PE/wEawsDgiI=" }, "http-cache-semantics": { "version": "3.8.1", @@ -3359,23 +4307,15 @@ "dev": true }, "http-errors": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", - "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", "dev": true, - "requires": { - "depd": "1.1.1", - "inherits": "2.0.3", - "setprototypeof": "1.0.3", - "statuses": "1.4.0" - }, - "dependencies": { - "depd": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", - "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=", - "dev": true - } + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" } }, "http-signature": { @@ -3384,22 +4324,35 @@ "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", "dev": true, "requires": { - "assert-plus": "1.0.0", - "jsprim": "1.4.1", - "sshpk": "1.14.1" + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" } }, "iconv-lite": { "version": "0.4.19", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", - "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==", - "dev": true + "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" + }, + "ignore": { + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.8.tgz", + "integrity": "sha1-P46cNdOHCKOn4Omrtsc+fudweys=" + }, + "import-local": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-1.0.0.tgz", + "integrity": "sha512-vAaZHieK9qjGo58agRBg+bhHX3hoTZU/Oa3GESWLz7t1U62fk63aHuDJJEteXoDeTCcPmUT+z38gkHPZkkmpmQ==", + "dev": true, + "requires": { + "pkg-dir": "^2.0.0", + "resolve-cwd": "^2.0.0" + } }, "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" }, "indent-string": { "version": "2.1.0", @@ -3407,24 +4360,22 @@ "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", "dev": true, "requires": { - "repeating": "2.0.1" + "repeating": "^2.0.0" } }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" + "once": "^1.3.0", + "wrappy": "1" } }, "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, "ini": { "version": "1.3.5", @@ -3433,24 +4384,24 @@ "dev": true }, "inquirer": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-5.1.0.tgz", - "integrity": "sha512-kn7N70US1MSZHZHSGJLiZ7iCwwncc7b0gc68YtlX29OjI3Mp0tSVV+snVXpZ1G+ONS3Ac9zd1m6hve2ibLDYfA==", - "dev": true, - "requires": { - "ansi-escapes": "3.0.0", - "chalk": "2.3.2", - "cli-cursor": "2.1.0", - "cli-width": "2.2.0", - "external-editor": "2.1.0", - "figures": "2.0.0", - "lodash": "4.17.5", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-5.2.0.tgz", + "integrity": "sha512-E9BmnJbAKLPGonz0HeWHtbKf+EeSP93paWO3ZYoUpq/aowXvYGjjCSuashhXPpzbArIjBbji39THkxTz9ZeEUQ==", + "dev": true, + "requires": { + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.0", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^2.1.0", + "figures": "^2.0.0", + "lodash": "^4.3.0", "mute-stream": "0.0.7", - "run-async": "2.3.0", - "rxjs": "5.5.7", - "string-width": "2.1.1", - "strip-ansi": "4.0.0", - "through": "2.3.8" + "run-async": "^2.2.0", + "rxjs": "^5.5.2", + "string-width": "^2.1.0", + "strip-ansi": "^4.0.0", + "through": "^2.3.6" } }, "interpret": { @@ -3465,23 +4416,22 @@ "integrity": "sha1-lvsKk2wSur1v8XUqF9BWFqvQlMY=", "dev": true, "requires": { - "from2": "2.3.0", - "p-is-promise": "1.1.0" + "from2": "^2.1.1", + "p-is-promise": "^1.1.0" } }, "invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "integrity": "sha1-YQ88ksk1nOHbYW5TgAjSP/NRWOY=", "requires": { - "loose-envify": "1.3.1" + "loose-envify": "^1.0.0" } }, "invert-kv": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", - "dev": true + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=" }, "ipaddr.js": { "version": "1.6.0", @@ -3489,11 +4439,19 @@ "integrity": "sha1-4/o1e3c9phnybpXwSdBVxyeW+Gs=", "dev": true }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" }, "is-binary-path": { "version": "1.0.1", @@ -3502,22 +4460,55 @@ "dev": true, "optional": true, "requires": { - "binary-extensions": "1.11.0" + "binary-extensions": "^1.0.0" } }, "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "integrity": "sha1-76ouqdqg16suoTqXsritUf776L4=", "dev": true }, "is-builtin-module": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", + "requires": { + "builtin-modules": "^1.0.0" + } + }, + "is-callable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.3.tgz", + "integrity": "sha1-hut1OSgF3cM69xySoO7fdO52BLI=", + "dev": true + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", "dev": true, "requires": { - "builtin-modules": "1.1.1" + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } } }, "is-dotfile": { @@ -3532,7 +4523,7 @@ "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", "dev": true, "requires": { - "is-primitive": "2.0.0" + "is-primitive": "^2.0.0" } }, "is-extendable": { @@ -3553,14 +4544,13 @@ "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", "dev": true, "requires": { - "number-is-nan": "1.0.1" + "number-is-nan": "^1.0.0" } }, "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" }, "is-function": { "version": "1.0.1", @@ -3574,7 +4564,7 @@ "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", "dev": true, "requires": { - "is-extglob": "1.0.0" + "is-extglob": "^1.0.0" } }, "is-hex-prefixed": { @@ -3589,7 +4579,7 @@ "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", "dev": true, "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.0.2" } }, "is-object": { @@ -3599,28 +4589,83 @@ "dev": true }, "is-observable": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/is-observable/-/is-observable-0.2.0.tgz", - "integrity": "sha1-s2ExHYPG5dcmyr9eJQsCNxBvWuI=", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-observable/-/is-observable-1.1.0.tgz", + "integrity": "sha512-NqCa4Sa2d+u7BWc6CukaObG3Fh+CU9bvixbpcXYhy2VvYS7vVGIdAgnIS5Ks3A/cqk4rebLJ9s8zBstT2aKnIA==", "dev": true, "requires": { - "symbol-observable": "0.2.4" + "symbol-observable": "^1.1.0" }, "dependencies": { "symbol-observable": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-0.2.4.tgz", - "integrity": "sha1-lag9smGG1q9+ehjb2XYKL4bQj0A=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", + "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==", + "dev": true + } + } + }, + "is-odd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-odd/-/is-odd-2.0.0.tgz", + "integrity": "sha512-OTiixgpZAT1M4NHgS5IguFp/Vz2VI3U7Goh4/HA1adtwyLtSBrxYlcSYkhpAE07s4fKEcjrFxyvtQBND4vFQyQ==", + "dev": true, + "requires": { + "is-number": "^4.0.0" + }, + "dependencies": { + "is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", "dev": true } } }, + "is-path-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", + "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=" + }, + "is-path-in-cwd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", + "integrity": "sha1-WsSLNF72dTOb1sekipEhELJBz1I=", + "requires": { + "is-path-inside": "^1.0.0" + } + }, + "is-path-inside": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "requires": { + "path-is-inside": "^1.0.1" + } + }, "is-plain-obj": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", "dev": true }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, "is-posix-bracket": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", @@ -3636,8 +4681,12 @@ "is-promise": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", - "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", - "dev": true + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=" + }, + "is-resolvable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", + "integrity": "sha1-+xj4fOH+uSUWnJpAfBkxijIG7Yg=" }, "is-retry-allowed": { "version": "1.1.0", @@ -3651,7 +4700,7 @@ "integrity": "sha1-RJypgpnnEwOCViieyytUDcQ3yzA=", "dev": true, "requires": { - "scoped-regex": "1.0.0" + "scoped-regex": "^1.0.0" } }, "is-stream": { @@ -3669,8 +4718,7 @@ "is-utf8": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", - "dev": true + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=" }, "is-windows": { "version": "1.0.2", @@ -3681,14 +4729,18 @@ "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isbinaryfile": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-3.0.2.tgz", + "integrity": "sha1-Sj6XTsDLqQBNP8bN5yCeppNopiE=", "dev": true }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, "isobject": { "version": "2.1.0", @@ -3711,20 +4763,20 @@ "integrity": "sha1-ZcfXPUxNqE1POsMQuRj7C4Azczs=", "dev": true, "requires": { - "abbrev": "1.0.9", - "async": "1.5.2", - "escodegen": "1.8.1", - "esprima": "2.7.3", - "glob": "5.0.15", - "handlebars": "4.0.11", - "js-yaml": "3.11.0", - "mkdirp": "0.5.1", - "nopt": "3.0.6", - "once": "1.4.0", - "resolve": "1.1.7", - "supports-color": "3.2.3", - "which": "1.3.0", - "wordwrap": "1.0.0" + "abbrev": "1.0.x", + "async": "1.x", + "escodegen": "1.8.x", + "esprima": "2.7.x", + "glob": "^5.0.15", + "handlebars": "^4.0.1", + "js-yaml": "3.x", + "mkdirp": "0.5.x", + "nopt": "3.x", + "once": "1.x", + "resolve": "1.1.x", + "supports-color": "^3.1.0", + "which": "^1.1.1", + "wordwrap": "^1.0.0" }, "dependencies": { "esprima": { @@ -3739,11 +4791,11 @@ "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", "dev": true, "requires": { - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } }, "has-flag": { @@ -3764,7 +4816,7 @@ "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", "dev": true, "requires": { - "has-flag": "1.0.0" + "has-flag": "^1.0.0" } } } @@ -3775,9 +4827,9 @@ "integrity": "sha512-TS+hoFl8Z5FAFMK38nhBkdLt44CclNRgDHWeMgsV8ko3nDlr/9UI2Sf839sW7enijf8oKsZYXRvM8g0it9Zmcw==", "dev": true, "requires": { - "binaryextensions": "2.1.1", - "editions": "1.3.4", - "textextensions": "2.2.0" + "binaryextensions": "2", + "editions": "^1.3.3", + "textextensions": "2" } }, "isurl": { @@ -3786,32 +4838,8 @@ "integrity": "sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w==", "dev": true, "requires": { - "has-to-string-tag-x": "1.4.1", - "is-object": "1.0.1" - } - }, - "jade": { - "version": "0.26.3", - "resolved": "https://registry.npmjs.org/jade/-/jade-0.26.3.tgz", - "integrity": "sha1-jxDXl32NefL2/4YqgbBRPMslaGw=", - "dev": true, - "requires": { - "commander": "0.6.1", - "mkdirp": "0.3.0" - }, - "dependencies": { - "commander": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz", - "integrity": "sha1-+mihT2qUXVTbvlDYzbMyDp47GgY=", - "dev": true - }, - "mkdirp": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz", - "integrity": "sha1-G79asbqCevI1dRQ0kEJkVfSB/h4=", - "dev": true - } + "has-to-string-tag-x": "^1.2.0", + "is-object": "^1.0.1" } }, "js-sha3": { @@ -3826,13 +4854,12 @@ "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=" }, "js-yaml": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.11.0.tgz", - "integrity": "sha512-saJstZWv7oNeOyBh3+Dx1qWzhW0+e6/8eDzo7p5rDFqxntSztloLtuKu+Ejhtq82jsilwOIZYsCz+lIjthg1Hw==", - "dev": true, + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz", + "integrity": "sha1-6u1lbsg0TxD1J8a/obbiJE3hZ9E=", "requires": { - "argparse": "1.0.10", - "esprima": "4.0.0" + "argparse": "^1.0.7", + "esprima": "^4.0.0" } }, "jsbn": { @@ -3843,26 +4870,26 @@ "optional": true }, "jscodeshift": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jscodeshift/-/jscodeshift-0.5.0.tgz", - "integrity": "sha512-JAcQINNMFpdzzpKJN8k5xXjF3XDuckB1/48uScSzcnNyK199iWEc9AxKL9OoX5144M2w5zEx9Qs4/E/eBZZUlw==", - "dev": true, - "requires": { - "babel-plugin-transform-flow-strip-types": "6.22.0", - "babel-preset-es2015": "6.24.1", - "babel-preset-stage-1": "6.24.1", - "babel-register": "6.26.0", - "babylon": "7.0.0-beta.42", - "colors": "1.2.1", - "flow-parser": "0.68.0", - "lodash": "4.17.5", - "micromatch": "2.3.11", - "neo-async": "2.5.0", + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/jscodeshift/-/jscodeshift-0.5.1.tgz", + "integrity": "sha512-sRMollbhbmSDrR79JMAnhEjyZJlQQVozeeY9A6/KNuV26DNcuB3mGSCWXp0hks9dcwRNOELbNOiwraZaXXRk5Q==", + "dev": true, + "requires": { + "babel-plugin-transform-flow-strip-types": "^6.8.0", + "babel-preset-es2015": "^6.9.0", + "babel-preset-stage-1": "^6.5.0", + "babel-register": "^6.9.0", + "babylon": "^7.0.0-beta.47", + "colors": "^1.1.2", + "flow-parser": "^0.*", + "lodash": "^4.13.1", + "micromatch": "^2.3.7", + "neo-async": "^2.5.0", "node-dir": "0.1.8", - "nomnom": "1.8.1", - "recast": "0.14.5", - "temp": "0.8.3", - "write-file-atomic": "1.3.4" + "nomnom": "^1.8.1", + "recast": "^0.15.0", + "temp": "^0.8.1", + "write-file-atomic": "^1.2.0" } }, "jsesc": { @@ -3884,9 +4911,9 @@ "dev": true }, "json-parse-better-errors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.1.tgz", - "integrity": "sha512-xyQpxeWWMKyJps9CuGJYeng6ssI5bpqS9ltQpdVQ90t4ql6NdnxFKh95JcRt2cun/DjMVNrdjniLPuMA69xmCw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", "dev": true }, "json-schema": { @@ -3898,8 +4925,12 @@ "json-schema-traverse": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", - "dev": true + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=" }, "json-stringify-safe": { "version": "5.0.1", @@ -3907,12 +4938,6 @@ "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", "dev": true }, - "json3": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz", - "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=", - "dev": true - }, "json5": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", @@ -3923,9 +4948,8 @@ "version": "2.4.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", - "dev": true, "requires": { - "graceful-fs": "4.1.11" + "graceful-fs": "^4.1.6" } }, "jsprim": { @@ -3946,8 +4970,8 @@ "integrity": "sha1-HWM6+QfvMFu/ny+mFtVsRFYd+k0=", "dev": true, "requires": { - "browserify-sha3": "0.0.1", - "sha3": "1.2.0" + "browserify-sha3": "^0.0.1", + "sha3": "^1.1.0" } }, "keyv": { @@ -3965,16 +4989,15 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "1.1.6" + "is-buffer": "^1.1.5" } }, "klaw": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", "integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=", - "dev": true, "requires": { - "graceful-fs": "4.1.11" + "graceful-fs": "^4.1.9" } }, "lazy-cache": { @@ -3988,44 +5011,41 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", - "dev": true, "requires": { - "invert-kv": "1.0.0" + "invert-kv": "^1.0.0" } }, "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dev": true, "requires": { - "prelude-ls": "1.1.2", - "type-check": "0.3.2" + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" } }, "listr": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/listr/-/listr-0.13.0.tgz", - "integrity": "sha1-ILsLowuuZg7oTMBQPfS+PVYjiH0=", - "dev": true, - "requires": { - "chalk": "1.1.3", - "cli-truncate": "0.2.1", - "figures": "1.7.0", - "indent-string": "2.1.0", - "is-observable": "0.2.0", - "is-promise": "2.1.0", - "is-stream": "1.1.0", - "listr-silent-renderer": "1.1.1", - "listr-update-renderer": "0.4.0", - "listr-verbose-renderer": "0.4.1", - "log-symbols": "1.0.2", - "log-update": "1.0.2", - "ora": "0.2.3", - "p-map": "1.2.0", - "rxjs": "5.5.7", - "stream-to-observable": "0.2.0", - "strip-ansi": "3.0.1" + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/listr/-/listr-0.14.1.tgz", + "integrity": "sha512-MSMUUVN1f8aRnPi4034RkOqdiUlpYW+FqwFE3aL0uYNPRavkt2S2SsSpDDofn8BDpqv2RNnsdOcCHWsChcq77A==", + "dev": true, + "requires": { + "@samverschueren/stream-to-observable": "^0.3.0", + "cli-truncate": "^0.2.1", + "figures": "^1.7.0", + "indent-string": "^2.1.0", + "is-observable": "^1.1.0", + "is-promise": "^2.1.0", + "is-stream": "^1.1.0", + "listr-silent-renderer": "^1.1.1", + "listr-update-renderer": "^0.4.0", + "listr-verbose-renderer": "^0.4.0", + "log-symbols": "^1.0.2", + "log-update": "^1.0.2", + "ora": "^0.2.3", + "p-map": "^1.1.1", + "rxjs": "^6.1.0", + "strip-ansi": "^3.0.1" }, "dependencies": { "ansi-regex": { @@ -4046,11 +5066,11 @@ "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" } }, "figures": { @@ -4059,8 +5079,8 @@ "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", "dev": true, "requires": { - "escape-string-regexp": "1.0.5", - "object-assign": "4.1.1" + "escape-string-regexp": "^1.0.5", + "object-assign": "^4.1.0" } }, "log-symbols": { @@ -4069,7 +5089,16 @@ "integrity": "sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=", "dev": true, "requires": { - "chalk": "1.1.3" + "chalk": "^1.0.0" + } + }, + "rxjs": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.2.1.tgz", + "integrity": "sha512-OwMxHxmnmHTUpgO+V7dZChf3Tixf4ih95cmXjzzadULziVl/FKhHScGLj4goEw9weePVOH2Q0+GcCBUhKCZc/g==", + "dev": true, + "requires": { + "tslib": "^1.9.0" } }, "strip-ansi": { @@ -4078,7 +5107,7 @@ "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { - "ansi-regex": "2.1.1" + "ansi-regex": "^2.0.0" } }, "supports-color": { @@ -4101,14 +5130,14 @@ "integrity": "sha1-NE2YDaLKLosUW6MFkI8yrj9MyKc=", "dev": true, "requires": { - "chalk": "1.1.3", - "cli-truncate": "0.2.1", - "elegant-spinner": "1.0.1", - "figures": "1.7.0", - "indent-string": "3.2.0", - "log-symbols": "1.0.2", - "log-update": "1.0.2", - "strip-ansi": "3.0.1" + "chalk": "^1.1.3", + "cli-truncate": "^0.2.1", + "elegant-spinner": "^1.0.1", + "figures": "^1.7.0", + "indent-string": "^3.0.0", + "log-symbols": "^1.0.2", + "log-update": "^1.0.2", + "strip-ansi": "^3.0.1" }, "dependencies": { "ansi-regex": { @@ -4129,11 +5158,11 @@ "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" } }, "figures": { @@ -4142,8 +5171,8 @@ "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", "dev": true, "requires": { - "escape-string-regexp": "1.0.5", - "object-assign": "4.1.1" + "escape-string-regexp": "^1.0.5", + "object-assign": "^4.1.0" } }, "indent-string": { @@ -4158,7 +5187,7 @@ "integrity": "sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=", "dev": true, "requires": { - "chalk": "1.1.3" + "chalk": "^1.0.0" } }, "strip-ansi": { @@ -4167,7 +5196,7 @@ "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { - "ansi-regex": "2.1.1" + "ansi-regex": "^2.0.0" } }, "supports-color": { @@ -4184,10 +5213,10 @@ "integrity": "sha1-ggb0z21S3cWCfl/RSYng6WWTOjU=", "dev": true, "requires": { - "chalk": "1.1.3", - "cli-cursor": "1.0.2", - "date-fns": "1.29.0", - "figures": "1.7.0" + "chalk": "^1.1.3", + "cli-cursor": "^1.0.2", + "date-fns": "^1.27.2", + "figures": "^1.7.0" }, "dependencies": { "ansi-regex": { @@ -4208,11 +5237,11 @@ "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" } }, "cli-cursor": { @@ -4221,7 +5250,7 @@ "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", "dev": true, "requires": { - "restore-cursor": "1.0.1" + "restore-cursor": "^1.0.1" } }, "figures": { @@ -4230,8 +5259,8 @@ "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", "dev": true, "requires": { - "escape-string-regexp": "1.0.5", - "object-assign": "4.1.1" + "escape-string-regexp": "^1.0.5", + "object-assign": "^4.1.0" } }, "onetime": { @@ -4246,8 +5275,8 @@ "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", "dev": true, "requires": { - "exit-hook": "1.1.1", - "onetime": "1.1.0" + "exit-hook": "^1.0.0", + "onetime": "^1.0.0" } }, "strip-ansi": { @@ -4256,7 +5285,7 @@ "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { - "ansi-regex": "2.1.1" + "ansi-regex": "^2.0.0" } }, "supports-color": { @@ -4273,10 +5302,10 @@ "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", "dev": true, "requires": { - "graceful-fs": "4.1.11", - "parse-json": "4.0.0", - "pify": "3.0.0", - "strip-bom": "3.0.0" + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" }, "dependencies": { "strip-bom": { @@ -4293,9 +5322,9 @@ "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", "dev": true, "requires": { - "big.js": "3.2.0", - "emojis-list": "2.1.0", - "json5": "0.5.1" + "big.js": "^3.1.3", + "emojis-list": "^2.0.0", + "json5": "^0.5.0" } }, "locate-path": { @@ -4304,8 +5333,8 @@ "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", "dev": true, "requires": { - "p-locate": "2.0.0", - "path-exists": "3.0.0" + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" } }, "lodash": { @@ -4313,79 +5342,10 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.5.tgz", "integrity": "sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw==" }, - "lodash._baseassign": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", - "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=", - "dev": true, - "requires": { - "lodash._basecopy": "3.0.1", - "lodash.keys": "3.1.2" - } - }, - "lodash._basecopy": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", - "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", - "dev": true - }, - "lodash._basecreate": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz", - "integrity": "sha1-G8ZhYU2qf8MRt9A78WgGoCE8+CE=", - "dev": true - }, - "lodash._getnative": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", - "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", - "dev": true - }, - "lodash._isiterateecall": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", - "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", - "dev": true - }, "lodash.assign": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", - "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=", - "dev": true - }, - "lodash.create": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lodash.create/-/lodash.create-3.1.1.tgz", - "integrity": "sha1-1/KEnw29p+BGgruM1yqwIkYd6+c=", - "dev": true, - "requires": { - "lodash._baseassign": "3.2.0", - "lodash._basecreate": "3.0.3", - "lodash._isiterateecall": "3.0.9" - } - }, - "lodash.isarguments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", - "dev": true - }, - "lodash.isarray": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", - "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", - "dev": true - }, - "lodash.keys": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", - "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", - "dev": true, - "requires": { - "lodash._getnative": "3.9.1", - "lodash.isarguments": "3.1.0", - "lodash.isarray": "3.0.4" - } + "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=" }, "log-symbols": { "version": "2.2.0", @@ -4393,7 +5353,7 @@ "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", "dev": true, "requires": { - "chalk": "2.3.2" + "chalk": "^2.0.1" } }, "log-update": { @@ -4402,8 +5362,8 @@ "integrity": "sha1-GZKfZMQJPS0ucHWh2tivWcKWuNE=", "dev": true, "requires": { - "ansi-escapes": "1.4.0", - "cli-cursor": "1.0.2" + "ansi-escapes": "^1.0.0", + "cli-cursor": "^1.0.2" }, "dependencies": { "ansi-escapes": { @@ -4418,7 +5378,7 @@ "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", "dev": true, "requires": { - "restore-cursor": "1.0.1" + "restore-cursor": "^1.0.1" } }, "onetime": { @@ -4433,8 +5393,8 @@ "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", "dev": true, "requires": { - "exit-hook": "1.1.1", - "onetime": "1.1.0" + "exit-hook": "^1.0.0", + "onetime": "^1.0.0" } } } @@ -4450,34 +5410,54 @@ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz", "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=", "requires": { - "js-tokens": "3.0.2" + "js-tokens": "^3.0.0" } }, "lowercase-keys": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz", - "integrity": "sha1-TjNms55/VFfjXxMkvfb4jQv8cwY=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", "dev": true }, "lru-cache": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.2.tgz", "integrity": "sha512-wgeVXhrDwAWnIF/yZARsFnMBtdFXOg1b8RIrhilp+0iDYN4mdQcNZElDZ0e4B64BhaxeQ5zN7PMyvu7we1kPeQ==", - "dev": true, "requires": { - "pseudomap": "1.0.2", - "yallist": "2.1.2" + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" } }, "make-dir": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.2.0.tgz", - "integrity": "sha512-aNUAa4UMg/UougV25bbrU4ZaaKNjJ/3/xnvg/twpmKROPdKZPZ9wGgI0opdZzO8q/zUFawoUuixuOv33eZ61Iw==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", "dev": true, "requires": { - "pify": "3.0.0" + "object-visit": "^1.0.0" } }, + "math-random": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.1.tgz", + "integrity": "sha1-izqsWIuKZuSXXjzepn97sylgH6w=", + "dev": true + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -4490,7 +5470,7 @@ "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", "dev": true, "requires": { - "mimic-fn": "1.2.0" + "mimic-fn": "^1.0.0" } }, "mem-fs": { @@ -4499,27 +5479,28 @@ "integrity": "sha1-uK6NLj/Lb10/kWXBLUVRoGXZicw=", "dev": true, "requires": { - "through2": "2.0.3", - "vinyl": "1.2.0", - "vinyl-file": "2.0.0" + "through2": "^2.0.0", + "vinyl": "^1.1.0", + "vinyl-file": "^2.0.0" } }, "mem-fs-editor": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mem-fs-editor/-/mem-fs-editor-3.0.2.tgz", - "integrity": "sha1-3Qpuryu4prN3QAZ6pUnrUwEFr58=", - "dev": true, - "requires": { - "commondir": "1.0.1", - "deep-extend": "0.4.2", - "ejs": "2.5.7", - "glob": "7.1.2", - "globby": "6.1.0", - "mkdirp": "0.5.1", - "multimatch": "2.1.0", - "rimraf": "2.2.8", - "through2": "2.0.3", - "vinyl": "2.1.0" + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/mem-fs-editor/-/mem-fs-editor-4.0.2.tgz", + "integrity": "sha512-QHvdXLLNmwJXxKdf7x27aNUren6IoPxwcM8Sfd+S6/ddQQMcYdEtVKsh6ilpqMrU18VQuKZEaH0aCGt3JDbA0g==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "deep-extend": "^0.5.1", + "ejs": "^2.5.9", + "glob": "^7.0.3", + "globby": "^8.0.0", + "isbinaryfile": "^3.0.2", + "mkdirp": "^0.5.0", + "multimatch": "^2.0.0", + "rimraf": "^2.2.8", + "through2": "^2.0.0", + "vinyl": "^2.0.1" }, "dependencies": { "clone": { @@ -4546,12 +5527,12 @@ "integrity": "sha1-Ah+cLPlR1rk5lDyJ617lrdT9kkw=", "dev": true, "requires": { - "clone": "2.1.1", - "clone-buffer": "1.0.0", - "clone-stats": "1.0.0", - "cloneable-readable": "1.1.2", - "remove-trailing-separator": "1.1.0", - "replace-ext": "1.0.0" + "clone": "^2.1.1", + "clone-buffer": "^1.0.0", + "clone-stats": "^1.0.0", + "cloneable-readable": "^1.0.0", + "remove-trailing-separator": "^1.0.1", + "replace-ext": "^1.0.0" } } } @@ -4562,15 +5543,14 @@ "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", "dev": true, "requires": { - "errno": "0.1.7", - "readable-stream": "2.3.5" + "errno": "^0.1.3", + "readable-stream": "^2.0.1" } }, "memorystream": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", - "integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI=", - "dev": true + "integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI=" }, "merge-descriptors": { "version": "1.0.1", @@ -4578,6 +5558,12 @@ "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", "dev": true }, + "merge2": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.2.2.tgz", + "integrity": "sha512-bgM8twH86rWni21thii6WCMQMRMmwqqdW3sGWi9IipnVAszdLXRjwDwAnyrVXo6DuP3AjRMMttZKUB48QWIFGg==", + "dev": true + }, "methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -4590,47 +5576,46 @@ "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", "dev": true, "requires": { - "arr-diff": "2.0.0", - "array-unique": "0.2.1", - "braces": "1.8.5", - "expand-brackets": "0.1.5", - "extglob": "0.3.2", - "filename-regex": "2.0.1", - "is-extglob": "1.0.0", - "is-glob": "2.0.1", - "kind-of": "3.2.2", - "normalize-path": "2.1.1", - "object.omit": "2.0.1", - "parse-glob": "3.0.4", - "regex-cache": "0.4.4" + "arr-diff": "^2.0.0", + "array-unique": "^0.2.1", + "braces": "^1.8.2", + "expand-brackets": "^0.1.4", + "extglob": "^0.3.1", + "filename-regex": "^2.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.1", + "kind-of": "^3.0.2", + "normalize-path": "^2.0.1", + "object.omit": "^2.0.0", + "parse-glob": "^3.0.4", + "regex-cache": "^0.4.2" } }, "mime": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", - "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", + "integrity": "sha1-Eh+evEnjdm8xGnbh+hyAA8SwOqY=", "dev": true }, "mime-db": { "version": "1.33.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", - "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", + "integrity": "sha1-o0kgUKXLm2NFBUHjnZeI0icng9s=", "dev": true }, "mime-types": { "version": "2.1.18", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", - "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "integrity": "sha1-bzI/YKg9ERRvgx/xH9ZuL+VQO7g=", "dev": true, "requires": { - "mime-db": "1.33.0" + "mime-db": "~1.33.0" } }, "mimic-fn": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", - "dev": true + "integrity": "sha1-ggyGo5M0ZA6ZUWkovQP8qIBX0CI=" }, "mimic-response": { "version": "1.0.0", @@ -4644,13 +5629,13 @@ "integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=", "dev": true, "requires": { - "dom-walk": "0.1.1" + "dom-walk": "^0.1.0" } }, "minimalistic-assert": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz", - "integrity": "sha1-cCvi3aazf0g2vLP121ZkG2Sh09M=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", "dev": true }, "minimalistic-crypto-utils": { @@ -4662,10 +5647,9 @@ "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, + "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", "requires": { - "brace-expansion": "1.1.11" + "brace-expansion": "^1.1.7" } }, "minimist": { @@ -4674,11 +5658,31 @@ "integrity": "sha1-md9lelJXTCHJBXSX33QnkLK0wN4=", "dev": true }, + "mixin-deep": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", + "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", + "dev": true, + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, "mkdirp": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, "requires": { "minimist": "0.0.8" }, @@ -4686,82 +5690,56 @@ "minimist": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" } } }, "mocha": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-3.5.3.tgz", - "integrity": "sha512-/6na001MJWEtYxHOV1WLfsmR4YIynkUEhBwzsb+fk2qmQ3iqsi258l/Q2MWHJMImAcNpZ8DEdYAK72NHoIQ9Eg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-4.1.0.tgz", + "integrity": "sha512-0RVnjg1HJsXY2YFDoTNzcc1NKhYuXKRrBAG2gDygmJJA136Cs2QlRliZG1mA0ap7cuaT30mw16luAeln+4RiNA==", "dev": true, "requires": { "browser-stdout": "1.3.0", - "commander": "2.9.0", - "debug": "2.6.8", - "diff": "3.2.0", + "commander": "2.11.0", + "debug": "3.1.0", + "diff": "3.3.1", "escape-string-regexp": "1.0.5", - "glob": "7.1.1", - "growl": "1.9.2", + "glob": "7.1.2", + "growl": "1.10.3", "he": "1.1.1", - "json3": "3.3.2", - "lodash.create": "3.1.1", "mkdirp": "0.5.1", - "supports-color": "3.1.2" + "supports-color": "4.4.0" }, "dependencies": { - "commander": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", - "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", - "dev": true, - "requires": { - "graceful-readlink": "1.0.1" - } - }, "debug": { - "version": "2.6.8", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", - "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "dev": true, "requires": { "ms": "2.0.0" } }, "diff": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.2.0.tgz", - "integrity": "sha1-yc45Okt8vQsFinJck98pkCeGj/k=", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.3.1.tgz", + "integrity": "sha512-MKPHZDMB0o6yHyDryUOScqZibp914ksXwAMYMTHj6KO8UeKsRYNJD3oNCKjTqZon+V488P7N/HzXF8t7ZR95ww==", "dev": true }, - "glob": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz", - "integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg=", - "dev": true, - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", "dev": true }, "supports-color": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz", - "integrity": "sha1-cqJiiU2dQIuVbKBf83su2KbiotU=", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", + "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", "dev": true, "requires": { - "has-flag": "1.0.0" + "has-flag": "^2.0.0" } } } @@ -4777,22 +5755,21 @@ "integrity": "sha1-nHkGoi+0wCkZ4vX3UWG0zb1LKis=", "dev": true, "requires": { - "array-differ": "1.0.0", - "array-union": "1.0.2", - "arrify": "1.0.1", - "minimatch": "3.0.4" + "array-differ": "^1.0.0", + "array-union": "^1.0.1", + "arrify": "^1.0.0", + "minimatch": "^3.0.0" } }, "mute-stream": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", - "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", - "dev": true + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=" }, "nan": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", - "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==", + "integrity": "sha1-ltDNYQ69WNS03pzAxoKM2pnHVI8=", "dev": true }, "nano-json-stream-parser": { @@ -4801,6 +5778,51 @@ "integrity": "sha1-DMj20OK2IrR5xA1JnEbWS3Vcb18=", "dev": true }, + "nanomatch": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.9.tgz", + "integrity": "sha512-n8R9bS8yQ6eSXaV6jHUpKzD8gLsin02w1HSFiegwrs9E098Ylhw5jdyKPaYqvHknHaSCKTPp7C8dGCQ0q9koXA==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-odd": "^2.0.0", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } + } + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=" + }, "negotiator": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", @@ -4808,9 +5830,9 @@ "dev": true }, "neo-async": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.5.0.tgz", - "integrity": "sha512-nJmSswG4As/MkRq7QZFuH/sf/yuv8ODdMZrY4Bedjp77a5MK4A6s7YbBB64c9u79EBUOfXUXBvArmvzTD0X+6g==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.5.1.tgz", + "integrity": "sha512-3KL3fvuRkZ7s4IFOMfztb7zJp3QaVWnBeGoJlgB38XnCRPj/0tLzzLG5IB8NYOHbJ8g8UGrgZv44GLDk6CxTxA==", "dev": true }, "nice-try": { @@ -4831,8 +5853,8 @@ "integrity": "sha1-IVH3Ikcrp55Qp2/BJbuMjy5Nwqc=", "dev": true, "requires": { - "chalk": "0.4.0", - "underscore": "1.6.0" + "chalk": "~0.4.0", + "underscore": "~1.6.0" }, "dependencies": { "ansi-styles": { @@ -4847,9 +5869,9 @@ "integrity": "sha1-UZmj3c0MHv4jvAjBsCewYXbgxk8=", "dev": true, "requires": { - "ansi-styles": "1.0.0", - "has-color": "0.1.7", - "strip-ansi": "0.1.1" + "ansi-styles": "~1.0.0", + "has-color": "~0.1.0", + "strip-ansi": "~0.1.0" } }, "strip-ansi": { @@ -4866,19 +5888,18 @@ "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", "dev": true, "requires": { - "abbrev": "1.0.9" + "abbrev": "1" } }, "normalize-package-data": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", - "dev": true, + "integrity": "sha1-EvlaMH1YNSB1oEkHuErIvpisAS8=", "requires": { - "hosted-git-info": "2.6.0", - "is-builtin-module": "1.0.0", - "semver": "5.5.0", - "validate-npm-package-license": "3.0.3" + "hosted-git-info": "^2.1.4", + "is-builtin-module": "^1.0.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" } }, "normalize-path": { @@ -4887,7 +5908,7 @@ "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", "dev": true, "requires": { - "remove-trailing-separator": "1.1.0" + "remove-trailing-separator": "^1.0.1" } }, "normalize-url": { @@ -4896,9 +5917,9 @@ "integrity": "sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw==", "dev": true, "requires": { - "prepend-http": "2.0.0", - "query-string": "5.1.1", - "sort-keys": "2.0.0" + "prepend-http": "^2.0.0", + "query-string": "^5.0.1", + "sort-keys": "^2.0.0" } }, "npm-run-path": { @@ -4907,14 +5928,13 @@ "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", "dev": true, "requires": { - "path-key": "2.0.1" + "path-key": "^2.0.0" } }, "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" }, "number-to-bn": { "version": "1.7.0", @@ -4935,8 +5955,46 @@ "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "requires": { + "isobject": "^3.0.0" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } }, "object.omit": { "version": "2.0.1", @@ -4944,8 +6002,25 @@ "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", "dev": true, "requires": { - "for-own": "0.1.5", - "is-extendable": "0.1.1" + "for-own": "^0.1.4", + "is-extendable": "^0.1.1" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "requires": { + "isobject": "^3.0.1" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } } }, "on-finished": { @@ -4961,18 +6036,16 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, "requires": { - "wrappy": "1.0.2" + "wrappy": "1" } }, "onetime": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", - "dev": true, "requires": { - "mimic-fn": "1.2.0" + "mimic-fn": "^1.0.0" } }, "optimist": { @@ -4981,8 +6054,8 @@ "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", "dev": true, "requires": { - "minimist": "0.0.10", - "wordwrap": "0.0.3" + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" }, "dependencies": { "minimist": { @@ -5003,14 +6076,13 @@ "version": "0.8.2", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", - "dev": true, "requires": { - "deep-is": "0.1.3", - "fast-levenshtein": "2.0.6", - "levn": "0.3.0", - "prelude-ls": "1.1.2", - "type-check": "0.3.2", - "wordwrap": "1.0.0" + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.4", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "wordwrap": "~1.0.0" } }, "ora": { @@ -5019,10 +6091,10 @@ "integrity": "sha1-N1J9Igrc1Tw5tzVx11QVbV22V6Q=", "dev": true, "requires": { - "chalk": "1.1.3", - "cli-cursor": "1.0.2", - "cli-spinners": "0.1.2", - "object-assign": "4.1.1" + "chalk": "^1.1.1", + "cli-cursor": "^1.0.2", + "cli-spinners": "^0.1.2", + "object-assign": "^4.0.1" }, "dependencies": { "ansi-regex": { @@ -5043,11 +6115,11 @@ "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" } }, "cli-cursor": { @@ -5056,7 +6128,7 @@ "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", "dev": true, "requires": { - "restore-cursor": "1.0.1" + "restore-cursor": "^1.0.1" } }, "onetime": { @@ -5071,8 +6143,8 @@ "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", "dev": true, "requires": { - "exit-hook": "1.1.1", - "onetime": "1.1.0" + "exit-hook": "^1.0.0", + "onetime": "^1.0.0" } }, "strip-ansi": { @@ -5081,7 +6153,7 @@ "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { - "ansi-regex": "2.1.1" + "ansi-regex": "^2.0.0" } }, "supports-color": { @@ -5110,16 +6182,15 @@ "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", "dev": true, "requires": { - "execa": "0.7.0", - "lcid": "1.0.0", - "mem": "1.1.0" + "execa": "^0.7.0", + "lcid": "^1.0.0", + "mem": "^1.1.0" } }, "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" }, "output-file-sync": { "version": "1.1.2", @@ -5127,15 +6198,15 @@ "integrity": "sha1-0KM+7+YaIF+suQCS6CZZjVJFznY=", "dev": true, "requires": { - "graceful-fs": "4.1.11", - "mkdirp": "0.5.1", - "object-assign": "4.1.1" + "graceful-fs": "^4.1.4", + "mkdirp": "^0.5.1", + "object-assign": "^4.1.0" } }, "p-cancelable": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.4.0.tgz", - "integrity": "sha512-/AodqPe1y/GYbhSlnMjxukLGQfQIgsmjSy2CXCNB96kg4ozKvmlovuHEKICToOO/yS3LLWgrWI1dFtFfrePS1g==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.4.1.tgz", + "integrity": "sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ==", "dev": true }, "p-each-series": { @@ -5144,7 +6215,7 @@ "integrity": "sha1-kw89Et0fUOdDRFeiLNbwSsatf3E=", "dev": true, "requires": { - "p-reduce": "1.0.0" + "p-reduce": "^1.0.0" } }, "p-finally": { @@ -5166,12 +6237,12 @@ "dev": true }, "p-limit": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.2.0.tgz", - "integrity": "sha512-Y/OtIaXtUPr4/YpMv1pCL5L5ed0rumAaAeBSj12F+bSlMdys7i8oQF/GUJmfpTS/QoaRrS/k6pma29haJpsMng==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", "dev": true, "requires": { - "p-try": "1.0.0" + "p-try": "^1.0.0" } }, "p-locate": { @@ -5180,7 +6251,7 @@ "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", "dev": true, "requires": { - "p-limit": "1.2.0" + "p-limit": "^1.1.0" } }, "p-map": { @@ -5201,7 +6272,7 @@ "integrity": "sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA==", "dev": true, "requires": { - "p-finally": "1.0.0" + "p-finally": "^1.0.0" } }, "p-try": { @@ -5216,10 +6287,10 @@ "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", "dev": true, "requires": { - "glob-base": "0.3.0", - "is-dotfile": "1.0.3", - "is-extglob": "1.0.0", - "is-glob": "2.0.1" + "glob-base": "^0.3.0", + "is-dotfile": "^1.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.0" } }, "parse-headers": { @@ -5228,7 +6299,7 @@ "integrity": "sha1-aug6eqJanZtwCswoaYzR8e1+lTY=", "dev": true, "requires": { - "for-each": "0.3.2", + "for-each": "^0.3.2", "trim": "0.0.1" } }, @@ -5238,8 +6309,8 @@ "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", "dev": true, "requires": { - "error-ex": "1.3.1", - "json-parse-better-errors": "1.0.1" + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" } }, "parse-passwd": { @@ -5254,6 +6325,18 @@ "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=", "dev": true }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "dev": true + }, "path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", @@ -5263,8 +6346,12 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=" }, "path-key": { "version": "2.0.1", @@ -5290,7 +6377,7 @@ "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", "dev": true, "requires": { - "pify": "3.0.0" + "pify": "^3.0.0" } }, "pathval": { @@ -5320,23 +6407,40 @@ "pinkie": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", - "dev": true + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" }, "pinkie-promise": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "requires": { + "pinkie": "^2.0.0" + } + }, + "pkg-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", + "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", "dev": true, "requires": { - "pinkie": "2.0.4" + "find-up": "^2.1.0" } }, + "pluralize": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", + "integrity": "sha1-KYuJ34uTsCIdv0Ia0rGx6iP8Z3c=" + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true + }, "prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" }, "prepend-http": { "version": "2.0.0", @@ -5351,9 +6455,9 @@ "dev": true }, "prettier": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.11.1.tgz", - "integrity": "sha512-T/KD65Ot0PB97xTrG8afQ46x3oiVhnfGjGESSI9NWYcG92+OUPZKkwHqGWXH2t9jK1crnQjubECW0FuOth+hxw==", + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.13.5.tgz", + "integrity": "sha512-4M90mfvLz6yRf2Dhzd+xPIE6b4xkI8nHMJhsSm9IlfG17g6wujrrm7+H1X8x52tC4cSNm6HmuhCUSNe6Hd5wfw==", "dev": true }, "pretty-bytes": { @@ -5365,7 +6469,7 @@ "private": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", - "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", + "integrity": "sha1-I4Hts2ifelPWUxkAYPz4ItLzaP8=", "dev": true }, "process": { @@ -5377,16 +6481,20 @@ "process-nextick-args": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", - "dev": true + "integrity": "sha1-o31zL0JxtKsa0HDTVQjoKQeI/6o=" + }, + "progress": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.0.tgz", + "integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=" }, "proxy-addr": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.3.tgz", - "integrity": "sha512-jQTChiCJteusULxjBp8+jftSQE5Obdl3k4cnmLA6WXtK6XFuWRnvVL7aCiBqaLPM8c4ph0S4tKna8XvmIwEnXQ==", + "integrity": "sha1-NV8mJQWmIWRrMTCnKOtkfiIFU0E=", "dev": true, "requires": { - "forwarded": "0.1.2", + "forwarded": "~0.1.2", "ipaddr.js": "1.6.0" } }, @@ -5399,8 +6507,7 @@ "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", - "dev": true + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" }, "punycode": { "version": "1.4.1", @@ -5409,60 +6516,44 @@ "dev": true }, "qs": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", - "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==", + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", "dev": true }, "query-string": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz", - "integrity": "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==", + "integrity": "sha1-p4wBK3HBfgXy4/ojGd0zBoLvs8s=", "dev": true, "requires": { - "decode-uri-component": "0.2.0", - "object-assign": "4.1.1", - "strict-uri-encode": "1.1.0" + "decode-uri-component": "^0.2.0", + "object-assign": "^4.1.0", + "strict-uri-encode": "^1.0.0" } }, "randomatic": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz", - "integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.0.0.tgz", + "integrity": "sha512-VdxFOIEY3mNO5PtSRkkle/hPJDHvQhK21oa73K4yAc9qmp6N429gAyF1gZMOTMeS0/AYzaV/2Trcef+NaIonSA==", "dev": true, "requires": { - "is-number": "3.0.0", - "kind-of": "4.0.0" + "is-number": "^4.0.0", + "kind-of": "^6.0.0", + "math-random": "^1.0.1" }, "dependencies": { "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "3.2.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } - } - } + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "dev": true }, "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true } } }, @@ -5479,15 +6570,26 @@ "dev": true }, "raw-body": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", - "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", + "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", "dev": true, "requires": { "bytes": "3.0.0", - "http-errors": "1.6.2", - "iconv-lite": "0.4.19", + "http-errors": "1.6.3", + "iconv-lite": "0.4.23", "unpipe": "1.0.0" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + } } }, "read-chunk": { @@ -5496,8 +6598,8 @@ "integrity": "sha1-agTAkoAF7Z1C4aasVgDhnLx/9lU=", "dev": true, "requires": { - "pify": "3.0.0", - "safe-buffer": "5.1.1" + "pify": "^3.0.0", + "safe-buffer": "^5.1.1" } }, "read-pkg": { @@ -5506,9 +6608,9 @@ "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", "dev": true, "requires": { - "load-json-file": "4.0.0", - "normalize-package-data": "2.4.0", - "path-type": "3.0.0" + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" } }, "read-pkg-up": { @@ -5517,23 +6619,22 @@ "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", "dev": true, "requires": { - "find-up": "2.1.0", - "read-pkg": "3.0.0" + "find-up": "^2.0.0", + "read-pkg": "^3.0.0" } }, "readable-stream": { "version": "2.3.5", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.5.tgz", "integrity": "sha512-tK0yDhrkygt/knjowCUiWP9YdV7c5R+8cR0r/kt9ZhBU906Fs6RpQJCEilamRJj1Nx2rWI6LkW9gKqjTkshhEw==", - "dev": true, "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "2.0.0", - "safe-buffer": "5.1.1", - "string_decoder": "1.0.3", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.0.3", + "util-deprecate": "~1.0.1" } }, "readdirp": { @@ -5543,22 +6644,22 @@ "dev": true, "optional": true, "requires": { - "graceful-fs": "4.1.11", - "minimatch": "3.0.4", - "readable-stream": "2.3.5", - "set-immediate-shim": "1.0.1" + "graceful-fs": "^4.1.2", + "minimatch": "^3.0.2", + "readable-stream": "^2.0.2", + "set-immediate-shim": "^1.0.1" } }, "recast": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/recast/-/recast-0.14.5.tgz", - "integrity": "sha512-GNFQGQrqW1R8w9XhhgYIN8H7ePPp088D+svHlb7DdP5DCqNDqTwH7lt378EouM+L18kCwkmqpAz1unLqpPhHmw==", + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.15.0.tgz", + "integrity": "sha512-47C2mIxQYvFICrTNuV4+xGgBa1nAoq42ANN5oDTSBIJ50NX7jcki7gAC6HWYptnQgHmqIRTHJq8OKdi3fwgyNQ==", "dev": true, "requires": { - "ast-types": "0.11.3", - "esprima": "4.0.0", - "private": "0.1.8", - "source-map": "0.6.1" + "ast-types": "0.11.5", + "esprima": "~4.0.0", + "private": "~0.1.5", + "source-map": "~0.6.1" } }, "rechoir": { @@ -5567,7 +6668,7 @@ "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", "dev": true, "requires": { - "resolve": "1.5.0" + "resolve": "^1.1.6" } }, "regenerate": { @@ -5579,37 +6680,52 @@ "regenerator-runtime": { "version": "0.11.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + "integrity": "sha1-vgWtf5v30i4Fb5cmzuUBf78Z4uk=" }, "regenerator-transform": { "version": "0.10.1", "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.10.1.tgz", - "integrity": "sha512-PJepbvDbuK1xgIgnau7Y90cwaAmO/LCLMI2mPvaXq2heGMR3aWW5/BQvYrhJ8jgmQjXewXvBjzfqKcVOmhjZ6Q==", + "integrity": "sha1-HkmWg3Ix2ot/PPQRTXG1aRoGgN0=", "dev": true, "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0", - "private": "0.1.8" + "babel-runtime": "^6.18.0", + "babel-types": "^6.19.0", + "private": "^0.1.6" } }, "regex-cache": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", - "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", + "integrity": "sha1-db3FiioUls7EihKDW8VMjVYjNt0=", + "dev": true, + "requires": { + "is-equal-shallow": "^0.1.3" + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", "dev": true, "requires": { - "is-equal-shallow": "0.1.3" + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" } }, + "regexpp": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-1.1.0.tgz", + "integrity": "sha1-DjUW3Qt5BPQT0tQZPc5GGMOmias=" + }, "regexpu-core": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-2.0.0.tgz", "integrity": "sha1-SdA4g3uNz4v6W5pCE5k45uoq4kA=", "dev": true, "requires": { - "regenerate": "1.3.3", - "regjsgen": "0.2.0", - "regjsparser": "0.1.5" + "regenerate": "^1.2.1", + "regjsgen": "^0.2.0", + "regjsparser": "^0.1.4" } }, "regjsgen": { @@ -5624,7 +6740,7 @@ "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", "dev": true, "requires": { - "jsesc": "0.5.0" + "jsesc": "~0.5.0" } }, "remove-trailing-separator": { @@ -5651,7 +6767,7 @@ "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", "dev": true, "requires": { - "is-finite": "1.0.2" + "is-finite": "^1.0.0" } }, "replace-ext": { @@ -5666,7 +6782,7 @@ "integrity": "sha1-DXOurpJm5penj3l2AZZ352rPD/8=", "dev": true, "requires": { - "req-from": "1.0.1" + "req-from": "^1.0.1" } }, "req-from": { @@ -5675,7 +6791,7 @@ "integrity": "sha1-v4HaUUeUfTLRO5R9wSpYrUWHNQ4=", "dev": true, "requires": { - "resolve-from": "2.0.0" + "resolve-from": "^2.0.0" }, "dependencies": { "resolve-from": { @@ -5687,52 +6803,63 @@ } }, "request": { - "version": "2.85.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.85.0.tgz", - "integrity": "sha512-8H7Ehijd4js+s6wuVPLjwORxD4zeuyjYugprdOXlPSqaApmL/QOy+EB/beICHVCHkGMKNh5rvihb5ov+IDw4mg==", - "dev": true, - "requires": { - "aws-sign2": "0.7.0", - "aws4": "1.6.0", - "caseless": "0.12.0", - "combined-stream": "1.0.6", - "extend": "3.0.1", - "forever-agent": "0.6.1", - "form-data": "2.3.2", - "har-validator": "5.0.3", - "hawk": "6.0.2", - "http-signature": "1.2.0", - "is-typedarray": "1.0.0", - "isstream": "0.1.2", - "json-stringify-safe": "5.0.1", - "mime-types": "2.1.18", - "oauth-sign": "0.8.2", - "performance-now": "2.1.0", - "qs": "6.5.1", - "safe-buffer": "5.1.1", - "stringstream": "0.0.5", - "tough-cookie": "2.3.4", - "tunnel-agent": "0.6.0", - "uuid": "3.2.1" + "version": "2.87.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.87.0.tgz", + "integrity": "sha512-fcogkm7Az5bsS6Sl0sibkbhcKsnyon/jV1kF3ajGmF0c8HrttdKTPRT9hieOaQHA5HEq6r8OyWOo/o781C1tNw==", + "dev": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.6.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.5", + "extend": "~3.0.1", + "forever-agent": "~0.6.1", + "form-data": "~2.3.1", + "har-validator": "~5.0.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.17", + "oauth-sign": "~0.8.2", + "performance-now": "^2.1.0", + "qs": "~6.5.1", + "safe-buffer": "^5.1.1", + "tough-cookie": "~2.3.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.1.0" } }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" }, "require-from-string": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-1.2.1.tgz", - "integrity": "sha1-UpyczvJzgK3+yaL5ZbZJu+5jZBg=", - "dev": true + "integrity": "sha1-UpyczvJzgK3+yaL5ZbZJu+5jZBg=" }, "require-main-filename": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", - "dev": true + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" + }, + "require-uncached": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", + "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", + "requires": { + "caller-path": "^0.1.0", + "resolve-from": "^1.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", + "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=" + } + } }, "resolve": { "version": "1.5.0", @@ -5740,7 +6867,7 @@ "integrity": "sha512-hgoSGrc3pjzAPHNBg+KnFcK2HwlHTs/YrAGUr6qgTVUZmXv1UEXXl0bZNBKMA9fud6lRYFdPGz0xXxycPzmmiw==", "dev": true, "requires": { - "path-parse": "1.0.5" + "path-parse": "^1.0.5" } }, "resolve-cwd": { @@ -5749,7 +6876,7 @@ "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", "dev": true, "requires": { - "resolve-from": "3.0.0" + "resolve-from": "^3.0.0" } }, "resolve-dir": { @@ -5758,8 +6885,8 @@ "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", "dev": true, "requires": { - "expand-tilde": "2.0.2", - "global-modules": "1.0.0" + "expand-tilde": "^2.0.0", + "global-modules": "^1.0.0" } }, "resolve-from": { @@ -5768,25 +6895,36 @@ "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", "dev": true }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "dev": true + }, "responselike": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", "dev": true, "requires": { - "lowercase-keys": "1.0.0" + "lowercase-keys": "^1.0.0" } }, "restore-cursor": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", - "dev": true, "requires": { - "onetime": "2.0.1", - "signal-exit": "3.0.2" + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" } }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true + }, "right-align": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", @@ -5794,43 +6932,39 @@ "dev": true, "optional": true, "requires": { - "align-text": "0.1.4" + "align-text": "^0.1.1" } }, "rimraf": { "version": "2.2.8", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", - "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=", - "dev": true + "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=" }, "run-async": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", - "dev": true, "requires": { - "is-promise": "2.1.0" + "is-promise": "^2.1.0" } }, "rx-lite": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz", - "integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=", - "dev": true + "integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=" }, "rx-lite-aggregates": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz", "integrity": "sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=", - "dev": true, "requires": { - "rx-lite": "4.0.8" + "rx-lite": "*" } }, "rxjs": { - "version": "5.5.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.7.tgz", - "integrity": "sha512-Hxo2ac8gRQjwjtKgukMIwBRbq5+KAeEV5hXM4obYBOAghev41bDQWgFH4svYiU9UnQ5kNww2LgfyBdevCd2HXA==", + "version": "5.5.11", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.11.tgz", + "integrity": "sha512-3bjO7UwWfA2CV7lmwYMBzj4fQ6Cq+ftHc2MvUe+WMS7wcdJ1LosDWmdjPQanYp2dBRj572p7PeU81JUxHKOcBA==", "dev": true, "requires": { "symbol-observable": "1.0.1" @@ -5839,7 +6973,21 @@ "safe-buffer": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "requires": { + "ret": "~0.1.10" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, "scoped-regex": { @@ -5851,60 +6999,66 @@ "semver": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", - "dev": true + "integrity": "sha1-3Eu8emyp2Rbe5dQ1FvAJK1j3uKs=" }, "send": { "version": "0.16.2", "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", - "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", + "integrity": "sha1-bsyh4PjBVtFBWXVZhI32RzCmu8E=", "dev": true, "requires": { "debug": "2.6.9", - "depd": "1.1.2", - "destroy": "1.0.4", - "encodeurl": "1.0.2", - "escape-html": "1.0.3", - "etag": "1.8.1", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", "fresh": "0.5.2", - "http-errors": "1.6.2", + "http-errors": "~1.6.2", "mime": "1.4.1", "ms": "2.0.0", - "on-finished": "2.3.0", - "range-parser": "1.2.0", - "statuses": "1.4.0" + "on-finished": "~2.3.0", + "range-parser": "~1.2.0", + "statuses": "~1.4.0" + }, + "dependencies": { + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", + "dev": true + } } }, "serve-static": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", - "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", + "integrity": "sha1-CV6Ecv1bRiN9tQzkhqQ/S4bGzsE=", "dev": true, "requires": { - "encodeurl": "1.0.2", - "escape-html": "1.0.3", - "parseurl": "1.3.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.2", "send": "0.16.2" } }, "servify": { "version": "0.1.12", "resolved": "https://registry.npmjs.org/servify/-/servify-0.1.12.tgz", - "integrity": "sha512-/xE6GvsKKqyo1BAY+KxOWXcLpPsUUyji7Qg3bVD7hh1eRze5bR1uYiuDA/k3Gof1s9BTzQZEJK8sNcNGFIzeWw==", + "integrity": "sha1-FCq3vuHx0DO2bQcHCGCFsXwG25U=", "dev": true, "requires": { - "body-parser": "1.18.2", - "cors": "2.8.4", - "express": "4.16.3", - "request": "2.85.0", - "xhr": "2.4.1" + "body-parser": "^1.16.0", + "cors": "^2.8.1", + "express": "^4.14.0", + "request": "^2.79.0", + "xhr": "^2.3.3" } }, "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, "set-immediate-shim": { "version": "1.0.1", @@ -5913,10 +7067,33 @@ "dev": true, "optional": true }, + "set-value": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", + "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, "setprototypeof": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", - "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", "dev": true }, "sha3": { @@ -5925,46 +7102,37 @@ "integrity": "sha1-aYnxtwpJhwWHajc+LGKs6WqpOZo=", "dev": true, "requires": { - "nan": "2.10.0" + "nan": "^2.0.5" } }, "shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, "requires": { - "shebang-regex": "1.0.0" + "shebang-regex": "^1.0.0" } }, "shebang-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" }, "shelljs": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.1.tgz", - "integrity": "sha512-YA/iYtZpzFe5HyWVGrb02FjPxc4EMCfpoU/Phg9fQoyMC72u9598OUBrsU8IrtwAKG0tO8IYaqbaLIw+k3IRGA==", + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.2.tgz", + "integrity": "sha512-pRXeNrCA2Wd9itwhvLp5LZQvPJ0wU6bcjaTMywHHGX5XWhVN2nzSu7WV0q+oUY7mGK3mgSkDDzP3MgjqdyIgbQ==", "dev": true, "requires": { - "glob": "7.1.2", - "interpret": "1.1.0", - "rechoir": "0.6.2" + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" } }, - "sigmund": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", - "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=", - "dev": true - }, "signal-exit": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", - "dev": true + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" }, "simple-concat": { "version": "1.0.0", @@ -5973,14 +7141,14 @@ "dev": true }, "simple-get": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-2.7.0.tgz", - "integrity": "sha512-RkE9rGPHcxYZ/baYmgJtOSM63vH0Vyq+ma5TijBcLla41SWlh8t6XYIGMR/oeZcmr+/G8k+zrClkkVrtnQ0esg==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-2.8.1.tgz", + "integrity": "sha512-lSSHRSw3mQNUGPAYRqo7xy9dhKmxFXIjLjp4KHpf99GEH2VH7C3AM+Qfx6du6jhfUi6Vm7XnbEVEf7Wb6N8jRw==", "dev": true, "requires": { - "decompress-response": "3.3.0", - "once": "1.4.0", - "simple-concat": "1.0.0" + "decompress-response": "^3.3.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" } }, "slash": { @@ -6001,13 +7169,118 @@ "integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=", "dev": true }, - "sntp": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz", - "integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==", + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", "dev": true, "requires": { - "hoek": "4.2.1" + "kind-of": "^3.2.0" } }, "sol-explore": { @@ -6017,219 +7290,215 @@ "dev": true }, "solc": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/solc/-/solc-0.4.23.tgz", - "integrity": "sha512-AT7anLHY6uIRg2It6N0UlCHeZ7YeecIkUhnlirrCgCPCUevtnoN48BxvgigN/4jJTRljv5oFhAJtI6gvHzT5DQ==", - "dev": true, - "requires": { - "fs-extra": "0.30.0", - "memorystream": "0.3.1", - "require-from-string": "1.2.1", - "semver": "5.5.0", - "yargs": "4.8.1" + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/solc/-/solc-0.4.24.tgz", + "integrity": "sha1-NU8UsmmzjLqoKkfR/xUXI1ArlU4=", + "requires": { + "fs-extra": "^0.30.0", + "memorystream": "^0.3.1", + "require-from-string": "^1.1.0", + "semver": "^5.3.0", + "yargs": "^4.7.1" }, "dependencies": { "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, "camelcase": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", - "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", - "dev": true + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=" }, "cliui": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", - "dev": true, "requires": { - "string-width": "1.0.2", - "strip-ansi": "3.0.1", - "wrap-ansi": "2.1.0" + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" } }, "find-up": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", - "dev": true, "requires": { - "path-exists": "2.1.0", - "pinkie-promise": "2.0.1" + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" } }, "is-fullwidth-code-point": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, "requires": { - "number-is-nan": "1.0.1" + "number-is-nan": "^1.0.0" } }, "load-json-file": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", - "dev": true, "requires": { - "graceful-fs": "4.1.11", - "parse-json": "2.2.0", - "pify": "2.3.0", - "pinkie-promise": "2.0.1", - "strip-bom": "2.0.0" + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" } }, "os-locale": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", - "dev": true, "requires": { - "lcid": "1.0.0" + "lcid": "^1.0.0" } }, "parse-json": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "dev": true, "requires": { - "error-ex": "1.3.1" + "error-ex": "^1.2.0" } }, "path-exists": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", - "dev": true, "requires": { - "pinkie-promise": "2.0.1" + "pinkie-promise": "^2.0.0" } }, "path-type": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", - "dev": true, "requires": { - "graceful-fs": "4.1.11", - "pify": "2.3.0", - "pinkie-promise": "2.0.1" + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" } }, "pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" }, "read-pkg": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", - "dev": true, "requires": { - "load-json-file": "1.1.0", - "normalize-package-data": "2.4.0", - "path-type": "1.1.0" + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" } }, "read-pkg-up": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", - "dev": true, "requires": { - "find-up": "1.1.2", - "read-pkg": "1.1.0" + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" } }, "string-width": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" } }, "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, "requires": { - "ansi-regex": "2.1.1" + "ansi-regex": "^2.0.0" } }, "which-module": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", - "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", - "dev": true - }, - "window-size": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.2.0.tgz", - "integrity": "sha1-tDFbtCFKPXBY6+7okuE/ok2YsHU=", - "dev": true + "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=" }, "yargs": { "version": "4.8.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-4.8.1.tgz", "integrity": "sha1-wMQpJMpKqmsObaFznfshZDn53cA=", - "dev": true, "requires": { - "cliui": "3.2.0", - "decamelize": "1.2.0", - "get-caller-file": "1.0.2", - "lodash.assign": "4.2.0", - "os-locale": "1.4.0", - "read-pkg-up": "1.0.1", - "require-directory": "2.1.1", - "require-main-filename": "1.0.1", - "set-blocking": "2.0.0", - "string-width": "1.0.2", - "which-module": "1.0.0", - "window-size": "0.2.0", - "y18n": "3.2.1", - "yargs-parser": "2.4.1" + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "lodash.assign": "^4.0.3", + "os-locale": "^1.4.0", + "read-pkg-up": "^1.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^1.0.1", + "which-module": "^1.0.0", + "window-size": "^0.2.0", + "y18n": "^3.2.1", + "yargs-parser": "^2.4.1" } }, "yargs-parser": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-2.4.1.tgz", "integrity": "sha1-hVaN488VD/SfpRgl8DqMiA3cxcQ=", - "dev": true, "requires": { - "camelcase": "3.0.0", - "lodash.assign": "4.2.0" + "camelcase": "^3.0.0", + "lodash.assign": "^4.0.6" } } } }, + "solhint": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/solhint/-/solhint-1.2.1.tgz", + "integrity": "sha1-WaFBbO+U2jjVh/dopzU21uNAPdM=", + "requires": { + "antlr4": "4.7.0", + "commander": "2.11.0", + "eslint": "^4.19.1", + "glob": "7.1.2", + "ignore": "^3.3.7", + "lodash": "^4.17.10" + }, + "dependencies": { + "lodash": { + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" + } + } + }, "solidity-coverage": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/solidity-coverage/-/solidity-coverage-0.4.14.tgz", - "integrity": "sha512-4IizUjKsBpdIe5cVB/jfjuwO9On91JLxxCyID9poSW9owaQaXRHGlQltWUFbLlAbzb0dBsPscRnv2byDbKYQTA==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/solidity-coverage/-/solidity-coverage-0.5.4.tgz", + "integrity": "sha1-9h2N4xD8FxAv/YusONZgtwF8EMM=", "dev": true, "requires": { - "death": "1.1.0", + "death": "^1.1.0", "ethereumjs-testrpc-sc": "6.1.2", - "istanbul": "0.4.5", - "keccakjs": "0.2.1", - "req-cwd": "1.0.1", - "shelljs": "0.7.8", - "sol-explore": "1.6.2", - "solidity-parser-sc": "0.4.6", - "web3": "0.18.4" + "istanbul": "^0.4.5", + "keccakjs": "^0.2.1", + "req-cwd": "^1.0.1", + "shelljs": "^0.7.4", + "sol-explore": "^1.6.2", + "solidity-parser-sc": "0.4.10", + "tree-kill": "^1.2.0", + "web3": "^0.18.4" }, "dependencies": { "shelljs": { @@ -6238,22 +7507,22 @@ "integrity": "sha1-3svPh0sNHl+3LhSxZKloMEjprLM=", "dev": true, "requires": { - "glob": "7.1.2", - "interpret": "1.1.0", - "rechoir": "0.6.2" + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" } } } }, "solidity-parser-sc": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/solidity-parser-sc/-/solidity-parser-sc-0.4.6.tgz", - "integrity": "sha512-+fu+4L4XomonLf/BvG0hv/otIpWr7wFB0X/QHzfQP68ER+FrQ8vou11/OPsOqc7yHWhowi0oe4JcKFZgAEUThw==", + "version": "0.4.10", + "resolved": "https://registry.npmjs.org/solidity-parser-sc/-/solidity-parser-sc-0.4.10.tgz", + "integrity": "sha1-WllM1eDWzhpyrdeBp3cGY7q2XT0=", "dev": true, "requires": { - "mocha": "2.5.3", - "pegjs": "0.10.0", - "yargs": "4.8.1" + "mocha": "^4.1.0", + "pegjs": "^0.10.0", + "yargs": "^4.6.0" }, "dependencies": { "ansi-regex": { @@ -6274,30 +7543,24 @@ "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", "dev": true, "requires": { - "string-width": "1.0.2", - "strip-ansi": "3.0.1", - "wrap-ansi": "2.1.0" + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" } }, "debug": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", - "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "dev": true, "requires": { - "ms": "0.7.1" + "ms": "2.0.0" } }, - "diff": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-1.4.0.tgz", - "integrity": "sha1-fyjS657nsVqX79ic5j3P2qPMur8=", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.2.tgz", - "integrity": "sha1-Tbwv5nTnGUnK8/smlc5/LcHZqNE=", + "diff": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.3.1.tgz", + "integrity": "sha512-MKPHZDMB0o6yHyDryUOScqZibp914ksXwAMYMTHj6KO8UeKsRYNJD3oNCKjTqZon+V488P7N/HzXF8t7ZR95ww==", "dev": true }, "find-up": { @@ -6306,19 +7569,21 @@ "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", "dev": true, "requires": { - "path-exists": "2.1.0", - "pinkie-promise": "2.0.1" + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" } }, - "glob": { - "version": "3.2.11", - "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz", - "integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0=", - "dev": true, - "requires": { - "inherits": "2.0.3", - "minimatch": "0.3.0" - } + "growl": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.3.tgz", + "integrity": "sha512-hKlsbA5Vu3xsh1Cg3J7jSmX/WaW6A5oBeqzM88oNbCRQFz+zUaXm6yxS4RVytp1scBoJzSYl4YAEOQIt6O8V1Q==", + "dev": true + }, + "has-flag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "dev": true }, "is-fullwidth-code-point": { "version": "1.0.0", @@ -6326,7 +7591,7 @@ "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, "requires": { - "number-is-nan": "1.0.1" + "number-is-nan": "^1.0.0" } }, "load-json-file": { @@ -6335,60 +7600,38 @@ "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", "dev": true, "requires": { - "graceful-fs": "4.1.11", - "parse-json": "2.2.0", - "pify": "2.3.0", - "pinkie-promise": "2.0.1", - "strip-bom": "2.0.0" + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" } }, - "lru-cache": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", - "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=", - "dev": true - }, - "minimatch": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz", - "integrity": "sha1-J12O2qxPG7MyZHIInnlJyDlGmd0=", + "mocha": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-4.1.0.tgz", + "integrity": "sha512-0RVnjg1HJsXY2YFDoTNzcc1NKhYuXKRrBAG2gDygmJJA136Cs2QlRliZG1mA0ap7cuaT30mw16luAeln+4RiNA==", "dev": true, "requires": { - "lru-cache": "2.7.3", - "sigmund": "1.0.1" - } - }, - "mocha": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-2.5.3.tgz", - "integrity": "sha1-FhvlvetJZ3HrmzV0UFC2IrWu/Fg=", - "dev": true, - "requires": { - "commander": "2.3.0", - "debug": "2.2.0", - "diff": "1.4.0", - "escape-string-regexp": "1.0.2", - "glob": "3.2.11", - "growl": "1.9.2", - "jade": "0.26.3", + "browser-stdout": "1.3.0", + "commander": "2.11.0", + "debug": "3.1.0", + "diff": "3.3.1", + "escape-string-regexp": "1.0.5", + "glob": "7.1.2", + "growl": "1.10.3", + "he": "1.1.1", "mkdirp": "0.5.1", - "supports-color": "1.2.0", - "to-iso-string": "0.0.2" + "supports-color": "4.4.0" } }, - "ms": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", - "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", - "dev": true - }, "os-locale": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", "dev": true, "requires": { - "lcid": "1.0.0" + "lcid": "^1.0.0" } }, "parse-json": { @@ -6397,7 +7640,7 @@ "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", "dev": true, "requires": { - "error-ex": "1.3.1" + "error-ex": "^1.2.0" } }, "path-exists": { @@ -6406,7 +7649,7 @@ "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", "dev": true, "requires": { - "pinkie-promise": "2.0.1" + "pinkie-promise": "^2.0.0" } }, "path-type": { @@ -6415,9 +7658,9 @@ "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", "dev": true, "requires": { - "graceful-fs": "4.1.11", - "pify": "2.3.0", - "pinkie-promise": "2.0.1" + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" } }, "pify": { @@ -6432,9 +7675,9 @@ "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", "dev": true, "requires": { - "load-json-file": "1.1.0", - "normalize-package-data": "2.4.0", - "path-type": "1.1.0" + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" } }, "read-pkg-up": { @@ -6443,8 +7686,8 @@ "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", "dev": true, "requires": { - "find-up": "1.1.2", - "read-pkg": "1.1.0" + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" } }, "string-width": { @@ -6453,9 +7696,9 @@ "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" } }, "strip-ansi": { @@ -6464,14 +7707,17 @@ "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { - "ansi-regex": "2.1.1" + "ansi-regex": "^2.0.0" } }, "supports-color": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-1.2.0.tgz", - "integrity": "sha1-/x7R5hFp0Gs88tWI4YixjYhH4X4=", - "dev": true + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", + "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", + "dev": true, + "requires": { + "has-flag": "^2.0.0" + } }, "which-module": { "version": "1.0.0", @@ -6479,32 +7725,26 @@ "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", "dev": true }, - "window-size": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.2.0.tgz", - "integrity": "sha1-tDFbtCFKPXBY6+7okuE/ok2YsHU=", - "dev": true - }, "yargs": { "version": "4.8.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-4.8.1.tgz", "integrity": "sha1-wMQpJMpKqmsObaFznfshZDn53cA=", "dev": true, "requires": { - "cliui": "3.2.0", - "decamelize": "1.2.0", - "get-caller-file": "1.0.2", - "lodash.assign": "4.2.0", - "os-locale": "1.4.0", - "read-pkg-up": "1.0.1", - "require-directory": "2.1.1", - "require-main-filename": "1.0.1", - "set-blocking": "2.0.0", - "string-width": "1.0.2", - "which-module": "1.0.0", - "window-size": "0.2.0", - "y18n": "3.2.1", - "yargs-parser": "2.4.1" + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "lodash.assign": "^4.0.3", + "os-locale": "^1.4.0", + "read-pkg-up": "^1.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^1.0.1", + "which-module": "^1.0.0", + "window-size": "^0.2.0", + "y18n": "^3.2.1", + "yargs-parser": "^2.4.1" } }, "yargs-parser": { @@ -6513,8 +7753,8 @@ "integrity": "sha1-hVaN488VD/SfpRgl8DqMiA3cxcQ=", "dev": true, "requires": { - "camelcase": "3.0.0", - "lodash.assign": "4.2.0" + "camelcase": "^3.0.0", + "lodash.assign": "^4.0.6" } } } @@ -6525,93 +7765,129 @@ "integrity": "sha1-ZYU1WEhh7JfXMNbPQYIuH1ZoQSg=", "dev": true, "requires": { - "is-plain-obj": "1.1.0" + "is-plain-obj": "^1.0.0" } }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", "dev": true }, + "source-map-resolve": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", + "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", + "dev": true, + "requires": { + "atob": "^2.1.1", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, "source-map-support": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.4.tgz", "integrity": "sha512-PETSPG6BjY1AHs2t64vS2aqAgu6dMIMXJULWFBGbh2Gr8nVLbCFDo6i/RMMvviIQ2h1Z8+5gQhVKSn2je9nmdg==", "dev": true, "requires": { - "source-map": "0.6.1" + "source-map": "^0.6.0" } }, + "source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", + "dev": true + }, "spdx-correct": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz", - "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==", - "dev": true, + "integrity": "sha1-BaW01xU6GVvJLDxCW2nzsqlSTII=", "requires": { - "spdx-expression-parse": "3.0.0", - "spdx-license-ids": "3.0.0" + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" } }, "spdx-exceptions": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz", - "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==", - "dev": true + "integrity": "sha1-LHrmEFbHFKW5ubKyr30xHvXHj+k=" }, "spdx-expression-parse": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", - "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", - "dev": true, + "integrity": "sha1-meEZt6XaAOBUkcn6M4t5BII7QdA=", "requires": { - "spdx-exceptions": "2.1.0", - "spdx-license-ids": "3.0.0" + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" } }, "spdx-license-ids": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz", - "integrity": "sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA==", - "dev": true + "integrity": "sha1-enzShHDMbToc/m1miG9rxDDTrIc=" + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.0" + } }, "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" }, "sshpk": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.1.tgz", - "integrity": "sha1-Ew9Zde3a2WPx1W+SuaxsUfqfg+s=", + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.2.tgz", + "integrity": "sha1-xvxhZIo9nE52T9P8306hBeSSupg=", "dev": true, "requires": { - "asn1": "0.2.3", - "assert-plus": "1.0.0", - "bcrypt-pbkdf": "1.0.1", - "dashdash": "1.14.1", - "ecc-jsbn": "0.1.1", - "getpass": "0.1.7", - "jsbn": "0.1.1", - "tweetnacl": "0.14.5" + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" } }, - "statuses": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", - "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", - "dev": true - }, - "stream-to-observable": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/stream-to-observable/-/stream-to-observable-0.2.0.tgz", - "integrity": "sha1-WdbqOT2HwsDdrBCqDVYbxrpvDhA=", + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", "dev": true, "requires": { - "any-observable": "0.2.0" + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } } }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true + }, "strict-uri-encode": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", @@ -6628,43 +7904,33 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, "requires": { - "is-fullwidth-code-point": "2.0.0", - "strip-ansi": "4.0.0" + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" } }, "string_decoder": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", - "dev": true, "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "~5.1.0" } }, - "stringstream": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", - "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=", - "dev": true - }, "strip-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, "requires": { - "ansi-regex": "3.0.0" + "ansi-regex": "^3.0.0" } }, "strip-bom": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "dev": true, "requires": { - "is-utf8": "0.2.1" + "is-utf8": "^0.2.0" } }, "strip-bom-stream": { @@ -6673,8 +7939,8 @@ "integrity": "sha1-+H217yYT9paKpUWr/h7HKLaoKco=", "dev": true, "requires": { - "first-chunk-stream": "2.0.0", - "strip-bom": "2.0.0" + "first-chunk-stream": "^2.0.0", + "strip-bom": "^2.0.0" } }, "strip-eof": { @@ -6692,13 +7958,17 @@ "is-hex-prefixed": "1.0.0" } }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" + }, "supports-color": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", - "dev": true, "requires": { - "has-flag": "3.0.0" + "has-flag": "^3.0.0" } }, "symbol-observable": { @@ -6707,6 +7977,29 @@ "integrity": "sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ=", "dev": true }, + "table": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/table/-/table-4.0.2.tgz", + "integrity": "sha1-ozRHN1OR52atNNNIbm4q7chNLjY=", + "requires": { + "ajv": "^5.2.3", + "ajv-keywords": "^2.1.0", + "chalk": "^2.1.0", + "lodash": "^4.17.4", + "slice-ansi": "1.0.0", + "string-width": "^2.1.1" + }, + "dependencies": { + "slice-ansi": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", + "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", + "requires": { + "is-fullwidth-code-point": "^2.0.0" + } + } + } + }, "tapable": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.0.0.tgz", @@ -6719,15 +8012,14 @@ "integrity": "sha1-4Ma8TSa5AxJEEOT+2BEDAU38H1k=", "dev": true, "requires": { - "os-tmpdir": "1.0.2", - "rimraf": "2.2.8" + "os-tmpdir": "^1.0.0", + "rimraf": "~2.2.6" } }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=" }, "textextensions": { "version": "2.2.0", @@ -6738,8 +8030,7 @@ "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" }, "through2": { "version": "2.0.3", @@ -6747,8 +8038,8 @@ "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", "dev": true, "requires": { - "readable-stream": "2.3.5", - "xtend": "4.0.1" + "readable-stream": "^2.1.5", + "xtend": "~4.0.1" } }, "timed-out": { @@ -6760,10 +8051,9 @@ "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, + "integrity": "sha1-bTQzWIl2jSGyvNoKonfO07G/rfk=", "requires": { - "os-tmpdir": "1.0.2" + "os-tmpdir": "~1.0.2" } }, "to-fast-properties": { @@ -6771,21 +8061,63 @@ "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=" }, - "to-iso-string": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/to-iso-string/-/to-iso-string-0.0.2.tgz", - "integrity": "sha1-TcGeZk38y+Jb2NtQiwDG2hWCVdE=", - "dev": true + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + } + } }, "tough-cookie": { "version": "2.3.4", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", - "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", + "integrity": "sha1-7GDO44rGdQY//JelwYlwV47oNlU=", "dev": true, "requires": { - "punycode": "1.4.1" + "punycode": "^1.4.1" } }, + "tree-kill": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.0.tgz", + "integrity": "sha1-WEZ4Yje0I5AU8F2xVrZDIS1MbzY=", + "dev": true + }, "trim": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz", @@ -6799,23 +8131,29 @@ "dev": true }, "truffle": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/truffle/-/truffle-4.1.7.tgz", - "integrity": "sha512-fe6BIcD9xo6iIJvV1m6ZhOk56kmB8k38kdoWOKYnPPw7ZUUSupgojeTb2K5e+4qIpIHvEvmET4yLUjSGR+hvwA==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/truffle/-/truffle-4.1.11.tgz", + "integrity": "sha512-VNhc6jexZeM92sNJJr4U8ln3uJ/mJEQO/0y9ZLYc4pccyIskPtl+3r4mzymgGM/Mq5v6MpoQVD6NZgHUVKX+Dw==", "dev": true, "requires": { - "mocha": "3.5.3", - "original-require": "1.0.1", - "solc": "0.4.23" + "mocha": "^4.1.0", + "original-require": "^1.0.1", + "solc": "0.4.24" } }, + "tslib": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.2.tgz", + "integrity": "sha512-AVP5Xol3WivEr7hnssHDsaM+lVrVXWUvd1cfXTRkTj80b//6g2wIFEH6hZG0muGZRnHGrfttpdzRk3YlBkWjKw==", + "dev": true + }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", "dev": true, "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "^5.0.1" } }, "tweetnacl": { @@ -6829,27 +8167,31 @@ "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "dev": true, "requires": { - "prelude-ls": "1.1.2" + "prelude-ls": "~1.1.2" } }, "type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "integrity": "sha1-dkb7XxiHHPu3dJ5pvTmmOI63RQw=", "dev": true }, "type-is": { "version": "1.6.16", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", - "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", + "integrity": "sha1-+JzjQVQcZysl7nrjxz3uOyvlAZQ=", "dev": true, "requires": { "media-typer": "0.3.0", - "mime-types": "2.1.18" + "mime-types": "~2.1.18" } }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" + }, "uglify-js": { "version": "2.8.29", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", @@ -6857,9 +8199,9 @@ "dev": true, "optional": true, "requires": { - "source-map": "0.5.7", - "uglify-to-browserify": "1.0.2", - "yargs": "3.10.0" + "source-map": "~0.5.1", + "uglify-to-browserify": "~1.0.0", + "yargs": "~3.10.0" }, "dependencies": { "camelcase": { @@ -6876,8 +8218,8 @@ "dev": true, "optional": true, "requires": { - "center-align": "0.1.3", - "right-align": "0.1.3", + "center-align": "^0.1.1", + "right-align": "^0.1.1", "wordwrap": "0.0.2" } }, @@ -6888,6 +8230,13 @@ "dev": true, "optional": true }, + "window-size": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", + "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", + "dev": true, + "optional": true + }, "wordwrap": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", @@ -6902,9 +8251,9 @@ "dev": true, "optional": true, "requires": { - "camelcase": "1.2.1", - "cliui": "2.1.0", - "decamelize": "1.2.0", + "camelcase": "^1.0.2", + "cliui": "^2.1.0", + "decamelize": "^1.0.0", "window-size": "0.1.0" } } @@ -6920,7 +8269,7 @@ "ultron": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", - "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==", + "integrity": "sha1-n+FTahCmZKZSZqHjzPhf02MCvJw=", "dev": true }, "underscore": { @@ -6929,16 +8278,103 @@ "integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=", "dev": true }, + "union-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", + "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^0.4.3" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "set-value": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", + "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.1", + "to-object-path": "^0.3.0" + } + } + } + }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", "dev": true }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, "untildify": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/untildify/-/untildify-3.0.2.tgz", - "integrity": "sha1-fx8wIFWz/qDz6B3HjrNnZstl4/E=", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-3.0.3.tgz", + "integrity": "sha512-iSk/J8efr8uPT/Z4eSUywnqyrQU7DSdMfdqK4iWEaUVVmcP5JcnpRqmVMwcwcnmI1ATFNgC5V90u09tBynNFKA==", + "dev": true + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", "dev": true }, "url-parse-lax": { @@ -6947,7 +8383,7 @@ "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", "dev": true, "requires": { - "prepend-http": "2.0.0" + "prepend-http": "^2.0.0" } }, "url-set-query": { @@ -6962,6 +8398,23 @@ "integrity": "sha1-FQWgOiiaSMvXpDTvuu7FBV9WM6k=", "dev": true }, + "use": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.0.tgz", + "integrity": "sha512-6UJEQM/L+mzC3ZJNM56Q4DFGLX/evKGRg15UJHGB9X5j5Z3AFbgZvjUh2yq/UJUY4U5dh7Fal++XbNg1uzpRAw==", + "dev": true, + "requires": { + "kind-of": "^6.0.2" + }, + "dependencies": { + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } + } + }, "user-home": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/user-home/-/user-home-1.1.1.tgz", @@ -6977,8 +8430,7 @@ "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "utils-merge": { "version": "1.0.1", @@ -6989,13 +8441,13 @@ "uuid": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", - "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==", + "integrity": "sha1-EsUou51Y0LkmXZovbw/ovhf/HxQ=", "dev": true }, "v8-compile-cache": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-1.1.2.tgz", - "integrity": "sha512-ejdrifsIydN1XDH7EuR2hn8ZrkRKUYF7tUcBjBy/lhrCvs2K+zRlbW9UHc0IQ9RsYFZJFqJrieoIHfkCa0DBRA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.0.0.tgz", + "integrity": "sha512-qNdTUMaCjPs4eEnM3W9H94R3sU70YCuT+/ST7nUf+id1bVOrdjrpUaeZLqPBPRph3hsgn4a4BvwpxhHZx+oSDg==", "dev": true }, "v8flags": { @@ -7004,17 +8456,16 @@ "integrity": "sha1-qrGh+jDUX4jdMhFIh1rALAtV5bQ=", "dev": true, "requires": { - "user-home": "1.1.1" + "user-home": "^1.1.1" } }, "validate-npm-package-license": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz", - "integrity": "sha512-63ZOUnL4SIXj4L0NixR3L1lcjO38crAbgrTpl28t8jjrfuiOBL5Iygm+60qPs/KsZGzPNg6Smnc/oY16QTjF0g==", - "dev": true, + "integrity": "sha1-gWQ7y+8b3+zUYjeT3EZIlIupgzg=", "requires": { - "spdx-correct": "3.0.0", - "spdx-expression-parse": "3.0.0" + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" } }, "vary": { @@ -7029,9 +8480,9 @@ "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", "dev": true, "requires": { - "assert-plus": "1.0.0", + "assert-plus": "^1.0.0", "core-util-is": "1.0.2", - "extsprintf": "1.3.0" + "extsprintf": "^1.2.0" } }, "vinyl": { @@ -7040,8 +8491,8 @@ "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", "dev": true, "requires": { - "clone": "1.0.3", - "clone-stats": "0.0.1", + "clone": "^1.0.0", + "clone-stats": "^0.0.1", "replace-ext": "0.0.1" } }, @@ -7051,12 +8502,12 @@ "integrity": "sha1-p+v1/779obfRjRQPyweyI++2dRo=", "dev": true, "requires": { - "graceful-fs": "4.1.11", - "pify": "2.3.0", - "pinkie-promise": "2.0.1", - "strip-bom": "2.0.0", - "strip-bom-stream": "2.0.0", - "vinyl": "1.2.0" + "graceful-fs": "^4.1.2", + "pify": "^2.3.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0", + "strip-bom-stream": "^2.0.0", + "vinyl": "^1.1.0" }, "dependencies": { "pify": { @@ -7074,21 +8525,21 @@ "dev": true, "requires": { "bignumber.js": "git+https://github.com/debris/bignumber.js.git#94d7146671b9719e00a09c29b01a691bc85048c2", - "crypto-js": "3.1.8", - "utf8": "2.1.2", - "xhr2": "0.1.4", - "xmlhttprequest": "1.8.0" + "crypto-js": "^3.1.4", + "utf8": "^2.1.1", + "xhr2": "*", + "xmlhttprequest": "*" } }, "web3-core-helpers": { - "version": "1.0.0-beta.33", - "resolved": "https://registry.npmjs.org/web3-core-helpers/-/web3-core-helpers-1.0.0-beta.33.tgz", - "integrity": "sha1-Kvcz5QTbBefDZIwdrPV3sOwV3EM=", + "version": "1.0.0-beta.34", + "resolved": "https://registry.npmjs.org/web3-core-helpers/-/web3-core-helpers-1.0.0-beta.34.tgz", + "integrity": "sha1-sWjaANPhnhVrwVriAyA91N/uLQM=", "dev": true, "requires": { "underscore": "1.8.3", - "web3-eth-iban": "1.0.0-beta.33", - "web3-utils": "1.0.0-beta.33" + "web3-eth-iban": "1.0.0-beta.34", + "web3-utils": "1.0.0-beta.34" }, "dependencies": { "underscore": { @@ -7100,15 +8551,15 @@ } }, "web3-eth-abi": { - "version": "1.0.0-beta.33", - "resolved": "https://registry.npmjs.org/web3-eth-abi/-/web3-eth-abi-1.0.0-beta.33.tgz", - "integrity": "sha1-IiH3FRZDZgAypN80D2EjSRaMgko=", + "version": "1.0.0-beta.34", + "resolved": "https://registry.npmjs.org/web3-eth-abi/-/web3-eth-abi-1.0.0-beta.34.tgz", + "integrity": "sha1-A0Uz46ovfln/MXk+rqaFwO1a9no=", "dev": true, "requires": { "bn.js": "4.11.6", "underscore": "1.8.3", - "web3-core-helpers": "1.0.0-beta.33", - "web3-utils": "1.0.0-beta.33" + "web3-core-helpers": "1.0.0-beta.34", + "web3-utils": "1.0.0-beta.34" }, "dependencies": { "underscore": { @@ -7120,19 +8571,19 @@ } }, "web3-eth-iban": { - "version": "1.0.0-beta.33", - "resolved": "https://registry.npmjs.org/web3-eth-iban/-/web3-eth-iban-1.0.0-beta.33.tgz", - "integrity": "sha1-HXPQxSiKRWWxdUp1tfs+oLd6Uy8=", + "version": "1.0.0-beta.34", + "resolved": "https://registry.npmjs.org/web3-eth-iban/-/web3-eth-iban-1.0.0-beta.34.tgz", + "integrity": "sha1-mvRYYFhnzPdOqXmq8yazi6alugw=", "dev": true, "requires": { "bn.js": "4.11.6", - "web3-utils": "1.0.0-beta.33" + "web3-utils": "1.0.0-beta.34" } }, "web3-utils": { - "version": "1.0.0-beta.33", - "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.0.0-beta.33.tgz", - "integrity": "sha1-4JG3mU8JtxSwGYpAV9OtLrjL4jg=", + "version": "1.0.0-beta.34", + "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.0.0-beta.34.tgz", + "integrity": "sha1-lBH8OarvOcpOBhafdiKX2f8CCXA=", "dev": true, "requires": { "bn.js": "4.11.6", @@ -7164,7 +8615,7 @@ "integrity": "sha512-MGO0nVniCLFAQz1qv22zM02QPjcpAoJdy7ED0i3Zy7SY1IecgXCm460ib7H/Wq7e9oL5VL6S2BxaObxwIcag0g==", "dev": true, "requires": { - "jscodeshift": "0.4.1" + "jscodeshift": "^0.4.0" }, "dependencies": { "ast-types": { @@ -7185,21 +8636,21 @@ "integrity": "sha512-iOX6If+hsw0q99V3n31t4f5VlD1TQZddH08xbT65ZqA7T4Vkx68emrDZMUOLVvCEAJ6NpAk7DECe3fjC/t52AQ==", "dev": true, "requires": { - "async": "1.5.2", - "babel-plugin-transform-flow-strip-types": "6.22.0", - "babel-preset-es2015": "6.24.1", - "babel-preset-stage-1": "6.24.1", - "babel-register": "6.26.0", - "babylon": "6.18.0", - "colors": "1.2.1", - "flow-parser": "0.68.0", - "lodash": "4.17.5", - "micromatch": "2.3.11", + "async": "^1.5.0", + "babel-plugin-transform-flow-strip-types": "^6.8.0", + "babel-preset-es2015": "^6.9.0", + "babel-preset-stage-1": "^6.5.0", + "babel-register": "^6.9.0", + "babylon": "^6.17.3", + "colors": "^1.1.2", + "flow-parser": "^0.*", + "lodash": "^4.13.1", + "micromatch": "^2.3.7", "node-dir": "0.1.8", - "nomnom": "1.8.1", - "recast": "0.12.9", - "temp": "0.8.3", - "write-file-atomic": "1.3.4" + "nomnom": "^1.8.1", + "recast": "^0.12.5", + "temp": "^0.8.1", + "write-file-atomic": "^1.2.0" } }, "recast": { @@ -7209,54 +8660,82 @@ "dev": true, "requires": { "ast-types": "0.10.1", - "core-js": "2.5.3", - "esprima": "4.0.0", - "private": "0.1.8", - "source-map": "0.6.1" + "core-js": "^2.4.1", + "esprima": "~4.0.0", + "private": "~0.1.5", + "source-map": "~0.6.1" } } } }, "webpack-cli": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-2.0.12.tgz", - "integrity": "sha512-kMi6NquWwUhmQok2IFrtAEIbaVvujzYvtDGb5WElkwylbLboDsCgizv8IjSi/Q6SQRJ8Crayl1JCBnIJ3rU4Rg==", - "dev": true, - "requires": { - "chalk": "2.3.2", - "cross-spawn": "6.0.5", - "diff": "3.5.0", - "enhanced-resolve": "4.0.0", - "glob-all": "3.1.0", - "global-modules": "1.0.0", - "got": "8.3.0", - "inquirer": "5.1.0", - "interpret": "1.1.0", - "jscodeshift": "0.5.0", - "listr": "0.13.0", - "loader-utils": "1.1.0", - "lodash": "4.17.5", - "log-symbols": "2.2.0", - "mkdirp": "0.5.1", - "p-each-series": "1.0.0", - "p-lazy": "1.0.0", - "prettier": "1.11.1", - "resolve-cwd": "2.0.0", - "supports-color": "5.3.0", - "v8-compile-cache": "1.1.2", - "webpack-addons": "1.1.5", - "yargs": "11.0.0", - "yeoman-environment": "2.0.5", - "yeoman-generator": "2.0.3" + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-2.1.5.tgz", + "integrity": "sha512-CiWQR+1JS77rmyiO6y1q8Kt/O+e8nUUC9YfJ25JtSmzDwbqJV7vIsh3+QKRHVTbTCa0DaVh8iY1LBiagUIDB3g==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "cross-spawn": "^6.0.5", + "diff": "^3.5.0", + "enhanced-resolve": "^4.0.0", + "envinfo": "^5.7.0", + "glob-all": "^3.1.0", + "global-modules": "^1.0.0", + "got": "^8.3.1", + "import-local": "^1.0.0", + "inquirer": "^5.2.0", + "interpret": "^1.1.0", + "jscodeshift": "^0.5.0", + "listr": "^0.14.1", + "loader-utils": "^1.1.0", + "lodash": "^4.17.10", + "log-symbols": "^2.2.0", + "mkdirp": "^0.5.1", + "p-each-series": "^1.0.0", + "p-lazy": "^1.0.0", + "prettier": "^1.12.1", + "supports-color": "^5.4.0", + "v8-compile-cache": "^2.0.0", + "webpack-addons": "^1.1.5", + "yargs": "^11.1.0", + "yeoman-environment": "^2.1.1", + "yeoman-generator": "^2.0.5" + }, + "dependencies": { + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "lodash": { + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", + "dev": true + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, "which": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", - "dev": true, "requires": { - "isexe": "2.0.0" + "isexe": "^2.0.0" } }, "which-module": { @@ -7266,61 +8745,53 @@ "dev": true }, "window-size": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", - "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", - "dev": true, - "optional": true + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.2.0.tgz", + "integrity": "sha1-tDFbtCFKPXBY6+7okuE/ok2YsHU=" }, "wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", - "dev": true + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=" }, "wrap-ansi": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", - "dev": true, "requires": { - "string-width": "1.0.2", - "strip-ansi": "3.0.1" + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" }, "dependencies": { "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, "is-fullwidth-code-point": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, "requires": { - "number-is-nan": "1.0.1" + "number-is-nan": "^1.0.0" } }, "string-width": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" } }, "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, "requires": { - "ansi-regex": "2.1.1" + "ansi-regex": "^2.0.0" } } } @@ -7328,8 +8799,15 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "write": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", + "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", + "requires": { + "mkdirp": "^0.5.1" + } }, "write-file-atomic": { "version": "1.3.4", @@ -7337,47 +8815,47 @@ "integrity": "sha1-+Aek8LHZ6ROuekgRLmzDrxmRtF8=", "dev": true, "requires": { - "graceful-fs": "4.1.11", - "imurmurhash": "0.1.4", - "slide": "1.1.6" + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "slide": "^1.1.5" } }, "ws": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", - "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "integrity": "sha1-8c+E/i1ekB686U767OeF8YeiKPI=", "dev": true, "requires": { - "async-limiter": "1.0.0", - "safe-buffer": "5.1.1", - "ultron": "1.1.1" + "async-limiter": "~1.0.0", + "safe-buffer": "~5.1.0", + "ultron": "~1.1.0" } }, "xhr": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.4.1.tgz", - "integrity": "sha512-pAIU5vBr9Hiy5cpFIbPnwf0C18ZF86DBsZKrlsf87N5De/JbA6RJ83UP/cv+aljl4S40iRVMqP4pr4sF9Dnj0A==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.5.0.tgz", + "integrity": "sha512-4nlO/14t3BNUZRXIXfXe+3N6w3s1KoxcJUUURctd64BLRe67E4gRwp4PjywtDY72fXpZ1y6Ch0VZQRY/gMPzzQ==", "dev": true, "requires": { - "global": "4.3.2", - "is-function": "1.0.1", - "parse-headers": "2.0.1", - "xtend": "4.0.1" + "global": "~4.3.0", + "is-function": "^1.0.1", + "parse-headers": "^2.0.0", + "xtend": "^4.0.0" } }, "xhr-request": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/xhr-request/-/xhr-request-1.1.0.tgz", - "integrity": "sha512-Y7qzEaR3FDtL3fP30k9wO/e+FBnBByZeybKOhASsGP30NIkRAAkKD/sCnLvgEfAIEC1rcmK7YG8f4oEnIrrWzA==", + "integrity": "sha1-9KfBhoufGYcjRE2C3K4xdkPy4u0=", "dev": true, "requires": { - "buffer-to-arraybuffer": "0.0.5", - "object-assign": "4.1.1", - "query-string": "5.1.1", - "simple-get": "2.7.0", - "timed-out": "4.0.1", - "url-set-query": "1.0.0", - "xhr": "2.4.1" + "buffer-to-arraybuffer": "^0.0.5", + "object-assign": "^4.1.1", + "query-string": "^5.0.1", + "simple-get": "^2.7.0", + "timed-out": "^4.0.1", + "url-set-query": "^1.0.0", + "xhr": "^2.0.4" } }, "xhr-request-promise": { @@ -7386,7 +8864,7 @@ "integrity": "sha1-NDxE0e53JrhkgGloLQ+EDIO0Jh0=", "dev": true, "requires": { - "xhr-request": "1.1.0" + "xhr-request": "^1.0.1" } }, "xhr2": { @@ -7410,33 +8888,31 @@ "y18n": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", - "dev": true + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=" }, "yallist": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", - "dev": true + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" }, "yargs": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-11.0.0.tgz", - "integrity": "sha512-Rjp+lMYQOWtgqojx1dEWorjCofi1YN7AoFvYV7b1gx/7dAAeuI4kN5SZiEvr0ZmsZTOpDRcCqrpI10L31tFkBw==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-11.1.0.tgz", + "integrity": "sha512-NwW69J42EsCSanF8kyn5upxvjp5ds+t3+udGBeTbFnERA+lF541DDpMawzo4z6W/QrzNM18D+BPMiOBibnFV5A==", "dev": true, "requires": { - "cliui": "4.0.0", - "decamelize": "1.2.0", - "find-up": "2.1.0", - "get-caller-file": "1.0.2", - "os-locale": "2.1.0", - "require-directory": "2.1.1", - "require-main-filename": "1.0.1", - "set-blocking": "2.0.0", - "string-width": "2.1.1", - "which-module": "2.0.0", - "y18n": "3.2.1", - "yargs-parser": "9.0.2" + "cliui": "^4.0.0", + "decamelize": "^1.1.1", + "find-up": "^2.1.0", + "get-caller-file": "^1.0.1", + "os-locale": "^2.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^9.0.2" } }, "yargs-parser": { @@ -7445,28 +8921,30 @@ "integrity": "sha1-nM9qQ0YP5O1Aqbto9I1DuKaMwHc=", "dev": true, "requires": { - "camelcase": "4.1.0" + "camelcase": "^4.1.0" } }, "yeoman-environment": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/yeoman-environment/-/yeoman-environment-2.0.5.tgz", - "integrity": "sha512-6/W7/B54OPHJXob0n0+pmkwFsirC8cokuQkPSmT/D0lCcSxkKtg/BA6ZnjUBIwjuGqmw3DTrT4en++htaUju5g==", - "dev": true, - "requires": { - "chalk": "2.3.2", - "debug": "3.1.0", - "diff": "3.5.0", - "escape-string-regexp": "1.0.5", - "globby": "6.1.0", - "grouped-queue": "0.3.3", - "inquirer": "3.3.0", - "is-scoped": "1.0.0", - "lodash": "4.17.5", - "log-symbols": "2.2.0", - "mem-fs": "1.1.3", - "text-table": "0.2.0", - "untildify": "3.0.2" + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/yeoman-environment/-/yeoman-environment-2.2.0.tgz", + "integrity": "sha512-gQ+hIW8QRlUo4jGBDCm++qg01SXaIVJ7VyLrtSwk2jQG4vtvluWpsGIl7V8DqT2jGiqukdec0uEyffVEyQgaZA==", + "dev": true, + "requires": { + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^3.1.0", + "diff": "^3.3.1", + "escape-string-regexp": "^1.0.2", + "globby": "^8.0.1", + "grouped-queue": "^0.3.3", + "inquirer": "^5.2.0", + "is-scoped": "^1.0.0", + "lodash": "^4.17.10", + "log-symbols": "^2.1.0", + "mem-fs": "^1.1.0", + "strip-ansi": "^4.0.0", + "text-table": "^0.2.0", + "untildify": "^3.0.2" }, "dependencies": { "debug": { @@ -7478,81 +8956,54 @@ "ms": "2.0.0" } }, - "inquirer": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz", - "integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==", - "dev": true, - "requires": { - "ansi-escapes": "3.0.0", - "chalk": "2.3.2", - "cli-cursor": "2.1.0", - "cli-width": "2.2.0", - "external-editor": "2.1.0", - "figures": "2.0.0", - "lodash": "4.17.5", - "mute-stream": "0.0.7", - "run-async": "2.3.0", - "rx-lite": "4.0.8", - "rx-lite-aggregates": "4.0.8", - "string-width": "2.1.1", - "strip-ansi": "4.0.0", - "through": "2.3.8" - } + "lodash": { + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", + "dev": true } } }, "yeoman-generator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/yeoman-generator/-/yeoman-generator-2.0.3.tgz", - "integrity": "sha512-mODmrZ26a94djmGZZuIiomSGlN4wULdou29ZwcySupb2e9FdvoCl7Ps2FqHFjEHio3kOl/iBeaNqrnx3C3NwWg==", - "dev": true, - "requires": { - "async": "2.6.0", - "chalk": "2.3.2", - "cli-table": "0.3.1", - "cross-spawn": "5.1.0", - "dargs": "5.1.0", - "dateformat": "3.0.3", - "debug": "3.1.0", - "detect-conflict": "1.0.1", - "error": "7.0.2", - "find-up": "2.1.0", - "github-username": "4.1.0", - "istextorbinary": "2.2.1", - "lodash": "4.17.5", - "make-dir": "1.2.0", - "mem-fs-editor": "3.0.2", - "minimist": "1.2.0", - "pretty-bytes": "4.0.2", - "read-chunk": "2.1.0", - "read-pkg-up": "3.0.0", - "rimraf": "2.6.2", - "run-async": "2.3.0", - "shelljs": "0.8.1", - "text-table": "0.2.0", - "through2": "2.0.3", - "yeoman-environment": "2.0.5" + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/yeoman-generator/-/yeoman-generator-2.0.5.tgz", + "integrity": "sha512-rV6tJ8oYzm4mmdF2T3wjY+Q42jKF2YiiD0VKfJ8/0ZYwmhCKC9Xs2346HVLPj/xE13i68psnFJv7iS6gWRkeAg==", + "dev": true, + "requires": { + "async": "^2.6.0", + "chalk": "^2.3.0", + "cli-table": "^0.3.1", + "cross-spawn": "^6.0.5", + "dargs": "^5.1.0", + "dateformat": "^3.0.3", + "debug": "^3.1.0", + "detect-conflict": "^1.0.0", + "error": "^7.0.2", + "find-up": "^2.1.0", + "github-username": "^4.0.0", + "istextorbinary": "^2.2.1", + "lodash": "^4.17.10", + "make-dir": "^1.1.0", + "mem-fs-editor": "^4.0.0", + "minimist": "^1.2.0", + "pretty-bytes": "^4.0.2", + "read-chunk": "^2.1.0", + "read-pkg-up": "^3.0.0", + "rimraf": "^2.6.2", + "run-async": "^2.0.0", + "shelljs": "^0.8.0", + "text-table": "^0.2.0", + "through2": "^2.0.0", + "yeoman-environment": "^2.0.5" }, "dependencies": { "async": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.0.tgz", - "integrity": "sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==", - "dev": true, - "requires": { - "lodash": "4.17.5" - } - }, - "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz", + "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", "dev": true, "requires": { - "lru-cache": "4.1.2", - "shebang-command": "1.2.0", - "which": "1.3.0" + "lodash": "^4.17.10" } }, "debug": { @@ -7564,6 +9015,12 @@ "ms": "2.0.0" } }, + "lodash": { + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", + "dev": true + }, "minimist": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", @@ -7576,7 +9033,7 @@ "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", "dev": true, "requires": { - "glob": "7.1.2" + "glob": "^7.0.5" } } } diff --git a/package.json b/package.json index d6640da..1bb10a1 100644 --- a/package.json +++ b/package.json @@ -8,31 +8,30 @@ "license": "LGPL-3.0+", "dependencies": { "babel-preset-node6": "^11.0.0", - "solc": "^0.4.23", - "solhint": "^1.1.10", - "solidity-coverage": "^0.5.0 " + "solc": "^0.4.24", + "solhint": "^1.1.10" }, "devDependencies": { "babel-cli": "^6.26.0", "babel-polyfill": "^6.26.0", - "babel-preset-env": "^1.6.1", + "babel-preset-env": "^1.7.0", "babel-preset-es2015": "^6.24.1", "babel-register": "^6.26.0", "bluebird": "^3.5.1", "chai": "^4.1.2", "chai-as-promised": "^7.1.1", "chai-bignumber": "^2.0.2", - "ganache-cli": "^6.0.0", + "ganache-cli": "^6.1.3", "json-bigint-string": "^1.0.0", - "solidity-coverage": "^0.4.0", - "truffle": "^4.1.7", - "web3-eth-abi": "^1.0.0-beta.33", - "web3-utils": "^1.0.0-beta.33" + "solidity-coverage": "^0.5.4", + "truffle": "^4.1.11", + "web3-eth-abi": "^1.0.0-beta.34", + "web3-utils": "^1.0.0-beta.34" }, "scripts": { - "testrpc": "ganache-cli --gasLimit 0xFFFFFFF --gasPrice 0 --network-id 1234 --deterministic grape crisp enroll avoid satoshi picnic term dice gown grocery situate depart", - "testrpca": "ganache-cli --port 8545 --gasLimit 0xFFFFFFF --gasPrice 0 --networkId 1234 --deterministic grape crisp enroll avoid satoshi picnic term dice gown grocery situate depart", - "testrpcb": "ganache-cli --port 8546 --gasLimit 0xFFFFFFF --gasPrice 0 --networkId 5678 --deterministic grape crisp enroll avoid satoshi picnic term dice gown grocery situate depart", + "testrpc": "ganache-cli --gasLimit 0xFFFFFFF --gasPrice 1 --network-id 1234 --deterministic grape crisp enroll avoid satoshi picnic term dice gown grocery situate depart", + "testrpca": "ganache-cli --port 8545 --gasLimit 0xFFFFFFF --gasPrice 1 --networkId 1234 --deterministic grape crisp enroll avoid satoshi picnic term dice gown grocery situate depart", + "testrpcb": "ganache-cli --port 8546 --gasLimit 0xFFFFFFF --gasPrice 1 --networkId 5678 --deterministic grape crisp enroll avoid satoshi picnic term dice gown grocery situate depart", "compile": "truffle compile", "deploy": "truffle deploy", "deploya": "truffle deploy --network testrpca", diff --git a/utils/truffle-debug.sh b/utils/truffle-debug.sh new file mode 100755 index 0000000..5d9e088 --- /dev/null +++ b/utils/truffle-debug.sh @@ -0,0 +1,3 @@ +#!/bin/sh +ME=`realpath $0` +`dirname $ME`/../node_modules/.bin/truffle debug $1 From af9e87762422e9f8b90023e11de65885ee77c9c7 Mon Sep 17 00:00:00 2001 From: user Date: Thu, 14 Jun 2018 16:03:39 +0100 Subject: [PATCH 20/49] Moved solhint into devDependencies --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 1bb10a1..ce5a590 100644 --- a/package.json +++ b/package.json @@ -8,8 +8,7 @@ "license": "LGPL-3.0+", "dependencies": { "babel-preset-node6": "^11.0.0", - "solc": "^0.4.24", - "solhint": "^1.1.10" + "solc": "^0.4.24" }, "devDependencies": { "babel-cli": "^6.26.0", @@ -24,6 +23,7 @@ "ganache-cli": "^6.1.3", "json-bigint-string": "^1.0.0", "solidity-coverage": "^0.5.4", + "solhint": "^1.1.10", "truffle": "^4.1.11", "web3-eth-abi": "^1.0.0-beta.34", "web3-utils": "^1.0.0-beta.34" From 7ae9505ed609780e62a97010bd20c420cd0dd619 Mon Sep 17 00:00:00 2001 From: user Date: Thu, 14 Jun 2018 19:14:41 +0100 Subject: [PATCH 21/49] Fixed call with transaction in Solproxy, returns EthTransaction object --- ion/ethrpc.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ion/ethrpc.py b/ion/ethrpc.py index f539943..101130a 100644 --- a/ion/ethrpc.py +++ b/ion/ethrpc.py @@ -29,6 +29,7 @@ import requests import time import warnings + from collections import namedtuple from ethereum.abi import encode_abi, decode_abi from requests.adapters import HTTPAdapter @@ -203,14 +204,13 @@ def _solproxy_bind(self, method, address, account): sig = method['name'] + '(' + ','.join(ins) + ')' # XXX: messy... if method['constant']: - return lambda *args, **kwa: (self.call(address, sig, args, outs, **kwa) - if len(outs) > 1 else - self.call(address, sig, args, outs, **kwa)[0]) + # XXX: document len(outs) and different behaviour... + if len(outs) > 1: + return lambda *args, **kwa: self.call(address, sig, args, outs, **kwa) + return lambda *args, **kwa: self.call(address, sig, args, outs, **kwa)[0] if account is None: raise RuntimeError("Without account, cannot call non-constant methods") - return lambda *args, **kwa: (self.call_with_transaction(account, address, sig, args, **kwa) - if len(outs) > 1 else - self.call_with_transaction(account, address, sig, args, **kwa)[0]) + return lambda *args, **kwa: self.call_with_transaction(account, address, sig, args, **kwa) def proxy(self, abi, address, account=None): """ From d51c01d5666d5e1af1eb44b4bdba16c3253040eb Mon Sep 17 00:00:00 2001 From: user Date: Thu, 14 Jun 2018 19:51:39 +0100 Subject: [PATCH 22/49] Coordinator client waits for transaction to finish, raises upon error --- abi/HTLC.abi | 2 +- ion/htlc/coordclient.py | 50 +++++++++++++++++++++++------------------ 2 files changed, 29 insertions(+), 23 deletions(-) diff --git a/abi/HTLC.abi b/abi/HTLC.abi index 87a1e40..cac840d 100644 --- a/abi/HTLC.abi +++ b/abi/HTLC.abi @@ -1 +1 @@ -[{"constant":false,"inputs":[{"name":"inReceiver","type":"address"},{"name":"inSecretHashed","type":"bytes32"},{"name":"inExpiry","type":"uint256"}],"name":"Deposit","outputs":[{"name":"","type":"bytes32"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"inImage","type":"bytes32"}],"name":"Refund","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"inExchGUID","type":"bytes32"},{"name":"inSecret","type":"bytes32"}],"name":"Withdraw","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"inExchGUID","type":"bytes32"}],"name":"GetState","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"exchanges","outputs":[{"name":"secretHashed","type":"bytes32"},{"name":"sender","type":"address"},{"name":"receiver","type":"address"},{"name":"amount","type":"uint256"},{"name":"expiry","type":"uint256"},{"name":"state","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"exchGUID","type":"bytes32"},{"indexed":true,"name":"receiver","type":"address"},{"indexed":false,"name":"secretHashed","type":"bytes32"},{"indexed":false,"name":"expiry","type":"uint256"}],"name":"OnDeposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"exchGUID","type":"bytes32"}],"name":"OnRefund","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"exchGUID","type":"bytes32"},{"indexed":false,"name":"secret","type":"bytes32"}],"name":"OnWithdraw","type":"event"}] \ No newline at end of file +[{"constant":false,"inputs":[{"name":"inReceiver","type":"address"},{"name":"inSecretHashed","type":"bytes32"},{"name":"inExpiry","type":"uint256"}],"name":"Deposit","outputs":[{"name":"","type":"bytes32"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"inExchGUID","type":"bytes32"}],"name":"Refund","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"inExchGUID","type":"bytes32"},{"name":"inSecret","type":"bytes32"}],"name":"Withdraw","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"inExchGUID","type":"bytes32"}],"name":"GetState","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"exchanges","outputs":[{"name":"secretHashed","type":"bytes32"},{"name":"sender","type":"address"},{"name":"receiver","type":"address"},{"name":"amount","type":"uint256"},{"name":"expiry","type":"uint256"},{"name":"state","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"exchGUID","type":"bytes32"},{"indexed":true,"name":"receiver","type":"address"},{"indexed":false,"name":"secretHashed","type":"bytes32"},{"indexed":false,"name":"expiry","type":"uint256"}],"name":"OnDeposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"exchGUID","type":"bytes32"}],"name":"OnRefund","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"exchGUID","type":"bytes32"},{"indexed":false,"name":"secret","type":"bytes32"}],"name":"OnWithdraw","type":"event"}] \ No newline at end of file diff --git a/ion/htlc/coordclient.py b/ion/htlc/coordclient.py index 4b8cd8c..fde5431 100644 --- a/ion/htlc/coordclient.py +++ b/ion/htlc/coordclient.py @@ -38,7 +38,7 @@ def secret(self): def secret_hashed(self): return self._data['secret_hashed'] - def confirm(self): + def confirm(self, wait=True): """ Confirm the exchange by depositing your side of the deal You must be the original deal offerer to confirm the exchange @@ -60,16 +60,19 @@ def confirm(self): # Offerer deposits their side of the deal, locked to same hashed secret htlc_address = exch_data['offer_htlc_address'] htlc_contract = make_htlc_proxy(ethrpc, htlc_address, my_address) - htlc_contract.Deposit(conf_receiver, conf_secret_hashed.decode('hex'), conf_expiry, value=conf_value) + txn = htlc_contract.Deposit(conf_receiver, conf_secret_hashed.decode('hex'), conf_expiry, value=conf_value) - # TODO: wait for Deposit to complete, or submit transaction receipt + receipt = txn.receipt(wait=wait) + if receipt and int(receipt['status'], 16) == 0: + raise RuntimeError("Confirm failed, txn: " + txn.txid) self._resource.confirm.POST( - xxx=123 #... what to do here? - # TODO: add transaction receipt + txid=txn.txid ) - def release(self, secret): + return txn + + def release(self, secret, wait=True): """ Reveal the secret by withdrawing from your side of the exchange This must be performed by party B (the proposer) @@ -91,15 +94,19 @@ def release(self, secret): htlc_address = exch_data['offer_htlc_address'] htlc_contract = make_htlc_proxy(ethrpc, htlc_address, my_address) - htlc_contract.Withdraw(exch_guid, secret) + txn = htlc_contract.Withdraw(exch_guid, secret) + + receipt = txn.receipt(wait=wait) + if receipt and int(receipt['status'], 16) == 0: + raise RuntimeError("Release failed, txn: " + txn.txid) # Reveal secret, posting back to server self._resource.release.POST( secret=secret_hex, - # TODO: add transaction receipt + txid=txn.txid ) - def finish(self): + def finish(self, wait=True): """ After the secret has been revealed by the proposer the offerer can withdraws funds using the same secret. @@ -110,8 +117,6 @@ def finish(self): secret_hex = self._data['secret'] secret = secret_hex.decode('hex') - secret_hashed_hex = self._data['secret_hashed'] - secret_hashed = secret_hashed_hex.decode('hex') # TODO: verify secret hashes to hashed image @@ -119,11 +124,14 @@ def finish(self): htlc_address = exch_data['want_htlc_address'] htlc_contract = make_htlc_proxy(ethrpc, htlc_address, my_address) - htlc_contract.Withdraw(exch_guid, secret) + txn = htlc_contract.Withdraw(exch_guid, secret) + + receipt = txn.receipt(wait=wait) + if receipt and int(receipt['status'], 16) == 0: + raise RuntimeError("Finish failed, txn: " + txn.txid) self._resource.finish.POST( - xxx=123, - # TODO: add transaction receipt + txid=txn.txid, ) def refund(self): @@ -191,7 +199,7 @@ def proposals(self): def proposal(self, secret_hashed_hex): return self.proposals[secret_hashed_hex] - def propose(self): + def propose(self, wait=True): """ Submit a proposal for the exchange by depositing your tokens into a HTLC contract. @@ -217,20 +225,18 @@ def propose(self): htlc_address = self._data['want_htlc_address'] htlc_contract = make_htlc_proxy(ethrpc, htlc_address, my_address) - result = htlc_contract.Deposit(prop_receiver, secret_hashed, prop_expiry, value=prop_value) - print("Deposit result is", result) + txn = htlc_contract.Deposit(prop_receiver, secret_hashed, prop_expiry, value=prop_value) - # TODO: wait for deposit to go through? - # or provide some kind of receipt... - # should be able to wait for the OnDeposit event to be emitted - # TODO: add 'wait for event' method to ethrpc + receipt = txn.receipt(wait=wait) + if receipt and int(receipt['status'], 16) == 0: + raise RuntimeError("Propose deposit failed, txn: " + txn.txid) # Notify coordinator of proposal proposal_resource = self._resource(secret_hashed_hex) response = proposal_resource.POST( expiry=prop_expiry, depositor=my_address, - # TODO: add transaction receipt + txid=txn.txid ) require(response['ok'] == 1, "Proposal coordinator API error") From 4d78f47c0e78718180f7eaa4ef3b5a83e03b3f3c Mon Sep 17 00:00:00 2001 From: Harry Date: Sun, 17 Jun 2018 00:33:46 +0100 Subject: [PATCH 23/49] Fixes #40 - Initial work to port to Python3 --- test/test_coordclient.py | 74 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100755 test/test_coordclient.py diff --git a/test/test_coordclient.py b/test/test_coordclient.py new file mode 100755 index 0000000..695e069 --- /dev/null +++ b/test/test_coordclient.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python +## Copyright (c) 2018 Harry Roberts. +## SPDX-License-Identifier: LGPL-3.0+ + +import sys + +from ion.ethrpc import EthJsonRpc +from ion.htlc.coordclient import CoordinatorClient + + +def main(api_url='http://127.0.0.1:5000/htlc'): + # Connect to Ethereum instance + ethrpc = EthJsonRpc('127.0.0.1', 8545) + accounts = ethrpc.eth_accounts() + assert len(accounts) > 1 + addr_A = accounts[0] + addr_B = accounts[1] + balance_A_start = ethrpc.eth_getBalance(addr_A) + balance_B_start = ethrpc.eth_getBalance(addr_B) + + offer = 1000 + want = 500 + + # Setup a coordinator client for each A and B + client_A = CoordinatorClient(addr_A, ethrpc, api_url) + client_B = CoordinatorClient(addr_B, ethrpc, api_url) + + # A advertises exchange, offers 1000 for 500 + exch_A = client_A.advertise(offer, want) + + # B retrieves exchanges + exchanges_B = client_B.list() + print("B list", exchanges_B) + + # Verify details of the exchange + exch_B = [_ for _ in exchanges_B if _.guid == exch_A.guid][0] + exch_B_data = exch_B.data + print("Data", exch_B.data) + + # B decides to participate in the swap + # Proposing deposits the funds + secret, prop_B = exch_B.propose() + print("Proposal is", prop_B) + balance_B_deposited = ethrpc.eth_getBalance(addr_B) + + # A then confirms the proposal, depositing their side of the deal + # This 'locks-in' the trade between A and B, denying any further proposals + exch_A.refresh() + print("Updated data:", exch_A.data) + prop_A = exch_A.proposal(prop_B.secret_hashed) + prop_A.confirm() + + # Verify proposal has been chosen + exch_A.refresh() + exch_B.refresh() + print("Chosen proposal is", exch_A.chosen_proposal) + + # B side then releases the secret, withdrawing the funds A deposited + prop_B.release(secret) + + balance_B_released = ethrpc.eth_getBalance(addr_B) + print("B Balance start", balance_B_start) + print("B Balance deposited", balance_B_deposited) + print("B Balance released", balance_B_released) + print("B Difference", balance_B_deposited - balance_B_released) + + # Then A finishes, using the released secret to withdraw the funds B deposited + prop_A.refresh() + print("Prop A data", prop_A.data) + prop_A.finish() + + +if __name__ == "__main__": + main(*sys.argv[1:]) From 1aba109ec96e9e29ecc7ec6a50266b5902599546 Mon Sep 17 00:00:00 2001 From: Harry Date: Sun, 17 Jun 2018 00:37:29 +0100 Subject: [PATCH 24/49] Initial work to migrate to Python3 --- Makefile | 2 +- ion/Ion.py | 2 +- ion/crypto.py | 7 ++-- ion/ethrpc.py | 11 +++--- ion/htlc/coordclient.py | 2 +- ion/lithium/__init__.py | 1 - ion/lithium/api.py | 26 +++++++-------- ion/lithium/lithium.py | 18 ++++------ ion/merkle.py | 10 +++--- ion/utils.py | 11 +++--- setup.py | 1 + test/test_api.py | 8 ++++- test/test_lithium.py | 50 +++++++++++++++------------- test/test_merkle.py | 74 ++++++++++++++++++++--------------------- test_coordclient.py | 74 ----------------------------------------- 15 files changed, 117 insertions(+), 180 deletions(-) delete mode 100755 test_coordclient.py diff --git a/Makefile b/Makefile index b338144..5b66b09 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ ROOT_DIR := $(shell dirname $(realpath $(MAKEFILE_LIST))) SOLC=$(ROOT_DIR)/node_modules/.bin/solcjs -PYTHON=python +PYTHON=python3 NPM=npm GANACHE=$(ROOT_DIR)/node_modules/.bin/ganache-cli TRUFFLE=$(ROOT_DIR)/node_modules/.bin/truffle diff --git a/ion/Ion.py b/ion/Ion.py index edc8a10..73c7c58 100644 --- a/ion/Ion.py +++ b/ion/Ion.py @@ -13,7 +13,7 @@ from ethereum.utils import keccak from .ethrpc import BadStatusCodeError, BadJsonError, BadResponseError, ConnectionError from .args import arg_ethrpc, arg_bytes20 #, arg_lithium_api -PRIMITIVE = (int, long, float, str, bool) +PRIMITIVE = (int, float, str, bool) def rpc_call_with_exceptions(function, *args): """ diff --git a/ion/crypto.py b/ion/crypto.py index 031095d..492eb48 100644 --- a/ion/crypto.py +++ b/ion/crypto.py @@ -5,10 +5,9 @@ """ Crypto: Has a load of useful crypto stuff """ +import struct from collections import namedtuple - from ethereum.utils import big_endian_to_int, encode_int32 -from rlp.utils_py2 import ascii_chr from sha3 import keccak_256 @@ -23,6 +22,10 @@ coincurve = None +def ascii_chr(x): + return struct.back('B', x) + + # -------------------------------------------------------------------- # Datatypes diff --git a/ion/ethrpc.py b/ion/ethrpc.py index 101130a..6b8ec18 100644 --- a/ion/ethrpc.py +++ b/ion/ethrpc.py @@ -29,6 +29,7 @@ import requests import time import warnings +from io import IOBase from collections import namedtuple from ethereum.abi import encode_abi, decode_abi @@ -93,7 +94,7 @@ def clean_hex(d): return hex(d).rstrip('L') def validate_block(block): - if isinstance(block, basestring): + if isinstance(block, str): if block not in BLOCK_TAGS: raise ValueError('invalid block tag') if isinstance(block, int): @@ -223,7 +224,7 @@ def proxy(self, abi, address, account=None): if account is not None: account = normalise_address(account) - if isinstance(abi, file): + if isinstance(abi, IOBase): abi = json.load(abi) elif isinstance(abi, str): with open(abi) as jsonfile: @@ -479,7 +480,7 @@ def eth_getCode(self, address, default_block=BLOCK_TAG_LATEST): NEEDS TESTING ''' - if isinstance(default_block, basestring): + if isinstance(default_block, str): if default_block not in BLOCK_TAGS: raise ValueError return self._call('eth_getCode', [address, default_block]) @@ -535,7 +536,7 @@ def eth_call(self, to_address, from_address=None, gas=None, gas_price=None, valu NEEDS TESTING ''' - if isinstance(default_block, basestring): + if isinstance(default_block, str): if default_block not in BLOCK_TAGS: raise ValueError if from_address is not None and len(from_address) == 20: @@ -563,7 +564,7 @@ def eth_estimateGas(self, to_address=None, from_address=None, gas=None, gas_pric NEEDS TESTING ''' - if isinstance(default_block, basestring): + if isinstance(default_block, str): if default_block not in BLOCK_TAGS: raise ValueError obj = {} diff --git a/ion/htlc/coordclient.py b/ion/htlc/coordclient.py index fde5431..d25acbc 100644 --- a/ion/htlc/coordclient.py +++ b/ion/htlc/coordclient.py @@ -146,7 +146,7 @@ def refund(self): secret_hashed = secret_hashed_hex.decode('hex') # TODO: detertmine which side we're on, automagically call correct one - if x: + if False: htlc_address = self._data['depositor'] else: htlc_address = exch_data['want_htlc_address'] diff --git a/ion/lithium/__init__.py b/ion/lithium/__init__.py index 8819ca2..e69de29 100644 --- a/ion/lithium/__init__.py +++ b/ion/lithium/__init__.py @@ -1 +0,0 @@ -from .lithium import etheventrelay \ No newline at end of file diff --git a/ion/lithium/api.py b/ion/lithium/api.py index 607741a..82bdccd 100644 --- a/ion/lithium/api.py +++ b/ion/lithium/api.py @@ -9,6 +9,7 @@ which is required when withdrawing funds from IonLock """ +from binascii import hexlify, unhexlify from flask import Flask, request, jsonify # from flask import Flask, url_for @@ -16,6 +17,7 @@ app = Flask(__name__) +app.config['JSON_AS_ASCII'] = False @app.route('/') @@ -33,15 +35,15 @@ def api_leaves(): if blockid is not None: nleaves = app.lithium.checkpoints[blockid] byte_leaves = app.lithium.leaves[0:nleaves] - hex_leaves = [x.encode('hex') for x in byte_leaves] + hex_leaves = [hexlify(x) for x in byte_leaves] elif request.method == 'GET': byte_leaves = app.lithium.leaves - hex_leaves = [x.encode('hex') for x in byte_leaves] + hex_leaves = [hexlify(x) for x in byte_leaves] - dict = {u'leaves': hex_leaves} - - return jsonify(dict) + # XXX: python3, json.dumps doesn't handle `bytes` very well.... + data = {u'leaves': [_.decode('utf-8') for _ in hex_leaves]} + return jsonify(data) @app.route('/api/root', methods=['GET']) def api_root(): @@ -50,8 +52,7 @@ def api_root(): """ byte_leaves = app.lithium.leaves tree, root = merkle_tree(byte_leaves) - dict = {u'root': root} - return jsonify(dict) + return jsonify({u'root': root}) @app.route('/api/checkpoints', methods=['GET']) def api_checkpoint(): @@ -66,9 +67,9 @@ def api_blockid(): """ if request.method == 'POST': json = request.get_json() - leaf = json[u'leaf'] + leaf = json[u'leaf'].encode('utf-8') - hex_leaves = [x.encode('hex') for x in app.lithium.leaves] + hex_leaves = [hexlify(x) for x in app.lithium.leaves] byte_checkpoints = app.lithium.checkpoints if leaf is not None: @@ -81,8 +82,7 @@ def api_blockid(): blockid = block break - dict = {u'blockid': str(blockid)} - return jsonify(dict) + return jsonify({u'blockid': str(blockid)}) else: return "No valid leaf received." @@ -104,7 +104,7 @@ def api_proof(): nleaves = app.lithium.checkpoints[blockid] tree, root = merkle_tree(app.lithium.leaves[:nleaves]) - hex_leaf = leaf.decode('hex') + hex_leaf = unhexlify(leaf) path = merkle_path(hex_leaf, tree) @@ -133,7 +133,7 @@ def api_verify_proof(): leaves = app.lithium.leaves[0:nleaves] tree, root = merkle_tree(leaves) - hex_leaf = leaf.decode('hex') + hex_leaf = unhexlify(leaf) proof = merkle_proof(hex_leaf, proof, root) return jsonify({"verified":proof}) diff --git a/ion/lithium/lithium.py b/ion/lithium/lithium.py index 2afa4e9..6ee802a 100644 --- a/ion/lithium/lithium.py +++ b/ion/lithium/lithium.py @@ -13,16 +13,15 @@ import random import string import click -from ethereum.utils import scan_bin, sha3, keccak +from sha3 import keccak_256 +from ..utils import scan_bin from ..args import arg_bytes20, arg_ethrpc from ..merkle import merkle_tree, merkle_hash from .api import app -TRANSFER_SIGNATURE = keccak.new(digest_bits=256) \ - .update('IonTransfer(address,address,uint256,bytes32,bytes)') \ - .hexdigest() +TRANSFER_SIGNATURE = keccak_256(b'IonTransfer(address,address,uint256,bytes32,bytes)').hexdigest() EVENT_SIGNATURES = [TRANSFER_SIGNATURE] @@ -40,10 +39,10 @@ def pack_txn(txn): Packs all the information about a transaction into a deterministic fixed-sized array of bytes from || to """ - tx_from, tx_to, tx_value, tx_input = [scan_bin(x + ('0' * (len(x) % 2))) \ + tx_from, tx_to, tx_value, tx_input = [scan_bin(x + (b'0' * (len(x) % 2))) \ for x in [txn['from'], txn['to'], txn['value'], txn['input']]] - return ''.join([ + return b''.join([ tx_from, tx_to ]) @@ -54,8 +53,7 @@ def pack_log(txn, log): Packs a log entry into one or more entries. sender account || token address of opposite chain from sender || ionLock address of opposite chain from sender || value || hash(reference) """ - print(scan_bin(log['topics'][2]).encode('hex')) - return ''.join([ + return b''.join([ scan_bin(txn['from']), scan_bin(txn['to']), scan_bin(log['address']), @@ -72,9 +70,7 @@ def pack_items(items): if start < 4: for _ in range(start, 4): new_item = random_string(16) - items.append(sha3(new_item)) - else: - pass + items.append(keccak_256(new_item).digest()) class Lithium(object): diff --git a/ion/merkle.py b/ion/merkle.py index 8492acd..a39710a 100755 --- a/ion/merkle.py +++ b/ion/merkle.py @@ -17,13 +17,15 @@ def serialize(v): """Convert to value to a hashable scalar""" if isinstance(v, str): + return v.encode('utf8') + if isinstance(v, bytes): return v - if isinstance(v, (int, long)): + if isinstance(v, int): return zpad(int_to_big_endian(v), 32) - raise NotImplementedError(v) + raise NotImplementedError((v, type(v))) -hashs = lambda *x: bytes_to_int(keccak_256(''.join(map(serialize, x))).digest()) +hashs = lambda *x: bytes_to_int(keccak_256(b''.join(map(serialize, x))).digest()) merkle_hash = lambda *x: bit_clear(hashs(*x), 0xFF) @@ -119,7 +121,7 @@ def merkle_proof(leaf, path, root): def main(): # Create 99 trees of 1..N items for i in range(1, 100): - items = range(0, i) + items = list(range(0, i)) tree, root = merkle_tree(items) # Verify all items exist within the root diff --git a/ion/utils.py b/ion/utils.py index ba5cd91..06c66c7 100644 --- a/ion/utils.py +++ b/ion/utils.py @@ -6,6 +6,7 @@ from base64 import b64encode, b64decode import binascii import json +from functools import reduce from rlp.sedes import big_endian_int from rlp.utils import decode_hex, str_to_bytes @@ -60,7 +61,7 @@ def big_endian_to_int(x): return big_endian_int.deserialize( def is_numeric(x): - return isinstance(x, (int, long)) + return isinstance(x, int) def encode_int(v): @@ -107,9 +108,9 @@ def tojson(x): def marshal(x): - if isinstance(x, (int, long, type(None))): + if isinstance(x, (int, type(None))): return x - if isinstance(x, (str, bytes, unicode)): + if isinstance(x, (str, bytes)): return b64encode(x) if isinstance(x, (tuple, list)): return map(marshal, x) @@ -119,9 +120,9 @@ def marshal(x): def unmarshal(x): - if x is None or isinstance(x, (int, long)): + if x is None or isinstance(x, int): return x - if isinstance(x, (str, bytes, unicode)): + if isinstance(x, (str, bytes)): return b64decode(x) if isinstance(x, (tuple, list)): return map(unmarshal, x) diff --git a/setup.py b/setup.py index faa4639..e1ac66e 100644 --- a/setup.py +++ b/setup.py @@ -8,6 +8,7 @@ version='0.1', packages=['ion'], py_modules=['__main__'], + python_requires='>3.5.0', install_requires=[str(ir.req) for ir in parse_requirements('requirements.txt', session=PipSession())], entry_points=''' [console_scripts] diff --git a/test/test_api.py b/test/test_api.py index 6a40d66..e773e18 100644 --- a/test/test_api.py +++ b/test/test_api.py @@ -48,7 +48,13 @@ } class MockLithium(): - leaves = ['\xff\xcf\x8f\xde\xe7*\xc1\x1b\\T$(\xb3^\xefWi\xc4\t\xf0\xc8\x9c\xe4sX\x82\xc9\xf0\xf0\xfe&hlS\x07N\t\xb0\xd5P\xd83!\\\xbc\xc3\xf9\x14\xbd\x1c\x9e\xce>\xe7\xbf\x8b\x14\xf8A\xbb\x03\xe8\xe6\xa7v_F\xf7!\xf4\xbe\xe7\xbf\x8b\x14\xf8A\xbb\x03\xe8\xe6\xa7v_F\xf7!\xf4\xbe\xe7\xbf\x8b\x14\xf8A\xbb\x03\xe8\xe6\xa7v_F\xf7!\xf4\xbe\xe7\xbf\x8b\x14\xf8A\xbb\x03\xe8\xe6\xa7v_F\xf7!\xf4\xbe 1 - addr_A = accounts[0] - addr_B = accounts[1] - balance_A_start = ethrpc.eth_getBalance(addr_A) - balance_B_start = ethrpc.eth_getBalance(addr_B) - - offer = 1000 - want = 500 - - # Setup a coordinator client for each A and B - client_A = CoordinatorClient(addr_A, ethrpc, api_url) - client_B = CoordinatorClient(addr_B, ethrpc, api_url) - - # A advertises exchange, offers 1000 for 500 - exch_A = client_A.advertise(offer, want) - - # B retrieves exchanges - exchanges_B = client_B.list() - print("B list", exchanges_B) - - # Verify details of the exchange - exch_B = [_ for _ in exchanges_B if _.guid == exch_A.guid][0] - exch_B_data = exch_B.data - print("Data", exch_B.data) - - # B decides to participate in the swap - # Proposing deposits the funds - secret, prop_B = exch_B.propose() - print("Proposal is", prop_B) - balance_B_deposited = ethrpc.eth_getBalance(addr_B) - - # A then confirms the proposal, depositing their side of the deal - # This 'locks-in' the trade between A and B, denying any further proposals - exch_A.refresh() - print("Updated data:", exch_A.data) - prop_A = exch_A.proposal(prop_B.secret_hashed) - prop_A.confirm() - - # Verify proposal has been chosen - exch_A.refresh() - exch_B.refresh() - print("Chosen proposal is", exch_A.chosen_proposal) - - # B side then releases the secret, withdrawing the funds A deposited - prop_B.release(secret) - - balance_B_released = ethrpc.eth_getBalance(addr_B) - print("B Balance start", balance_B_start) - print("B Balance deposited", balance_B_deposited) - print("B Balance released", balance_B_released) - print("B Difference", balance_B_deposited - balance_B_released) - - # Then A finishes, using the released secret to withdraw the funds B deposited - prop_A.refresh() - print("Prop A data", prop_A.data) - prop_A.finish() - - -if __name__ == "__main__": - main(*sys.argv[1:]) From 59fc0b8cd6074c448c80e39d039250ea0c492719 Mon Sep 17 00:00:00 2001 From: Harry Date: Sun, 17 Jun 2018 07:08:48 +0100 Subject: [PATCH 25/49] Python3 fixes to Lithium component --- Dockerfile.alpine-python | 2 +- Makefile | 9 +++++++++ example.sh | 4 ++++ ion/Ion.py | 18 ++++++++++-------- ion/ethrpc.py | 36 +++++++++++++++++++----------------- ion/lithium/lithium.py | 24 +++++++++++------------- ion/merkle.py | 2 +- ion/utils.py | 8 ++++---- 8 files changed, 59 insertions(+), 44 deletions(-) diff --git a/Dockerfile.alpine-python b/Dockerfile.alpine-python index 92b6d79..b4a12f5 100644 --- a/Dockerfile.alpine-python +++ b/Dockerfile.alpine-python @@ -1,4 +1,4 @@ -FROM jfloff/alpine-python:2.7 +FROM jfloff/alpine-python:3.6 COPY . /app/ WORKDIR /app diff --git a/Makefile b/Makefile index 5b66b09..fbfe643 100644 --- a/Makefile +++ b/Makefile @@ -131,6 +131,9 @@ build/%.combined.sol: contracts/%.sol build testrpc: $(NPM) run testrpca +testrpc-b: + $(NPM) run testrpcb + test-js: $(NPM) run test @@ -148,5 +151,11 @@ test: test-unit test-js truffle-deploy: $(TRUFFLE) deploy +truffle-deploy-a: + $(TRUFFLE) deploy --network testrpca --reset + +truffle-deploy-b: + $(TRUFFLE) deployb --network testrpcb --reset + truffle-console: $(TRUFFLE) console \ No newline at end of file diff --git a/example.sh b/example.sh index 9dc76fe..90d80a5 100755 --- a/example.sh +++ b/example.sh @@ -12,6 +12,10 @@ IP_B=127.0.0.1 API_PORT_A=8555 API_PORT_B=8556 +echo python3 -mion lithium --from-account $ACC_A --to-account $ACC_B --rpc-from $IP_A:$PORT_A --rpc-to $IP_B:$PORT_B --lock $LOCK_ADDR --link $LINK_ADDR --api-port $API_PORT_A +echo python3 -mion lithium --from-account $ACC_A --to-account $ACC_B --rpc-from $IP_B:$PORT_B --rpc-to $IP_A:$PORT_A --lock $LOCK_ADDR --link $LINK_ADDR --api-port $API_PORT_B +read enter + echo "==== Chain A ====" echo "...Minting" python -mion ion mint --rpc $IP_A:$PORT_A --account $ACC_A --tkn $TOKEN_ADDR --value 5000 diff --git a/ion/Ion.py b/ion/Ion.py index 73c7c58..3b75124 100644 --- a/ion/Ion.py +++ b/ion/Ion.py @@ -5,14 +5,16 @@ """ from __future__ import print_function +from binascii import hexlify + import click import requests -import simplejson +from sha3 import keccak_256 from .merkle import merkle_hash -from ethereum.utils import keccak from .ethrpc import BadStatusCodeError, BadJsonError, BadResponseError, ConnectionError from .args import arg_ethrpc, arg_bytes20 #, arg_lithium_api + PRIMITIVE = (int, float, str, bool) def rpc_call_with_exceptions(function, *args): @@ -136,7 +138,7 @@ def ionlock_withdraw(lithium_port, rpc, account, lock, tkn, value, ref): ionlock = rpc.proxy("abi/IonLock.abi", lock, account) token = rpc.proxy("abi/Token.abi", tkn, account) - joined_data = account.encode('hex') + tkn.encode('hex') + lock.encode('hex') + "{0:0{1}x}".format(value,64) + keccak.new(digest_bits=256).update(str(ref)).hexdigest() + joined_data = hexlify(account) + hexlify(tkn) + hexlify(lock) + "{0:0{1}x}".format(value,64) + keccak_256(ref).hexdigest() api_url = 'http://127.0.0.1:' + str(lithium_port) r = requests.post(api_url + "/api/blockid", json={'leaf': joined_data}) @@ -146,9 +148,9 @@ def ionlock_withdraw(lithium_port, rpc, account, lock, tkn, value, ref): path = r.json()['proof'] path = [int(x) for x in path] - hashed_ref = keccak.new(digest_bits=256).update(str(ref)).hexdigest() + hashed_ref = keccak_256(ref).hexdigest() - result = rpc_call_with_exceptions(ionlock.Withdraw, value, hashed_ref.decode('hex'), int(blockid), path) + result = rpc_call_with_exceptions(ionlock.Withdraw, value, unhexlify(hashed_ref), int(blockid), path) result = rpc_call_with_exceptions(token.balanceOf, account) if result is not None: @@ -186,7 +188,7 @@ def ionlink_verify(lithium_port, rpc, account, link, lock, tkn, value, ref): """ ionlink = rpc.proxy("abi/IonLink.abi", link, account) - joined_data = account.encode('hex') + tkn.encode('hex') + lock.encode('hex') + "{0:0{1}x}".format(value,64) + keccak.new(digest_bits=256).update(str(ref)).hexdigest() + joined_data = hexlify(account) + hexlify(tkn) + hexlify(lock) + "{0:0{1}x}".format(value,64) + keccak_256(ref).hexdigest() hashed_data = merkle_hash(int(joined_data, 16)) api_url = 'http://127.0.0.1:' + str(lithium_port) r = requests.post(api_url + "/api/blockid", json={'leaf': joined_data}) @@ -231,7 +233,7 @@ def merkle_proof_path(lithium_port, account, lock, tkn, value, ref): :param ref: str, reference used for the deposit :return: 0, merkle path is printed to the console """ - joined_data = account.encode('hex') + tkn.encode('hex') + lock.encode('hex') + "{0:0{1}x}".format(value,64) + keccak.new(digest_bits=256).update(str(ref)).hexdigest() + joined_data = hexlify(account) + hexlify(tkn) + hexlify(lock) + "{0:0{1}x}".format(value,64) + keccak_256(ref).hexdigest() api_url = 'http://127.0.0.1:' + str(lithium_port) r = requests.post(api_url + "/api/blockid", json={'leaf': joined_data}) @@ -269,7 +271,7 @@ def merkle_verify(proof, lithium_port, account, lock, tkn, value, ref): :param ref: str, reference used for the deposit :return: 0, result is printed to the console """ - joined_data = account.encode('hex') + tkn.encode('hex') + lock.encode('hex') + "{0:0{1}x}".format(value,64) + keccak.new(digest_bits=256).update(str(ref)).hexdigest() + joined_data = hexlify(account) + hexlify(tkn) + hexlify(lock) + "{0:0{1}x}".format(value,64) + keccak_256(ref).hexdigest() proof = [int(x) for x in proof] api_url = 'http://127.0.0.1:' + str(lithium_port) r = requests.post(api_url + "/api/blockid", json={'leaf': joined_data}) diff --git a/ion/ethrpc.py b/ion/ethrpc.py index 6b8ec18..8780d39 100644 --- a/ion/ethrpc.py +++ b/ion/ethrpc.py @@ -29,6 +29,7 @@ import requests import time import warnings +from binascii import hexlify, unhexlify from io import IOBase from collections import namedtuple @@ -171,7 +172,8 @@ def _call(self, method, params=None, _id=1): url = '{}://{}:{}'.format(scheme, self.host, self.port) headers = {'Content-Type': JSON_MEDIA_TYPE} try: - r = self.session.post(url, headers=headers, data=json.dumps(data)) + encoded_data = json.dumps(data) + r = self.session.post(url, headers=headers, data=encoded_data) except RequestsConnectionError: raise ConnectionError(url) if r.status_code / 100 != 2: @@ -241,7 +243,7 @@ def proxy(self, abi, address, account=None): continue sig = "%s(%s)" % (method['name'], ','.join([i['type'] for i in method['inputs']])) - sig_hash = keccak_256(bytes(sig)).hexdigest()[:8] + sig_hash = keccak_256(sig.encode('utf-8')).hexdigest()[:8] # Provide an alternate, where the explicit function signature proxy[method['name']] = handler @@ -268,7 +270,7 @@ def create_contract(self, from_, code, gas, sig=None, args=None): if sig is not None and args is not None: types = sig[sig.find('(') + 1: sig.find(')')].split(',') encoded_params = encode_abi(types, args) - code += encoded_params.encode('hex') + code += hexlify(encoded_params) return self.eth_sendTransaction(from_address=from_, gas=gas, data=code) def get_contract_address(self, tx): @@ -284,12 +286,12 @@ def call(self, address, sig, args, result_types): transaction (useful for reading data) ''' data = self._encode_function(sig, args) - data_hex = data.encode('hex') + data_hex = hexlify(data) response = self.eth_call(to_address=address, data=data_hex) # XXX: horrible hack for when RPC returns '0x0'... if (len(result_types) == 0 or result_types[0] == 'uint256') and response == '0x0': response = '0x' + ('0' * 64) - return decode_abi(result_types, response[2:].decode('hex')) + return decode_abi(result_types, unhexlify(response[2:])) def call_with_transaction(self, from_, address, sig, args, gas=None, gas_price=None, value=None): ''' @@ -299,7 +301,7 @@ def call_with_transaction(self, from_, address, sig, args, gas=None, gas_price=N gas = gas or self.DEFAULT_GAS_PER_TX gas_price = gas_price or self.DEFAULT_GAS_PRICE data = self._encode_function(sig, args) - data_hex = data.encode('hex') + data_hex = hexlify(data) return self.eth_sendTransaction(from_address=from_, to_address=address, data=data_hex, gas=gas, gas_price=gas_price, value=value) @@ -321,7 +323,7 @@ def web3_sha3(self, data): TESTED ''' - data = str(data).encode('hex') + data = hexlify(str(data)) return self._call('web3_sha3', [data]) def net_version(self): @@ -501,13 +503,13 @@ def eth_sendTransaction(self, to_address=None, from_address=None, gas=None, gas_ NEEDS TESTING ''' if len(to_address) == 20: - to_address = to_address.encode('hex') + to_address = hexlify(to_address) if len(from_address) == 20: - from_address = from_address.encode('hex') + from_address = hexlify(from_address) params = {} - params['from'] = from_address or self.eth_coinbase() + params['from'] = from_address.decode('utf-8') or self.eth_coinbase() if to_address is not None: - params['to'] = to_address + params['to'] = to_address.decode('utf-8') if gas is not None: params['gas'] = hex(gas) if gas_price is not None: @@ -515,7 +517,7 @@ def eth_sendTransaction(self, to_address=None, from_address=None, gas=None, gas_ if value is not None: params['value'] = clean_hex(value) if data is not None: - params['data'] = data + params['data'] = data.decode('utf-8') if nonce is not None: params['nonce'] = hex(nonce) txid = self._call('eth_sendTransaction', [params]) @@ -540,13 +542,13 @@ def eth_call(self, to_address, from_address=None, gas=None, gas_price=None, valu if default_block not in BLOCK_TAGS: raise ValueError if from_address is not None and len(from_address) == 20: - from_address = from_address.encode('hex') + from_address = hexlify(from_address) if len(to_address) == 20: - to_address = to_address.encode('hex') + to_address = hexlify(to_address) obj = {} - obj['to'] = to_address + obj['to'] = to_address.decode('utf-8') if from_address is not None: - obj['from'] = from_address + obj['from'] = from_address.decode('utf-8') if gas is not None: obj['gas'] = hex(gas) if gas_price is not None: @@ -554,7 +556,7 @@ def eth_call(self, to_address, from_address=None, gas=None, gas_price=None, valu if value is not None: obj['value'] = value if data is not None: - obj['data'] = data + obj['data'] = data.decode('utf-8') return self._call('eth_call', [obj, default_block]) def eth_estimateGas(self, to_address=None, from_address=None, gas=None, gas_price=None, value=None, data=None, diff --git a/ion/lithium/lithium.py b/ion/lithium/lithium.py index 6ee802a..4fcdfb1 100644 --- a/ion/lithium/lithium.py +++ b/ion/lithium/lithium.py @@ -9,10 +9,12 @@ """ from __future__ import print_function +import time import threading -import random -import string +from os import urandom + import click + from sha3 import keccak_256 from ..utils import scan_bin @@ -26,21 +28,13 @@ EVENT_SIGNATURES = [TRANSFER_SIGNATURE] -def random_string(amount): - """ - Returns a random string to hash as pseudo data - """ - return ''.join(random.SystemRandom() \ - .choice(string.ascii_uppercase + string.digits) for _ in range(amount)) - - def pack_txn(txn): """ Packs all the information about a transaction into a deterministic fixed-sized array of bytes from || to """ tx_from, tx_to, tx_value, tx_input = [scan_bin(x + (b'0' * (len(x) % 2))) \ - for x in [txn['from'], txn['to'], txn['value'], txn['input']]] + for x in [_.encode('utf-8') for _ in [txn['from'], txn['to'], txn['value'], txn['input']]]] return b''.join([ tx_from, @@ -69,7 +63,7 @@ def pack_items(items): start = len(items) if start < 4: for _ in range(start, 4): - new_item = random_string(16) + new_item = urandom(16) items.append(keccak_256(new_item).digest()) @@ -154,6 +148,10 @@ def iter_blocks(self, run_event, rpc, start=1, group=1, backlog=0, interval=1): blocks = [] is_latest = False old_head = head + try: + time.sleep(interval) + except KeyboardInterrupt: + raise StopIteration def lithium_submit(self, batch, prev_root, rpc, link, account, checkpoints, leaves): @@ -185,7 +183,7 @@ def lithium_instance(self, run_event, rpc_from, rpc_to, from_account, to_account prev_root = merkle_hash("merkle-tree-extra") print("Starting block iterator") - print("Latest Block: ", ionlock.LatestBlock) + print("Latest Block: ", ionlock.LatestBlock()) for is_latest, block_group in self.iter_blocks(run_event, rpc_from, ionlock.LatestBlock()): items, group_tx_count, group_log_count, transfers = self.process_block_group(rpc_from, block_group) diff --git a/ion/merkle.py b/ion/merkle.py index a39710a..002da68 100755 --- a/ion/merkle.py +++ b/ion/merkle.py @@ -17,7 +17,7 @@ def serialize(v): """Convert to value to a hashable scalar""" if isinstance(v, str): - return v.encode('utf8') + return v.encode('ascii', 'backslashreplace') if isinstance(v, bytes): return v if isinstance(v, int): diff --git a/ion/utils.py b/ion/utils.py index 06c66c7..43a0ee2 100644 --- a/ion/utils.py +++ b/ion/utils.py @@ -4,7 +4,7 @@ import sys from base64 import b64encode, b64decode -import binascii +from binascii import hexlify, unhexlify import json from functools import reduce @@ -41,7 +41,7 @@ def packl(lnum): s = hex(lnum)[2:].rstrip('L') if len(s) & 1: s = '0' + s - return binascii.unhexlify(s) + return unhexlify(s) int_to_big_endian = packl @@ -84,10 +84,10 @@ def require(arg, msg=None): def normalise_address(addr): if len(addr) == 20: - addr = addr.encode('hex') + addr = hexlify(addr) if addr[:2] == '0x': addr = addr[2:] - require(len(addr) == 40, "Invalid address: " + addr) + require(len(addr) == 40, "Invalid address: " + str(addr)) return addr From 5085c0a0cdda7dda5680a9a63d58d428679726b3 Mon Sep 17 00:00:00 2001 From: Harry Date: Sun, 17 Jun 2018 07:36:35 +0100 Subject: [PATCH 26/49] example.sh now completes --- ion/Ion.py | 57 +++++++++++++++++++++++------------------------------- 1 file changed, 24 insertions(+), 33 deletions(-) diff --git a/ion/Ion.py b/ion/Ion.py index 3b75124..9cac93d 100644 --- a/ion/Ion.py +++ b/ion/Ion.py @@ -5,7 +5,7 @@ """ from __future__ import print_function -from binascii import hexlify +from binascii import hexlify, unhexlify import click import requests @@ -138,27 +138,22 @@ def ionlock_withdraw(lithium_port, rpc, account, lock, tkn, value, ref): ionlock = rpc.proxy("abi/IonLock.abi", lock, account) token = rpc.proxy("abi/Token.abi", tkn, account) - joined_data = hexlify(account) + hexlify(tkn) + hexlify(lock) + "{0:0{1}x}".format(value,64) + keccak_256(ref).hexdigest() + joined_data = hexlify(account) + hexlify(tkn) + hexlify(lock) + "{0:0{1}x}".format(value,64) + keccak_256(ref.encode('utf-8')).hexdigest() api_url = 'http://127.0.0.1:' + str(lithium_port) r = requests.post(api_url + "/api/blockid", json={'leaf': joined_data}) - try: - blockid = r.json()['blockid'] - r = requests.post(api_url + "/api/proof", json={'leaf': joined_data, 'blockid': blockid}) + blockid = r.json()['blockid'] + r = requests.post(api_url + "/api/proof", json={'leaf': joined_data, 'blockid': blockid}) - path = r.json()['proof'] - path = [int(x) for x in path] - hashed_ref = keccak_256(ref).hexdigest() + path = r.json()['proof'] + path = [int(x) for x in path] + hashed_ref = keccak_256(ref.encode('utf-8')).hexdigest() - result = rpc_call_with_exceptions(ionlock.Withdraw, value, unhexlify(hashed_ref), int(blockid), path) + result = rpc_call_with_exceptions(ionlock.Withdraw, value, unhexlify(hashed_ref), int(blockid), path) - result = rpc_call_with_exceptions(token.balanceOf, account) - if result is not None: - print("New balance =", result) - - - except simplejson.errors.JSONDecodeError as e: - print(e.message) + result = rpc_call_with_exceptions(token.balanceOf, account) + if result is not None: + print("New balance =", result) return 0 @@ -188,28 +183,24 @@ def ionlink_verify(lithium_port, rpc, account, link, lock, tkn, value, ref): """ ionlink = rpc.proxy("abi/IonLink.abi", link, account) - joined_data = hexlify(account) + hexlify(tkn) + hexlify(lock) + "{0:0{1}x}".format(value,64) + keccak_256(ref).hexdigest() + joined_data = hexlify(account) + hexlify(tkn) + hexlify(lock) + "{0:0{1}x}".format(value,64) + keccak_256(ref.encode('utf-8')).hexdigest() hashed_data = merkle_hash(int(joined_data, 16)) api_url = 'http://127.0.0.1:' + str(lithium_port) r = requests.post(api_url + "/api/blockid", json={'leaf': joined_data}) - try: - blockid = r.json()['blockid'] - r = requests.post(api_url + "/api/proof", json={'leaf': joined_data, 'blockid': blockid}) - - path = r.json()['proof'] - path = [int(x) for x in path] + blockid = r.json()['blockid'] + r = requests.post(api_url + "/api/proof", json={'leaf': joined_data, 'blockid': blockid}) - r = requests.post(api_url + "/api/verify", json={'leaf': joined_data, 'proof': path, 'blockid': blockid}) - print("Lithium proof:") - print(r.text) + path = r.json()['proof'] + path = [int(x) for x in path] - print("IonLink Proof at block id", blockid) - result = rpc_call_with_exceptions(ionlink.Verify, int(blockid), hashed_data, path) - print(result) + r = requests.post(api_url + "/api/verify", json={'leaf': joined_data, 'proof': path, 'blockid': blockid}) + print("Lithium proof:") + print(r.text) - except simplejson.errors.JSONDecodeError as e: - print(e.message) + print("IonLink Proof at block id", blockid) + result = rpc_call_with_exceptions(ionlink.Verify, int(blockid), hashed_data, path) + print(result) return 0 @@ -233,7 +224,7 @@ def merkle_proof_path(lithium_port, account, lock, tkn, value, ref): :param ref: str, reference used for the deposit :return: 0, merkle path is printed to the console """ - joined_data = hexlify(account) + hexlify(tkn) + hexlify(lock) + "{0:0{1}x}".format(value,64) + keccak_256(ref).hexdigest() + joined_data = hexlify(account) + hexlify(tkn) + hexlify(lock) + "{0:0{1}x}".format(value,64) + keccak_256(ref.encode('utf-8')).hexdigest() api_url = 'http://127.0.0.1:' + str(lithium_port) r = requests.post(api_url + "/api/blockid", json={'leaf': joined_data}) @@ -271,7 +262,7 @@ def merkle_verify(proof, lithium_port, account, lock, tkn, value, ref): :param ref: str, reference used for the deposit :return: 0, result is printed to the console """ - joined_data = hexlify(account) + hexlify(tkn) + hexlify(lock) + "{0:0{1}x}".format(value,64) + keccak_256(ref).hexdigest() + joined_data = hexlify(account) + hexlify(tkn) + hexlify(lock) + "{0:0{1}x}".format(value,64) + keccak_256(ref.encode('utf-8')).hexdigest() proof = [int(x) for x in proof] api_url = 'http://127.0.0.1:' + str(lithium_port) r = requests.post(api_url + "/api/blockid", json={'leaf': joined_data}) From d5f13911533b4c94c7dd99786c7d85bfcdcc4490 Mon Sep 17 00:00:00 2001 From: Harry Date: Mon, 18 Jun 2018 07:34:06 +0100 Subject: [PATCH 27/49] Fixed most of Ion.py --- example.sh | 19 +++++++++--------- ion/Ion.py | 55 +++++++++++++++++++++++---------------------------- ion/ethrpc.py | 6 ++---- ion/utils.py | 12 ++++++++++- 4 files changed, 48 insertions(+), 44 deletions(-) diff --git a/example.sh b/example.sh index 90d80a5..a9786d3 100755 --- a/example.sh +++ b/example.sh @@ -1,5 +1,5 @@ #!/bin/bash - +PYTHON=python3 ACC_A=0x22d491bde2303f2f43325b2108d26f1eaba1e32b ACC_B=0xffcf8fdee72ac11b5c542428b35eef5769c409f0 TOKEN_ADDR=0x254dffcd3277c0b1660f6d42efbb754edababc2b @@ -11,6 +11,7 @@ IP_A=127.0.0.1 IP_B=127.0.0.1 API_PORT_A=8555 API_PORT_B=8556 +REF=`openssl rand 32 | sha256sum | cut -f 1 -d ' '` echo python3 -mion lithium --from-account $ACC_A --to-account $ACC_B --rpc-from $IP_A:$PORT_A --rpc-to $IP_B:$PORT_B --lock $LOCK_ADDR --link $LINK_ADDR --api-port $API_PORT_A echo python3 -mion lithium --from-account $ACC_A --to-account $ACC_B --rpc-from $IP_B:$PORT_B --rpc-to $IP_A:$PORT_A --lock $LOCK_ADDR --link $LINK_ADDR --api-port $API_PORT_B @@ -18,47 +19,47 @@ read enter echo "==== Chain A ====" echo "...Minting" -python -mion ion mint --rpc $IP_A:$PORT_A --account $ACC_A --tkn $TOKEN_ADDR --value 5000 +$PYTHON -mion ion mint --rpc $IP_A:$PORT_A --account $ACC_A --tkn $TOKEN_ADDR --value 5000 echo "" echo "Press any key to proceed" read enter echo "...Depositing" -python -mion ion deposit --rpc $IP_A:$PORT_A --account $ACC_A --lock $LOCK_ADDR --tkn $TOKEN_ADDR --value 5000 --ref stuff +$PYTHON -mion ion deposit --rpc $IP_A:$PORT_A --account $ACC_A --lock $LOCK_ADDR --tkn $TOKEN_ADDR --value 5000 --ref $REF echo "" echo "Press any key to proceed" read enter echo "...Fetching proof" -python -mion ion proof --lithium-port $API_PORT_A --account $ACC_A --lock $LOCK_ADDR --tkn $TOKEN_ADDR --value 5000 --ref stuff +$PYTHON -mion ion proof --lithium-port $API_PORT_A --account $ACC_A --lock $LOCK_ADDR --tkn $TOKEN_ADDR --value 5000 --ref $REF echo "" echo "Press any key to proceed" read enter echo "==== Chain B ====" echo "...Minting" -python -mion ion mint --rpc $IP_B:$PORT_B --account $ACC_B --tkn $TOKEN_ADDR --value 5000 +$PYTHON -mion ion mint --rpc $IP_B:$PORT_B --account $ACC_B --tkn $TOKEN_ADDR --value 5000 echo "" echo "Press any key to proceed" read enter echo "...Depositing" -python -mion ion deposit --rpc $IP_B:$PORT_B --account $ACC_B --lock $LOCK_ADDR --tkn $TOKEN_ADDR --value 5000 --ref stuff +$PYTHON -mion ion deposit --rpc $IP_B:$PORT_B --account $ACC_B --lock $LOCK_ADDR --tkn $TOKEN_ADDR --value 5000 --ref stuff echo "" echo "Press any key to proceed" read enter echo "...Fetching proof" -python -mion ion proof --lithium-port $API_PORT_B --account $ACC_B --lock $LOCK_ADDR --tkn $TOKEN_ADDR --value 5000 --ref stuff +$PYTHON -mion ion proof --lithium-port $API_PORT_B --account $ACC_B --lock $LOCK_ADDR --tkn $TOKEN_ADDR --value 5000 --ref stuff echo "" echo "Press any key to proceed" read enter echo "==== Withdrawing from Chain A ====" -python -mion ion withdraw --lithium-port $API_PORT_B --rpc $IP_A:$PORT_A --account $ACC_B --lock $LOCK_ADDR --tkn $TOKEN_ADDR --value 5000 --ref stuff +$PYTHON -mion ion withdraw --lithium-port $API_PORT_B --rpc $IP_A:$PORT_A --account $ACC_B --lock $LOCK_ADDR --tkn $TOKEN_ADDR --value 5000 --ref $REF echo "" echo "Press any key to proceed" read enter echo "==== Withdrawing from Chain B ====" -python -mion ion withdraw --lithium-port $API_PORT_A --rpc $IP_B:$PORT_B --account $ACC_A --lock $LOCK_ADDR --tkn $TOKEN_ADDR --value 5000 --ref stuff +$PYTHON -mion ion withdraw --lithium-port $API_PORT_A --rpc $IP_B:$PORT_B --account $ACC_A --lock $LOCK_ADDR --tkn $TOKEN_ADDR --value 5000 --ref $REF diff --git a/ion/Ion.py b/ion/Ion.py index 9cac93d..9df52fa 100644 --- a/ion/Ion.py +++ b/ion/Ion.py @@ -14,8 +14,9 @@ from .merkle import merkle_hash from .ethrpc import BadStatusCodeError, BadJsonError, BadResponseError, ConnectionError from .args import arg_ethrpc, arg_bytes20 #, arg_lithium_api +from .utils import json_dumps -PRIMITIVE = (int, float, str, bool) +PRIMITIVE = (int, float, str, bool, bytes) def rpc_call_with_exceptions(function, *args): """ @@ -138,16 +139,16 @@ def ionlock_withdraw(lithium_port, rpc, account, lock, tkn, value, ref): ionlock = rpc.proxy("abi/IonLock.abi", lock, account) token = rpc.proxy("abi/Token.abi", tkn, account) - joined_data = hexlify(account) + hexlify(tkn) + hexlify(lock) + "{0:0{1}x}".format(value,64) + keccak_256(ref.encode('utf-8')).hexdigest() + joined_data = hexlify(account) + hexlify(tkn) + hexlify(lock) + "{0:0{1}x}".format(value,64).encode('utf-8') + hexlify(keccak_256(ref.encode('utf-8')).digest()) api_url = 'http://127.0.0.1:' + str(lithium_port) - r = requests.post(api_url + "/api/blockid", json={'leaf': joined_data}) + r = requests.post(api_url + "/api/blockid", json={'leaf': joined_data.decode('ascii')}) blockid = r.json()['blockid'] - r = requests.post(api_url + "/api/proof", json={'leaf': joined_data, 'blockid': blockid}) + r = requests.post(api_url + "/api/proof", json={'leaf': joined_data.decode('ascii'), 'blockid': blockid}) path = r.json()['proof'] path = [int(x) for x in path] - hashed_ref = keccak_256(ref.encode('utf-8')).hexdigest() + hashed_ref = hexlify(keccak_256(ref.encode('utf-8')).digest()) result = rpc_call_with_exceptions(ionlock.Withdraw, value, unhexlify(hashed_ref), int(blockid), path) @@ -183,18 +184,18 @@ def ionlink_verify(lithium_port, rpc, account, link, lock, tkn, value, ref): """ ionlink = rpc.proxy("abi/IonLink.abi", link, account) - joined_data = hexlify(account) + hexlify(tkn) + hexlify(lock) + "{0:0{1}x}".format(value,64) + keccak_256(ref.encode('utf-8')).hexdigest() + joined_data = hexlify(account) + hexlify(tkn) + hexlify(lock) + "{0:0{1}x}".format(value,64).encode('utf-8') + hexlify(keccak_256(ref.encode('utf-8')).digest()) hashed_data = merkle_hash(int(joined_data, 16)) api_url = 'http://127.0.0.1:' + str(lithium_port) - r = requests.post(api_url + "/api/blockid", json={'leaf': joined_data}) + r = requests.post(api_url + "/api/blockid", json={'leaf': joined_data.decode('ascii')}) blockid = r.json()['blockid'] - r = requests.post(api_url + "/api/proof", json={'leaf': joined_data, 'blockid': blockid}) + r = requests.post(api_url + "/api/proof", json={'leaf': joined_data.decode('ascii'), 'blockid': blockid}) path = r.json()['proof'] path = [int(x) for x in path] - r = requests.post(api_url + "/api/verify", json={'leaf': joined_data, 'proof': path, 'blockid': blockid}) + r = requests.post(api_url + "/api/verify", json={'leaf': joined_data.decode('ascii'), 'proof': path, 'blockid': blockid}) print("Lithium proof:") print(r.text) @@ -224,21 +225,19 @@ def merkle_proof_path(lithium_port, account, lock, tkn, value, ref): :param ref: str, reference used for the deposit :return: 0, merkle path is printed to the console """ - joined_data = hexlify(account) + hexlify(tkn) + hexlify(lock) + "{0:0{1}x}".format(value,64) + keccak_256(ref.encode('utf-8')).hexdigest() - api_url = 'http://127.0.0.1:' + str(lithium_port) - r = requests.post(api_url + "/api/blockid", json={'leaf': joined_data}) + joined_data = hexlify(account) + hexlify(tkn) + hexlify(lock) + "{0:0{1}x}".format(value,64).encode('utf-8') + hexlify(keccak_256(ref.encode('utf-8')).digest()) + print("Joined data", joined_data) - try: - blockid = r.json()['blockid'] - r = requests.post(api_url + "/api/proof", json={'leaf': joined_data, 'blockid':blockid}) + api_url = 'http://127.0.0.1:' + str(lithium_port) + r = requests.post(api_url + "/api/blockid", json={'leaf': joined_data.decode('ascii')}) - print("Received proof:") - [print("Path ", r.json()['proof'].index(x), " : ", x) for x in r.json()['proof']] + blockid = r.json()['blockid'] + r = requests.post(api_url + "/api/proof", json={'leaf': joined_data.decode('ascii'), 'blockid':blockid}) - print("Latest IonLink block",blockid) + print("Received proof:") + [print("Path ", r.json()['proof'].index(x), " : ", x) for x in r.json()['proof']] - except simplejson.errors.JSONDecodeError as e: - print(e.message) + print("Latest IonLink block",blockid) return 0 @@ -262,19 +261,15 @@ def merkle_verify(proof, lithium_port, account, lock, tkn, value, ref): :param ref: str, reference used for the deposit :return: 0, result is printed to the console """ - joined_data = hexlify(account) + hexlify(tkn) + hexlify(lock) + "{0:0{1}x}".format(value,64) + keccak_256(ref.encode('utf-8')).hexdigest() + joined_data = hexlify(account) + hexlify(tkn) + hexlify(lock) + "{0:0{1}x}".format(value,64).encode('utf-8') + hexlify(keccak_256(ref.encode('utf-8')).digest()) proof = [int(x) for x in proof] api_url = 'http://127.0.0.1:' + str(lithium_port) - r = requests.post(api_url + "/api/blockid", json={'leaf': joined_data}) + r = requests.post(api_url + "/api/blockid", json={'leaf': joined_data.decode('ascii')}) - try: - blockid = r.json()['blockid'] - r = requests.post(api_url + "/api/verify", json={'leaf': joined_data, 'proof': proof, 'blockid':blockid}) - print("Received proof:") - print(r.text) - - except simplejson.errors.JSONDecodeError as e: - print(e.message) + blockid = r.json()['blockid'] + r = requests.post(api_url + "/api/verify", json={'leaf': joined_data.decode('ascii'), 'proof': proof, 'blockid':blockid}) + print("Received proof:") + print(r.text) return 0 diff --git a/ion/ethrpc.py b/ion/ethrpc.py index 8780d39..5977dbe 100644 --- a/ion/ethrpc.py +++ b/ion/ethrpc.py @@ -38,7 +38,7 @@ from requests.exceptions import ConnectionError as RequestsConnectionError from .crypto import keccak_256 -from .utils import require, big_endian_to_int, zpad, encode_int, normalise_address +from .utils import CustomJSONEncoder, require, big_endian_to_int, zpad, encode_int, normalise_address GETH_DEFAULT_RPC_PORT = 8545 ETH_DEFAULT_RPC_PORT = 8545 @@ -78,8 +78,6 @@ class BadResponseError(EthJsonRpcError): pass - - def hex_to_dec(x): ''' Convert hex to decimal @@ -172,7 +170,7 @@ def _call(self, method, params=None, _id=1): url = '{}://{}:{}'.format(scheme, self.host, self.port) headers = {'Content-Type': JSON_MEDIA_TYPE} try: - encoded_data = json.dumps(data) + encoded_data = json.dumps(data, cls=CustomJSONEncoder) r = self.session.post(url, headers=headers, data=encoded_data) except RequestsConnectionError: raise ConnectionError(url) diff --git a/ion/utils.py b/ion/utils.py index 43a0ee2..2684113 100644 --- a/ion/utils.py +++ b/ion/utils.py @@ -3,9 +3,9 @@ ## SPDX-License-Identifier: LGPL-3.0+ import sys +import json from base64 import b64encode, b64decode from binascii import hexlify, unhexlify -import json from functools import reduce from rlp.sedes import big_endian_int @@ -129,3 +129,13 @@ def unmarshal(x): if isinstance(x, Marshalled): return x.unmarshal(x) raise ValueError("Cannot unmarshal type: %r - %r" % (type(x), x)) + + +class CustomJSONEncoder(json.JSONEncoder): + def default(self, obj): + if isinstance(obj, bytes): + return obj.decode('utf-8', 'backslashreplace') + return json.JSONEncoder.default(self, obj) + +def json_dumps(obj): + return json.dumps(obj, cls=CustomJSONEncoder) From 8c79462aa968eba8e5aa410a01c9168d6b50aafd Mon Sep 17 00:00:00 2001 From: Harry Date: Mon, 18 Jun 2018 22:13:32 +0100 Subject: [PATCH 28/49] example.sh uses a random value, fixed bug with test --- example.sh | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/example.sh b/example.sh index a9786d3..a450001 100755 --- a/example.sh +++ b/example.sh @@ -12,54 +12,55 @@ IP_B=127.0.0.1 API_PORT_A=8555 API_PORT_B=8556 REF=`openssl rand 32 | sha256sum | cut -f 1 -d ' '` +VALUE=$(( ( RANDOM % 10000 ) + 1000 )) -echo python3 -mion lithium --from-account $ACC_A --to-account $ACC_B --rpc-from $IP_A:$PORT_A --rpc-to $IP_B:$PORT_B --lock $LOCK_ADDR --link $LINK_ADDR --api-port $API_PORT_A -echo python3 -mion lithium --from-account $ACC_A --to-account $ACC_B --rpc-from $IP_B:$PORT_B --rpc-to $IP_A:$PORT_A --lock $LOCK_ADDR --link $LINK_ADDR --api-port $API_PORT_B +echo $PYTHON -mion lithium --from-account $ACC_A --to-account $ACC_B --rpc-from $IP_A:$PORT_A --rpc-to $IP_B:$PORT_B --lock $LOCK_ADDR --link $LINK_ADDR --api-port $API_PORT_A +echo $PYTHON -mion lithium --from-account $ACC_A --to-account $ACC_B --rpc-from $IP_B:$PORT_B --rpc-to $IP_A:$PORT_A --lock $LOCK_ADDR --link $LINK_ADDR --api-port $API_PORT_B read enter echo "==== Chain A ====" echo "...Minting" -$PYTHON -mion ion mint --rpc $IP_A:$PORT_A --account $ACC_A --tkn $TOKEN_ADDR --value 5000 +$PYTHON -mion ion mint --rpc $IP_A:$PORT_A --account $ACC_A --tkn $TOKEN_ADDR --value $VALUE echo "" echo "Press any key to proceed" read enter echo "...Depositing" -$PYTHON -mion ion deposit --rpc $IP_A:$PORT_A --account $ACC_A --lock $LOCK_ADDR --tkn $TOKEN_ADDR --value 5000 --ref $REF +$PYTHON -mion ion deposit --rpc $IP_A:$PORT_A --account $ACC_A --lock $LOCK_ADDR --tkn $TOKEN_ADDR --value $VALUE --ref $REF echo "" echo "Press any key to proceed" read enter echo "...Fetching proof" -$PYTHON -mion ion proof --lithium-port $API_PORT_A --account $ACC_A --lock $LOCK_ADDR --tkn $TOKEN_ADDR --value 5000 --ref $REF +$PYTHON -mion ion proof --lithium-port $API_PORT_A --account $ACC_A --lock $LOCK_ADDR --tkn $TOKEN_ADDR --value $VALUE --ref $REF echo "" echo "Press any key to proceed" read enter echo "==== Chain B ====" echo "...Minting" -$PYTHON -mion ion mint --rpc $IP_B:$PORT_B --account $ACC_B --tkn $TOKEN_ADDR --value 5000 +$PYTHON -mion ion mint --rpc $IP_B:$PORT_B --account $ACC_B --tkn $TOKEN_ADDR --value $VALUE echo "" echo "Press any key to proceed" read enter echo "...Depositing" -$PYTHON -mion ion deposit --rpc $IP_B:$PORT_B --account $ACC_B --lock $LOCK_ADDR --tkn $TOKEN_ADDR --value 5000 --ref stuff +$PYTHON -mion ion deposit --rpc $IP_B:$PORT_B --account $ACC_B --lock $LOCK_ADDR --tkn $TOKEN_ADDR --value $VALUE --ref $REF echo "" echo "Press any key to proceed" read enter echo "...Fetching proof" -$PYTHON -mion ion proof --lithium-port $API_PORT_B --account $ACC_B --lock $LOCK_ADDR --tkn $TOKEN_ADDR --value 5000 --ref stuff +$PYTHON -mion ion proof --lithium-port $API_PORT_B --account $ACC_B --lock $LOCK_ADDR --tkn $TOKEN_ADDR --value $VALUE --ref $REF echo "" echo "Press any key to proceed" read enter echo "==== Withdrawing from Chain A ====" -$PYTHON -mion ion withdraw --lithium-port $API_PORT_B --rpc $IP_A:$PORT_A --account $ACC_B --lock $LOCK_ADDR --tkn $TOKEN_ADDR --value 5000 --ref $REF +$PYTHON -mion ion withdraw --lithium-port $API_PORT_B --rpc $IP_A:$PORT_A --account $ACC_B --lock $LOCK_ADDR --tkn $TOKEN_ADDR --value $VALUE --ref $REF echo "" echo "Press any key to proceed" read enter echo "==== Withdrawing from Chain B ====" -$PYTHON -mion ion withdraw --lithium-port $API_PORT_A --rpc $IP_B:$PORT_B --account $ACC_A --lock $LOCK_ADDR --tkn $TOKEN_ADDR --value 5000 --ref $REF +$PYTHON -mion ion withdraw --lithium-port $API_PORT_A --rpc $IP_B:$PORT_B --account $ACC_A --lock $LOCK_ADDR --tkn $TOKEN_ADDR --value $VALUE --ref $REF From 4653670cd966645dcd7a4a667f0c35c0b275d30b Mon Sep 17 00:00:00 2001 From: Harry Date: Mon, 18 Jun 2018 23:47:03 +0100 Subject: [PATCH 29/49] example.sh runs, lithium api runs, unit tests borked (py2/py3 encoding issues) --- ion/lithium/lithium.py | 7 +++++-- ion/merkle.py | 4 ++-- test/test_lithium.py | 24 ++++++++++++------------ 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/ion/lithium/lithium.py b/ion/lithium/lithium.py index 4fcdfb1..3cd62bf 100644 --- a/ion/lithium/lithium.py +++ b/ion/lithium/lithium.py @@ -33,9 +33,12 @@ def pack_txn(txn): Packs all the information about a transaction into a deterministic fixed-sized array of bytes from || to """ - tx_from, tx_to, tx_value, tx_input = [scan_bin(x + (b'0' * (len(x) % 2))) \ - for x in [_.encode('utf-8') for _ in [txn['from'], txn['to'], txn['value'], txn['input']]]] + fields = [txn['from'], txn['to'], txn['value'], txn['input']] + print("Fields is", fields) + encoded_fields = [scan_bin(x + ('0' * (len(x) % 2))) for x in fields] + tx_from, tx_to, tx_value, tx_input = encoded_fields + # XXX: why is only the From and To fields... ? return b''.join([ tx_from, tx_to diff --git a/ion/merkle.py b/ion/merkle.py index 002da68..e627e00 100755 --- a/ion/merkle.py +++ b/ion/merkle.py @@ -17,7 +17,7 @@ def serialize(v): """Convert to value to a hashable scalar""" if isinstance(v, str): - return v.encode('ascii', 'backslashreplace') + return v.encode('utf-8', 'backslashreplace') if isinstance(v, bytes): return v if isinstance(v, int): @@ -50,7 +50,7 @@ def merkle_tree(items): :return: list, long """ tree = [sorted(map(merkle_hash, items))] - extra = merkle_hash("merkle-tree-extra") + extra = merkle_hash(b"merkle-tree-extra") while True: level = tree[-1] # Ensure level has an even number of items, pad it with an 'extra item' diff --git a/test/test_lithium.py b/test/test_lithium.py index f3bfe74..2f6a15f 100644 --- a/test/test_lithium.py +++ b/test/test_lithium.py @@ -18,11 +18,11 @@ from ethereum.utils import scan_bin, sha3, decode_int256, zpad, int_to_big_endian from ion.lithium.lithium import Lithium, pack_txn, pack_log -test_tx_hash = b'0x999999' -test_sender_addr = b'0x123456' -test_recipient_addr = b'0x678910' -test_input = b'0x11111111' -test_value = b'0xfffd' +test_tx_hash = '0x999999' +test_sender_addr = '0x123456' +test_recipient_addr = '0x678910' +test_input = '0x11111111' +test_value = '0xfffd' class MockRPC(): def port(): @@ -65,7 +65,7 @@ def test_pack_txn(self): txn = rpc.eth_getTransactionByHash() packed_txn = hexlify(pack_txn(txn)) - expected_result = b'' + (test_sender_addr[2:]) + (test_recipient_addr[2:]) + expected_result = '' + (test_sender_addr[2:]) + (test_recipient_addr[2:]) self.assertEqual(packed_txn, expected_result) print("Test: Pack Transaction Success") @@ -86,7 +86,7 @@ def test_pack_log(self): topic1 = hexlify(scan_bin(log['topics'][1])) topic2 = hexlify(scan_bin(log['topics'][2])) - expected_result = b'' + (test_sender_addr[2:]) + (test_recipient_addr[2:]) + address + topic1 + topic2 + expected_result = '' + (test_sender_addr[2:]) + (test_recipient_addr[2:]) + address + topic1 + topic2 self.assertEqual(hexlify(packed_log), expected_result) @@ -112,7 +112,7 @@ def test_process_block(self): topic1 = hexlify(scan_bin(log['topics'][1])) topic2 = hexlify(scan_bin(log['topics'][2])) - expected_txn_result = b'' + (test_sender_addr[2:]) + (test_recipient_addr[2:]) + address + topic1 + topic2 + expected_txn_result = '' + (test_sender_addr[2:]) + (test_recipient_addr[2:]) + address + topic1 + topic2 self.assertEqual(hexlify(packed_log), expected_txn_result) self.assertEqual(len(items), 1) @@ -137,11 +137,11 @@ def test_process_block_group(self): log = receipt['logs'][0] packed_log = pack_log(txn, log) - address = hexlify(scan_bin(log['address'])) - topic1 = hexlify(scan_bin(log['topics'][1])) - topic2 = hexlify(scan_bin(log['topics'][2])) + address = hexlify(scan_bin(log['address'])).decode('ascii') + topic1 = hexlify(scan_bin(log['topics'][1])).decode('ascii') + topic2 = hexlify(scan_bin(log['topics'][2])).decode('ascii') - expected_txn_result = b'' + (test_sender_addr[2:]) + (test_recipient_addr[2:]) + address + topic1 + topic2 + expected_txn_result = '' + (test_sender_addr[2:]) + (test_recipient_addr[2:]) + address + topic1 + topic2 self.assertEqual(hexlify(packed_log), expected_txn_result) self.assertEqual(len(items), 1) From be9ab044dac72bf00ce7c01453a3b4f8a26c98b8 Mon Sep 17 00:00:00 2001 From: Harry Date: Mon, 18 Jun 2018 23:58:33 +0100 Subject: [PATCH 30/49] Remove spurious print statement --- ion/lithium/lithium.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ion/lithium/lithium.py b/ion/lithium/lithium.py index 3cd62bf..ac83730 100644 --- a/ion/lithium/lithium.py +++ b/ion/lithium/lithium.py @@ -34,7 +34,6 @@ def pack_txn(txn): from || to """ fields = [txn['from'], txn['to'], txn['value'], txn['input']] - print("Fields is", fields) encoded_fields = [scan_bin(x + ('0' * (len(x) % 2))) for x in fields] tx_from, tx_to, tx_value, tx_input = encoded_fields From 40e035b97dc8cf25e027fd09f60fb94606e468de Mon Sep 17 00:00:00 2001 From: Harry Date: Tue, 19 Jun 2018 10:08:01 +0100 Subject: [PATCH 31/49] Fixed the remaining encoding bugs in Lithium tests --- test/test_lithium.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/test/test_lithium.py b/test/test_lithium.py index 2f6a15f..1985d14 100644 --- a/test/test_lithium.py +++ b/test/test_lithium.py @@ -67,7 +67,7 @@ def test_pack_txn(self): packed_txn = hexlify(pack_txn(txn)) expected_result = '' + (test_sender_addr[2:]) + (test_recipient_addr[2:]) - self.assertEqual(packed_txn, expected_result) + self.assertEqual(packed_txn, bytes(expected_result, 'ascii')) print("Test: Pack Transaction Success") @@ -82,13 +82,13 @@ def test_pack_log(self): for log in receipt['logs']: packed_log = pack_log(txn, log) - address = hexlify(scan_bin(log['address'])) - topic1 = hexlify(scan_bin(log['topics'][1])) - topic2 = hexlify(scan_bin(log['topics'][2])) + address = hexlify(scan_bin(log['address'])).decode('ascii') + topic1 = hexlify(scan_bin(log['topics'][1])).decode('ascii') + topic2 = hexlify(scan_bin(log['topics'][2])).decode('ascii') expected_result = '' + (test_sender_addr[2:]) + (test_recipient_addr[2:]) + address + topic1 + topic2 - self.assertEqual(hexlify(packed_log), expected_result) + self.assertEqual(hexlify(packed_log), bytes(expected_result, 'ascii')) print("Test: Pack Transaction Logs Success") @@ -108,11 +108,12 @@ def test_process_block(self): log = receipt['logs'][0] packed_log = pack_log(txn, log) - address = hexlify(scan_bin(log['address'])) - topic1 = hexlify(scan_bin(log['topics'][1])) - topic2 = hexlify(scan_bin(log['topics'][2])) + address = hexlify(scan_bin(log['address'])).decode('ascii') + topic1 = hexlify(scan_bin(log['topics'][1])).decode('ascii') + topic2 = hexlify(scan_bin(log['topics'][2])).decode('ascii') expected_txn_result = '' + (test_sender_addr[2:]) + (test_recipient_addr[2:]) + address + topic1 + topic2 + expected_txn_result = bytes(expected_txn_result, 'ascii') self.assertEqual(hexlify(packed_log), expected_txn_result) self.assertEqual(len(items), 1) @@ -142,10 +143,10 @@ def test_process_block_group(self): topic2 = hexlify(scan_bin(log['topics'][2])).decode('ascii') expected_txn_result = '' + (test_sender_addr[2:]) + (test_recipient_addr[2:]) + address + topic1 + topic2 - self.assertEqual(hexlify(packed_log), expected_txn_result) + self.assertEqual(hexlify(packed_log), bytes(expected_txn_result, 'ascii')) self.assertEqual(len(items), 1) - self.assertEqual(hexlify(items[0]), expected_txn_result) + self.assertEqual(hexlify(items[0]), bytes(expected_txn_result, 'ascii')) self.assertEqual(group_tx_count, 1) self.assertEqual(group_log_count, 1) From 27a96f7c4a802751cc373e108a10f510bd410148 Mon Sep 17 00:00:00 2001 From: Harry Date: Tue, 19 Jun 2018 11:02:05 +0100 Subject: [PATCH 32/49] api_blockid is O(n) where `n` is the number of checkpoints. I'm sure there's a better way to do this. --- ion/lithium/api.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ion/lithium/api.py b/ion/lithium/api.py index 82bdccd..cfef0ae 100644 --- a/ion/lithium/api.py +++ b/ion/lithium/api.py @@ -72,6 +72,7 @@ def api_blockid(): hex_leaves = [hexlify(x) for x in app.lithium.leaves] byte_checkpoints = app.lithium.checkpoints + # XXX: this is an O(n) operation.... ewwww if leaf is not None: leaf_index = hex_leaves.index(leaf) blockid = None From 05f55d5006cae7404c5913fa7175f8d176b2ffea Mon Sep 17 00:00:00 2001 From: Harry Date: Tue, 19 Jun 2018 11:33:55 +0100 Subject: [PATCH 33/49] See #79 - Fixed hard-coded values in test_api.py - the hard-coded data was flawed --- ion/lithium/api.py | 1 - test/test_api.py | 11 +++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/ion/lithium/api.py b/ion/lithium/api.py index cfef0ae..82bdccd 100644 --- a/ion/lithium/api.py +++ b/ion/lithium/api.py @@ -72,7 +72,6 @@ def api_blockid(): hex_leaves = [hexlify(x) for x in app.lithium.leaves] byte_checkpoints = app.lithium.checkpoints - # XXX: this is an O(n) operation.... ewwww if leaf is not None: leaf_index = hex_leaves.index(leaf) blockid = None diff --git a/test/test_api.py b/test/test_api.py index e773e18..f762cff 100644 --- a/test/test_api.py +++ b/test/test_api.py @@ -35,15 +35,14 @@ EXPECTED_BLOCKID = \ { - u'blockid': u'6ce75c011eac6f587c54493784ce2139b70e38b5b04fedab2bf5a84b500d0d92' + u'blockid': u'81d9d8277b8f741b859de5455b9b56ff240d2ecf19101df3da9b76b137e5a7e6' } PROOF = \ { u'proof': [ - u'54014011439648363204354998496393219114331058923926644452979013882180058964973', - u'111156487848132035204691335325227635200969078435864690888530225168808220587159', - u'112042151246272191572954036892630855408766946838390755837084327398547991526295' + u'97923772266235395715382770652917280357200452858347804453224054177914019790625', + u'98712613182025294006941209131503858290191202453957227771980554773023716000070' ] } @@ -111,7 +110,7 @@ def test_blockid(self): def test_proof(self): print("\nTest: /api/proof Internals") value = '45b6d9232f9a2d8808fef6ee5339482aed37a8588a8668cb88fde5ffaab67ba1' - blockid = '6ce75c011eac6f587c54493784ce2139b70e38b5b04fedab2bf5a84b500d0d92' + blockid = '81d9d8277b8f741b859de5455b9b56ff240d2ecf19101df3da9b76b137e5a7e6' response = self.app.post('/api/proof', json={'leaf': value, 'blockid': blockid}) self.assertEqual(response.status_code, 200) self.assertEqual(response.get_json(), PROOF) @@ -120,7 +119,7 @@ def test_proof(self): def test_proof_verification(self): print("\nTest: /api/verify Internals") value = '45b6d9232f9a2d8808fef6ee5339482aed37a8588a8668cb88fde5ffaab67ba1' - blockid = '6ce75c011eac6f587c54493784ce2139b70e38b5b04fedab2bf5a84b500d0d92' + blockid = '81d9d8277b8f741b859de5455b9b56ff240d2ecf19101df3da9b76b137e5a7e6' response = self.app.post('/api/proof', json={'leaf': value, 'blockid': blockid}) proof = response.get_json() self.assertEqual(response.status_code, 200) From 8d2e0bc6bd8c6066f8a281b8c41317b59ef952ed Mon Sep 17 00:00:00 2001 From: Harry Date: Tue, 19 Jun 2018 18:17:38 +0100 Subject: [PATCH 34/49] Removed pyflakes lint error --- ion/Ion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ion/Ion.py b/ion/Ion.py index 9df52fa..383549b 100644 --- a/ion/Ion.py +++ b/ion/Ion.py @@ -14,7 +14,7 @@ from .merkle import merkle_hash from .ethrpc import BadStatusCodeError, BadJsonError, BadResponseError, ConnectionError from .args import arg_ethrpc, arg_bytes20 #, arg_lithium_api -from .utils import json_dumps + PRIMITIVE = (int, float, str, bool, bytes) From 5156001b52023d49bed659bb1c126b649b5d4d47 Mon Sep 17 00:00:00 2001 From: Harry Date: Wed, 20 Jun 2018 01:09:30 +0100 Subject: [PATCH 35/49] Split logic from web api, ExchangeManager now handles all --- ion/htlc/coordinator.py | 160 ++++++++++++++-------------------------- ion/htlc/manager.py | 116 +++++++++++++++++++++++++++++ 2 files changed, 173 insertions(+), 103 deletions(-) create mode 100644 ion/htlc/manager.py diff --git a/ion/htlc/coordinator.py b/ion/htlc/coordinator.py index 25087e1..0a2f1ef 100644 --- a/ion/htlc/coordinator.py +++ b/ion/htlc/coordinator.py @@ -2,9 +2,6 @@ ## SPDX-License-Identifier: LGPL-3.0+ import sys -import os -import time -from hashlib import sha256 from flask import Flask, Blueprint, request, abort, jsonify, make_response from werkzeug.routing import BaseConverter @@ -12,7 +9,7 @@ from ..args import arg_bytes32, arg_bytes20, arg_uint256 from ..utils import scan_bin -from .common import MINIMUM_EXPIRY_DURATION +from .manager import ExchangeManager, ExchangeError ####################################################################### @@ -80,6 +77,17 @@ class Bytes20Converter(BytesConverter): BYTES_LEN = 20 +def params_parse(data, params): + """ + Performs param unmarshalling operations to return a dictionary + params_parse({...}, {name: unmarshal}) + """ + out = dict() + for key, val in params.items(): + out[key] = val(data, key) + return out + + ####################################################################### @@ -91,8 +99,7 @@ class CoordinatorBlueprint(Blueprint): def __init__(self, htlc_address, **kwa): Blueprint.__init__(self, 'htlc', __name__, **kwa) - self._htlc_address = htlc_address - self._exchanges = dict() + self._manager = ExchangeManager(htlc_address) # XXX: This sure looks hacky... assignment not allowed in lambda, callback etc. self.record(lambda s: s.app.url_map.converters.__setitem__('bytes32', Bytes32Converter)) @@ -113,23 +120,11 @@ def __init__(self, htlc_address, **kwa): self.add_url_rule("///finish", 'finish', self.exch_finish, methods=['POST']) - def _get_exch(self, exch_id): - if exch_id not in self._exchanges: - return api_abort("Unknown exchange", code=404) - return self._exchanges[exch_id] - - def _get_proposal(self, exch_id, secret_hashed): - exch = self._get_exch(exch_id) - proposal = exch['proposals'].get(secret_hashed) - if not proposal: - return api_abort("Unknown proposal", code=404) - return exch, proposal - def index(self): """ Display list of all exchanges, and their details """ - return jsonify(self._exchanges) + return jsonify(self._manager.exchanges) def exch_advertise(self): """ @@ -138,33 +133,20 @@ def exch_advertise(self): This is performed by Alice """ - # Parse and validate input parameters - offer_address = param_bytes20(request.form, 'offer_address') - offer_amount = param_uint256(request.form, 'offer_amount') - want_amount = param_uint256(request.form, 'want_amount') - - # TODO: validate contract addresses etc. and verify on-chain stuff - - exch_id = os.urandom(20).encode('hex') - - # Save exchange details - # TODO: replace with class instance, `Exchange` ? - self._exchanges[exch_id] = dict( - guid=exch_id, - offer_address=offer_address, - offer_amount=offer_amount, - want_amount=want_amount, - proposals=dict(), - chosen_proposal=None, - - # Temporary placeholders - # TODO: replace with correct contracts - offer_htlc_address=self._htlc_address.encode('hex'), - want_htlc_address=self._htlc_address.encode('hex') - ) + + params = params_parse(request.form, dict( + offer_address=param_bytes20, + offer_amount=param_uint256, + want_amount=param_uint256 + )) + + try: + exch_guid = self._manager.advertise(**params) + except ExchangeError as ex: + return api_abort(str(ex)) return jsonify(dict( - id=exch_id, + id=exch_guid, ok=1 )) @@ -172,7 +154,10 @@ def exch_get(self, exch_id): """ Retrieve details of exchange """ - exch = self._get_exch(exch_id) + try: + exch = self._manager.get_exchange(exch_id) + except ExchangeError as ex: + return api_abort(str(ex)) return jsonify(exch) def exch_propose(self, exch_id, secret_hashed): @@ -183,43 +168,15 @@ def exch_propose(self, exch_id, secret_hashed): This is performed by Bob """ - exch = self._get_exch(exch_id) - - if exch['chosen_proposal']: - return api_abort("Proposal has already been chosen", code=409) - - # Hashed secret is the 'image', pre-image can be supplied to prove knowledge of secret - if secret_hashed in exch['proposals']: - return api_abort("Duplicate proposal secret", code=409) - - # TODO: verify either side of the exchange aren't the same - - expiry = param_uint256(request.form, 'expiry') - depositor = param_bytes20(request.form, 'depositor') - - # TODO: verify details on-chain, expiry, depositor and secret must match - - # Verify expiry time is acceptable - # XXX: should minimum expiry be left to the contract, or the coordinator? - now = int(time.time()) - min_expiry = now + MINIMUM_EXPIRY_DURATION - if expiry < min_expiry: - return api_abort("Expiry too short") - - # GUID used for the exchanges - # offer_guid = Deposit() by B (the proposer) - offer_guid = sha256(exch['offer_address'].decode('hex') + secret_hashed.decode('hex')).digest() - # taker_guid = Deposit() by A (the initial offerer) - taker_guid = sha256(depositor.decode('hex') + secret_hashed.decode('hex')).digest() + params = params_parse(request.form, dict( + expiry=param_uint256, + depositor=param_bytes20 + )) - # Store proposal - exch['proposals'][secret_hashed] = dict( - secret_hashed=secret_hashed, - expiry=expiry, - depositor=depositor, - offer_guid=offer_guid.encode('hex'), - taker_guid=taker_guid.encode('hex'), - ) + try: + exch, proposal = self._manager.propose(exch_id, secret_hashed, **params) + except ExchangeError as ex: + return api_abort(str(ex)) # TODO: redirect to proposal URL? - or avoid another GET request... return jsonify(dict( @@ -230,7 +187,10 @@ def exch_proposal_get(self, exch_id, secret_hashed): """ Retrieve details for a specific exchange proposal """ - exch, proposal = self._get_proposal(exch_id, secret_hashed) + try: + exch, proposal = self._manager.get_proposal(exch_id, secret_hashed) + except ExchangeError as ex: + return api_abort(str(ex)) return jsonify(proposal) def exch_confirm(self, exch_id, secret_hashed): @@ -241,12 +201,10 @@ def exch_confirm(self, exch_id, secret_hashed): This is performed by Alice """ - exch, proposal = self._get_proposal(exch_id, secret_hashed) - - # XXX: one side of the expiry must be longer than the other to handle failure case - # TODO: verify on-chain details match the proposal - - exch['chosen_proposal'] = secret_hashed + try: + self._manager.confirm(exch_id, secret_hashed) + except ExchangeError as ex: + return api_abort(str(ex)) return jsonify(dict( ok=1 @@ -258,17 +216,14 @@ def exch_release(self, exch_id, secret_hashed): This is performed by Bob """ - exch, proposal = self._get_proposal(exch_id, secret_hashed) - - secret_hex = param_bytes32(request.form, 'secret') - secret = secret_hex.decode('hex') - secret_hashed_check = sha256(secret).digest() - secret_hashed_check_hex = secret_hashed_check.encode('hex') - - if secret_hashed_check_hex != secret_hashed: - return api_abort(' '.join(["Secret doesn't match! Got", secret_hashed_check_hex, 'expected', secret_hashed])) + params = params_parse(request.form, dict( + secret=param_bytes32, + )) - proposal['secret'] = secret_hex + try: + self._manager.release(exch_id, secret_hashed, **params) + except ExchangeError as ex: + return api_abort(str(ex)) return jsonify(dict( ok=1 @@ -282,11 +237,10 @@ def exch_finish(self, exch_id, secret_hashed): This completes the exchange. """ - exch, proposal = self._get_proposal(exch_id, secret_hashed) - - # XXX: technically a web API call isn't necessary for this step - # the API should monitor the state of both sides of the exchange - # and update the status / information automagically + try: + self._manager.finish(exch_id, secret_hashed) + except ExchangeError as ex: + return api_abort(str(ex)) return jsonify(dict( ok=1 diff --git a/ion/htlc/manager.py b/ion/htlc/manager.py new file mode 100644 index 0000000..0b2ad5a --- /dev/null +++ b/ion/htlc/manager.py @@ -0,0 +1,116 @@ +import os +import time +from hashlib import sha256 + +from .common import MINIMUM_EXPIRY_DURATION + + +class ExchangeError(Exception): + pass + + +class ExchangeManager(object): + def __init__(self, htlc_address): + self._exchanges = dict() + self._htlc_address = htlc_address + + + @property + def exchanges(self): + return self._exchanges + + def get_exchange(self, exch_guid): + return self._exchanges.get(exch_guid) + + def get_proposal(self, exch_guid, secret_hashed): + exch = self.get_exchange(exch_guid) + proposal = exch['proposals'].get(secret_hashed) + if not proposal: + raise ExchangeError("Unknown proposal") + return exch, proposal + + def advertise(self, **kwa): + exch_guid = os.urandom(20).encode('hex') + + # Save exchange details + # TODO: replace with class instance, `Exchange` ? + + exch = dict( + guid=exch_guid, + offer_address=kwa['offer_address'], + offer_amount=kwa['offer_amount'], + want_amount=kwa['want_amount'], + proposals=dict(), + chosen_proposal=None, + + # Temporary placeholders + # TODO: replace with correct contracts + offer_htlc_address=self._htlc_address.encode('hex'), + want_htlc_address=self._htlc_address.encode('hex') + ) + + self._exchanges[exch_guid] = exch + + return exch_guid + + def propose(self, exch_guid, secret_hashed, **kwa): + exch = self.get_exchange(exch_guid) + + expiry = kwa['expiry'] + depositor = kwa['depositor'] + + if exch['chosen_proposal']: + raise ExchangeError("Proposal has already been chosen") + + # Hashed secret is the 'image', pre-image can be supplied to prove knowledge of secret + if secret_hashed in exch['proposals']: + raise ExchangeError("Duplicate proposal secret") + + # TODO: verify details on-chain, expiry, depositor and secret must match + + # Verify expiry time is acceptable + # XXX: should minimum expiry be left to the contract, or the coordinator? + now = int(time.time()) + min_expiry = now + MINIMUM_EXPIRY_DURATION + if expiry < min_expiry: + raise ExchangeError("Expiry too short") + + # GUID used for the exchanges + # XXX: these are swapped + # offer_guid = Deposit() by B (the proposer) + offer_guid = sha256(exch['offer_address'].decode('hex') + secret_hashed.decode('hex')).digest() + # taker_guid = Deposit() by A (the initial offerer) + taker_guid = sha256(depositor.decode('hex') + secret_hashed.decode('hex')).digest() + + # Store proposal + proposal = dict( + secret_hashed=secret_hashed, + expiry=expiry, + depositor=depositor, + offer_guid=offer_guid.encode('hex'), + taker_guid=taker_guid.encode('hex'), + ) + exch['proposals'][secret_hashed] = proposal + + return exch, proposal + + def confirm(self, exch_guid, secret_hashed, **kwa): + exch, proposal = self.get_proposal(exch_guid, secret_hashed) + + exch['chosen_proposal'] = secret_hashed + + def release(self, exch_guid, secret_hashed, **kwa): + exch, proposal = self.get_proposal(exch_guid, secret_hashed) + + secret_hex = kwa['secret'] + secret = secret_hex.decode('hex') + secret_hashed_check = sha256(secret).digest() + secret_hashed_check_hex = secret_hashed_check.encode('hex') + + if secret_hashed_check_hex != secret_hashed: + raise ExchangeError(' '.join(["Secret doesn't match! Got", secret_hashed_check_hex, 'expected', secret_hashed])) + + proposal['secret'] = secret_hex + + def finish(self, exch_guid, secret_hashed, **kwa): + exch, proposal = self.get_proposal(exch_guid, secret_hashed) From 1bfce127a5a73a1c8c6fd8a97c8ed1a45da1268b Mon Sep 17 00:00:00 2001 From: Harry Date: Wed, 20 Jun 2018 10:52:41 +0100 Subject: [PATCH 36/49] Moved stuff from htlc/coordinator to webutils for more re-use --- ion/htlc/coordinator.py | 85 ++--------------------------------------- ion/htlc/manager.py | 9 +++-- ion/restclient.py | 8 ++-- ion/webutils.py | 78 +++++++++++++++++++++++++++++++++++++ 4 files changed, 89 insertions(+), 91 deletions(-) create mode 100644 ion/webutils.py diff --git a/ion/htlc/coordinator.py b/ion/htlc/coordinator.py index 0a2f1ef..ee48797 100644 --- a/ion/htlc/coordinator.py +++ b/ion/htlc/coordinator.py @@ -3,94 +3,15 @@ import sys -from flask import Flask, Blueprint, request, abort, jsonify, make_response -from werkzeug.routing import BaseConverter +from flask import Flask, Blueprint, request, jsonify -from ..args import arg_bytes32, arg_bytes20, arg_uint256 from ..utils import scan_bin +from ..webutils import (Bytes32Converter, Bytes20Converter, params_parse, api_abort, + param_bytes20, param_bytes32, param_uint256) from .manager import ExchangeManager, ExchangeError -####################################################################### -# TODO: move to ..webutils or something - -def api_abort(message, code=400): - return abort(make_response(jsonify(dict(_error=message)), code)) - - -def param(the_dict, key): - if key not in the_dict: - return api_abort("Parameter required: " + key) - return the_dict[key] - - -def param_filter_arg(the_dict, key, filter_fn): - """ - Applies a click argument filter from `..args` to a value from a dictionary - Does the things with HTTP errors etc. upon failure. - """ - value = param(the_dict, key) - try: - value = filter_fn(None, None, value) - except Exception as ex: - return api_abort("Invalid parameter '%s' - %s" % (key, str(ex))) - return value - - -def param_bytes32(the_dict, key): - return param_filter_arg(the_dict, key, arg_bytes32).encode('hex') - - -def param_bytes20(the_dict, key): - return param_filter_arg(the_dict, key, arg_bytes20).encode('hex') - - -def param_uint256(the_dict, key): - return param_filter_arg(the_dict, key, arg_uint256) - - -class BytesConverter(BaseConverter): - """ - Accepts hex encoded bytes as an argument - Provides raw bytes to Python - Marshals between raw bytes and hex encoded - """ - BYTES_LEN = None - def __init__(self, url_map, *items): - assert self.BYTES_LEN is not None - super(BytesConverter, self).__init__(url_map) - self.regex = '(0x)?[a-fA-F0-9]{' + str(self.BYTES_LEN * 2) + '}' - - def to_python(self, value): - # Normalise hex encoding... - return scan_bin(value).encode('hex') - - -class Bytes32Converter(BytesConverter): - """Accept 32 hex-encoded bytes as URL param""" - BYTES_LEN = 32 - - -class Bytes20Converter(BytesConverter): - """Accept 20 hex-encoded bytes as URL param""" - BYTES_LEN = 20 - - -def params_parse(data, params): - """ - Performs param unmarshalling operations to return a dictionary - params_parse({...}, {name: unmarshal}) - """ - out = dict() - for key, val in params.items(): - out[key] = val(data, key) - return out - - -####################################################################### - - class CoordinatorBlueprint(Blueprint): """ Provides a web API for coordinating cross-chain HTLC exchanges diff --git a/ion/htlc/manager.py b/ion/htlc/manager.py index 0b2ad5a..0b030dd 100644 --- a/ion/htlc/manager.py +++ b/ion/htlc/manager.py @@ -2,6 +2,8 @@ import time from hashlib import sha256 +from ..utils import normalise_address + from .common import MINIMUM_EXPIRY_DURATION @@ -12,8 +14,7 @@ class ExchangeError(Exception): class ExchangeManager(object): def __init__(self, htlc_address): self._exchanges = dict() - self._htlc_address = htlc_address - + self._htlc_address = normalise_address(htlc_address) @property def exchanges(self): @@ -45,8 +46,8 @@ def advertise(self, **kwa): # Temporary placeholders # TODO: replace with correct contracts - offer_htlc_address=self._htlc_address.encode('hex'), - want_htlc_address=self._htlc_address.encode('hex') + offer_htlc_address=self._htlc_address, + want_htlc_address=self._htlc_address ) self._exchanges[exch_guid] = exch diff --git a/ion/restclient.py b/ion/restclient.py index 7170617..9fd9f53 100644 --- a/ion/restclient.py +++ b/ion/restclient.py @@ -17,7 +17,7 @@ x = RestClient('http://example.com/') x('test').POST(abc=123) # POST /test abc=123 x.test.derp.GET() # GET /test/derp - x.test() # GET /test + x.test.GET()() # GET /test see... it's nice, and predictable, and Pythonic, and flexible, etc... """ @@ -31,20 +31,18 @@ import requests -from .utils import require - class RestClient(object): __slots__ = ('_api', '_url', '_session') def __init__(self, url, api=None): - require(url is not None, "Must provide REST API HTTP URL") + assert url is not None self._url = url self._session = None if api is None: self._session = requests.Session() self._api = self if api is None else api - require(isinstance(self._api, RestClient)) + assert isinstance(self._api, RestClient) def __getattr__(self, name): if name[0] == '_': diff --git a/ion/webutils.py b/ion/webutils.py new file mode 100644 index 0000000..bfbce6c --- /dev/null +++ b/ion/webutils.py @@ -0,0 +1,78 @@ +from flask import jsonify, abort, make_response +from werkzeug.routing import BaseConverter + +from .args import arg_bytes32, arg_bytes20, arg_uint256 +from .utils import scan_bin + + +def api_abort(message, code=400): + return abort(make_response(jsonify(dict(_error=message)), code)) + + +def param(the_dict, key): + if key not in the_dict: + return api_abort("Parameter required: " + key) + return the_dict[key] + + +def param_filter_arg(the_dict, key, filter_fn): + """ + Applies a click argument filter from `..args` to a value from a dictionary + Does the things with HTTP errors etc. upon failure. + """ + value = param(the_dict, key) + try: + value = filter_fn(None, None, value) + except Exception as ex: + return api_abort("Invalid parameter '%s' - %s" % (key, str(ex))) + return value + + +def param_bytes32(the_dict, key): + return param_filter_arg(the_dict, key, arg_bytes32).encode('hex') + + +def param_bytes20(the_dict, key): + return param_filter_arg(the_dict, key, arg_bytes20).encode('hex') + + +def param_uint256(the_dict, key): + return param_filter_arg(the_dict, key, arg_uint256) + + +class BytesConverter(BaseConverter): + """ + Accepts hex encoded bytes as an argument + Provides raw bytes to Python + Marshals between raw bytes and hex encoded + """ + BYTES_LEN = None + def __init__(self, url_map, *items): + assert self.BYTES_LEN is not None + super(BytesConverter, self).__init__(url_map) + self.regex = '(0x)?[a-fA-F0-9]{' + str(self.BYTES_LEN * 2) + '}' + + def to_python(self, value): + # Normalise hex encoding... + return scan_bin(value).encode('hex') + + +class Bytes32Converter(BytesConverter): + """Accept 32 hex-encoded bytes as URL param""" + BYTES_LEN = 32 + + +class Bytes20Converter(BytesConverter): + """Accept 20 hex-encoded bytes as URL param""" + BYTES_LEN = 20 + + +def params_parse(data, params): + """ + Performs param unmarshalling operations to return a dictionary + params_parse({...}, {name: unmarshal}) + """ + out = dict() + for key, val in params.items(): + out[key] = val(data, key) + return out From 487483ba731287fc4b1a1d3fd18d77a0751ec0fb Mon Sep 17 00:00:00 2001 From: Harry Date: Wed, 20 Jun 2018 11:01:18 +0100 Subject: [PATCH 37/49] Lint cleanups` --- ion/htlc/cli.py | 1 - ion/htlc/coordclient.py | 1 + ion/htlc/coordinator.py | 13 +++++-------- ion/htlc/manager.py | 2 +- ion/utils.py | 5 ++++- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/ion/htlc/cli.py b/ion/htlc/cli.py index 4655555..2bd297c 100644 --- a/ion/htlc/cli.py +++ b/ion/htlc/cli.py @@ -24,7 +24,6 @@ # -# TODO: add value... @click.command() @click.pass_obj @click.option('--receiver', callback=arg_bytes20, metavar="0x...20", required=True, help="Receiver address") diff --git a/ion/htlc/coordclient.py b/ion/htlc/coordclient.py index d25acbc..8a60e68 100644 --- a/ion/htlc/coordclient.py +++ b/ion/htlc/coordclient.py @@ -191,6 +191,7 @@ def chosen_proposal(self): prop_id = self._data['chosen_proposal'] if prop_id: return self.proposal(prop_id) + return None @property def proposals(self): diff --git a/ion/htlc/coordinator.py b/ion/htlc/coordinator.py index ee48797..60a78f7 100644 --- a/ion/htlc/coordinator.py +++ b/ion/htlc/coordinator.py @@ -95,21 +95,19 @@ def exch_propose(self, exch_id, secret_hashed): )) try: - exch, proposal = self._manager.propose(exch_id, secret_hashed, **params) + _, proposal = self._manager.propose(exch_id, secret_hashed, **params) except ExchangeError as ex: return api_abort(str(ex)) # TODO: redirect to proposal URL? - or avoid another GET request... - return jsonify(dict( - ok=1 - )) + return jsonify(proposal) def exch_proposal_get(self, exch_id, secret_hashed): """ Retrieve details for a specific exchange proposal """ try: - exch, proposal = self._manager.get_proposal(exch_id, secret_hashed) + _, proposal = self._manager.get_proposal(exch_id, secret_hashed) except ExchangeError as ex: return api_abort(str(ex)) return jsonify(proposal) @@ -177,14 +175,13 @@ def main(htlc_address): """ if len(htlc_address) != 20: htlc_address = scan_bin(htlc_address) - print("HTLC address:", htlc_address.encode('hex')) coordinator = CoordinatorBlueprint(htlc_address) + app = Flask(__name__) - # app.debug = 1 app.register_blueprint(coordinator, url_prefix='/htlc') - # NOTE: Flask reloader is DAF, doesn't work well with packages *shakes-fists* + # NOTE: Flask reloader doesn't work well with packages *shakes-fists* app.run(use_reloader=False) return 0 diff --git a/ion/htlc/manager.py b/ion/htlc/manager.py index 0b030dd..5d596bb 100644 --- a/ion/htlc/manager.py +++ b/ion/htlc/manager.py @@ -21,7 +21,7 @@ def exchanges(self): return self._exchanges def get_exchange(self, exch_guid): - return self._exchanges.get(exch_guid) + return self._exchanges.get(exch_guid) def get_proposal(self, exch_guid, secret_hashed): exch = self.get_exchange(exch_guid) diff --git a/ion/utils.py b/ion/utils.py index 2684113..70d3268 100644 --- a/ion/utils.py +++ b/ion/utils.py @@ -82,6 +82,7 @@ def require(arg, msg=None): if not arg: raise RuntimeError(msg or "Requirement failed") + def normalise_address(addr): if len(addr) == 20: addr = hexlify(addr) @@ -104,7 +105,7 @@ def unmarshal(cls, args): def tojson(x): - return json.dumps(marshal(x)) + return json.dumps(marshal(x), cls=CustomJSONEncoder) def marshal(x): @@ -137,5 +138,7 @@ def default(self, obj): return obj.decode('utf-8', 'backslashreplace') return json.JSONEncoder.default(self, obj) + +# XXX: about about tojson? def json_dumps(obj): return json.dumps(obj, cls=CustomJSONEncoder) From 2d2192b42b12655a250a2efa1d20d7e6c168dd1c Mon Sep 17 00:00:00 2001 From: Harry Date: Wed, 20 Jun 2018 13:49:55 +0100 Subject: [PATCH 38/49] Added on-chain verification to HTLC. Fixed more Py2->Py3 issues --- Makefile | 3 ++ abi/HTLC.abi | 2 +- contracts/HTLC.sol | 38 +++++++++++++++++++++++++ ion/ethrpc.py | 16 +++++------ ion/htlc/cli.py | 10 ++++--- ion/htlc/common.py | 2 +- ion/htlc/coordclient.py | 5 ++-- ion/htlc/coordinator.py | 16 ++++++----- ion/htlc/manager.py | 61 +++++++++++++++++++++++++++++++---------- ion/utils.py | 2 +- ion/webutils.py | 8 ++++-- 11 files changed, 121 insertions(+), 42 deletions(-) diff --git a/Makefile b/Makefile index fbfe643..482d6cf 100644 --- a/Makefile +++ b/Makefile @@ -140,6 +140,9 @@ test-js: test-unit: $(PYTHON) -m unittest discover test/ +test-coordserver: + $(PYTHON) -mion htlc coordinator --contract 0xd833215cbcc3f914bd1c9ece3ee7bf8b14f841bb + test: test-unit test-js diff --git a/abi/HTLC.abi b/abi/HTLC.abi index cac840d..f18b97b 100644 --- a/abi/HTLC.abi +++ b/abi/HTLC.abi @@ -1 +1 @@ -[{"constant":false,"inputs":[{"name":"inReceiver","type":"address"},{"name":"inSecretHashed","type":"bytes32"},{"name":"inExpiry","type":"uint256"}],"name":"Deposit","outputs":[{"name":"","type":"bytes32"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"inExchGUID","type":"bytes32"}],"name":"Refund","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"inExchGUID","type":"bytes32"},{"name":"inSecret","type":"bytes32"}],"name":"Withdraw","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"inExchGUID","type":"bytes32"}],"name":"GetState","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"exchanges","outputs":[{"name":"secretHashed","type":"bytes32"},{"name":"sender","type":"address"},{"name":"receiver","type":"address"},{"name":"amount","type":"uint256"},{"name":"expiry","type":"uint256"},{"name":"state","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"exchGUID","type":"bytes32"},{"indexed":true,"name":"receiver","type":"address"},{"indexed":false,"name":"secretHashed","type":"bytes32"},{"indexed":false,"name":"expiry","type":"uint256"}],"name":"OnDeposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"exchGUID","type":"bytes32"}],"name":"OnRefund","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"exchGUID","type":"bytes32"},{"indexed":false,"name":"secret","type":"bytes32"}],"name":"OnWithdraw","type":"event"}] \ No newline at end of file +[{"constant":true,"inputs":[{"name":"inExchGUID","type":"bytes32"}],"name":"GetReceiver","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"inReceiver","type":"address"},{"name":"inSecretHashed","type":"bytes32"},{"name":"inExpiry","type":"uint256"}],"name":"Deposit","outputs":[{"name":"","type":"bytes32"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[{"name":"inExchGUID","type":"bytes32"}],"name":"GetAmount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"inExchGUID","type":"bytes32"}],"name":"Refund","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"inExchGUID","type":"bytes32"},{"name":"inSecret","type":"bytes32"}],"name":"Withdraw","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"inExchGUID","type":"bytes32"}],"name":"GetExpiry","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"inExchGUID","type":"bytes32"}],"name":"GetSender","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"inExchGUID","type":"bytes32"}],"name":"GetState","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"exchanges","outputs":[{"name":"secretHashed","type":"bytes32"},{"name":"sender","type":"address"},{"name":"receiver","type":"address"},{"name":"amount","type":"uint256"},{"name":"expiry","type":"uint256"},{"name":"state","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"inExchGUID","type":"bytes32"}],"name":"GetSecretHashed","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"exchGUID","type":"bytes32"},{"indexed":true,"name":"receiver","type":"address"},{"indexed":false,"name":"secretHashed","type":"bytes32"},{"indexed":false,"name":"expiry","type":"uint256"}],"name":"OnDeposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"exchGUID","type":"bytes32"}],"name":"OnRefund","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"exchGUID","type":"bytes32"},{"indexed":false,"name":"secret","type":"bytes32"}],"name":"OnWithdraw","type":"event"}] \ No newline at end of file diff --git a/contracts/HTLC.sol b/contracts/HTLC.sol index 6c852d9..f0aabce 100644 --- a/contracts/HTLC.sol +++ b/contracts/HTLC.sol @@ -31,6 +31,44 @@ contract HTLC { mapping (bytes32 => Exchange) public exchanges; + function GetExchange ( bytes32 inExchGUID ) + internal view returns (Exchange storage) + { + Exchange storage exch = exchanges[inExchGUID]; + require( exch.state != ExchangeState.Invalid ); + return exch; + } + + function GetSender ( bytes32 inExchGUID ) + public view returns (address) + { + return GetExchange(inExchGUID).sender; + } + + function GetReceiver ( bytes32 inExchGUID ) + public view returns (address) + { + return GetExchange(inExchGUID).receiver; + } + + function GetSecretHashed ( bytes32 inExchGUID ) + public view returns (bytes32) + { + return GetExchange(inExchGUID).secretHashed; + } + + function GetExpiry ( bytes32 inExchGUID ) + public view returns (uint256) + { + return GetExchange(inExchGUID).expiry; + } + + function GetAmount ( bytes32 inExchGUID ) + public view returns (uint256) + { + return GetExchange(inExchGUID).amount; + } + function GetState ( bytes32 inExchGUID ) public view returns (ExchangeState) { diff --git a/ion/ethrpc.py b/ion/ethrpc.py index 5977dbe..d6939cc 100644 --- a/ion/ethrpc.py +++ b/ion/ethrpc.py @@ -203,14 +203,14 @@ def _solproxy_bind(self, method, address, account): ins = [_['type'] for _ in method['inputs']] outs = [_['type'] for _ in method['outputs']] sig = method['name'] + '(' + ','.join(ins) + ')' - # XXX: messy... + if method['constant']: # XXX: document len(outs) and different behaviour... if len(outs) > 1: return lambda *args, **kwa: self.call(address, sig, args, outs, **kwa) return lambda *args, **kwa: self.call(address, sig, args, outs, **kwa)[0] if account is None: - raise RuntimeError("Without account, cannot call non-constant methods") + return None return lambda *args, **kwa: self.call_with_transaction(account, address, sig, args, **kwa) def proxy(self, abi, address, account=None): @@ -505,9 +505,9 @@ def eth_sendTransaction(self, to_address=None, from_address=None, gas=None, gas_ if len(from_address) == 20: from_address = hexlify(from_address) params = {} - params['from'] = from_address.decode('utf-8') or self.eth_coinbase() + params['from'] = normalise_address(from_address) or self.eth_coinbase() if to_address is not None: - params['to'] = to_address.decode('utf-8') + params['to'] = normalise_address(to_address) if gas is not None: params['gas'] = hex(gas) if gas_price is not None: @@ -544,9 +544,9 @@ def eth_call(self, to_address, from_address=None, gas=None, gas_price=None, valu if len(to_address) == 20: to_address = hexlify(to_address) obj = {} - obj['to'] = to_address.decode('utf-8') + obj['to'] = normalise_address(to_address) if from_address is not None: - obj['from'] = from_address.decode('utf-8') + obj['from'] = normalise_address(from_address) if gas is not None: obj['gas'] = hex(gas) if gas_price is not None: @@ -569,9 +569,9 @@ def eth_estimateGas(self, to_address=None, from_address=None, gas=None, gas_pric raise ValueError obj = {} if to_address is not None: - obj['to'] = to_address + obj['to'] = normalise_address(to_address) if from_address is not None: - obj['from'] = from_address + obj['from'] = normalise_address(from_address) if gas is not None: obj['gas'] = hex(gas) if gas_price is not None: diff --git a/ion/htlc/cli.py b/ion/htlc/cli.py index 2bd297c..42075d4 100644 --- a/ion/htlc/cli.py +++ b/ion/htlc/cli.py @@ -72,14 +72,16 @@ def contract_multicommand(ctx, rpc, account, contract): ####################################################################### # -# Multi-command entry-point +# HTLC coordinator server # @click.command() @click.option('--contract', callback=arg_bytes20, metavar="0x...20", required=True, help="HTLC contract address") -def coordinator(contract): - from .coordinator import main - main(contract) +@click.option('--rpc', callback=arg_ethrpc, metavar="ip:port", default='127.0.0.1:8545', help="Ethereum JSON-RPC server") +@click.option('--account', callback=arg_bytes20, metavar="0x...20", required=False, help="Account to transfer from.") +def coordinator(contract, rpc, account): + from .coordinator import main as coordinator_main + return coordinator_main(contract, rpc) ####################################################################### diff --git a/ion/htlc/common.py b/ion/htlc/common.py index 05d93de..8a1684b 100644 --- a/ion/htlc/common.py +++ b/ion/htlc/common.py @@ -12,7 +12,7 @@ DEFAULT_EXPIRY_DURATION = 10 * ONE_MINUTE MINIMUM_EXPIRY_DURATION = 2 * ONE_MINUTE -def make_htlc_proxy(rpc, contract, account): +def make_htlc_proxy(rpc, contract, account=None): """ TODO: embed 'abi/HTLC.abi' file in package resources? """ diff --git a/ion/htlc/coordclient.py b/ion/htlc/coordclient.py index 8a60e68..dce5ffa 100644 --- a/ion/htlc/coordclient.py +++ b/ion/htlc/coordclient.py @@ -234,15 +234,14 @@ def propose(self, wait=True): # Notify coordinator of proposal proposal_resource = self._resource(secret_hashed_hex) - response = proposal_resource.POST( + propdata = proposal_resource.POST( expiry=prop_expiry, depositor=my_address, txid=txn.txid ) - require(response['ok'] == 1, "Proposal coordinator API error") # Add proposal to list, then return it - proposal = self._make_proposal(secret_hashed_hex) + proposal = self._make_proposal(secret_hashed_hex, propdata) self.proposals[secret_hashed_hex] = proposal return secret, proposal diff --git a/ion/htlc/coordinator.py b/ion/htlc/coordinator.py index 60a78f7..c3b750d 100644 --- a/ion/htlc/coordinator.py +++ b/ion/htlc/coordinator.py @@ -5,7 +5,8 @@ from flask import Flask, Blueprint, request, jsonify -from ..utils import scan_bin +from ..ethrpc import EthJsonRpc +from ..utils import normalise_address from ..webutils import (Bytes32Converter, Bytes20Converter, params_parse, api_abort, param_bytes20, param_bytes32, param_uint256) @@ -17,10 +18,10 @@ class CoordinatorBlueprint(Blueprint): Provides a web API for coordinating cross-chain HTLC exchanges """ - def __init__(self, htlc_address, **kwa): + def __init__(self, htlc_address, rpc, **kwa): Blueprint.__init__(self, 'htlc', __name__, **kwa) - self._manager = ExchangeManager(htlc_address) + self._manager = ExchangeManager(htlc_address, rpc) # XXX: This sure looks hacky... assignment not allowed in lambda, callback etc. self.record(lambda s: s.app.url_map.converters.__setitem__('bytes32', Bytes32Converter)) @@ -166,17 +167,18 @@ def exch_finish(self, exch_id, secret_hashed): )) -def main(htlc_address): +def main(htlc_address, rpc=None): """ Development server for coordinator NOTE: not suitable for 'production' SEE: http://flask.pocoo.org/docs/1.0/deploying/#deployment """ - if len(htlc_address) != 20: - htlc_address = scan_bin(htlc_address) + htlc_address = normalise_address(htlc_address) + if rpc is None: + rpc = EthJsonRpc() - coordinator = CoordinatorBlueprint(htlc_address) + coordinator = CoordinatorBlueprint(htlc_address, rpc) app = Flask(__name__) app.register_blueprint(coordinator, url_prefix='/htlc') diff --git a/ion/htlc/manager.py b/ion/htlc/manager.py index 5d596bb..c965bd7 100644 --- a/ion/htlc/manager.py +++ b/ion/htlc/manager.py @@ -1,10 +1,12 @@ import os import time +from binascii import hexlify, unhexlify from hashlib import sha256 from ..utils import normalise_address +from ..ethrpc import EthJsonRpc -from .common import MINIMUM_EXPIRY_DURATION +from .common import MINIMUM_EXPIRY_DURATION, make_htlc_proxy class ExchangeError(Exception): @@ -12,9 +14,11 @@ class ExchangeError(Exception): class ExchangeManager(object): - def __init__(self, htlc_address): + def __init__(self, htlc_address, ethrpc): self._exchanges = dict() self._htlc_address = normalise_address(htlc_address) + self._rpc = ethrpc + assert isinstance(ethrpc, EthJsonRpc) @property def exchanges(self): @@ -25,15 +29,14 @@ def get_exchange(self, exch_guid): def get_proposal(self, exch_guid, secret_hashed): exch = self.get_exchange(exch_guid) - proposal = exch['proposals'].get(secret_hashed) + proposal = exch['proposals'].get(secret_hashed, None) if not proposal: raise ExchangeError("Unknown proposal") return exch, proposal def advertise(self, **kwa): - exch_guid = os.urandom(20).encode('hex') + exch_guid = hexlify(os.urandom(20)).decode('ascii') - # Save exchange details # TODO: replace with class instance, `Exchange` ? exch = dict( @@ -49,6 +52,7 @@ def advertise(self, **kwa): offer_htlc_address=self._htlc_address, want_htlc_address=self._htlc_address ) + print("Exch is", exch) self._exchanges[exch_guid] = exch @@ -67,8 +71,6 @@ def propose(self, exch_guid, secret_hashed, **kwa): if secret_hashed in exch['proposals']: raise ExchangeError("Duplicate proposal secret") - # TODO: verify details on-chain, expiry, depositor and secret must match - # Verify expiry time is acceptable # XXX: should minimum expiry be left to the contract, or the coordinator? now = int(time.time()) @@ -77,19 +79,50 @@ def propose(self, exch_guid, secret_hashed, **kwa): raise ExchangeError("Expiry too short") # GUID used for the exchanges - # XXX: these are swapped # offer_guid = Deposit() by B (the proposer) - offer_guid = sha256(exch['offer_address'].decode('hex') + secret_hashed.decode('hex')).digest() + offer_guid = sha256(unhexlify(exch['offer_address']) + unhexlify(secret_hashed)).digest() # taker_guid = Deposit() by A (the initial offerer) - taker_guid = sha256(depositor.decode('hex') + secret_hashed.decode('hex')).digest() + taker_guid = sha256(unhexlify(depositor) + unhexlify(secret_hashed)).digest() + + # TODO: verify details on-chain, expiry, depositor and secret must match + contract = make_htlc_proxy(self._rpc, exch['want_htlc_address']) + + # Verify on-chain expiry matches + onchain_expiry = contract.GetExpiry(offer_guid) + if expiry != onchain_expiry: + raise ExchangeError("Submitted expiry doesn't match on-chain data") + + # Verify on-chain hashed secret + onchain_sechash = contract.GetSecretHashed(offer_guid) + if hexlify(onchain_sechash).decode('ascii') != secret_hashed: + raise ExchangeError("Submitted hashed secret doesn't match on-chain data") + + # 1 = Deposited + if contract.GetState(offer_guid) != 1: + raise ExchangeError("Exchange is in wrong state") + + onchain_receiver = contract.GetReceiver(offer_guid) + print("Receiver", onchain_receiver, exch['offer_address']) + + onchain_sender = contract.GetSender(offer_guid) + print("Sender", onchain_sender, depositor) + + onchain_amount = contract.GetAmount(offer_guid) + print("Amount", onchain_amount) + + # TODO: verify they deposited it for the right person + + # TODO: verify depositor is who they say they are + + # TODO: verify amount is what the correct value for the 'want' side (proposer offers what initial offerer wants) # Store proposal proposal = dict( secret_hashed=secret_hashed, expiry=expiry, depositor=depositor, - offer_guid=offer_guid.encode('hex'), - taker_guid=taker_guid.encode('hex'), + offer_guid=hexlify(offer_guid).decode('ascii'), + taker_guid=hexlify(taker_guid).decode('ascii'), ) exch['proposals'][secret_hashed] = proposal @@ -104,9 +137,9 @@ def release(self, exch_guid, secret_hashed, **kwa): exch, proposal = self.get_proposal(exch_guid, secret_hashed) secret_hex = kwa['secret'] - secret = secret_hex.decode('hex') + secret = unhexlify(secret_hex) secret_hashed_check = sha256(secret).digest() - secret_hashed_check_hex = secret_hashed_check.encode('hex') + secret_hashed_check_hex = hexlify(secret_hashed_check).decode('ascii') if secret_hashed_check_hex != secret_hashed: raise ExchangeError(' '.join(["Secret doesn't match! Got", secret_hashed_check_hex, 'expected', secret_hashed])) diff --git a/ion/utils.py b/ion/utils.py index 70d3268..250c868 100644 --- a/ion/utils.py +++ b/ion/utils.py @@ -85,7 +85,7 @@ def require(arg, msg=None): def normalise_address(addr): if len(addr) == 20: - addr = hexlify(addr) + addr = hexlify(addr).decode('ascii') if addr[:2] == '0x': addr = addr[2:] require(len(addr) == 40, "Invalid address: " + str(addr)) diff --git a/ion/webutils.py b/ion/webutils.py index bfbce6c..32846a9 100644 --- a/ion/webutils.py +++ b/ion/webutils.py @@ -1,3 +1,5 @@ +from binascii import hexlify + from flask import jsonify, abort, make_response from werkzeug.routing import BaseConverter @@ -29,11 +31,11 @@ def param_filter_arg(the_dict, key, filter_fn): def param_bytes32(the_dict, key): - return param_filter_arg(the_dict, key, arg_bytes32).encode('hex') + return hexlify(param_filter_arg(the_dict, key, arg_bytes32)).decode('ascii') def param_bytes20(the_dict, key): - return param_filter_arg(the_dict, key, arg_bytes20).encode('hex') + return hexlify(param_filter_arg(the_dict, key, arg_bytes20)).decode('ascii') def param_uint256(the_dict, key): @@ -54,7 +56,7 @@ def __init__(self, url_map, *items): def to_python(self, value): # Normalise hex encoding... - return scan_bin(value).encode('hex') + return hexlify(scan_bin(value)).decode('ascii') class Bytes32Converter(BytesConverter): From 99894400912a0b0393d1728597782fa2f15c9a2a Mon Sep 17 00:00:00 2001 From: Harry Date: Wed, 20 Jun 2018 14:41:36 +0100 Subject: [PATCH 39/49] Validate remaining on-chain parameters in proposal --- ion/htlc/manager.py | 25 ++++++++++++++----------- ion/merkle.py | 1 - ion/webutils.py | 3 +++ 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/ion/htlc/manager.py b/ion/htlc/manager.py index c965bd7..a4ed0dd 100644 --- a/ion/htlc/manager.py +++ b/ion/htlc/manager.py @@ -1,3 +1,6 @@ +## Copyright (c) 2018 Harry Roberts. All Rights Reserved. +## SPDX-License-Identifier: LGPL-3.0+ + import os import time from binascii import hexlify, unhexlify @@ -101,20 +104,20 @@ def propose(self, exch_guid, secret_hashed, **kwa): if contract.GetState(offer_guid) != 1: raise ExchangeError("Exchange is in wrong state") - onchain_receiver = contract.GetReceiver(offer_guid) - print("Receiver", onchain_receiver, exch['offer_address']) + # Verify receiver + onchain_receiver = normalise_address(contract.GetReceiver(offer_guid)) + if onchain_receiver != exch['offer_address']: + raise ExchangeError("Offer address differs from receiver") - onchain_sender = contract.GetSender(offer_guid) - print("Sender", onchain_sender, depositor) + # Verify sender + onchain_sender = normalise_address(contract.GetSender(offer_guid)) + if onchain_sender != depositor: + raise ExchangeError("Sender address differs from depositor") + # Ensure deposited amount is more or greater than what was wanted onchain_amount = contract.GetAmount(offer_guid) - print("Amount", onchain_amount) - - # TODO: verify they deposited it for the right person - - # TODO: verify depositor is who they say they are - - # TODO: verify amount is what the correct value for the 'want' side (proposer offers what initial offerer wants) + if onchain_amount >= exch['want_amount']: + raise ExchangeError("Propose amount differs from want amount") # Store proposal proposal = dict( diff --git a/ion/merkle.py b/ion/merkle.py index e627e00..eeda8c1 100755 --- a/ion/merkle.py +++ b/ion/merkle.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python ## Copyright (c) 2016-2018 Clearmatics Technologies Ltd ## SPDX-License-Identifier: LGPL-3.0+ diff --git a/ion/webutils.py b/ion/webutils.py index 32846a9..dd77aee 100644 --- a/ion/webutils.py +++ b/ion/webutils.py @@ -1,3 +1,6 @@ +## Copyright (c) 2018 Harry Roberts. All Rights Reserved. +## SPDX-License-Identifier: LGPL-3.0+ + from binascii import hexlify from flask import jsonify, abort, make_response From d7285194d91a57bdd4724e45833283e9edb27e83 Mon Sep 17 00:00:00 2001 From: Harry Date: Wed, 20 Jun 2018 21:02:00 +0100 Subject: [PATCH 40/49] Verify on-chain details upon confirm --- ion/htlc/manager.py | 49 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 43 insertions(+), 6 deletions(-) diff --git a/ion/htlc/manager.py b/ion/htlc/manager.py index a4ed0dd..3d8136e 100644 --- a/ion/htlc/manager.py +++ b/ion/htlc/manager.py @@ -40,8 +40,6 @@ def get_proposal(self, exch_guid, secret_hashed): def advertise(self, **kwa): exch_guid = hexlify(os.urandom(20)).decode('ascii') - # TODO: replace with class instance, `Exchange` ? - exch = dict( guid=exch_guid, offer_address=kwa['offer_address'], @@ -51,11 +49,10 @@ def advertise(self, **kwa): chosen_proposal=None, # Temporary placeholders - # TODO: replace with correct contracts + # TODO: replace with correct contracts depending on network offer_htlc_address=self._htlc_address, want_htlc_address=self._htlc_address ) - print("Exch is", exch) self._exchanges[exch_guid] = exch @@ -87,7 +84,7 @@ def propose(self, exch_guid, secret_hashed, **kwa): # taker_guid = Deposit() by A (the initial offerer) taker_guid = sha256(unhexlify(depositor) + unhexlify(secret_hashed)).digest() - # TODO: verify details on-chain, expiry, depositor and secret must match + # TODO: make sure we have the right contract address contract = make_htlc_proxy(self._rpc, exch['want_htlc_address']) # Verify on-chain expiry matches @@ -116,7 +113,7 @@ def propose(self, exch_guid, secret_hashed, **kwa): # Ensure deposited amount is more or greater than what was wanted onchain_amount = contract.GetAmount(offer_guid) - if onchain_amount >= exch['want_amount']: + if onchain_amount < exch['want_amount']: raise ExchangeError("Propose amount differs from want amount") # Store proposal @@ -136,6 +133,42 @@ def confirm(self, exch_guid, secret_hashed, **kwa): exch['chosen_proposal'] = secret_hashed + contract = make_htlc_proxy(self._rpc, exch['offer_htlc_address']) + + taker_guid = unhexlify(proposal['taker_guid']) + + # 1 = Deposited + if contract.GetState(taker_guid) != 1: + raise ExchangeError("Exchange is in wrong state") + + # Ensure deposited amount is more or greater than what was wanted + onchain_amount = contract.GetAmount(taker_guid) + if onchain_amount < exch['offer_amount']: + raise ExchangeError("Confirm amount differs from offer amount") + + # Verify on-chain hashed secret + onchain_sechash = contract.GetSecretHashed(taker_guid) + if hexlify(onchain_sechash).decode('ascii') != secret_hashed: + raise ExchangeError("Submitted hashed secret doesn't match on-chain data") + + # Verify on-chain expiry matches + expiry = proposal['expiry'] + onchain_expiry = contract.GetExpiry(taker_guid) + if expiry != onchain_expiry: + raise ExchangeError("Submitted expiry doesn't match on-chain data") + + # Verify receiver + onchain_receiver = normalise_address(contract.GetReceiver(taker_guid)) + if onchain_receiver != proposal['depositor']: + raise ExchangeError("Offer address differs from receiver") + + # Verify sender + deposited_for = exch['offer_address'] + onchain_sender = normalise_address(contract.GetSender(taker_guid)) + if onchain_sender != deposited_for: + raise ExchangeError("Sender address differs from depositor") + + def release(self, exch_guid, secret_hashed, **kwa): exch, proposal = self.get_proposal(exch_guid, secret_hashed) @@ -147,7 +180,11 @@ def release(self, exch_guid, secret_hashed, **kwa): if secret_hashed_check_hex != secret_hashed: raise ExchangeError(' '.join(["Secret doesn't match! Got", secret_hashed_check_hex, 'expected', secret_hashed])) + contract = make_htlc_proxy(self._rpc, exch['want_htlc_address']) + proposal['secret'] = secret_hex def finish(self, exch_guid, secret_hashed, **kwa): exch, proposal = self.get_proposal(exch_guid, secret_hashed) + + contract = make_htlc_proxy(self._rpc, exch['offer_htlc_address']) \ No newline at end of file From fdda5956350723515ca5f178af24e2a26a6241a3 Mon Sep 17 00:00:00 2001 From: Harry Date: Wed, 20 Jun 2018 21:07:20 +0100 Subject: [PATCH 41/49] Oops, forgot to auto-sign commits, testing signed commits again --- ion/htlc/manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ion/htlc/manager.py b/ion/htlc/manager.py index 3d8136e..6f7868e 100644 --- a/ion/htlc/manager.py +++ b/ion/htlc/manager.py @@ -167,7 +167,7 @@ def confirm(self, exch_guid, secret_hashed, **kwa): onchain_sender = normalise_address(contract.GetSender(taker_guid)) if onchain_sender != deposited_for: raise ExchangeError("Sender address differs from depositor") - + def release(self, exch_guid, secret_hashed, **kwa): exch, proposal = self.get_proposal(exch_guid, secret_hashed) From 954550d917525027bd9b6fc412b6b61c431902c0 Mon Sep 17 00:00:00 2001 From: Harry Date: Wed, 20 Jun 2018 22:42:11 +0100 Subject: [PATCH 42/49] Fixed some more Py2->Py3 bugs, found bug in Ganache-cli The bug in Ganache-cli is as follows: - The ethrpc client is unable to retrieve the receipt for a transaction committed by another client if the connection stays open. --- Makefile | 3 +++ ion/ethrpc.py | 2 +- ion/htlc/coordclient.py | 15 +++++++------ ion/htlc/coordinator.py | 7 +++++- ion/htlc/manager.py | 46 ++++++++++++++++++++++++++++++++++++---- test/test_coordclient.py | 2 +- 6 files changed, 61 insertions(+), 14 deletions(-) diff --git a/Makefile b/Makefile index 482d6cf..19f0152 100644 --- a/Makefile +++ b/Makefile @@ -143,6 +143,9 @@ test-unit: test-coordserver: $(PYTHON) -mion htlc coordinator --contract 0xd833215cbcc3f914bd1c9ece3ee7bf8b14f841bb +test-coordclient: + PYTHONPATH=. $(PYTHON) ./test/test_coordclient.py + test: test-unit test-js diff --git a/ion/ethrpc.py b/ion/ethrpc.py index d6939cc..795eadf 100644 --- a/ion/ethrpc.py +++ b/ion/ethrpc.py @@ -127,7 +127,7 @@ def receipt(self, wait=False, tick_fn=None): break try: if first: - if isinstance(wait, callable): + if hasattr(wait, '__call__'): wait() first = False elif tick_fn: diff --git a/ion/htlc/coordclient.py b/ion/htlc/coordclient.py index dce5ffa..d004591 100644 --- a/ion/htlc/coordclient.py +++ b/ion/htlc/coordclient.py @@ -3,6 +3,7 @@ import os from hashlib import sha256 +from binascii import hexlify, unhexlify from ..utils import require, normalise_address from ..ethrpc import EthJsonRpc @@ -60,7 +61,7 @@ def confirm(self, wait=True): # Offerer deposits their side of the deal, locked to same hashed secret htlc_address = exch_data['offer_htlc_address'] htlc_contract = make_htlc_proxy(ethrpc, htlc_address, my_address) - txn = htlc_contract.Deposit(conf_receiver, conf_secret_hashed.decode('hex'), conf_expiry, value=conf_value) + txn = htlc_contract.Deposit(conf_receiver, unhexlify(conf_secret_hashed), conf_expiry, value=conf_value) receipt = txn.receipt(wait=wait) if receipt and int(receipt['status'], 16) == 0: @@ -83,14 +84,14 @@ def release(self, secret, wait=True): my_address = self._coordapi.my_address exch_data = self._exch_obj.data - secret_hex = secret.encode('hex') + secret_hex = hexlify(secret).decode('ascii') secret_hashed = sha256(secret).digest() - secret_hashed_hex = secret_hashed.encode('hex') + secret_hashed_hex = hexlify(secret_hashed).decode('ascii') require(my_address == self._data['depositor'], "Only proposer can release") require(self._data['secret_hashed'] == secret_hashed_hex, "Secrets don't match!") - exch_guid = self._data['taker_guid'].decode('hex') + exch_guid = unhexlify(self._data['taker_guid']) htlc_address = exch_data['offer_htlc_address'] htlc_contract = make_htlc_proxy(ethrpc, htlc_address, my_address) @@ -116,11 +117,11 @@ def finish(self, wait=True): exch_data = self._exch_obj.data secret_hex = self._data['secret'] - secret = secret_hex.decode('hex') + secret = unhexlify(secret_hex) # TODO: verify secret hashes to hashed image - exch_guid = self._data['offer_guid'].decode('hex') + exch_guid = unhexlify(self._data['offer_guid']) htlc_address = exch_data['want_htlc_address'] htlc_contract = make_htlc_proxy(ethrpc, htlc_address, my_address) @@ -208,7 +209,7 @@ def propose(self, wait=True): # Create a random secret secret = os.urandom(32) secret_hashed = sha256(secret).digest() - secret_hashed_hex = secret_hashed.encode('hex') + secret_hashed_hex = hexlify(secret_hashed).decode('ascii') # Proposal parameters prop_receiver = self._data['offer_address'] diff --git a/ion/htlc/coordinator.py b/ion/htlc/coordinator.py index c3b750d..69d976a 100644 --- a/ion/htlc/coordinator.py +++ b/ion/htlc/coordinator.py @@ -138,6 +138,7 @@ def exch_release(self, exch_id, secret_hashed): """ params = params_parse(request.form, dict( secret=param_bytes32, + txid=param_bytes32, )) try: @@ -157,8 +158,12 @@ def exch_finish(self, exch_id, secret_hashed): This completes the exchange. """ + params = params_parse(request.form, dict( + txid=param_bytes32, + )) + try: - self._manager.finish(exch_id, secret_hashed) + self._manager.finish(exch_id, secret_hashed, **params) except ExchangeError as ex: return api_abort(str(ex)) diff --git a/ion/htlc/manager.py b/ion/htlc/manager.py index 6f7868e..64673c1 100644 --- a/ion/htlc/manager.py +++ b/ion/htlc/manager.py @@ -7,7 +7,7 @@ from hashlib import sha256 from ..utils import normalise_address -from ..ethrpc import EthJsonRpc +from ..ethrpc import EthJsonRpc, EthTransaction from .common import MINIMUM_EXPIRY_DURATION, make_htlc_proxy @@ -167,24 +167,62 @@ def confirm(self, exch_guid, secret_hashed, **kwa): onchain_sender = normalise_address(contract.GetSender(taker_guid)) if onchain_sender != deposited_for: raise ExchangeError("Sender address differs from depositor") - + def release(self, exch_guid, secret_hashed, **kwa): exch, proposal = self.get_proposal(exch_guid, secret_hashed) + secret_hex = kwa['secret'] secret = unhexlify(secret_hex) secret_hashed_check = sha256(secret).digest() secret_hashed_check_hex = hexlify(secret_hashed_check).decode('ascii') - if secret_hashed_check_hex != secret_hashed: raise ExchangeError(' '.join(["Secret doesn't match! Got", secret_hashed_check_hex, 'expected', secret_hashed])) + txid = kwa['txid'] + transaction = EthTransaction(self._rpc, txid) + print("Getting release receipt for tx", txid) + receipt = transaction.receipt(wait=False) + print("Release receipt is", receipt) + contract = make_htlc_proxy(self._rpc, exch['want_htlc_address']) + offer_guid = unhexlify(proposal['offer_guid']) + # XXX: if the server errors out here... then proposal won't get updated, this is bad! + + # TODO: wait for transaction? + + # 2 = Withdrawn + onchain_state = contract.GetState(offer_guid) + print("After release, State is ", onchain_state) + """ + if onchain_state != 2: + raise ExchangeError("Exchange is in wrong state") + """ + proposal['secret'] = secret_hex def finish(self, exch_guid, secret_hashed, **kwa): exch, proposal = self.get_proposal(exch_guid, secret_hashed) - contract = make_htlc_proxy(self._rpc, exch['offer_htlc_address']) \ No newline at end of file + contract = make_htlc_proxy(self._rpc, exch['offer_htlc_address']) + + taker_guid = unhexlify(proposal['taker_guid']) + + # TODO: wait for transaction? + + txid = kwa['txid'] + transaction = EthTransaction(self._rpc, txid) + print("Getting finish receipt for tx", txid) + receipt = transaction.receipt(wait=False) + print("Finish receipt is", receipt) + + # 2 = Withdrawn + onchain_state = contract.GetState(taker_guid) + print("After finish, state is", onchain_state) + if onchain_state != 2: + raise ExchangeError("Exchange is in wrong state") + + offer_guid = unhexlify(proposal['offer_guid']) + print("Other sides state is", contract.GetState(offer_guid)) diff --git a/test/test_coordclient.py b/test/test_coordclient.py index 695e069..fe4bf3a 100755 --- a/test/test_coordclient.py +++ b/test/test_coordclient.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 ## Copyright (c) 2018 Harry Roberts. ## SPDX-License-Identifier: LGPL-3.0+ From efdbd842869590aabe04a74db525041736b48970 Mon Sep 17 00:00:00 2001 From: Harry Date: Thu, 21 Jun 2018 00:07:27 +0100 Subject: [PATCH 43/49] Wait for transaction at all steps and record txid --- ion/ethrpc.py | 11 +++++++++- ion/htlc/coordclient.py | 1 - ion/htlc/coordinator.py | 9 ++++++-- ion/htlc/manager.py | 46 ++++++++++++++++++++++++++--------------- 4 files changed, 46 insertions(+), 21 deletions(-) diff --git a/ion/ethrpc.py b/ion/ethrpc.py index 795eadf..7422986 100644 --- a/ion/ethrpc.py +++ b/ion/ethrpc.py @@ -116,10 +116,19 @@ def ether_to_wei(ether): class EthTransaction(namedtuple('_TxStruct', ('rpc', 'txid'))): + def details(self): + txid = self.txid + if txid[:2] != '0x': + txid = '0x' + txid + return self.rpc.eth_getTransactionByHash(txid) + def receipt(self, wait=False, tick_fn=None): first = True + txid = self.txid + if txid[:2] != '0x': + txid = '0x' + txid while True: - receipt = self.rpc.eth_getTransactionReceipt(self.txid) + receipt = self.rpc.eth_getTransactionReceipt(txid) # TODO: turn into asynchronous notification / future if receipt: return receipt diff --git a/ion/htlc/coordclient.py b/ion/htlc/coordclient.py index d004591..02b2415 100644 --- a/ion/htlc/coordclient.py +++ b/ion/htlc/coordclient.py @@ -96,7 +96,6 @@ def release(self, secret, wait=True): htlc_address = exch_data['offer_htlc_address'] htlc_contract = make_htlc_proxy(ethrpc, htlc_address, my_address) txn = htlc_contract.Withdraw(exch_guid, secret) - receipt = txn.receipt(wait=wait) if receipt and int(receipt['status'], 16) == 0: raise RuntimeError("Release failed, txn: " + txn.txid) diff --git a/ion/htlc/coordinator.py b/ion/htlc/coordinator.py index 69d976a..3c4ff33 100644 --- a/ion/htlc/coordinator.py +++ b/ion/htlc/coordinator.py @@ -92,7 +92,8 @@ def exch_propose(self, exch_id, secret_hashed): """ params = params_parse(request.form, dict( expiry=param_uint256, - depositor=param_bytes20 + depositor=param_bytes20, + txid=param_bytes32, )) try: @@ -121,8 +122,12 @@ def exch_confirm(self, exch_id, secret_hashed): This is performed by Alice """ + params = params_parse(request.form, dict( + txid=param_bytes32, + )) + try: - self._manager.confirm(exch_id, secret_hashed) + self._manager.confirm(exch_id, secret_hashed, **params) except ExchangeError as ex: return api_abort(str(ex)) diff --git a/ion/htlc/manager.py b/ion/htlc/manager.py index 64673c1..050e6b6 100644 --- a/ion/htlc/manager.py +++ b/ion/htlc/manager.py @@ -84,6 +84,13 @@ def propose(self, exch_guid, secret_hashed, **kwa): # taker_guid = Deposit() by A (the initial offerer) taker_guid = sha256(unhexlify(depositor) + unhexlify(secret_hashed)).digest() + # Wait for transaction success + txid = kwa['txid'] + transaction = EthTransaction(self._rpc, txid) + receipt = transaction.receipt(wait=True) + if int(receipt['status'], 16) == 0: + raise ExchangeError("Transaction was aborted") + # TODO: make sure we have the right contract address contract = make_htlc_proxy(self._rpc, exch['want_htlc_address']) @@ -123,6 +130,7 @@ def propose(self, exch_guid, secret_hashed, **kwa): depositor=depositor, offer_guid=hexlify(offer_guid).decode('ascii'), taker_guid=hexlify(taker_guid).decode('ascii'), + propose_txid=txid, ) exch['proposals'][secret_hashed] = proposal @@ -131,7 +139,12 @@ def propose(self, exch_guid, secret_hashed, **kwa): def confirm(self, exch_guid, secret_hashed, **kwa): exch, proposal = self.get_proposal(exch_guid, secret_hashed) - exch['chosen_proposal'] = secret_hashed + # Wait for transaction success + txid = kwa['txid'] + transaction = EthTransaction(self._rpc, txid) + receipt = transaction.receipt(wait=True) + if int(receipt['status'], 16) == 0: + raise ExchangeError("Transaction was aborted") contract = make_htlc_proxy(self._rpc, exch['offer_htlc_address']) @@ -168,11 +181,13 @@ def confirm(self, exch_guid, secret_hashed, **kwa): if onchain_sender != deposited_for: raise ExchangeError("Sender address differs from depositor") + proposal['confirm_txid'] = txid + exch['chosen_proposal'] = secret_hashed + def release(self, exch_guid, secret_hashed, **kwa): exch, proposal = self.get_proposal(exch_guid, secret_hashed) - secret_hex = kwa['secret'] secret = unhexlify(secret_hex) secret_hashed_check = sha256(secret).digest() @@ -180,28 +195,29 @@ def release(self, exch_guid, secret_hashed, **kwa): if secret_hashed_check_hex != secret_hashed: raise ExchangeError(' '.join(["Secret doesn't match! Got", secret_hashed_check_hex, 'expected', secret_hashed])) + # Wait for transaction success txid = kwa['txid'] transaction = EthTransaction(self._rpc, txid) - print("Getting release receipt for tx", txid) - receipt = transaction.receipt(wait=False) - print("Release receipt is", receipt) + receipt = transaction.receipt(wait=True) + if int(receipt['status'], 16) == 0: + raise ExchangeError("Transaction was aborted") contract = make_htlc_proxy(self._rpc, exch['want_htlc_address']) - - offer_guid = unhexlify(proposal['offer_guid']) # XXX: if the server errors out here... then proposal won't get updated, this is bad! - # TODO: wait for transaction? - # 2 = Withdrawn + offer_guid = unhexlify(proposal['offer_guid']) onchain_state = contract.GetState(offer_guid) print("After release, State is ", onchain_state) """ + # XXX: even though we've waited for a successful receipt, the state is still `1` + # but in the 'finish' call, the state with the same params is `2` if onchain_state != 2: raise ExchangeError("Exchange is in wrong state") """ proposal['secret'] = secret_hex + proposal['release_txid'] = txid def finish(self, exch_guid, secret_hashed, **kwa): exch, proposal = self.get_proposal(exch_guid, secret_hashed) @@ -210,19 +226,15 @@ def finish(self, exch_guid, secret_hashed, **kwa): taker_guid = unhexlify(proposal['taker_guid']) - # TODO: wait for transaction? - txid = kwa['txid'] transaction = EthTransaction(self._rpc, txid) - print("Getting finish receipt for tx", txid) - receipt = transaction.receipt(wait=False) - print("Finish receipt is", receipt) + receipt = transaction.receipt(wait=True) + if int(receipt['status'], 16) == 0: + raise ExchangeError("Transaction was aborted") # 2 = Withdrawn onchain_state = contract.GetState(taker_guid) - print("After finish, state is", onchain_state) if onchain_state != 2: raise ExchangeError("Exchange is in wrong state") - offer_guid = unhexlify(proposal['offer_guid']) - print("Other sides state is", contract.GetState(offer_guid)) + proposal['finish_txid'] = txid From 92237044d4089d46ece8148c52b005761892b7fc Mon Sep 17 00:00:00 2001 From: Harry Date: Thu, 21 Jun 2018 04:45:47 +0100 Subject: [PATCH 44/49] Testing travis file --- .travis.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..747fc36 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,10 @@ +sudo: enabled +language: python +python: + - '3.6' +install: + - make requirements-dev requirements +script: + - make contracts test +git: + depth: 3 From 70990dcca688555cd5b2fcf15920ed83d9de0232 Mon Sep 17 00:00:00 2001 From: Harry Date: Thu, 21 Jun 2018 05:07:08 +0100 Subject: [PATCH 45/49] Added testrpc deployment for Travis --- .gitignore | 1 + .travis.yml | 2 +- Makefile | 24 +++++++++++++++++++++--- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 70aa61b..56c4d6a 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ lextab.py yacctab.py *.swp *.swo +*.pid diff --git a/.travis.yml b/.travis.yml index 747fc36..1769bec 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,6 @@ python: install: - make requirements-dev requirements script: - - make contracts test + - make travis git: depth: 3 diff --git a/Makefile b/Makefile index 19f0152..92c640e 100644 --- a/Makefile +++ b/Makefile @@ -28,7 +28,7 @@ check-prereqs: clean: rm -rf build chaindata dist find . -name '*.pyc' -exec rm '{}' ';' - rm -rf *.pyc *.pdf *.egg-info + rm -rf *.pyc *.pdf *.egg-info *.pid *.log ####################################################################### @@ -76,11 +76,16 @@ solidity-lint: nodejs-requirements: $(NPM) install +# Useful shortcut for development, install packages to user path by default +python-pip-user: + mkdir -p $(HOME)/.pip/ + echo -e "[global]\nuser = true\n" > $(HOME)/.pip/pip.conf + python-requirements: requirements.txt - $(PYTHON) -mpip install --user -r $< + $(PYTHON) -mpip install -r $< python-dev-requirements: requirements-dev.txt - $(PYTHON) -mpip install --user -r $< + $(PYTHON) -mpip install -r $< requirements-dev: nodejs-requirements python-dev-requirements @@ -128,6 +133,19 @@ build/%.combined.sol: contracts/%.sol build # Testing and unit test harnesses # +# runs an instance of testrpc in background, then waits for it to be ready +travis-testrpc-start: travis-testrpc-stop + $(NPM) run testrpca > .testrpc.log & echo $$! > .testrpc.pid + while true; do echo -n . ; curl http://localhost:8545 &> /dev/null && break || sleep 1; done + +# Stops previ +travis-testrpc-stop: + kill `cat .testrpc.pid` || true + rm -f .testrpc.pid + +travis: travis-testrpc-start truffle-deploy-a contracts test + + testrpc: $(NPM) run testrpca From ff40db0602eee455c2321389038eccc92aa7a716 Mon Sep 17 00:00:00 2001 From: Harry Date: Thu, 21 Jun 2018 05:14:31 +0100 Subject: [PATCH 46/49] Fixed small bug in `travis-testrpc-stop` target --- Makefile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 92c640e..7a0a705 100644 --- a/Makefile +++ b/Makefile @@ -140,8 +140,7 @@ travis-testrpc-start: travis-testrpc-stop # Stops previ travis-testrpc-stop: - kill `cat .testrpc.pid` || true - rm -f .testrpc.pid + if [ -f .testrpc.pid ]; then kill `cat .testrpc.pid` || true; rm -f .testrpc.pid; fi travis: travis-testrpc-start truffle-deploy-a contracts test From 2eed7a9217e034485ca1ce0f0dcaf81d2f9a9542 Mon Sep 17 00:00:00 2001 From: Harry Date: Fri, 22 Jun 2018 00:36:00 +0100 Subject: [PATCH 47/49] Moved HTLC deposit verification into ion.htlc.verify --- Makefile | 2 + ion/ethrpc.py | 19 +++++++ ion/htlc/common.py | 5 ++ ion/htlc/coordclient.py | 6 +-- ion/htlc/coordinator.py | 1 + ion/htlc/manager.py | 107 ++++------------------------------------ ion/htlc/verify.py | 84 +++++++++++++++++++++++++++++++ 7 files changed, 122 insertions(+), 102 deletions(-) create mode 100644 ion/htlc/verify.py diff --git a/Makefile b/Makefile index 7a0a705..51b18e2 100644 --- a/Makefile +++ b/Makefile @@ -28,7 +28,9 @@ check-prereqs: clean: rm -rf build chaindata dist find . -name '*.pyc' -exec rm '{}' ';' + find . -name '__pycache__' -exec rm -rf '{}' ';' rm -rf *.pyc *.pdf *.egg-info *.pid *.log + rm -f lextab.py yacctab.py ####################################################################### diff --git a/ion/ethrpc.py b/ion/ethrpc.py index 7422986..bc32b3c 100644 --- a/ion/ethrpc.py +++ b/ion/ethrpc.py @@ -122,7 +122,11 @@ def details(self): txid = '0x' + txid return self.rpc.eth_getTransactionByHash(txid) + def wait(self): + return self.receipt(wait=True) + def receipt(self, wait=False, tick_fn=None): + # TODO: add `timeout` param first = True txid = self.txid if txid[:2] != '0x': @@ -262,6 +266,21 @@ def proxy(self, abi, address, account=None): # high-level methods ################################################################################ + def receipt(self, txid, wait=False, raise_on_error=False): + # TODO: add `timeout` param + transaction = EthTransaction(self, txid) + receipt = transaction.receipt(wait=wait) + if raise_on_error: + if int(receipt['status'], 16) == 0: + raise EthJsonRpcError("Transaction was aborted") + return receipt + + def receipt_wait(self, txid, raise_on_error=True): + """ + Wait for the transaction to be mined, then return receipt + """ + return self.receipt(txid, raise_on_error) + def transfer(self, from_, to, amount): ''' Send wei from one address to another diff --git a/ion/htlc/common.py b/ion/htlc/common.py index 8a1684b..136e206 100644 --- a/ion/htlc/common.py +++ b/ion/htlc/common.py @@ -12,6 +12,11 @@ DEFAULT_EXPIRY_DURATION = 10 * ONE_MINUTE MINIMUM_EXPIRY_DURATION = 2 * ONE_MINUTE + +class ExchangeError(Exception): + pass + + def make_htlc_proxy(rpc, contract, account=None): """ TODO: embed 'abi/HTLC.abi' file in package resources? diff --git a/ion/htlc/coordclient.py b/ion/htlc/coordclient.py index 02b2415..0edab2e 100644 --- a/ion/htlc/coordclient.py +++ b/ion/htlc/coordclient.py @@ -96,9 +96,7 @@ def release(self, secret, wait=True): htlc_address = exch_data['offer_htlc_address'] htlc_contract = make_htlc_proxy(ethrpc, htlc_address, my_address) txn = htlc_contract.Withdraw(exch_guid, secret) - receipt = txn.receipt(wait=wait) - if receipt and int(receipt['status'], 16) == 0: - raise RuntimeError("Release failed, txn: " + txn.txid) + txn.wait() # Reveal secret, posting back to server self._resource.release.POST( @@ -126,7 +124,7 @@ def finish(self, wait=True): htlc_contract = make_htlc_proxy(ethrpc, htlc_address, my_address) txn = htlc_contract.Withdraw(exch_guid, secret) - receipt = txn.receipt(wait=wait) + receipt = txn.wait() if receipt and int(receipt['status'], 16) == 0: raise RuntimeError("Finish failed, txn: " + txn.txid) diff --git a/ion/htlc/coordinator.py b/ion/htlc/coordinator.py index 3c4ff33..d83fdac 100644 --- a/ion/htlc/coordinator.py +++ b/ion/htlc/coordinator.py @@ -131,6 +131,7 @@ def exch_confirm(self, exch_id, secret_hashed): except ExchangeError as ex: return api_abort(str(ex)) + # TODO: return updated proposal object return jsonify(dict( ok=1 )) diff --git a/ion/htlc/manager.py b/ion/htlc/manager.py index 050e6b6..964d11c 100644 --- a/ion/htlc/manager.py +++ b/ion/htlc/manager.py @@ -7,13 +7,10 @@ from hashlib import sha256 from ..utils import normalise_address -from ..ethrpc import EthJsonRpc, EthTransaction +from ..ethrpc import EthJsonRpc -from .common import MINIMUM_EXPIRY_DURATION, make_htlc_proxy - - -class ExchangeError(Exception): - pass +from .common import MINIMUM_EXPIRY_DURATION, make_htlc_proxy, ExchangeError +from .verify import verify_deposit class ExchangeManager(object): @@ -71,13 +68,6 @@ def propose(self, exch_guid, secret_hashed, **kwa): if secret_hashed in exch['proposals']: raise ExchangeError("Duplicate proposal secret") - # Verify expiry time is acceptable - # XXX: should minimum expiry be left to the contract, or the coordinator? - now = int(time.time()) - min_expiry = now + MINIMUM_EXPIRY_DURATION - if expiry < min_expiry: - raise ExchangeError("Expiry too short") - # GUID used for the exchanges # offer_guid = Deposit() by B (the proposer) offer_guid = sha256(unhexlify(exch['offer_address']) + unhexlify(secret_hashed)).digest() @@ -86,44 +76,7 @@ def propose(self, exch_guid, secret_hashed, **kwa): # Wait for transaction success txid = kwa['txid'] - transaction = EthTransaction(self._rpc, txid) - receipt = transaction.receipt(wait=True) - if int(receipt['status'], 16) == 0: - raise ExchangeError("Transaction was aborted") - - # TODO: make sure we have the right contract address - contract = make_htlc_proxy(self._rpc, exch['want_htlc_address']) - - # Verify on-chain expiry matches - onchain_expiry = contract.GetExpiry(offer_guid) - if expiry != onchain_expiry: - raise ExchangeError("Submitted expiry doesn't match on-chain data") - - # Verify on-chain hashed secret - onchain_sechash = contract.GetSecretHashed(offer_guid) - if hexlify(onchain_sechash).decode('ascii') != secret_hashed: - raise ExchangeError("Submitted hashed secret doesn't match on-chain data") - - # 1 = Deposited - if contract.GetState(offer_guid) != 1: - raise ExchangeError("Exchange is in wrong state") - - # Verify receiver - onchain_receiver = normalise_address(contract.GetReceiver(offer_guid)) - if onchain_receiver != exch['offer_address']: - raise ExchangeError("Offer address differs from receiver") - - # Verify sender - onchain_sender = normalise_address(contract.GetSender(offer_guid)) - if onchain_sender != depositor: - raise ExchangeError("Sender address differs from depositor") - # Ensure deposited amount is more or greater than what was wanted - onchain_amount = contract.GetAmount(offer_guid) - if onchain_amount < exch['want_amount']: - raise ExchangeError("Propose amount differs from want amount") - - # Store proposal proposal = dict( secret_hashed=secret_hashed, expiry=expiry, @@ -132,54 +85,18 @@ def propose(self, exch_guid, secret_hashed, **kwa): taker_guid=hexlify(taker_guid).decode('ascii'), propose_txid=txid, ) - exch['proposals'][secret_hashed] = proposal + verify_deposit('proposer', self._rpc, exch, proposal, txid) + + exch['proposals'][secret_hashed] = proposal return exch, proposal def confirm(self, exch_guid, secret_hashed, **kwa): exch, proposal = self.get_proposal(exch_guid, secret_hashed) - # Wait for transaction success txid = kwa['txid'] - transaction = EthTransaction(self._rpc, txid) - receipt = transaction.receipt(wait=True) - if int(receipt['status'], 16) == 0: - raise ExchangeError("Transaction was aborted") - - contract = make_htlc_proxy(self._rpc, exch['offer_htlc_address']) - - taker_guid = unhexlify(proposal['taker_guid']) - - # 1 = Deposited - if contract.GetState(taker_guid) != 1: - raise ExchangeError("Exchange is in wrong state") - # Ensure deposited amount is more or greater than what was wanted - onchain_amount = contract.GetAmount(taker_guid) - if onchain_amount < exch['offer_amount']: - raise ExchangeError("Confirm amount differs from offer amount") - - # Verify on-chain hashed secret - onchain_sechash = contract.GetSecretHashed(taker_guid) - if hexlify(onchain_sechash).decode('ascii') != secret_hashed: - raise ExchangeError("Submitted hashed secret doesn't match on-chain data") - - # Verify on-chain expiry matches - expiry = proposal['expiry'] - onchain_expiry = contract.GetExpiry(taker_guid) - if expiry != onchain_expiry: - raise ExchangeError("Submitted expiry doesn't match on-chain data") - - # Verify receiver - onchain_receiver = normalise_address(contract.GetReceiver(taker_guid)) - if onchain_receiver != proposal['depositor']: - raise ExchangeError("Offer address differs from receiver") - - # Verify sender - deposited_for = exch['offer_address'] - onchain_sender = normalise_address(contract.GetSender(taker_guid)) - if onchain_sender != deposited_for: - raise ExchangeError("Sender address differs from depositor") + verify_deposit('proposer', self._rpc, exch, proposal, txid) proposal['confirm_txid'] = txid exch['chosen_proposal'] = secret_hashed @@ -197,10 +114,7 @@ def release(self, exch_guid, secret_hashed, **kwa): # Wait for transaction success txid = kwa['txid'] - transaction = EthTransaction(self._rpc, txid) - receipt = transaction.receipt(wait=True) - if int(receipt['status'], 16) == 0: - raise ExchangeError("Transaction was aborted") + self._rpc.receipt_wait(txid) contract = make_htlc_proxy(self._rpc, exch['want_htlc_address']) # XXX: if the server errors out here... then proposal won't get updated, this is bad! @@ -227,10 +141,7 @@ def finish(self, exch_guid, secret_hashed, **kwa): taker_guid = unhexlify(proposal['taker_guid']) txid = kwa['txid'] - transaction = EthTransaction(self._rpc, txid) - receipt = transaction.receipt(wait=True) - if int(receipt['status'], 16) == 0: - raise ExchangeError("Transaction was aborted") + self._rpc.receipt_wait(txid) # 2 = Withdrawn onchain_state = contract.GetState(taker_guid) diff --git a/ion/htlc/verify.py b/ion/htlc/verify.py new file mode 100644 index 0000000..2afee5e --- /dev/null +++ b/ion/htlc/verify.py @@ -0,0 +1,84 @@ +## Copyright (c) 2018 Harry Roberts. All Rights Reserved. +## SPDX-License-Identifier: LGPL-3.0+ + +import time +from binascii import hexlify, unhexlify + +from ..utils import normalise_address, require + +from .common import MINIMUM_EXPIRY_DURATION, make_htlc_proxy + + +def verify_deposit(side, rpc, exch, proposal, txid): + """ + Verifies that the contract deposit matches the exchange and proposal + """ + require(side in ['proposer', 'confirmer'], "Side must be 'proposer' or 'confirmer'") + + expiry = proposal['expiry'] + secret_hashed = proposal['secret_hashed'] + + # Verification logic is the same, but uses different parameters + # depending on which side, the proposer or the confirmer + if side == 'proposer': + deposit_guid = unhexlify(proposal['offer_guid']) + htlc_address = exch['want_htlc_address'] + expected_amount = exch['want_amount'] + expected_receiver = exch['offer_address'] + expected_sender = proposal['depositor'] + else: + deposit_guid = unhexlify(proposal['taker_guid']) + htlc_address = exch['offer_htlc_address'] + expected_amount = exch['offer_amount'] + expected_receiver = proposal['depositor'] + expected_sender = exch['offer_address'] + + rpc.receipt_wait(txid) + + contract = make_htlc_proxy(rpc, htlc_address) + + # Verify expiry time is acceptable + # XXX: should minimum expiry be left to the contract, or the coordinator? + now = int(time.time()) + min_expiry = now + MINIMUM_EXPIRY_DURATION + if expiry < min_expiry: + raise ExchangeError("Expiry too short, got %d expected >= %d" % ( + expiry, min_expiry)) + + # Verify on-chain expiry matches + onchain_expiry = contract.GetExpiry(deposit_guid) + if expiry != onchain_expiry: + raise ExchangeError("Expiry doesn't match contract, got %d expected %d" % ( + expiry, onchain_expiry)) + + # Verify on-chain hashed secret + onchain_sechash = hexlify(contract.GetSecretHashed(deposit_guid)).decode('ascii') + if onchain_sechash != secret_hashed: + raise ExchangeError("Hashed secret doesn't match contract, got %s expected %s" % ( + onchain_sechash, secret_hashed)) + + # 1 = Deposited + onchain_state = contract.GetState(deposit_guid) + if onchain_state != 1: + raise ExchangeError("Exchange is in wrong state, got %d expected %d" % ( + onchain_state, 1)) + + # Verify receiver + onchain_receiver = normalise_address(contract.GetReceiver(deposit_guid)) + if onchain_receiver != expected_receiver: + raise ExchangeError("Wrong receiver address, got %s expected %s" % ( + onchain_receiver, expected_receiver)) + + # Verify sender + onchain_sender = normalise_address(contract.GetSender(deposit_guid)) + if onchain_sender != expected_sender: + raise ExchangeError("Wrong sender address, got %s expected %s" % ( + onchain_sender, expected_sender)) + + # Ensure deposited amount is more or greater than what was wanted + onchain_amount = contract.GetAmount(deposit_guid) + if onchain_amount < expected_amount: + raise ExchangeError("Propose amount differs from want amount, got %d expected %d" % ( + onchain_amount, expected_amount)) + + return True \ No newline at end of file From 6e9363b762066f4ff7c5e9246783cd74cffd82f8 Mon Sep 17 00:00:00 2001 From: Harry Date: Fri, 22 Jun 2018 00:38:25 +0100 Subject: [PATCH 48/49] Must use ''confirmer' in confirm action, rather than proposer This is... bad, because the actions are the same, a switch of a single keyword can make it so a deposit isn't verified. --- ion/htlc/manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ion/htlc/manager.py b/ion/htlc/manager.py index 964d11c..c7a9647 100644 --- a/ion/htlc/manager.py +++ b/ion/htlc/manager.py @@ -96,7 +96,7 @@ def confirm(self, exch_guid, secret_hashed, **kwa): txid = kwa['txid'] - verify_deposit('proposer', self._rpc, exch, proposal, txid) + verify_deposit('confirmer', self._rpc, exch, proposal, txid) proposal['confirm_txid'] = txid exch['chosen_proposal'] = secret_hashed From ef974c9903f44eeaf95a6f1608be992292e9276a Mon Sep 17 00:00:00 2001 From: Harry Date: Fri, 22 Jun 2018 01:24:22 +0100 Subject: [PATCH 49/49] Removed obsolete contract API from HTLC CLI --- ion/htlc/cli.py | 58 ----------------------------------------- ion/htlc/coordclient.py | 7 +++-- 2 files changed, 3 insertions(+), 62 deletions(-) diff --git a/ion/htlc/cli.py b/ion/htlc/cli.py index 42075d4..5a707c1 100644 --- a/ion/htlc/cli.py +++ b/ion/htlc/cli.py @@ -12,63 +12,6 @@ from .common import get_random_secret_32, get_default_expiry, make_htlc_proxy -####################################################################### -# -# Command-line interface to the HTLC contract -# -# $ ion htlc contract [options] sub-command [sub-options] -# -# e.g. -# -# $ ion htlc contract --account X --contract Y deposit --receiver Z ... -# - - -@click.command() -@click.pass_obj -@click.option('--receiver', callback=arg_bytes20, metavar="0x...20", required=True, help="Receiver address") -@click.option('--secret', callback=arg_bytes32, metavar="0x...32", default=get_random_secret_32, help="Secret to be supplied upon withdraw") -@click.option('--amount', callback=arg_uint256, metavar='wei', help='Amount of WEI to deposit') -@click.option('--expires', metavar="seconds|unixtime", callback=arg_expiry, type=int, default=get_default_expiry, help="Expiry time, as duration (seconds), or UNIX epoch") -def contract_deposit(contract, receiver, secret, amount, expires): - now = int(time.time()) - print("Expires in", expires - now, "seconds") - - # TODO: verify balance for account is above or equal to `amount` - - image = sha256(secret).digest() # the hash pre-image is the 'secret' - contract.Deposit(receiver, image, expires, value=amount) - - -@click.command() -@click.pass_obj -@click.option('--secret', callback=arg_bytes32, metavar="0x...32", required=True, help="Exchange ID") -def contract_withdraw(contract, secret): - image = sha256(secret).digest() # the hash pre-image is the 'secret' - contract.Withdraw(image, secret) - - -@click.command() -@click.pass_obj -@click.option('--image', callback=arg_bytes32, metavar="0x...32", required=True, help="Exchange hash image") -def contract_refund(contract, image): - contract.Refund(image) - - -@click.group('contract', help='Command-line interface to Ethereum HTLC contract') -@click.pass_context -@click.option('--rpc', callback=arg_ethrpc, metavar="ip:port", default='127.0.0.1:8545', help="Ethereum JSON-RPC server") -@click.option('--account', callback=arg_bytes20, metavar="0x...20", required=True, help="Account to transfer from.") -@click.option('--contract', callback=arg_bytes20, metavar="0x...20", required=True, help="HTLC contract address") -def contract_multicommand(ctx, rpc, account, contract): - # Contract will get passed to sub-commands as first object when using `@click.pass_obj` - ctx.obj = make_htlc_proxy(rpc, contract, account) - - -contract_multicommand.add_command(contract_deposit, "deposit") -contract_multicommand.add_command(contract_withdraw, "withdraw") -contract_multicommand.add_command(contract_refund, "refund") - ####################################################################### # @@ -90,7 +33,6 @@ def coordinator(contract, rpc, account): # COMMANDS = click.Group("htlc", help="Hash-Time-Lock Atomic Swap") -COMMANDS.add_command(contract_multicommand, 'contract') COMMANDS.add_command(coordinator, 'coordinator') diff --git a/ion/htlc/coordclient.py b/ion/htlc/coordclient.py index 0edab2e..178bd93 100644 --- a/ion/htlc/coordclient.py +++ b/ion/htlc/coordclient.py @@ -198,7 +198,7 @@ def proposals(self): def proposal(self, secret_hashed_hex): return self.proposals[secret_hashed_hex] - def propose(self, wait=True): + def propose(self): """ Submit a proposal for the exchange by depositing your tokens into a HTLC contract. @@ -219,14 +219,13 @@ def propose(self, wait=True): # TODO: verify adequate balance to cover the deposit - # XXX: for testing, we can be both sides... - #require(my_address != prop_receiver, "Cannot be both sides of exchange") + require(my_address != prop_receiver, "Cannot be both sides of exchange") htlc_address = self._data['want_htlc_address'] htlc_contract = make_htlc_proxy(ethrpc, htlc_address, my_address) txn = htlc_contract.Deposit(prop_receiver, secret_hashed, prop_expiry, value=prop_value) - receipt = txn.receipt(wait=wait) + receipt = txn.receipt(wait=True) if receipt and int(receipt['status'], 16) == 0: raise RuntimeError("Propose deposit failed, txn: " + txn.txid)