From 961e30d7214e0ad8796d93a4a9c3c7f98116ce72 Mon Sep 17 00:00:00 2001 From: Hamdi Allam Date: Wed, 19 Dec 2018 17:16:58 -0800 Subject: [PATCH] Eth module (#103) * starter * testing client with ganache * Squashed 'contracts/' content from commit ab4fd0e git-subtree-dir: contracts git-subtree-split: ab4fd0ed780c5828ca815946922ba3b05c1e9de4 * contract wrappers and bindings * subscribed to deposits/exists. Some tests * added wrappers to the contracts folder * Squashed 'contracts/' changes from ab4fd0e..dc28e6a dc28e6a [Audit Milestone] No rootchain finality checks (#79) b7ddf5c Simple Merkle Tree (#77) 62a9462 position in the exit struct. removed helpers (#78) git-subtree-dir: contracts git-subtree-split: dc28e6a17dc3e4b7eb9574464dd97d5a16861095 * added clean to npm script. temp removed wrappers * Squashed 'contracts/' changes from dc28e6a..4fcc49e 4fcc49e doc typo (#83) 088adc7 [Audit milestone] Fee withdrawal for validators (#80) bc1b116 Finalize Revert (#82) git-subtree-dir: contracts git-subtree-split: 4fcc49e72a86674bfb7ebb731bef59834ce7bd79 * updated wrappers * Squashed 'contracts/' changes from 4fcc49e..1b58d54 1b58d54 added eth blocknum to the deposit struct (#85) git-subtree-dir: contracts git-subtree-split: 1b58d54a6ba8d54bd3e802189e07f68b83afc1be * deposits with finality checks * removed err from HasTxBeenExited * refactors and added tests. Fixed deposit encoding/decoding * typo * changed rlp to json * gopkg fix, updated travis travis travis fix rearrange travis avoid timeout npm install longer sleep time cd back changed back to sleep 5 * Squashed 'contracts/' changes from 1b58d54..7404701 7404701 Hamdi/fee challenge (#87) git-subtree-dir: contracts git-subtree-split: 74047018dd36b0bf76adbb9b350e45aa285f1974 * subtree mods * Squashed 'contracts/' changes from 7404701..c2932c6 c2932c6 renamed docs and fixed a typo (#89) 646fcd9 Colin/deposit priority (#90) git-subtree-dir: contracts git-subtree-split: c2932c687191cb75167782cd9dfcd19ab3441199 * first revision of the eth module * hastxbeenexited more robust like getdeposit * travis * race condition * addressed feedback --- .gitignore | 5 +- .travis.yml | 11 +- Gopkg.lock | 43 +- contracts/.gitattributes | 1 + contracts/.gitignore | 4 + contracts/.solcover.js | 3 + contracts/.travis.yml | 16 + contracts/CONTRIBUTING.md | 35 + contracts/LICENSE | 201 + contracts/README.md | 49 + contracts/contracts/Migrations.sol | 23 + contracts/contracts/PlasmaMVP.sol | 516 ++ contracts/contracts/libraries/BytesUtil.sol | 53 + .../contracts/libraries/BytesUtil_Test.sol | 9 + .../contracts/libraries/PriorityQueue.sol | 93 + .../libraries/PriorityQueue_Test.sol | 15 + .../libraries/TMSimpleMerkleTree.sol | 72 + .../libraries/TMSimpleMerkleTree_Test.sol | 15 + contracts/contracts/libraries/Validator.sol | 46 + .../contracts/libraries/Validator_Test.sol | 27 + contracts/docs/plasmaMVPFunctions.md | 132 + contracts/generate.js | 26 + contracts/migrations/1_initial_migration.js | 5 + contracts/migrations/2_deploy_rootchain.js | 5 + contracts/package-lock.json | 6150 +++++++++++++++++ contracts/package.json | 25 + .../test/libraries/TMSimpleMerkleTree.js | 92 + contracts/test/libraries/bytesUtil.js | 43 + contracts/test/libraries/priorityQueue.js | 132 + contracts/test/libraries/validator.js | 167 + contracts/test/plasmamvp/blockSubmissions.js | 92 + contracts/test/plasmamvp/deposits.js | 265 + contracts/test/plasmamvp/plasmamvp_helpers.js | 87 + contracts/test/plasmamvp/transactions.js | 797 +++ contracts/test/utilities.js | 20 + contracts/truffle-config.js | 4 + contracts/truffle.js | 11 + contracts/wrappers/plasma_mvp.go | 1672 +++++ eth/main.go | 96 + eth/plasma.go | 295 + eth/plasma_test.go | 318 + eth/util.go | 38 + eth/util_test.go | 53 + types/utxo.go | 10 +- 44 files changed, 11765 insertions(+), 7 deletions(-) create mode 100644 contracts/.gitattributes create mode 100644 contracts/.gitignore create mode 100644 contracts/.solcover.js create mode 100644 contracts/.travis.yml create mode 100644 contracts/CONTRIBUTING.md create mode 100644 contracts/LICENSE create mode 100644 contracts/README.md create mode 100644 contracts/contracts/Migrations.sol create mode 100644 contracts/contracts/PlasmaMVP.sol create mode 100644 contracts/contracts/libraries/BytesUtil.sol create mode 100644 contracts/contracts/libraries/BytesUtil_Test.sol create mode 100644 contracts/contracts/libraries/PriorityQueue.sol create mode 100644 contracts/contracts/libraries/PriorityQueue_Test.sol create mode 100644 contracts/contracts/libraries/TMSimpleMerkleTree.sol create mode 100644 contracts/contracts/libraries/TMSimpleMerkleTree_Test.sol create mode 100644 contracts/contracts/libraries/Validator.sol create mode 100644 contracts/contracts/libraries/Validator_Test.sol create mode 100644 contracts/docs/plasmaMVPFunctions.md create mode 100755 contracts/generate.js create mode 100644 contracts/migrations/1_initial_migration.js create mode 100644 contracts/migrations/2_deploy_rootchain.js create mode 100644 contracts/package-lock.json create mode 100644 contracts/package.json create mode 100644 contracts/test/libraries/TMSimpleMerkleTree.js create mode 100644 contracts/test/libraries/bytesUtil.js create mode 100644 contracts/test/libraries/priorityQueue.js create mode 100644 contracts/test/libraries/validator.js create mode 100644 contracts/test/plasmamvp/blockSubmissions.js create mode 100644 contracts/test/plasmamvp/deposits.js create mode 100644 contracts/test/plasmamvp/plasmamvp_helpers.js create mode 100644 contracts/test/plasmamvp/transactions.js create mode 100644 contracts/test/utilities.js create mode 100644 contracts/truffle-config.js create mode 100644 contracts/truffle.js create mode 100644 contracts/wrappers/plasma_mvp.go create mode 100644 eth/main.go create mode 100644 eth/plasma.go create mode 100644 eth/plasma_test.go create mode 100644 eth/util.go create mode 100644 eth/util_test.go diff --git a/.gitignore b/.gitignore index a725465..7aa493d 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ -vendor/ \ No newline at end of file +.DS_Store +vendor/ +contracts/abi +contracts/node_modules diff --git a/.travis.yml b/.travis.yml index 1bf5948..f0897dd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: go go: - - 1.10.x + - 1.11.x env: - DEP_VERSION="0.4.1" @@ -15,7 +15,16 @@ before_install: install: - dep ensure -vendor-only +before_script: + - npm install -g truffle@4.1.14 ganache-cli@6.2.3 + script: + - ganache-cli -m=plasma > /dev/null & + - sleep 5 + - cd contracts/ + - npm install + - truffle migrate + - cd ../ - go test -race -coverprofile=coverage.txt -covermode=atomic -v ./... after_success: diff --git a/Gopkg.lock b/Gopkg.lock index 78429fe..3e7fb34 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -103,11 +103,13 @@ version = "v1.7.1" [[projects]] - digest = "1:a8ca8dd35e0110f87f54ac5c0dcf5f21faae1b60e35cbc01e40ab5bcb756dfad" + digest = "1:157ad8964124f4baaaf6d4d8b6434bc3db1cb43936c7ad665bb73d2b2a0d2c2b" name = "github.com/ethereum/go-ethereum" packages = [ ".", "accounts", + "accounts/abi", + "accounts/abi/bind", "accounts/keystore", "common", "common/hexutil", @@ -122,12 +124,15 @@ "crypto/secp256k1/libsecp256k1/src", "crypto/secp256k1/libsecp256k1/src/modules/recovery", "crypto/sha3", + "ethclient", "ethdb", "event", "log", "metrics", + "p2p/netutil", "params", "rlp", + "rpc", "trie", ] pruneopts = "UT" @@ -415,6 +420,14 @@ revision = "69d839f37b13a8cb7a78366f7633a4071cb43be7" version = "v0.9.2" +[[projects]] + digest = "1:b0c25f00bad20d783d259af2af8666969e2fc343fa0dc9efe52936bbd67fb758" + name = "github.com/rs/cors" + packages = ["."] + pruneopts = "UT" + revision = "9a47f48565a795472d43519dd49aac781f3034fb" + version = "v1.6.0" + [[projects]] digest = "1:6a4a11ba764a56d2758899ec6f3848d24698d48442ebce85ee7a3f63284526cd" name = "github.com/spf13/afero" @@ -627,7 +640,7 @@ source = "https://github.com/tendermint/crypto" [[projects]] - digest = "1:d36f55a999540d29b6ea3c2ea29d71c76b1d9853fdcd3e5c5cb4836f2ba118f1" + digest = "1:f1afc9fbc40f720896e8090b19bd6ab2ae4f88a1e12ce0edbefa641d1a6cfc80" name = "golang.org/x/net" packages = [ "context", @@ -638,6 +651,7 @@ "internal/timeseries", "netutil", "trace", + "websocket", ] pruneopts = "UT" revision = "292b43bbf7cb8d35ddf40f8d5100ef3837cced3f" @@ -718,6 +732,14 @@ revision = "168a6198bcb0ef175f7dacec0b8691fc141dc9b8" version = "v1.13.0" +[[projects]] + branch = "v2" + digest = "1:3d3f9391ab615be8655ae0d686a1564f3fec413979bb1aaf018bac1ec1bb1cc7" + name = "gopkg.in/natefinch/npipe.v2" + packages = ["."] + pruneopts = "UT" + revision = "c1b8fa8bdccecb0b8db834ee0b92fdbcfa606dd6" + [[projects]] digest = "1:342378ac4dcb378a5448dd723f0784ae519383532f5e70ade24132c4c8693202" name = "gopkg.in/yaml.v2" @@ -732,16 +754,23 @@ input-imports = [ "github.com/bgentry/speakeasy", "github.com/cosmos/cosmos-sdk/baseapp", + "github.com/cosmos/cosmos-sdk/client", "github.com/cosmos/cosmos-sdk/codec", "github.com/cosmos/cosmos-sdk/server", - "github.com/cosmos/cosmos-sdk/server/config", "github.com/cosmos/cosmos-sdk/store", "github.com/cosmos/cosmos-sdk/types", + "github.com/ethereum/go-ethereum", "github.com/ethereum/go-ethereum/accounts", + "github.com/ethereum/go-ethereum/accounts/abi", + "github.com/ethereum/go-ethereum/accounts/abi/bind", "github.com/ethereum/go-ethereum/accounts/keystore", "github.com/ethereum/go-ethereum/common", + "github.com/ethereum/go-ethereum/core/types", "github.com/ethereum/go-ethereum/crypto", + "github.com/ethereum/go-ethereum/ethclient", + "github.com/ethereum/go-ethereum/event", "github.com/ethereum/go-ethereum/rlp", + "github.com/ethereum/go-ethereum/rpc", "github.com/mattn/go-isatty", "github.com/pkg/errors", "github.com/spf13/cobra", @@ -749,15 +778,23 @@ "github.com/spf13/viper", "github.com/stretchr/testify/assert", "github.com/stretchr/testify/require", + "github.com/syndtr/goleveldb/leveldb", + "github.com/syndtr/goleveldb/leveldb/comparer", + "github.com/syndtr/goleveldb/leveldb/memdb", "github.com/tendermint/go-amino", "github.com/tendermint/tendermint/abci/types", + "github.com/tendermint/tendermint/config", "github.com/tendermint/tendermint/crypto", "github.com/tendermint/tendermint/crypto/encoding/amino", "github.com/tendermint/tendermint/crypto/secp256k1", + "github.com/tendermint/tendermint/crypto/tmhash", "github.com/tendermint/tendermint/libs/cli", "github.com/tendermint/tendermint/libs/common", "github.com/tendermint/tendermint/libs/db", "github.com/tendermint/tendermint/libs/log", + "github.com/tendermint/tendermint/lite", + "github.com/tendermint/tendermint/p2p", + "github.com/tendermint/tendermint/privval", "github.com/tendermint/tendermint/rpc/client", "github.com/tendermint/tendermint/rpc/core/types", "github.com/tendermint/tendermint/types", diff --git a/contracts/.gitattributes b/contracts/.gitattributes new file mode 100644 index 0000000..52031de --- /dev/null +++ b/contracts/.gitattributes @@ -0,0 +1 @@ +*.sol linguist-language=Solidity diff --git a/contracts/.gitignore b/contracts/.gitignore new file mode 100644 index 0000000..dd5f792 --- /dev/null +++ b/contracts/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +coverage/ +coverage.json +build/ diff --git a/contracts/.solcover.js b/contracts/.solcover.js new file mode 100644 index 0000000..4494f88 --- /dev/null +++ b/contracts/.solcover.js @@ -0,0 +1,3 @@ +module.exports = { + copyNodeModules: true +} diff --git a/contracts/.travis.yml b/contracts/.travis.yml new file mode 100644 index 0000000..156c229 --- /dev/null +++ b/contracts/.travis.yml @@ -0,0 +1,16 @@ +language: node_js + +node_js: "node" + +before_script: + - npm install -g truffle@4.1.14 ganache-cli@6.1.8 + - npm install + +script: + - ganache-cli -m "they only media any modify banner suffer pole tag rule creek harvest" > /dev/null & + - sleep 5 + - truffle migrate + - truffle test + +after_script: + - npm run coverage && cat coverage/lcov.info | coveralls diff --git a/contracts/CONTRIBUTING.md b/contracts/CONTRIBUTING.md new file mode 100644 index 0000000..8cd1dd2 --- /dev/null +++ b/contracts/CONTRIBUTING.md @@ -0,0 +1,35 @@ +# Contributing + +Thank you for considering making contributions to Fourth State's Plasma MVP implementation! We welcome contributions from anyone! See the [open issues](https://github.com/FourthState/plasma-mvp-rootchain/issues) for things we need help with! + +Contribute to design discussions and conversation by joining our [Discord Server](https://discord.gg/YTB5A4P). + +## How to get started: + +Fork, then clone the repo: + +If you have ssh keys: +``git clone git@github.com:your-username/plasma-mvp-rootchain`` + +Otherwise: +``git clone https://github.com/your-username/plasma-mvp-rootchain`` + +Install dependencies with: +``npm install`` + +**Note**: requires Solidity 0.4.24 and Truffle 4.1.14 + +Make sure the tests pass: +1. Start ganache-cli: ``ganache-cli -m=plasma_mvp`` +2. Run tests: ``truffle test`` + +Create a branch that is named off the feature you are trying to implement. See these [guidelines](https://nvie.com/posts/a-successful-git-branching-model/) + +Make your changes. Add tests and comment those changes. + +If your tests pass, push to your fork and [submit a pull request](https://github.com/FourthState/plasma-mvp-rootchain/pulls) to the master branch. + +## Proposals: + +If you would like to propose a protocol change, open up an issue. If the reviewers decide the proposed change is in line with the project's aim, then a writeup should also be added to the [research repository](https://github.com/FourthState/plasma-research). It is also advisable to publish the proposed change to [Eth Research](https://ethresear.ch/), so other plasma implementations can benefit from the proposed change. + diff --git a/contracts/LICENSE b/contracts/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/contracts/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/contracts/README.md b/contracts/README.md new file mode 100644 index 0000000..316f81e --- /dev/null +++ b/contracts/README.md @@ -0,0 +1,49 @@ +# PLASMA MVP + +[![travis build](https://travis-ci.org/FourthState/plasma-mvp-rootchain.svg?branch=master)](https://travis-ci.org/FourthState/plasma-mvp-rootchain) +[![license](https://img.shields.io/github/license/FourthState/plasma-mvp-rootchain.svg)](https://github.com/FourthState/plasma-mvp-rootchain/blob/master/LICENSE) +[![Coverage Status](https://coveralls.io/repos/github/FourthState/plasma-mvp-rootchain/badge.svg?branch=master)](https://coveralls.io/github/FourthState/plasma-mvp-rootchain?branch=master) + +Implementation of [Minimum Viable Plasma](https://ethresear.ch/t/minimal-viable-plasma/426) + +## Overview +Plasma is a layer 2 scaling solution which conducts transaction processing off chain and allows for only merkle roots of each block to be reported to a root chain. This allows for users to benefit from off chain scaling while still relying on decentralized security. + +The root contract of a Plasma child chain represents an intermediary who can resolve any disputes. The root contract is responsible for maintaining a mapping from block number to merkle root, processing deposits, and processing withdrawals. + +## Root Contract Details +A transaction is encoded in the following form: + +``` +[ + [Blknum1, TxIndex1, Oindex1, DepositNonce1, Owner1, Input1ConfirmSig, + + Blknum2, TxIndex2, Oindex2, DepositNonce2, Owner2, Input2ConfirmSig, + + NewOwner, Denom1, NewOwner, Denom2, Fee], + + [Signature1, Signature2] +] +``` +The signatures are over the hash of the transaction list signed by the owner of each respective utxo input. + +### Documentation + +See our [documentation](https://github.com/FourthState/plasma-mvp-rootchain/blob/master/docs/rootchainFunctions.md) for a more detailed description of the smart contract functions. + +### Testing +1. ``git clone https://github.com/fourthstate/plasma-mvp-rootchain`` +2. ``cd plasma-mvp-rootchain`` +3. ``npm install`` +4. ``npm install -g truffle ganache-cli`` // if not installed already +5. ``ganache-cli`` // run as a background process +6. ``npm test`` + +### Running +The first migration file `1_initial_migration` deploys the `PriorityQueue` library and links it to the `RootChain` contract, while the second one `2_deploy_rootchain` finally makes the deployment. Ethereum requires libraries to already be deployed prior to be used by other contracts. + +If you encounter problems, make sure your local test rpc (e.g. [ganache](https://github.com/trufflesuite/ganache-core)) has the same network id as the contract's json from the `build` folder. + +### Contributing + +See our [contribution guidelines](https://github.com/FourthState/plasma-mvp-rootchain/blob/master/CONTRIBUTING.md). Join our [Discord Server](https://discord.gg/YTB5A4P). diff --git a/contracts/contracts/Migrations.sol b/contracts/contracts/Migrations.sol new file mode 100644 index 0000000..4ea0833 --- /dev/null +++ b/contracts/contracts/Migrations.sol @@ -0,0 +1,23 @@ +pragma solidity ^0.4.24; + +contract Migrations { + address public owner; + uint public last_completed_migration; + + modifier restricted() { + if (msg.sender == owner) _; + } + + constructor() public { + owner = msg.sender; + } + + function setCompleted(uint completed) public restricted { + last_completed_migration = completed; + } + + function upgrade(address new_address) public restricted { + Migrations upgraded = Migrations(new_address); + upgraded.setCompleted(last_completed_migration); + } +} diff --git a/contracts/contracts/PlasmaMVP.sol b/contracts/contracts/PlasmaMVP.sol new file mode 100644 index 0000000..a2e07ca --- /dev/null +++ b/contracts/contracts/PlasmaMVP.sol @@ -0,0 +1,516 @@ +pragma solidity ^0.4.24; + +// external modules +import "openzeppelin-solidity/contracts/math/SafeMath.sol"; +import "openzeppelin-solidity/contracts/cryptography/ECDSA.sol"; +import "solidity-rlp/contracts/RLPReader.sol"; + +import "./libraries/TMSimpleMerkleTree.sol"; +import "./libraries/Validator.sol"; +import "./libraries/PriorityQueue.sol"; + +contract PlasmaMVP { + using PriorityQueue for uint256[]; + using RLPReader for bytes; + using RLPReader for RLPReader.RLPItem; + using SafeMath for uint256; + + using Validator for bytes32; + using TMSimpleMerkleTree for bytes32; + + /* + * Events + */ + + event AddedToBalances(address owner, uint256 amount); + event BlockSubmitted(bytes32 root, uint256 blockNumber, uint256 numTxns, uint256 feeAmount); + event Deposit(address depositor, uint256 amount, uint256 depositNonce, uint256 ethBlockNum); + + event StartedTransactionExit(uint256[3] position, address owner, uint256 amount, bytes confirmSignatures, uint256 committedFee); + event StartedDepositExit(uint256 nonce, address owner, uint256 amount); + + event ChallengedExit(uint256[4] position, address owner, uint256 amount); + event FinalizedExit(uint256[4] position, address owner, uint256 amount); + + /* + * Storage + */ + + address operator; + + // child chain + uint256 public lastCommittedBlock; + uint256 public depositNonce; + mapping(uint256 => childBlock) public childChain; + mapping(uint256 => depositStruct) public deposits; + struct childBlock { + bytes32 root; + uint256 numTxns; + uint256 feeAmount; + uint256 createdAt; + } + struct depositStruct { + address owner; + uint256 amount; + uint256 createdAt; + uint256 ethBlockNum; + } + + // exits + uint256 minExitBond; + uint256[] txExitQueue; + uint256[] depositExitQueue; + mapping(uint256 => exit) public txExits; + mapping(uint256 => exit) public depositExits; + enum ExitState { NonExistent, Pending, Challenged, Finalized } + struct exit { + uint256 amount; + uint256 committedFee; + uint256 createdAt; + address owner; + uint256[4] position; // (blkNum, txIndex, outputIndex, depositNonce) + ExitState state; // default value is `NonExistent` + } + + // funds + mapping(address => uint256) public balances; + uint256 public totalWithdrawBalance; + + // constants + uint256 public constant txIndexFactor = 10; + uint256 public constant blockIndexFactor = 1000000; + uint256 public constant maxTxnsPerBLock = 2**16 - 1; + + /** Modifiers **/ + modifier isBonded() + { + require(msg.value >= minExitBond, "insufficient bond committed"); + if (msg.value > minExitBond) { + uint256 excess = msg.value.sub(minExitBond); + balances[msg.sender] = balances[msg.sender].add(excess); + totalWithdrawBalance = totalWithdrawBalance.add(excess); + } + + _; + } + + modifier onlyOperator() + { + require(msg.sender == operator, "unauthorized"); + _; + } + + constructor() public + { + operator = msg.sender; + + lastCommittedBlock = 0; + depositNonce = 1; + minExitBond = 10000; + } + + // @param blocks 32 byte merkle roots appended in ascending order + // @param txnsPerBlock number of transactions per block + // @param feesPerBlock amount of fees the validator has collected per block + // @param blockNum the block number of the first header + function submitBlock(bytes32[] headers, uint256[] txnsPerBlock, uint256[] feesPerBlock, uint256 blockNum) + public + onlyOperator + { + require(blockNum == lastCommittedBlock + 1, "inconsistent block number ordering"); + require(headers.length == txnsPerBlock.length && headers.length == feesPerBlock.length, "mismatch in the number of headers, txn numbers, and fees"); + + for (uint i = 0; i < headers.length; i++) { + require(txnsPerBlock[i] <= maxTxnsPerBLock, "number of transactions in block exceeds limit"); + + childChain[blockNum + i] = childBlock(headers[i], txnsPerBlock[i], feesPerBlock[i], block.timestamp); + emit BlockSubmitted(headers[i], blockNum + i, txnsPerBlock[i], feesPerBlock[i]); + } + + lastCommittedBlock = lastCommittedBlock.add(headers.length); + } + + // @param owner owner of this deposit + function deposit(address owner) + public + payable + { + deposits[depositNonce] = depositStruct(owner, msg.value, block.timestamp, block.number); + emit Deposit(owner, msg.value, depositNonce, block.number); + + depositNonce = depositNonce.add(1); + } + + // @param depositNonce the nonce of the specific deposit + function startDepositExit(uint256 nonce, uint256 committedFee) + public + payable + isBonded + { + require(deposits[nonce].owner == msg.sender, "mismatch in owner"); + require(depositExits[nonce].state == ExitState.NonExistent, "exit for this deposit already exists"); + + uint amount = deposits[nonce].amount; + address owner = deposits[nonce].owner; + uint256 priority = block.timestamp << 128 | nonce; + depositExitQueue.insert(priority); + depositExits[nonce] = exit({ + owner: owner, + amount: amount, + committedFee: committedFee, + createdAt: block.timestamp, + position: [0,0,0,nonce], + state: ExitState.Pending + }); + + emit StartedDepositExit(nonce, owner, amount); + } + + // Transaction encoding: + // [[Blknum1, TxIndex1, Oindex1, DepositNonce1, Owner1, Input1ConfirmSig, + // Blknum2, TxIndex2, Oindex2, DepositNonce2, Owner2, Input2ConfirmSig, + // NewOwner, Denom1, NewOwner, Denom2, Fee], + // [Signature1, Signature2]] + // + // @param txBytes rlp encoded transaction + // @notice this function will revert if the txBytes are malformed + function decodeTransaction(bytes txBytes) + internal + pure + returns (RLPReader.RLPItem[] memory txList, RLPReader.RLPItem[] memory sigList, bytes32 txHash) + { + RLPReader.RLPItem[] memory spendMsg = txBytes.toRlpItem().toList(); + require(spendMsg.length == 2, "incorrect encoding of the transcation"); + + txList = spendMsg[0].toList(); + require(txList.length == 17, "incorrect number of items in the transaction list"); + + sigList = spendMsg[1].toList(); + require(sigList.length == 2, "two signatures must be present"); + + // bytes the signatures are over + txHash = keccak256(spendMsg[0].toRlpBytes()); + } + + // @param txPos location of the transaction [blkNum, txIndex, outputIndex] + // @param txBytes raw transaction bytes + // @param proof merkle proof of inclusion in the child chain + // @param confirmSignatures confirm signatures sent by the owners of the inputs acknowledging the spend. + // @notice `confirmSignatures` and `ConfirmSig0`/`ConfirmSig1` are unrelated to each other. + // @notice `confirmSignatures` is either 65 or 130 bytes in length dependent on if input2 is used. + function startTransactionExit(uint256[3] txPos, bytes txBytes, bytes proof, bytes confirmSignatures, uint256 committedFee) + public + payable + isBonded + { + uint256 position = blockIndexFactor*txPos[0] + txIndexFactor*txPos[1] + txPos[2]; + require(txExits[position].state == ExitState.NonExistent, "this exit has already been started, challenged, or finalized"); + + uint256 amount = startTransactionExitHelper(txPos, txBytes, proof, confirmSignatures); + + // calculate the priority of the transaction taking into account the withdrawal delay attack + // withdrawal delay attack: https://github.com/FourthState/plasma-mvp-rootchain/issues/42 + uint256 createdAt = childChain[txPos[0]].createdAt; + txExitQueue.insert(max(createdAt + 1 weeks, block.timestamp) << 128 | position); + + // write exit to storage + txExits[position] = exit({ + owner: msg.sender, + amount: amount, + committedFee: committedFee, + createdAt: block.timestamp, + position: [txPos[0], txPos[1], txPos[2], 0], + state: ExitState.Pending + }); + + emit StartedTransactionExit(txPos, msg.sender, amount, confirmSignatures, committedFee); + } + + // @returns amount of the exiting transaction + // @notice the purpose of this helper was to work around the capped evm stack frame + function startTransactionExitHelper(uint256[3] txPos, bytes txBytes, bytes proof, bytes confirmSignatures) + private + view + returns (uint256) + { + bytes32 txHash; + RLPReader.RLPItem[] memory txList; + RLPReader.RLPItem[] memory sigList; + (txList, sigList, txHash) = decodeTransaction(txBytes); + + require(msg.sender == txList[12 + 2*txPos[2]].toAddress(), "mismatch in utxo owner"); + + childBlock memory plasmaBlock = childChain[txPos[0]]; + + // check signatures + bytes32 merkleHash = sha256(txBytes); + require(txHash.checkSignatures(sha256(abi.encodePacked(merkleHash, plasmaBlock.root)), // confirmation hash -- sha256(merkleHash, root) + // we always assume the first input is always present in a transaction. The second input is optional + txList[6].toUint() > 0 || txList[9].toUint() > 0, + sigList[0].toBytes(), sigList[1].toBytes(), confirmSignatures), "signature mismatch"); + + // check proof + require(merkleHash.checkMembership(txPos[1], plasmaBlock.root, proof, plasmaBlock.numTxns), "invalid merkle proof"); + + // check that the UTXO's two direct inputs have not been previously exited + require(validateTransactionExitInputs(txList), "an input is pending an exit or has been finalized"); + + return txList[13 + 2*txPos[2]].toUint(); + } + + // For any attempted exit of an UTXO, validate that the UTXO's two inputs have not + // been previously exited or are currently pending an exit. + function validateTransactionExitInputs(RLPReader.RLPItem[] memory txList) + private + view + returns (bool) + { + for (uint256 i = 0; i < 2; i++) { + ExitState state; + uint depositNonce_ = txList[6*i + 3].toUint(); + if (depositNonce_ == 0) { + uint256 blkNum = txList[6*i + 0].toUint(); + uint256 inputIndex = txList[6*i + 1].toUint(); + uint256 outputIndex = txList[6*i + 2].toUint(); + uint256 position = blockIndexFactor*blkNum + txIndexFactor*inputIndex + outputIndex; + state = txExits[position].state; + } else + state = depositExits[depositNonce_].state; + + if (state != ExitState.NonExistent && state != ExitState.Challenged) + return false; + } + + return true; + } + + // Validator of any block can call this function to exit the fees collected + // for that particular block. The fee exit is added to exit queue with the lowest priority for that block. + // In case of the fee UTXO already spent, anyone can challenge the fee exit by providing + // the spend of the fee UTXO. + // @param blockNumber the block for which the validator wants to exit fees + function startFeeExit(uint256 blockNumber) + public + payable + onlyOperator + isBonded + { + // specified blockNumber must exist in child chain + require(childChain[blockNumber].root != bytes32(0), "specified block does not exist in child chain."); + + // a fee UTXO has explicitly defined position [blockNumber, 2**16 - 1, 0] + uint256 txIndex = 2**16 - 1; + uint256 position = blockIndexFactor*blockNumber + txIndexFactor*txIndex; + require(txExits[position].state == ExitState.NonExistent, "this exit has already been started, challenged, or finalized"); + + txExitQueue.insert(max(childChain[blockNumber].createdAt + 1 weeks, block.timestamp) << 128 | position); + + uint256 feeAmount = childChain[blockNumber].feeAmount; + txExits[position] = exit({ + owner: msg.sender, + amount: feeAmount, + committedFee: 0, + createdAt: block.timestamp, + position: [blockNumber, txIndex, 0, 0], + state: ExitState.Pending + }); + + // pass in empty bytes for confirmSignatures for StartedTransactionExit event. + emit StartedTransactionExit([blockNumber, txIndex, 0], msg.sender, feeAmount, "", 0); + } + + // @param exitedTxPos transaction position. Full position - [blkNum, txIndex, outputIndex, depositNonce] + // @param challengingTxPos transaction position [blkNum, txIndex] + // @param txBytes raw bytes of the transaction + // @param proof merkle proof of the included transaction + function challengeFeeMismatch(uint256[4] exitingTxPos, uint256[2] challengingTxPos, bytes txBytes, bytes proof) + public + { + RLPReader.RLPItem[] memory txList; + (txList, , ) = decodeTransaction(txBytes); + + // exitingTxPos must be the first input of the challenging transaction + require(exitingTxPos[0] == txList[0].toUint() && exitingTxPos[1] == txList[1].toUint() + && exitingTxPos[2] == txList[2].toUint() && exitingTxPos[3] == txList[3].toUint(), + "exiting transcation must be the first input of the challenging transaction"); + + childBlock memory plasmaBlock = childChain[challengingTxPos[0]]; + require(sha256(txBytes).checkMembership(challengingTxPos[1], plasmaBlock.root, proof, plasmaBlock.numTxns), "incorrect merkle proof"); + + exit storage exit_ = exitingTxPos[3] == 0 ? + txExits[blockIndexFactor*exitingTxPos[0] + txIndexFactor*exitingTxPos[1] + exitingTxPos[2]] : depositExits[exitingTxPos[3]]; + require(exit_.state == ExitState.Pending, "an exit must be pending"); + + uint256 feeAmount = txList[16].toUint(); + require(exit_.committedFee != feeAmount, "no mismatch in committed fee"); + + // award the challenger the bond + balances[msg.sender] = balances[msg.sender].add(minExitBond); + totalWithdrawBalance = totalWithdrawBalance.add(minExitBond); + + // mark the exit as NonExistent. Can be reopened + exit_.state = ExitState.NonExistent; + emit ChallengedExit(exitingTxPos, exit_.owner, exit_.amount - exit_.committedFee); + } + + // @param exitingTxPos position of the invalid exiting transaction [blkNum, txIndex, outputIndex] + // @param challengingTxPos position of the challenging transaction [blkNum, txIndex] + // @param txBytes raw transaction bytes of the challenging transaction + // @param proof proof of inclusion for this merkle hash + // @param confirmSignature signature used to invalidate the invalid exit. Signature is over (merkleHash, block header) + function challengeExit(uint256[4] exitingTxPos, uint256[2] challengingTxPos, bytes txBytes, bytes proof, bytes confirmSignature) + public + { + RLPReader.RLPItem[] memory txList; + RLPReader.RLPItem[] memory sigList; + (txList, sigList, ) = decodeTransaction(txBytes); + + // must be a direct spend + require((exitingTxPos[0] == txList[0].toUint() && exitingTxPos[1] == txList[1].toUint()&& exitingTxPos[2] == txList[2].toUint() && exitingTxPos[3] == txList[3].toUint()) + || (exitingTxPos[0] == txList[6].toUint() && exitingTxPos[1] == txList[7].toUint()&& exitingTxPos[2] == txList[8].toUint() && exitingTxPos[3] == txList[9].toUint()), + "challenging transaction is not a direct spend"); + + // transaction to be challenged should have a pending exit + exit storage exit_ = exitingTxPos[3] == 0 ? + txExits[blockIndexFactor*exitingTxPos[0] + txIndexFactor*exitingTxPos[1] + exitingTxPos[2]] : depositExits[exitingTxPos[3]]; + require(exit_.state == ExitState.Pending, "no pending exit to challenge"); + + // confirm challenging transcation's inclusion and confirm signature + childBlock memory blk = childChain[challengingTxPos[0]]; + + bytes32 merkleHash = sha256(txBytes); + bytes32 confirmationHash = sha256(abi.encodePacked(merkleHash, blk.root)); + require(exit_.owner == confirmationHash.recover(confirmSignature), "mismatch in exit owner and confirm signature"); + require(merkleHash.checkMembership(challengingTxPos[1], blk.root, proof, blk.numTxns), "incorrect merkle proof"); + + // exit successfully challenged. Award the sender with the bond + balances[msg.sender] = balances[msg.sender].add(minExitBond); + totalWithdrawBalance = totalWithdrawBalance.add(minExitBond); + emit AddedToBalances(msg.sender, minExitBond); + + // reflect challenged state + exit_.state = ExitState.Challenged; + emit ChallengedExit(exit_.position, exit_.owner, exit_.amount - exit_.committedFee); + } + + function finalizeDepositExits() public { finalize(depositExitQueue, true); } + function finalizeTransactionExits() public { finalize(txExitQueue, false); } + + // Finalizes exits by iterating through either the depositExitQueue or txExitQueue. + // Users can determine the number of exits they're willing to process by varying + // the amount of gas allow finalize*Exits() to process. + // Each transaction takes < 80000 gas to process. + function finalize(uint256[] storage queue, bool isDeposits) + private + { + if (queue.length == 0) return; + + // retrieve the lowest priority and the appropriate exit struct + uint256 priority = queue[0]; + exit memory currentExit; + uint256 position; + // retrieve the right 128 bits from the priority to obtain the position + assembly { + position := and(priority, div(not(0x0), exp(256, 16))) + } + + currentExit = isDeposits ? depositExits[position] : txExits[position]; + + /* + * Conditions: + * 1. Exits exist + * 2. Exits must be a week old + * 3. Funds must exist for the exit to withdraw + */ + uint256 amountToAdd; + while ((block.timestamp - currentExit.createdAt) > 1 weeks && + currentExit.amount.add(minExitBond) <= address(this).balance - totalWithdrawBalance && + gasleft() > 80000) { + + // skip currentExit if it is not in 'started/pending' state. + if (currentExit.state != ExitState.Pending) { + queue.delMin(); + } else { + // reimburse the bond but remove fee allocated for the operator + amountToAdd = currentExit.amount.add(minExitBond).sub(currentExit.committedFee); + balances[currentExit.owner] = balances[currentExit.owner].add(amountToAdd); + totalWithdrawBalance = totalWithdrawBalance.add(amountToAdd); + + if (isDeposits) + depositExits[position].state = ExitState.Finalized; + else + txExits[position].state = ExitState.Finalized; + + emit FinalizedExit(currentExit.position, currentExit.owner, amountToAdd); + emit AddedToBalances(currentExit.owner, amountToAdd); + + // move onto the next oldest exit + queue.delMin(); + } + + if (queue.length == 0) { + return; + } + + // move onto the next oldest exit + priority = queue[0]; + + // retrieve the right 128 bits from the priority to obtain the position + assembly { + position := and(priority, div(not(0x0), exp(256, 16))) + } + + currentExit = isDeposits ? depositExits[position] : txExits[position]; + } + } + + function withdraw() + public + returns (uint256) + { + if (balances[msg.sender] == 0) { + return 0; + } + + uint256 transferAmount = balances[msg.sender]; + delete balances[msg.sender]; + totalWithdrawBalance = totalWithdrawBalance.sub(transferAmount); + + // will revert the above deletion if it fails + msg.sender.transfer(transferAmount); + return transferAmount; + } + + /* + * Getters + */ + + function childChainBalance() + public + view + returns (uint) + { + // takes into accounts the failed withdrawals + return address(this).balance - totalWithdrawBalance; + } + + function balanceOf(address _address) + public + view + returns (uint256) + { + return balances[_address]; + } + + /* + * Utils + */ + + function max(uint256 a, uint256 b) + private + pure + returns (uint256) + { + return a >= b ? a : b; + } +} diff --git a/contracts/contracts/libraries/BytesUtil.sol b/contracts/contracts/libraries/BytesUtil.sol new file mode 100644 index 0000000..01e947c --- /dev/null +++ b/contracts/contracts/libraries/BytesUtil.sol @@ -0,0 +1,53 @@ +pragma solidity ^0.4.24; + +library BytesUtil { + uint8 constant WORD_SIZE = 32; + + // @param _bytes raw bytes that needs to be slices + // @param start start of the slice relative to `_bytes` + // @param len length of the sliced byte array + function slice(bytes _bytes, uint start, uint len) + internal + pure + returns (bytes) + { + require(_bytes.length - start >= len, "slice out of bounds"); + + if (_bytes.length == len) + return _bytes; + + bytes memory result; + uint src; + uint dest; + assembly { + // memory & free memory pointer + result := mload(0x40) + mstore(result, len) // store the size in the prefix + mstore(0x40, add(result, and(add(add(0x20, len), 0x1f), not(0x1f)))) // padding + + // pointers + src := add(start, add(0x20, _bytes)) + dest := add(0x20, result) + } + + // copy as many word sizes as possible + for(; len >= WORD_SIZE; len -= WORD_SIZE) { + assembly { + mstore(dest, mload(src)) + } + + src += WORD_SIZE; + dest += WORD_SIZE; + } + + // copy remaining bytes + uint mask = 256 ** (WORD_SIZE - len) - 1; + assembly { + let srcpart := and(mload(src), not(mask)) // zero out src + let destpart := and(mload(dest), mask) // retrieve the bytes + mstore(dest, or(destpart, srcpart)) + } + + return result; + } +} diff --git a/contracts/contracts/libraries/BytesUtil_Test.sol b/contracts/contracts/libraries/BytesUtil_Test.sol new file mode 100644 index 0000000..3ab40c4 --- /dev/null +++ b/contracts/contracts/libraries/BytesUtil_Test.sol @@ -0,0 +1,9 @@ +pragma solidity ^0.4.24; + +import "./BytesUtil.sol"; + +contract BytesUtil_Test { + using BytesUtil for bytes; + + function slice(bytes a , uint start, uint len) public pure returns (bytes) { return a.slice(start, len); } +} diff --git a/contracts/contracts/libraries/PriorityQueue.sol b/contracts/contracts/libraries/PriorityQueue.sol new file mode 100644 index 0000000..b87d148 --- /dev/null +++ b/contracts/contracts/libraries/PriorityQueue.sol @@ -0,0 +1,93 @@ +pragma solidity ^0.4.24; + +// external modules +import "openzeppelin-solidity/contracts/math/SafeMath.sol"; + +library PriorityQueue { + using SafeMath for uint256; + + function insert(uint256[] storage heapList, uint256 k) + internal + { + heapList.push(k); + if (heapList.length > 1) + percUp(heapList, heapList.length.sub(1)); + } + + function getMin(uint256[] storage heapList) + internal + view + returns (uint256) + { + require(heapList.length > 0, "empty queue"); + return heapList[0]; + } + + function delMin(uint256[] storage heapList) + internal + returns (uint256) + { + require(heapList.length > 0, "empty queue"); + + uint256 min = heapList[0]; + + // move the last element to the front + heapList[0] = heapList[heapList.length.sub(1)]; + delete heapList[heapList.length.sub(1)]; + heapList.length = heapList.length.sub(1); + + if (heapList.length > 1) { + percDown(heapList, 0); + } + + return min; + } + + function minChild(uint256[] storage heapList, uint256 i) + private + view + returns (uint256) + { + uint lChild = i.mul(2).add(1); + uint rChild = i.mul(2).add(2); + + if (rChild > heapList.length.sub(1) || heapList[lChild] < heapList[rChild]) + return lChild; + else + return rChild; + } + + function percUp(uint256[] storage heapList, uint256 i) + private + { + uint256 position = i; + uint256 value = heapList[i]; + + // continue to percolate up while smaller than the parent + while (i != 0 && value < heapList[i.sub(1).div(2)]) { + heapList[i] = heapList[i.sub(1).div(2)]; + i = i.sub(1).div(2); + } + + // place the value in the correct parent + if (position != i) heapList[i] = value; + } + + function percDown(uint256[] storage heapList, uint256 i) + private + { + uint position = i; + uint value = heapList[i]; + + // continue to percolate down while larger than the child + uint child = minChild(heapList, i); + while(child < heapList.length && value > heapList[child]) { + heapList[i] = heapList[child]; + i = child; + child = minChild(heapList, i); + } + + // place value in the correct child + if (position != i) heapList[i] = value; + } +} diff --git a/contracts/contracts/libraries/PriorityQueue_Test.sol b/contracts/contracts/libraries/PriorityQueue_Test.sol new file mode 100644 index 0000000..8b04d4e --- /dev/null +++ b/contracts/contracts/libraries/PriorityQueue_Test.sol @@ -0,0 +1,15 @@ +pragma solidity ^0.4.24; + +import "./PriorityQueue.sol"; + +// Purpose of this contract is to forward calls to the library for testing +contract PriorityQueue_Test { + using PriorityQueue for uint256[]; + + uint256[] heapList; + + function insert(uint256 k) public { heapList.insert(k); } + function getMin() public view returns (uint256) { return heapList.getMin(); } + function delMin() public { heapList.delMin(); } + function currentSize() public view returns (uint256) { return heapList.length; } +} diff --git a/contracts/contracts/libraries/TMSimpleMerkleTree.sol b/contracts/contracts/libraries/TMSimpleMerkleTree.sol new file mode 100644 index 0000000..cf72b30 --- /dev/null +++ b/contracts/contracts/libraries/TMSimpleMerkleTree.sol @@ -0,0 +1,72 @@ +pragma solidity ^0.4.24; + +import "./BytesUtil.sol"; + +// from https://tendermint.com/docs/spec/blockchain/encoding.html#merkle-trees +library TMSimpleMerkleTree { + using BytesUtil for bytes; + + // @param leaf a leaf of the tree + // @param index position of this leaf in the tree that is zero indexed + // @param rootHash block header of the merkle tree + // @param proof sequence of 32-byte hashes from the leaf up to, but excluding, the root + // @paramt total total # of leafs in the tree + function checkMembership(bytes32 leaf, uint256 index, bytes32 rootHash, bytes proof, uint256 total) + internal + pure + returns (bool) + { + // variable size Merkle tree, but proof must consist of 32-byte hashes + require(proof.length % 32 == 0, "Incorrect proof length"); + + bytes32 computedHash = computeHashFromAunts(index, total, leaf, proof); + return computedHash == rootHash; + } + + // helper function as described in the tendermint docs + function computeHashFromAunts(uint256 index, uint256 total, bytes32 leaf, bytes innerHashes) + private + pure + returns (bytes32) + { + require(index < total, "Index must be less than total number of leaf nodes"); + require(total > 0, "Must have at least one leaf node"); + + if (total == 1) { + require(innerHashes.length == 0, "Simple Tree with 1 txn should have no innerHashes"); + return leaf; + } + require(innerHashes.length != 0, "Simple Tree with > 1 txn should have innerHashes"); + + uint256 numLeft = (total + 1) / 2; + bytes32 proofElement; + + // prepend 0x20 byte literal to hashes + // tendermint prefixes intermediate hashes with 0x20 bytes literals + // before hashing them. + bytes memory b = new bytes(1); + assembly { + let memPtr := add(b, 0x20) + mstore8(memPtr, 0x20) + } + + if (index < numLeft) { + bytes32 leftHash = computeHashFromAunts(index, numLeft, leaf, innerHashes.slice( 0, innerHashes.length - 32)); + uint innerHashesMemOffset = innerHashes.length - 32; + assembly { + // get the last 32-byte hash from innerHashes array + proofElement := mload(add(add(innerHashes, 0x20), innerHashesMemOffset)) + } + + return sha256(abi.encodePacked(b, leftHash, b, proofElement)); + } else { + bytes32 rightHash = computeHashFromAunts(index-numLeft, total-numLeft, leaf, innerHashes.slice(0, innerHashes.length - 32)); + innerHashesMemOffset = innerHashes.length - 32; + assembly { + // get the last 32-byte hash from innerHashes array + proofElement := mload(add(add(innerHashes, 0x20), innerHashesMemOffset)) + } + return sha256(abi.encodePacked(b, proofElement, b, rightHash)); + } + } +} diff --git a/contracts/contracts/libraries/TMSimpleMerkleTree_Test.sol b/contracts/contracts/libraries/TMSimpleMerkleTree_Test.sol new file mode 100644 index 0000000..885a217 --- /dev/null +++ b/contracts/contracts/libraries/TMSimpleMerkleTree_Test.sol @@ -0,0 +1,15 @@ +pragma solidity ^0.4.24; + +import "./TMSimpleMerkleTree.sol"; + +contract TMSimpleMerkleTree_Test { + using TMSimpleMerkleTree for bytes32; + + function checkMembership(bytes32 leaf, uint256 index, bytes32 rootHash, bytes proof, uint256 total) + public + pure + returns (bool) + { + return leaf.checkMembership(index, rootHash, proof, total); + } +} diff --git a/contracts/contracts/libraries/Validator.sol b/contracts/contracts/libraries/Validator.sol new file mode 100644 index 0000000..ba90009 --- /dev/null +++ b/contracts/contracts/libraries/Validator.sol @@ -0,0 +1,46 @@ +pragma solidity ^0.4.24; + +import "openzeppelin-solidity/contracts/cryptography/ECDSA.sol"; +import "./BytesUtil.sol"; + +library Validator { + using BytesUtil for bytes; + + // @param txHash transaction hash + // @param rootHash block header of the merkle tree + // @param input1 indicator for the second input + // @param sigs transaction signatures + // @notice when one input is present, we require it to be the first input by convention + function checkSignatures(bytes32 txHash, bytes32 confirmationHash, bool input1, bytes sig0, bytes sig1, bytes confirmSignatures) + internal + pure + returns (bool) + { + require(sig0.length == 65 && sig1.length == 65, "signatures must be 65 bytes in length"); + + if (input1) { + require(confirmSignatures.length == 130, "two confirm signatures required with two inputs"); + + address recoveredAddr0 = recover(txHash, sig0); + address recoveredAddr1 = recover(txHash, sig1); + + return recoveredAddr0 == recover(confirmationHash, confirmSignatures.slice(0, 65)) + && recoveredAddr1 == recover(confirmationHash, confirmSignatures.slice(65, 65)) + && recoveredAddr0 != address(0) && recoveredAddr1 != address(0); + } + + // only 1 input present + require(confirmSignatures.length == 65, "one confirm signature required with one input present"); + + address recoveredAddr = recover(txHash, sig0); + return recoveredAddr == recover(confirmationHash, confirmSignatures) && recoveredAddr != address(0); + } + + function recover(bytes32 hash, bytes sig) + internal + pure + returns (address) + { + return ECDSA.recover(ECDSA.toEthSignedMessageHash(hash), sig); + } +} diff --git a/contracts/contracts/libraries/Validator_Test.sol b/contracts/contracts/libraries/Validator_Test.sol new file mode 100644 index 0000000..a7f2794 --- /dev/null +++ b/contracts/contracts/libraries/Validator_Test.sol @@ -0,0 +1,27 @@ +pragma solidity ^0.4.24; + +import "./Validator.sol"; + +/* +* Used to proxy function calls to the Validator for testing +*/ + +contract Validator_Test { + using Validator for bytes32; + + function checkSignatures(bytes32 txHash, bytes32 confirmationHash, bool input1, bytes sig0, bytes sig1, bytes confirmSignatures) + public + pure + returns (bool) + { + return txHash.checkSignatures(confirmationHash, input1, sig0, sig1, confirmSignatures); + } + + function recover(bytes32 hash, bytes sig) + public + pure + returns (address) + { + return hash.recover(sig); + } +} diff --git a/contracts/docs/plasmaMVPFunctions.md b/contracts/docs/plasmaMVPFunctions.md new file mode 100644 index 0000000..9a8f4b6 --- /dev/null +++ b/contracts/docs/plasmaMVPFunctions.md @@ -0,0 +1,132 @@ +# PlasmaMVP Documentation + +The transaction bytes, `txBytes`, in the contract follow the convention: +``` +RLP_ENCODE ([ + [Blknum1, TxIndex1, Oindex1, DepositNonce1, Owner1, Input1ConfirmSig, + + Blknum2, TxIndex2, Oindex2, DepositNonce2, Owner2, Input2ConfirmSig, + + NewOwner, Denom1, NewOwner, Denom2, Fee], + + [Signature1, Signature2] +]) +``` +```solidity +function submitBlock(bytes32[] blocks, uint256[] txnsPerBlock, uint256[] feesPerBlock, uint256 blockNum) +``` +The validator submits an array of block headers in ascending order. Each block can be of variable block size(capped at 2^16 txns per block). The total number of transactions per block must be passed in through `txnsPerBlock`. The amount of transaction fees collected by the validator per block must be passed in through `feesPerBlock`. +`blockNum` must be the intended block number of the first header in this call. Ordering is enforced on each call. `blockNum == lastCommittedBlock + 1`. + +
+ +```solidity +function deposit(address owner) +``` +Entry point into the child chain. The user has the option to create a spendable utxo owned by the address, `owner`. Once created, +the private keys of the `owner` address has complete control of the new utxo. + +Deposits are not recorded in the child chain blocks and are entirely represented on the rootchain. Each deposit is identified with an incremental nonce. +Validators catch deposits through event handlers and maintain a collection of spendable deposits. +```solidity +mapping(uint256 => depositStruct) deposits; // The key is the incrementing nonce +struct depositStruct { + address owner; + uint256 amount; + uint256 created_at; +} +``` + +
+ +```solidity +function startTransactionExit(uint256[3] txPos, bytes txBytes, bytes proof, bytes confirmSignatures, uint256 committedFee) +``` +`txPos` follows the convention - `[blockNumber, transactionIndex, outputIndex]` + +Exit procedure for exiting a utxo on the child chain(not deposits). The `txPos` locates the transaction on the child chain. The leaf, hash(hash(`txBytes`), `sigs`) is checked against the block header using the `proof`. +The `confirmSignatures` represent the acknowledgement of the inclusion by both inputs. If only one input was used to create this transactions, only one confirm signature should be passed in for the corresponding +input. However, if there are two distinct inputs in the exiting transactions, both confirm signatures should be appended together in order for a total of 130 bytes. The owner of the exit must commit to any fees payed, `committedFee`. + +A valid exit satisfies the following properties: + - Exit has not previously been finalized or challenged + - The creator of this exit posted a sufficient bond. Excess funds are refunded the the senders rootchain balance and are immediately withdrawable. + - If present, the confirm signatures are correct and signed by the same address which signed the corresponding input signatures. + +
+ +```solidity +function startDepositExit(uint256 nonce, uint256 committedFee) +``` +Exit procedure for deposits that have not been spent. Deposits are purely identified by their `nonce`. The caller's address must match the owner of the deposit. +A valid exit must satisfy the same constraints listed above for normal utxo exits except confirm signatures. Deposits exits are also collected into their own seperate queue from normal transactions. +This is because of the differing priority calculation. The priority of a deposit is purely it's nonce while the priority of a utxo is calculated from it's location in the child chain. The owner of the exit must +commit to any fee, `committedFee`. + +
+ +```solidity +function startFeeExit(uint256 blockNumber) +``` +The validator of any block should call this function to exit the fees they've collected for that particular block. +The validator declares the `blockNumber` of the block for which they'd like to exit fees. This exit is then added to exit queue with the lowest priority for that block. +Note that if the validator attempts to start an exit for a fee-UTXO that has already been spent in a later block, the exit can be challenged through `startTransactionExit` the same way as a regular transaction exit. + +
+ +```solidity +function challengeFeeMismatch(uint256[4] exitingTxPos, uint256[2] challengingTxPos, bytes txBytes, bytes proof) +``` +`challengingTxPos` follows the convention - `[blockNumber, transactionIndex]` +`exitingTxPos` follows the convention - `[blockNumber, transactionIndex, outputIndex, depositNonce`] + +An exit which posts an invalid committed fee can be challenged with this function. The `txBytes` of `challengingTxPos` which includes the correct fee, along with it's merkle `proof` of inclusion is checked against the exiter's claimed +committed fee. If there is a mismatch, the exit is invalidated and the bond is awarded to the challenger. `exitingTxPos` must be the first input of `challengingTxPos`. `exitingTxPos` is the full position including the deposit nonce. + +
+ +```solidity +function challengeExit(uint256[4] exitingTxPos, uint256[2] challengingTxPos, bytes txBytes, bytes proof, bytes confirmSignature) +``` +`challengingTxPos` follows the convention - `[blockNumber, transactionIndex]` +`exitingTxPos` follows the convention - `[blockNumber, transactionIndex, outputIndex, depositNonce`] + +A uxto that has starting an exit phase but was already spent on the child chain can be challenged using this function call. A successful challenge awards the caller with the exit bond. +The `exitingTxPos` locates the malicious utxo and is used to calculate the priority. `challengingTxPos` locates the transaction that is the child (offending transaction is an input into this tx). +The `proof`, `txBytes` and `sigs` is sufficient for a proof of inclusion in the child chain of the parent transaction. The `confirmSignature`, signed by the owner of the malicious transaction, +acknowledges the inclusion of it's parent in the plasma chain and allows anyone with this confirm signature to challenge a malicious exit of the child. + +
+ +```solidity +function finalizeTransactionExits() +``` +Process all "finalized" exits in the priority queue. "Finalized" exits are those that have been in the priority queue for at least one week and have not been proven to be malicious through a challenge. + +
+ +```solidity +function finalizeDepositExits() +``` +Process all "finalized" deposit exits in the priority queue. "Finalized" exits are those that have been in the priority queue for at least one week and have not been proven to be malicious through a challenge. + +
+ +```solidity +function withdraw() +``` +Sender withdraws all funds associated with their balance from the contract. + +
+ +```solidity +function balanceOf(address _address) returns (uint256 amount) +``` +Getter for the withdrawable balance of `_address` + +
+ +```solidity +function childChainBalance() returns (uint256 funds) +``` +Query the total funds of the plasma chain diff --git a/contracts/generate.js b/contracts/generate.js new file mode 100755 index 0000000..ea8b781 --- /dev/null +++ b/contracts/generate.js @@ -0,0 +1,26 @@ +#!/usr/bin/env node +var shell = require('shelljs'); +var fs = require("fs"); + +console.log('Generating Go Wrappers...'); +console.log('Cleaning previous build...'); +shell.rm('-rf', 'build wrappers abi'); +shell.exec('truffle compile'); + +// files to generate +generate('./build/contracts/PlasmaMVP.json'); + +function generate(path) { + shell.mkdir('-p', ['wrappers', 'abi']); + + let contract = JSON.parse(fs.readFileSync(path, {encoding: 'utf8'})); + + //const filename = contract.contractName; + let snakeCasedFilename = ( + contract.contractName.replace(/([a-z])([A-Z])/g, '$1_$2').replace(/([A-Z])([A-Z][a-z])/g, '$1_$2') + ).toLowerCase(); + + + fs.writeFileSync(`abi/${contract.contractName}.abi`, JSON.stringify(contract.abi)) + shell.exec(`abigen --abi abi/${contract.contractName}.abi --pkg wrappers --type ${contract.contractName} --out wrappers/${snakeCasedFilename}.go`); +} diff --git a/contracts/migrations/1_initial_migration.js b/contracts/migrations/1_initial_migration.js new file mode 100644 index 0000000..1349ace --- /dev/null +++ b/contracts/migrations/1_initial_migration.js @@ -0,0 +1,5 @@ +let Migrations = artifacts.require("Migrations"); + +module.exports = function(deployer, network, accounts) { + deployer.deploy(Migrations); +}; diff --git a/contracts/migrations/2_deploy_rootchain.js b/contracts/migrations/2_deploy_rootchain.js new file mode 100644 index 0000000..33f3313 --- /dev/null +++ b/contracts/migrations/2_deploy_rootchain.js @@ -0,0 +1,5 @@ +let PlasmaMVP = artifacts.require("PlasmaMVP"); + +module.exports = function(deployer, network, accounts) { + deployer.deploy(PlasmaMVP, {from: accounts[0]}); +}; diff --git a/contracts/package-lock.json b/contracts/package-lock.json new file mode 100644 index 0000000..fa270f1 --- /dev/null +++ b/contracts/package-lock.json @@ -0,0 +1,6150 @@ +{ + "name": "plasma-mvp-rootchain", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "abbrev": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", + "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=", + "dev": true + }, + "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.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" + } + }, + "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.0.2", + "longest": "^1.0.1", + "repeat-string": "^1.5.2" + } + }, + "amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", + "dev": true + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dev": true, + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true + }, + "aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, + "optional": true, + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "bignumber.js": { + "version": "git+https://github.com/debris/bignumber.js.git#94d7146671b9719e00a09c29b01a691bc85048c2", + "from": "git+https://github.com/debris/bignumber.js.git#94d7146671b9719e00a09c29b01a691bc85048c2", + "dev": true + }, + "bindings": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.3.1.tgz", + "integrity": "sha512-i47mqjF9UbjxJhxGf+pZ6kSxrnI3wBLlnGI2ArWJ4r0VrvDS7ZYXkprq/pLaBWYq4GM0r4zdHY+NNRqEMU7uew==" + }, + "bip66": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/bip66/-/bip66-1.1.5.tgz", + "integrity": "sha1-AfqHSHhcpwlV1QESF9GzE5lpyiI=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "bn.js": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" + }, + "browser-stdout": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", + "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", + "dev": true + }, + "browserify-aes": { + "version": "1.2.0", + "resolved": "http://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "requires": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "browserify-sha3": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/browserify-sha3/-/browserify-sha3-0.0.1.tgz", + "integrity": "sha1-P/NKMAbvFcD7NWflQbkaI0ASPRE=", + "dev": true, + "requires": { + "js-sha3": "^0.3.1" + } + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=" + }, + "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 + }, + "camelcase": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", + "dev": true, + "optional": true + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, + "center-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", + "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", + "dev": true, + "optional": true, + "requires": { + "align-text": "^0.1.3", + "lazy-cache": "^1.0.3" + } + }, + "chai": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", + "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", + "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.5" + } + }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true + }, + "cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "cliui": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", + "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", + "dev": true, + "optional": true, + "requires": { + "center-align": "^0.1.1", + "right-align": "^0.1.1", + "wordwrap": "0.0.2" + }, + "dependencies": { + "wordwrap": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", + "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", + "dev": true, + "optional": true + } + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true + }, + "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 + }, + "combined-stream": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", + "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", + "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==", + "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=" + }, + "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 + }, + "coveralls": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.0.2.tgz", + "integrity": "sha512-Tv0LKe/MkBOilH2v7WBiTBdudg2ChfGbdXafc/s330djpF3zKOmuehTeRwjXWc7pzfj9FrDUTA7tEx6Div8NFw==", + "dev": true, + "requires": { + "growl": "~> 1.10.0", + "js-yaml": "^3.11.0", + "lcov-parse": "^0.0.10", + "log-driver": "^1.2.7", + "minimist": "^1.2.0", + "request": "^2.85.0" + } + }, + "create-hash": { + "version": "1.2.0", + "resolved": "http://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "requires": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "create-hmac": { + "version": "1.1.7", + "resolved": "http://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "requires": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "crypto-js": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.1.8.tgz", + "integrity": "sha1-cV8HC/YBTyrpkqmLOSkli3E/CNU=", + "dev": true + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "death": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/death/-/death-1.1.0.tgz", + "integrity": "sha1-AaqcQB7dknUFFEcLgmY5DGbGcxg=", + "dev": true + }, + "debug": { + "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" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "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==", + "dev": true, + "requires": { + "type-detect": "^4.0.0" + } + }, + "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 + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "diff": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.3.1.tgz", + "integrity": "sha512-MKPHZDMB0o6yHyDryUOScqZibp914ksXwAMYMTHj6KO8UeKsRYNJD3oNCKjTqZon+V488P7N/HzXF8t7ZR95ww==", + "dev": true + }, + "drbg.js": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/drbg.js/-/drbg.js-1.0.1.tgz", + "integrity": "sha1-Pja2xCs3BDgjzbwzLVjzHiRFSAs=", + "requires": { + "browserify-aes": "^1.0.6", + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4" + } + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, + "optional": true, + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "elliptic": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.1.tgz", + "integrity": "sha512-BsXLz5sqX8OHcsh7CqBMztyXARmGQ3LWPtGjJi6DiJHq5C/qvi9P3OqgswKSDftbu8+IoI/QDTAm2fFnQ9SZSQ==", + "requires": { + "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" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "escodegen": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", + "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=", + "dev": true, + "requires": { + "esprima": "^2.7.1", + "estraverse": "^1.9.1", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.2.0" + }, + "dependencies": { + "esprima": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", + "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", + "dev": true + }, + "source-map": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", + "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=", + "dev": true, + "optional": true, + "requires": { + "amdefine": ">=0.0.4" + } + } + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "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", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "ethereumjs-testrpc-sc": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/ethereumjs-testrpc-sc/-/ethereumjs-testrpc-sc-6.1.6.tgz", + "integrity": "sha512-iv2qiGBFgk9mn5Nq2enX8dG5WQ7Lk+FCqpnxfPfH4Ns8KLPwttmNOy264nh3SXDJJvcQwz/XnlLteDQVILotbg==", + "dev": true, + "requires": { + "source-map-support": "^0.5.3" + } + }, + "ethereumjs-util": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-6.0.0.tgz", + "integrity": "sha512-E3yKUyl0Fs95nvTFQZe/ZSNcofhDzUsDlA5y2uoRmf1+Ec7gpGhNCsgKkZBRh7Br5op8mJcYF/jFbmjj909+nQ==", + "requires": { + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "ethjs-util": "^0.1.6", + "keccak": "^1.0.2", + "rlp": "^2.0.0", + "safe-buffer": "^5.1.1", + "secp256k1": "^3.0.1" + } + }, + "ethjs-util": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/ethjs-util/-/ethjs-util-0.1.6.tgz", + "integrity": "sha512-CUnVOQq7gSpDHZVVrQW8ExxUETWrnrvXYvYz55wOU8Uj4VCgw56XC2B/fVqQN+f7gmrnRHSLVnFAwsCuNwji8w==", + "requires": { + "is-hex-prefixed": "1.0.0", + "strip-hex-prefix": "1.0.0" + } + }, + "evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "requires": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true + }, + "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 + }, + "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 + }, + "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 + }, + "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.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true + }, + "form-data": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", + "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "1.0.6", + "mime-types": "^2.1.12" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "dev": true + }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "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" + } + }, + "glob": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", + "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", + "dev": true, + "requires": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", + "dev": true + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "handlebars": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.11.tgz", + "integrity": "sha1-Ywo13+ApS8KB7a5v/F0yn8eYLcw=", + "dev": true, + "requires": { + "async": "^1.4.0", + "optimist": "^0.6.1", + "source-map": "^0.4.4", + "uglify-js": "^2.6" + }, + "dependencies": { + "source-map": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "dev": true, + "requires": { + "amdefine": ">=0.0.4" + } + } + } + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true + }, + "har-validator": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.0.tgz", + "integrity": "sha512-+qnmNjI4OfH2ipQ9VQOw23bBd/ibtfbVdK2fYbY4acTDqKTW/YDp9McimZdDbG8iV9fZizUqQMD5xvriB146TA==", + "dev": true, + "requires": { + "ajv": "^5.3.0", + "har-schema": "^2.0.0" + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "hash-base": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", + "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "hash.js": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.5.tgz", + "integrity": "sha512-eWI5HG9Np+eHV1KQhisXWwM+4EPPYe5dFX1UZZH7k/E3JzDEazVH+VGlZi6R94ZqImq+A3D1mCEtrFIfg/E7sA==", + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true + }, + "hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "requires": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "hosted-git-info": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", + "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", + "dev": true + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "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=" + }, + "interpret": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz", + "integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=" + }, + "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 + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "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=", + "dev": true, + "requires": { + "builtin-modules": "^1.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.0" + } + }, + "is-hex-prefixed": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-hex-prefixed/-/is-hex-prefixed-1.0.0.tgz", + "integrity": "sha1-fY035q135dEnFIkTxXPggtd39VQ=" + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "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 + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "istanbul": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-0.4.5.tgz", + "integrity": "sha1-ZcfXPUxNqE1POsMQuRj7C4Azczs=", + "dev": true, + "requires": { + "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": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", + "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", + "dev": true + } + } + }, + "js-sha3": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.3.1.tgz", + "integrity": "sha1-hhIoAhQvCChQKg0d7h2V4lO7AkM=", + "dev": true + }, + "js-yaml": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz", + "integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true, + "optional": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true + }, + "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 + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "keccak": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/keccak/-/keccak-1.4.0.tgz", + "integrity": "sha512-eZVaCpblK5formjPjeTBik7TAg+pqnDrMHIffSvi9Lh7PQgM1+hSzakUeZFCk9DVVG0dacZJuaz2ntwlzZUIBw==", + "requires": { + "bindings": "^1.2.1", + "inherits": "^2.0.3", + "nan": "^2.2.1", + "safe-buffer": "^5.1.0" + } + }, + "keccakjs": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/keccakjs/-/keccakjs-0.2.1.tgz", + "integrity": "sha1-HWM6+QfvMFu/ny+mFtVsRFYd+k0=", + "dev": true, + "requires": { + "browserify-sha3": "^0.0.1", + "sha3": "^1.1.0" + } + }, + "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" + } + }, + "lazy-cache": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", + "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", + "dev": true, + "optional": true + }, + "lcid": { + "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" + } + }, + "lcov-parse": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-0.0.10.tgz", + "integrity": "sha1-GwuP+ayceIklBYK3C3ExXZ2m2aM=", + "dev": true + }, + "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" + } + }, + "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.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + } + }, + "lodash.assign": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", + "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=", + "dev": true + }, + "log-driver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz", + "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==", + "dev": true + }, + "longest": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", + "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", + "dev": true + }, + "md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "mime-db": { + "version": "1.36.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.36.0.tgz", + "integrity": "sha512-L+xvyD9MkoYMXb1jAmzI/lWYAxAMCPvIBSWur0PZ5nOf5euahRLVqH//FKW9mWp2lkqUgYiXPgkzfMUFi4zVDw==", + "dev": true + }, + "mime-types": { + "version": "2.1.20", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.20.tgz", + "integrity": "sha512-HrkrPaP9vGuWbLK1B1FfgAkbqNjIuy4eHlIYnFi7kamZyLLrGlo2mpcx0bBmNpKqBtYtAfGbodDddIgddSJC2A==", + "dev": true, + "requires": { + "mime-db": "~1.36.0" + } + }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + }, + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + } + } + }, + "mocha": { + "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.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": "4.4.0" + }, + "dependencies": { + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "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" + } + }, + "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 + }, + "supports-color": { + "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" + } + } + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "nan": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", + "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==" + }, + "nopt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", + "dev": true, + "requires": { + "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, + "requires": { + "hosted-git-info": "^2.1.4", + "is-builtin-module": "^1.0.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "npm": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/npm/-/npm-5.10.0.tgz", + "integrity": "sha512-lvjvjgR5wG2RJ2uqak1xtZcVAWMwVOzN5HkUlUj/n8rU1f3A0fNn+7HwOzH9Lyf0Ppyu9ApgsEpHczOSnx1cwA==", + "requires": { + "JSONStream": "^1.3.2", + "abbrev": "~1.1.1", + "ansi-regex": "~3.0.0", + "ansicolors": "~0.3.2", + "ansistyles": "~0.1.3", + "aproba": "~1.2.0", + "archy": "~1.0.0", + "bin-links": "^1.1.0", + "bluebird": "~3.5.1", + "byte-size": "^4.0.2", + "cacache": "^10.0.4", + "call-limit": "~1.1.0", + "chownr": "~1.0.1", + "cli-columns": "^3.1.2", + "cli-table2": "~0.2.0", + "cmd-shim": "~2.0.2", + "columnify": "~1.5.4", + "config-chain": "~1.1.11", + "debuglog": "*", + "detect-indent": "~5.0.0", + "detect-newline": "^2.1.0", + "dezalgo": "~1.0.3", + "editor": "~1.0.0", + "find-npm-prefix": "^1.0.2", + "fs-vacuum": "~1.2.10", + "fs-write-stream-atomic": "~1.0.10", + "gentle-fs": "^2.0.1", + "glob": "~7.1.2", + "graceful-fs": "~4.1.11", + "has-unicode": "~2.0.1", + "hosted-git-info": "^2.6.0", + "iferr": "~0.1.5", + "imurmurhash": "*", + "inflight": "~1.0.6", + "inherits": "~2.0.3", + "ini": "^1.3.5", + "init-package-json": "^1.10.3", + "is-cidr": "~1.0.0", + "json-parse-better-errors": "^1.0.2", + "lazy-property": "~1.0.0", + "libcipm": "^1.6.2", + "libnpx": "^10.2.0", + "lock-verify": "^2.0.2", + "lockfile": "^1.0.4", + "lodash._baseindexof": "*", + "lodash._baseuniq": "~4.6.0", + "lodash._bindcallback": "*", + "lodash._cacheindexof": "*", + "lodash._createcache": "*", + "lodash._getnative": "*", + "lodash.clonedeep": "~4.5.0", + "lodash.restparam": "*", + "lodash.union": "~4.6.0", + "lodash.uniq": "~4.5.0", + "lodash.without": "~4.4.0", + "lru-cache": "^4.1.2", + "meant": "~1.0.1", + "mississippi": "^3.0.0", + "mkdirp": "~0.5.1", + "move-concurrently": "^1.0.1", + "node-gyp": "^3.6.2", + "nopt": "~4.0.1", + "normalize-package-data": "~2.4.0", + "npm-audit-report": "^1.0.9", + "npm-cache-filename": "~1.0.2", + "npm-install-checks": "~3.0.0", + "npm-lifecycle": "^2.0.1", + "npm-package-arg": "^6.1.0", + "npm-packlist": "~1.1.10", + "npm-profile": "^3.0.1", + "npm-registry-client": "^8.5.1", + "npm-registry-fetch": "^1.1.0", + "npm-user-validate": "~1.0.0", + "npmlog": "~4.1.2", + "once": "~1.4.0", + "opener": "~1.4.3", + "osenv": "^0.1.5", + "pacote": "^7.6.1", + "path-is-inside": "~1.0.2", + "promise-inflight": "~1.0.1", + "qrcode-terminal": "^0.12.0", + "query-string": "^6.1.0", + "qw": "~1.0.1", + "read": "~1.0.7", + "read-cmd-shim": "~1.0.1", + "read-installed": "~4.0.3", + "read-package-json": "^2.0.13", + "read-package-tree": "^5.2.1", + "readable-stream": "^2.3.6", + "readdir-scoped-modules": "*", + "request": "^2.85.0", + "retry": "^0.12.0", + "rimraf": "~2.6.2", + "safe-buffer": "^5.1.2", + "semver": "^5.5.0", + "sha": "~2.0.1", + "slide": "~1.1.6", + "sorted-object": "~2.0.1", + "sorted-union-stream": "~2.1.3", + "ssri": "^5.3.0", + "strip-ansi": "~4.0.0", + "tar": "^4.4.2", + "text-table": "~0.2.0", + "tiny-relative-date": "^1.3.0", + "uid-number": "0.0.6", + "umask": "~1.1.0", + "unique-filename": "~1.1.0", + "unpipe": "~1.0.0", + "update-notifier": "^2.5.0", + "uuid": "^3.2.1", + "validate-npm-package-license": "^3.0.3", + "validate-npm-package-name": "~3.0.0", + "which": "~1.3.0", + "worker-farm": "^1.6.0", + "wrappy": "~1.0.2", + "write-file-atomic": "^2.3.0" + }, + "dependencies": { + "JSONStream": { + "version": "1.3.2", + "bundled": true, + "requires": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + }, + "dependencies": { + "jsonparse": { + "version": "1.3.1", + "bundled": true + }, + "through": { + "version": "2.3.8", + "bundled": true + } + } + }, + "abbrev": { + "version": "1.1.1", + "bundled": true + }, + "ansi-regex": { + "version": "3.0.0", + "bundled": true + }, + "ansicolors": { + "version": "0.3.2", + "bundled": true + }, + "ansistyles": { + "version": "0.1.3", + "bundled": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true + }, + "archy": { + "version": "1.0.0", + "bundled": true + }, + "bin-links": { + "version": "1.1.0", + "bundled": true, + "requires": { + "bluebird": "^3.5.0", + "cmd-shim": "^2.0.2", + "fs-write-stream-atomic": "^1.0.10", + "gentle-fs": "^2.0.0", + "graceful-fs": "^4.1.11", + "slide": "^1.1.6" + } + }, + "bluebird": { + "version": "3.5.1", + "bundled": true + }, + "byte-size": { + "version": "4.0.2", + "bundled": true + }, + "cacache": { + "version": "10.0.4", + "bundled": true, + "requires": { + "bluebird": "^3.5.1", + "chownr": "^1.0.1", + "glob": "^7.1.2", + "graceful-fs": "^4.1.11", + "lru-cache": "^4.1.1", + "mississippi": "^2.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.2", + "ssri": "^5.2.4", + "unique-filename": "^1.1.0", + "y18n": "^4.0.0" + }, + "dependencies": { + "mississippi": { + "version": "2.0.0", + "bundled": true, + "requires": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^2.0.1", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + }, + "dependencies": { + "concat-stream": { + "version": "1.6.1", + "bundled": true, + "requires": { + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + }, + "dependencies": { + "typedarray": { + "version": "0.0.6", + "bundled": true + } + } + }, + "duplexify": { + "version": "3.5.4", + "bundled": true, + "requires": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + }, + "dependencies": { + "stream-shift": { + "version": "1.0.0", + "bundled": true + } + } + }, + "end-of-stream": { + "version": "1.4.1", + "bundled": true, + "requires": { + "once": "^1.4.0" + } + }, + "flush-write-stream": { + "version": "1.0.2", + "bundled": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.4" + } + }, + "from2": { + "version": "2.3.0", + "bundled": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + } + }, + "parallel-transform": { + "version": "1.1.0", + "bundled": true, + "requires": { + "cyclist": "~0.2.2", + "inherits": "^2.0.3", + "readable-stream": "^2.1.5" + }, + "dependencies": { + "cyclist": { + "version": "0.2.2", + "bundled": true + } + } + }, + "pump": { + "version": "2.0.1", + "bundled": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "pumpify": { + "version": "1.4.0", + "bundled": true, + "requires": { + "duplexify": "^3.5.3", + "inherits": "^2.0.3", + "pump": "^2.0.0" + } + }, + "stream-each": { + "version": "1.2.2", + "bundled": true, + "requires": { + "end-of-stream": "^1.1.0", + "stream-shift": "^1.0.0" + }, + "dependencies": { + "stream-shift": { + "version": "1.0.0", + "bundled": true + } + } + }, + "through2": { + "version": "2.0.3", + "bundled": true, + "requires": { + "readable-stream": "^2.1.5", + "xtend": "~4.0.1" + }, + "dependencies": { + "xtend": { + "version": "4.0.1", + "bundled": true + } + } + } + } + }, + "y18n": { + "version": "4.0.0", + "bundled": true + } + } + }, + "call-limit": { + "version": "1.1.0", + "bundled": true + }, + "chownr": { + "version": "1.0.1", + "bundled": true + }, + "cli-columns": { + "version": "3.1.2", + "bundled": true, + "requires": { + "string-width": "^2.0.0", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "string-width": { + "version": "2.1.1", + "bundled": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "2.0.0", + "bundled": true + }, + "strip-ansi": { + "version": "4.0.0", + "bundled": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "requires": { + "ansi-regex": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "bundled": true + } + } + } + } + }, + "cli-table2": { + "version": "0.2.0", + "bundled": true, + "requires": { + "colors": "^1.1.2", + "lodash": "^3.10.1", + "string-width": "^1.0.1" + }, + "dependencies": { + "colors": { + "version": "1.1.2", + "bundled": true, + "optional": true + }, + "lodash": { + "version": "3.10.1", + "bundled": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + }, + "dependencies": { + "code-point-at": { + "version": "1.1.0", + "bundled": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "requires": { + "number-is-nan": "^1.0.0" + }, + "dependencies": { + "number-is-nan": { + "version": "1.0.1", + "bundled": true + } + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "requires": { + "ansi-regex": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "bundled": true + } + } + } + } + } + } + }, + "cmd-shim": { + "version": "2.0.2", + "bundled": true, + "requires": { + "graceful-fs": "^4.1.2", + "mkdirp": "~0.5.0" + } + }, + "columnify": { + "version": "1.5.4", + "bundled": true, + "requires": { + "strip-ansi": "^3.0.0", + "wcwidth": "^1.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "requires": { + "ansi-regex": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "bundled": true + } + } + }, + "wcwidth": { + "version": "1.0.1", + "bundled": true, + "requires": { + "defaults": "^1.0.3" + }, + "dependencies": { + "defaults": { + "version": "1.0.3", + "bundled": true, + "requires": { + "clone": "^1.0.2" + }, + "dependencies": { + "clone": { + "version": "1.0.2", + "bundled": true + } + } + } + } + } + } + }, + "config-chain": { + "version": "1.1.11", + "bundled": true, + "requires": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + }, + "dependencies": { + "proto-list": { + "version": "1.2.4", + "bundled": true + } + } + }, + "debuglog": { + "version": "1.0.1", + "bundled": true + }, + "detect-indent": { + "version": "5.0.0", + "bundled": true + }, + "detect-newline": { + "version": "2.1.0", + "bundled": true + }, + "dezalgo": { + "version": "1.0.3", + "bundled": true, + "requires": { + "asap": "^2.0.0", + "wrappy": "1" + }, + "dependencies": { + "asap": { + "version": "2.0.5", + "bundled": true + } + } + }, + "editor": { + "version": "1.0.0", + "bundled": true + }, + "find-npm-prefix": { + "version": "1.0.2", + "bundled": true + }, + "fs-vacuum": { + "version": "1.2.10", + "bundled": true, + "requires": { + "graceful-fs": "^4.1.2", + "path-is-inside": "^1.0.1", + "rimraf": "^2.5.2" + } + }, + "fs-write-stream-atomic": { + "version": "1.0.10", + "bundled": true, + "requires": { + "graceful-fs": "^4.1.2", + "iferr": "^0.1.5", + "imurmurhash": "^0.1.4", + "readable-stream": "1 || 2" + } + }, + "gentle-fs": { + "version": "2.0.1", + "bundled": true, + "requires": { + "aproba": "^1.1.2", + "fs-vacuum": "^1.2.10", + "graceful-fs": "^4.1.11", + "iferr": "^0.1.5", + "mkdirp": "^0.5.1", + "path-is-inside": "^1.0.2", + "read-cmd-shim": "^1.0.1", + "slide": "^1.1.6" + } + }, + "glob": { + "version": "7.1.2", + "bundled": true, + "requires": { + "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" + }, + "dependencies": { + "fs.realpath": { + "version": "1.0.0", + "bundled": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "requires": { + "brace-expansion": "^1.1.7" + }, + "dependencies": { + "brace-expansion": { + "version": "1.1.8", + "bundled": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + }, + "dependencies": { + "balanced-match": { + "version": "1.0.0", + "bundled": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true + } + } + } + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true + } + } + }, + "graceful-fs": { + "version": "4.1.11", + "bundled": true + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true + }, + "hosted-git-info": { + "version": "2.6.0", + "bundled": true + }, + "iferr": { + "version": "0.1.5", + "bundled": true + }, + "imurmurhash": { + "version": "0.1.4", + "bundled": true + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true + }, + "ini": { + "version": "1.3.5", + "bundled": true + }, + "init-package-json": { + "version": "1.10.3", + "bundled": true, + "requires": { + "glob": "^7.1.1", + "npm-package-arg": "^4.0.0 || ^5.0.0 || ^6.0.0", + "promzard": "^0.3.0", + "read": "~1.0.1", + "read-package-json": "1 || 2", + "semver": "2.x || 3.x || 4 || 5", + "validate-npm-package-license": "^3.0.1", + "validate-npm-package-name": "^3.0.0" + }, + "dependencies": { + "promzard": { + "version": "0.3.0", + "bundled": true, + "requires": { + "read": "1" + } + } + } + }, + "is-cidr": { + "version": "1.0.0", + "bundled": true, + "requires": { + "cidr-regex": "1.0.6" + }, + "dependencies": { + "cidr-regex": { + "version": "1.0.6", + "bundled": true + } + } + }, + "json-parse-better-errors": { + "version": "1.0.2", + "bundled": true + }, + "lazy-property": { + "version": "1.0.0", + "bundled": true + }, + "libcipm": { + "version": "1.6.2", + "bundled": true, + "requires": { + "bin-links": "^1.1.0", + "bluebird": "^3.5.1", + "find-npm-prefix": "^1.0.2", + "graceful-fs": "^4.1.11", + "lock-verify": "^2.0.0", + "npm-lifecycle": "^2.0.0", + "npm-logical-tree": "^1.2.1", + "npm-package-arg": "^6.0.0", + "pacote": "^7.5.1", + "protoduck": "^5.0.0", + "read-package-json": "^2.0.12", + "rimraf": "^2.6.2", + "worker-farm": "^1.5.4" + }, + "dependencies": { + "lock-verify": { + "version": "2.0.1", + "bundled": true, + "requires": { + "npm-package-arg": "^5.1.2", + "semver": "^5.4.1" + }, + "dependencies": { + "npm-package-arg": { + "version": "5.1.2", + "bundled": true, + "requires": { + "hosted-git-info": "^2.4.2", + "osenv": "^0.1.4", + "semver": "^5.1.0", + "validate-npm-package-name": "^3.0.0" + } + } + } + }, + "npm-logical-tree": { + "version": "1.2.1", + "bundled": true + }, + "protoduck": { + "version": "5.0.0", + "bundled": true, + "requires": { + "genfun": "^4.0.1" + }, + "dependencies": { + "genfun": { + "version": "4.0.1", + "bundled": true + } + } + } + } + }, + "libnpx": { + "version": "10.2.0", + "bundled": true, + "requires": { + "dotenv": "^5.0.1", + "npm-package-arg": "^6.0.0", + "rimraf": "^2.6.2", + "safe-buffer": "^5.1.0", + "update-notifier": "^2.3.0", + "which": "^1.3.0", + "y18n": "^4.0.0", + "yargs": "^11.0.0" + }, + "dependencies": { + "dotenv": { + "version": "5.0.1", + "bundled": true + }, + "y18n": { + "version": "4.0.0", + "bundled": true + }, + "yargs": { + "version": "11.0.0", + "bundled": true, + "requires": { + "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" + }, + "dependencies": { + "cliui": { + "version": "4.1.0", + "bundled": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + }, + "dependencies": { + "wrap-ansi": { + "version": "2.1.0", + "bundled": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "string-width": { + "version": "1.0.2", + "bundled": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + }, + "dependencies": { + "code-point-at": { + "version": "1.1.0", + "bundled": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "requires": { + "number-is-nan": "^1.0.0" + }, + "dependencies": { + "number-is-nan": { + "version": "1.0.1", + "bundled": true + } + } + } + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "requires": { + "ansi-regex": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "bundled": true + } + } + } + } + } + } + }, + "decamelize": { + "version": "1.2.0", + "bundled": true + }, + "find-up": { + "version": "2.1.0", + "bundled": true, + "requires": { + "locate-path": "^2.0.0" + }, + "dependencies": { + "locate-path": { + "version": "2.0.0", + "bundled": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + }, + "dependencies": { + "p-locate": { + "version": "2.0.0", + "bundled": true, + "requires": { + "p-limit": "^1.1.0" + }, + "dependencies": { + "p-limit": { + "version": "1.2.0", + "bundled": true, + "requires": { + "p-try": "^1.0.0" + }, + "dependencies": { + "p-try": { + "version": "1.0.0", + "bundled": true + } + } + } + } + }, + "path-exists": { + "version": "3.0.0", + "bundled": true + } + } + } + } + }, + "get-caller-file": { + "version": "1.0.2", + "bundled": true + }, + "os-locale": { + "version": "2.1.0", + "bundled": true, + "requires": { + "execa": "^0.7.0", + "lcid": "^1.0.0", + "mem": "^1.1.0" + }, + "dependencies": { + "execa": { + "version": "0.7.0", + "bundled": true, + "requires": { + "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": { + "version": "5.1.0", + "bundled": true, + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "dependencies": { + "shebang-command": { + "version": "1.2.0", + "bundled": true, + "requires": { + "shebang-regex": "^1.0.0" + }, + "dependencies": { + "shebang-regex": { + "version": "1.0.0", + "bundled": true + } + } + } + } + }, + "get-stream": { + "version": "3.0.0", + "bundled": true + }, + "is-stream": { + "version": "1.1.0", + "bundled": true + }, + "npm-run-path": { + "version": "2.0.2", + "bundled": true, + "requires": { + "path-key": "^2.0.0" + }, + "dependencies": { + "path-key": { + "version": "2.0.1", + "bundled": true + } + } + }, + "p-finally": { + "version": "1.0.0", + "bundled": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true + }, + "strip-eof": { + "version": "1.0.0", + "bundled": true + } + } + }, + "lcid": { + "version": "1.0.0", + "bundled": true, + "requires": { + "invert-kv": "^1.0.0" + }, + "dependencies": { + "invert-kv": { + "version": "1.0.0", + "bundled": true + } + } + }, + "mem": { + "version": "1.1.0", + "bundled": true, + "requires": { + "mimic-fn": "^1.0.0" + }, + "dependencies": { + "mimic-fn": { + "version": "1.2.0", + "bundled": true + } + } + } + } + }, + "require-directory": { + "version": "2.1.1", + "bundled": true + }, + "require-main-filename": { + "version": "1.0.1", + "bundled": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true + }, + "string-width": { + "version": "2.1.1", + "bundled": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "2.0.0", + "bundled": true + } + } + }, + "which-module": { + "version": "2.0.0", + "bundled": true + }, + "y18n": { + "version": "3.2.1", + "bundled": true + }, + "yargs-parser": { + "version": "9.0.2", + "bundled": true, + "requires": { + "camelcase": "^4.1.0" + }, + "dependencies": { + "camelcase": { + "version": "4.1.0", + "bundled": true + } + } + } + } + } + } + }, + "lock-verify": { + "version": "2.0.2", + "bundled": true, + "requires": { + "npm-package-arg": "^5.1.2 || 6", + "semver": "^5.4.1" + } + }, + "lockfile": { + "version": "1.0.4", + "bundled": true, + "requires": { + "signal-exit": "^3.0.2" + }, + "dependencies": { + "signal-exit": { + "version": "3.0.2", + "bundled": true + } + } + }, + "lodash._baseindexof": { + "version": "3.1.0", + "bundled": true + }, + "lodash._baseuniq": { + "version": "4.6.0", + "bundled": true, + "requires": { + "lodash._createset": "~4.0.0", + "lodash._root": "~3.0.0" + }, + "dependencies": { + "lodash._createset": { + "version": "4.0.3", + "bundled": true + }, + "lodash._root": { + "version": "3.0.1", + "bundled": true + } + } + }, + "lodash._bindcallback": { + "version": "3.0.1", + "bundled": true + }, + "lodash._cacheindexof": { + "version": "3.0.2", + "bundled": true + }, + "lodash._createcache": { + "version": "3.1.2", + "bundled": true, + "requires": { + "lodash._getnative": "^3.0.0" + } + }, + "lodash._getnative": { + "version": "3.9.1", + "bundled": true + }, + "lodash.clonedeep": { + "version": "4.5.0", + "bundled": true + }, + "lodash.restparam": { + "version": "3.6.1", + "bundled": true + }, + "lodash.union": { + "version": "4.6.0", + "bundled": true + }, + "lodash.uniq": { + "version": "4.5.0", + "bundled": true + }, + "lodash.without": { + "version": "4.4.0", + "bundled": true + }, + "lru-cache": { + "version": "4.1.2", + "bundled": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + }, + "dependencies": { + "pseudomap": { + "version": "1.0.2", + "bundled": true + }, + "yallist": { + "version": "2.1.2", + "bundled": true + } + } + }, + "meant": { + "version": "1.0.1", + "bundled": true + }, + "mississippi": { + "version": "3.0.0", + "bundled": true, + "requires": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^3.0.0", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + }, + "dependencies": { + "concat-stream": { + "version": "1.6.1", + "bundled": true, + "requires": { + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + }, + "dependencies": { + "typedarray": { + "version": "0.0.6", + "bundled": true + } + } + }, + "duplexify": { + "version": "3.5.4", + "bundled": true, + "requires": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + }, + "dependencies": { + "stream-shift": { + "version": "1.0.0", + "bundled": true + } + } + }, + "end-of-stream": { + "version": "1.4.1", + "bundled": true, + "requires": { + "once": "^1.4.0" + } + }, + "flush-write-stream": { + "version": "1.0.2", + "bundled": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.4" + } + }, + "from2": { + "version": "2.3.0", + "bundled": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + } + }, + "parallel-transform": { + "version": "1.1.0", + "bundled": true, + "requires": { + "cyclist": "~0.2.2", + "inherits": "^2.0.3", + "readable-stream": "^2.1.5" + }, + "dependencies": { + "cyclist": { + "version": "0.2.2", + "bundled": true + } + } + }, + "pump": { + "version": "3.0.0", + "bundled": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "pumpify": { + "version": "1.4.0", + "bundled": true, + "requires": { + "duplexify": "^3.5.3", + "inherits": "^2.0.3", + "pump": "^2.0.0" + }, + "dependencies": { + "pump": { + "version": "2.0.1", + "bundled": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } + } + }, + "stream-each": { + "version": "1.2.2", + "bundled": true, + "requires": { + "end-of-stream": "^1.1.0", + "stream-shift": "^1.0.0" + }, + "dependencies": { + "stream-shift": { + "version": "1.0.0", + "bundled": true + } + } + }, + "through2": { + "version": "2.0.3", + "bundled": true, + "requires": { + "readable-stream": "^2.1.5", + "xtend": "~4.0.1" + }, + "dependencies": { + "xtend": { + "version": "4.0.1", + "bundled": true + } + } + } + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "requires": { + "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "bundled": true + } + } + }, + "move-concurrently": { + "version": "1.0.1", + "bundled": true, + "requires": { + "aproba": "^1.1.1", + "copy-concurrently": "^1.0.0", + "fs-write-stream-atomic": "^1.0.8", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.3" + }, + "dependencies": { + "copy-concurrently": { + "version": "1.0.5", + "bundled": true, + "requires": { + "aproba": "^1.1.1", + "fs-write-stream-atomic": "^1.0.8", + "iferr": "^0.1.5", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.0" + } + }, + "run-queue": { + "version": "1.0.3", + "bundled": true, + "requires": { + "aproba": "^1.1.1" + } + } + } + }, + "node-gyp": { + "version": "3.6.2", + "bundled": true, + "requires": { + "fstream": "^1.0.0", + "glob": "^7.0.3", + "graceful-fs": "^4.1.2", + "minimatch": "^3.0.2", + "mkdirp": "^0.5.0", + "nopt": "2 || 3", + "npmlog": "0 || 1 || 2 || 3 || 4", + "osenv": "0", + "request": "2", + "rimraf": "2", + "semver": "~5.3.0", + "tar": "^2.0.0", + "which": "1" + }, + "dependencies": { + "fstream": { + "version": "1.0.11", + "bundled": true, + "requires": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + } + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "requires": { + "brace-expansion": "^1.1.7" + }, + "dependencies": { + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + }, + "dependencies": { + "balanced-match": { + "version": "1.0.0", + "bundled": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true + } + } + } + } + }, + "nopt": { + "version": "3.0.6", + "bundled": true, + "requires": { + "abbrev": "1" + } + }, + "semver": { + "version": "5.3.0", + "bundled": true + }, + "tar": { + "version": "2.2.1", + "bundled": true, + "requires": { + "block-stream": "*", + "fstream": "^1.0.2", + "inherits": "2" + }, + "dependencies": { + "block-stream": { + "version": "0.0.9", + "bundled": true, + "requires": { + "inherits": "~2.0.0" + } + } + } + } + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "normalize-package-data": { + "version": "2.4.0", + "bundled": true, + "requires": { + "hosted-git-info": "^2.1.4", + "is-builtin-module": "^1.0.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + }, + "dependencies": { + "is-builtin-module": { + "version": "1.0.0", + "bundled": true, + "requires": { + "builtin-modules": "^1.0.0" + }, + "dependencies": { + "builtin-modules": { + "version": "1.1.1", + "bundled": true + } + } + } + } + }, + "npm-audit-report": { + "version": "1.0.9", + "bundled": true, + "requires": { + "cli-table2": "^0.2.0", + "console-control-strings": "^1.1.0" + }, + "dependencies": { + "console-control-strings": { + "version": "1.1.0", + "bundled": true + } + } + }, + "npm-cache-filename": { + "version": "1.0.2", + "bundled": true + }, + "npm-install-checks": { + "version": "3.0.0", + "bundled": true, + "requires": { + "semver": "^2.3.0 || 3.x || 4 || 5" + } + }, + "npm-lifecycle": { + "version": "2.0.1", + "bundled": true, + "requires": { + "byline": "^5.0.0", + "graceful-fs": "^4.1.11", + "node-gyp": "^3.6.2", + "resolve-from": "^4.0.0", + "slide": "^1.1.6", + "uid-number": "0.0.6", + "umask": "^1.1.0", + "which": "^1.3.0" + }, + "dependencies": { + "byline": { + "version": "5.0.0", + "bundled": true + }, + "resolve-from": { + "version": "4.0.0", + "bundled": true + } + } + }, + "npm-package-arg": { + "version": "6.1.0", + "bundled": true, + "requires": { + "hosted-git-info": "^2.6.0", + "osenv": "^0.1.5", + "semver": "^5.5.0", + "validate-npm-package-name": "^3.0.0" + } + }, + "npm-packlist": { + "version": "1.1.10", + "bundled": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + }, + "dependencies": { + "ignore-walk": { + "version": "3.0.1", + "bundled": true, + "requires": { + "minimatch": "^3.0.4" + }, + "dependencies": { + "minimatch": { + "version": "3.0.4", + "bundled": true, + "requires": { + "brace-expansion": "^1.1.7" + }, + "dependencies": { + "brace-expansion": { + "version": "1.1.8", + "bundled": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + }, + "dependencies": { + "balanced-match": { + "version": "1.0.0", + "bundled": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true + } + } + } + } + } + } + }, + "npm-bundled": { + "version": "1.0.3", + "bundled": true + } + } + }, + "npm-profile": { + "version": "3.0.1", + "bundled": true, + "requires": { + "aproba": "^1.1.2", + "make-fetch-happen": "^2.5.0" + }, + "dependencies": { + "make-fetch-happen": { + "version": "2.6.0", + "bundled": true, + "requires": { + "agentkeepalive": "^3.3.0", + "cacache": "^10.0.0", + "http-cache-semantics": "^3.8.0", + "http-proxy-agent": "^2.0.0", + "https-proxy-agent": "^2.1.0", + "lru-cache": "^4.1.1", + "mississippi": "^1.2.0", + "node-fetch-npm": "^2.0.2", + "promise-retry": "^1.1.1", + "socks-proxy-agent": "^3.0.1", + "ssri": "^5.0.0" + }, + "dependencies": { + "agentkeepalive": { + "version": "3.3.0", + "bundled": true, + "requires": { + "humanize-ms": "^1.2.1" + }, + "dependencies": { + "humanize-ms": { + "version": "1.2.1", + "bundled": true, + "requires": { + "ms": "^2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.1.1", + "bundled": true + } + } + } + } + }, + "http-cache-semantics": { + "version": "3.8.1", + "bundled": true + }, + "http-proxy-agent": { + "version": "2.0.0", + "bundled": true, + "requires": { + "agent-base": "4", + "debug": "2" + }, + "dependencies": { + "agent-base": { + "version": "4.2.0", + "bundled": true, + "requires": { + "es6-promisify": "^5.0.0" + }, + "dependencies": { + "es6-promisify": { + "version": "5.0.0", + "bundled": true, + "requires": { + "es6-promise": "^4.0.3" + }, + "dependencies": { + "es6-promise": { + "version": "4.2.4", + "bundled": true + } + } + } + } + }, + "debug": { + "version": "2.6.9", + "bundled": true, + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "bundled": true + } + } + } + } + }, + "https-proxy-agent": { + "version": "2.1.1", + "bundled": true, + "requires": { + "agent-base": "^4.1.0", + "debug": "^3.1.0" + }, + "dependencies": { + "agent-base": { + "version": "4.2.0", + "bundled": true, + "requires": { + "es6-promisify": "^5.0.0" + }, + "dependencies": { + "es6-promisify": { + "version": "5.0.0", + "bundled": true, + "requires": { + "es6-promise": "^4.0.3" + }, + "dependencies": { + "es6-promise": { + "version": "4.2.4", + "bundled": true + } + } + } + } + }, + "debug": { + "version": "3.1.0", + "bundled": true, + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "bundled": true + } + } + } + } + }, + "mississippi": { + "version": "1.3.1", + "bundled": true, + "requires": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^1.0.0", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + }, + "dependencies": { + "concat-stream": { + "version": "1.6.0", + "bundled": true, + "requires": { + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + }, + "dependencies": { + "typedarray": { + "version": "0.0.6", + "bundled": true + } + } + }, + "duplexify": { + "version": "3.5.3", + "bundled": true, + "requires": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + }, + "dependencies": { + "stream-shift": { + "version": "1.0.0", + "bundled": true + } + } + }, + "end-of-stream": { + "version": "1.4.1", + "bundled": true, + "requires": { + "once": "^1.4.0" + } + }, + "flush-write-stream": { + "version": "1.0.2", + "bundled": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.4" + } + }, + "from2": { + "version": "2.3.0", + "bundled": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + } + }, + "parallel-transform": { + "version": "1.1.0", + "bundled": true, + "requires": { + "cyclist": "~0.2.2", + "inherits": "^2.0.3", + "readable-stream": "^2.1.5" + }, + "dependencies": { + "cyclist": { + "version": "0.2.2", + "bundled": true + } + } + }, + "pump": { + "version": "1.0.3", + "bundled": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "pumpify": { + "version": "1.4.0", + "bundled": true, + "requires": { + "duplexify": "^3.5.3", + "inherits": "^2.0.3", + "pump": "^2.0.0" + }, + "dependencies": { + "pump": { + "version": "2.0.1", + "bundled": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } + } + }, + "stream-each": { + "version": "1.2.2", + "bundled": true, + "requires": { + "end-of-stream": "^1.1.0", + "stream-shift": "^1.0.0" + }, + "dependencies": { + "stream-shift": { + "version": "1.0.0", + "bundled": true + } + } + }, + "through2": { + "version": "2.0.3", + "bundled": true, + "requires": { + "readable-stream": "^2.1.5", + "xtend": "~4.0.1" + }, + "dependencies": { + "xtend": { + "version": "4.0.1", + "bundled": true + } + } + } + } + }, + "node-fetch-npm": { + "version": "2.0.2", + "bundled": true, + "requires": { + "encoding": "^0.1.11", + "json-parse-better-errors": "^1.0.0", + "safe-buffer": "^5.1.1" + }, + "dependencies": { + "encoding": { + "version": "0.1.12", + "bundled": true, + "requires": { + "iconv-lite": "~0.4.13" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.19", + "bundled": true + } + } + }, + "json-parse-better-errors": { + "version": "1.0.1", + "bundled": true + } + } + }, + "promise-retry": { + "version": "1.1.1", + "bundled": true, + "requires": { + "err-code": "^1.0.0", + "retry": "^0.10.0" + }, + "dependencies": { + "err-code": { + "version": "1.1.2", + "bundled": true + }, + "retry": { + "version": "0.10.1", + "bundled": true + } + } + }, + "socks-proxy-agent": { + "version": "3.0.1", + "bundled": true, + "requires": { + "agent-base": "^4.1.0", + "socks": "^1.1.10" + }, + "dependencies": { + "agent-base": { + "version": "4.2.0", + "bundled": true, + "requires": { + "es6-promisify": "^5.0.0" + }, + "dependencies": { + "es6-promisify": { + "version": "5.0.0", + "bundled": true, + "requires": { + "es6-promise": "^4.0.3" + }, + "dependencies": { + "es6-promise": { + "version": "4.2.4", + "bundled": true + } + } + } + } + }, + "socks": { + "version": "1.1.10", + "bundled": true, + "requires": { + "ip": "^1.1.4", + "smart-buffer": "^1.0.13" + }, + "dependencies": { + "ip": { + "version": "1.1.5", + "bundled": true + }, + "smart-buffer": { + "version": "1.1.15", + "bundled": true + } + } + } + } + } + } + } + } + }, + "npm-registry-client": { + "version": "8.5.1", + "bundled": true, + "requires": { + "concat-stream": "^1.5.2", + "graceful-fs": "^4.1.6", + "normalize-package-data": "~1.0.1 || ^2.0.0", + "npm-package-arg": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0", + "npmlog": "2 || ^3.1.0 || ^4.0.0", + "once": "^1.3.3", + "request": "^2.74.0", + "retry": "^0.10.0", + "safe-buffer": "^5.1.1", + "semver": "2 >=2.2.1 || 3.x || 4 || 5", + "slide": "^1.1.3", + "ssri": "^5.2.4" + }, + "dependencies": { + "concat-stream": { + "version": "1.6.1", + "bundled": true, + "requires": { + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + }, + "dependencies": { + "typedarray": { + "version": "0.0.6", + "bundled": true + } + } + }, + "retry": { + "version": "0.10.1", + "bundled": true + } + } + }, + "npm-registry-fetch": { + "version": "1.1.0", + "bundled": true, + "requires": { + "bluebird": "^3.5.1", + "figgy-pudding": "^2.0.1", + "lru-cache": "^4.1.2", + "make-fetch-happen": "^3.0.0", + "npm-package-arg": "^6.0.0", + "safe-buffer": "^5.1.1" + }, + "dependencies": { + "figgy-pudding": { + "version": "2.0.1", + "bundled": true + }, + "make-fetch-happen": { + "version": "3.0.0", + "bundled": true, + "requires": { + "agentkeepalive": "^3.4.1", + "cacache": "^10.0.4", + "http-cache-semantics": "^3.8.1", + "http-proxy-agent": "^2.1.0", + "https-proxy-agent": "^2.2.0", + "lru-cache": "^4.1.2", + "mississippi": "^3.0.0", + "node-fetch-npm": "^2.0.2", + "promise-retry": "^1.1.1", + "socks-proxy-agent": "^3.0.1", + "ssri": "^5.2.4" + }, + "dependencies": { + "agentkeepalive": { + "version": "3.4.1", + "bundled": true, + "requires": { + "humanize-ms": "^1.2.1" + }, + "dependencies": { + "humanize-ms": { + "version": "1.2.1", + "bundled": true, + "requires": { + "ms": "^2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.1.1", + "bundled": true + } + } + } + } + }, + "http-cache-semantics": { + "version": "3.8.1", + "bundled": true + }, + "http-proxy-agent": { + "version": "2.1.0", + "bundled": true, + "requires": { + "agent-base": "4", + "debug": "3.1.0" + }, + "dependencies": { + "agent-base": { + "version": "4.2.0", + "bundled": true, + "requires": { + "es6-promisify": "^5.0.0" + }, + "dependencies": { + "es6-promisify": { + "version": "5.0.0", + "bundled": true, + "requires": { + "es6-promise": "^4.0.3" + }, + "dependencies": { + "es6-promise": { + "version": "4.2.4", + "bundled": true + } + } + } + } + }, + "debug": { + "version": "3.1.0", + "bundled": true, + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "bundled": true + } + } + } + } + }, + "https-proxy-agent": { + "version": "2.2.1", + "bundled": true, + "requires": { + "agent-base": "^4.1.0", + "debug": "^3.1.0" + }, + "dependencies": { + "agent-base": { + "version": "4.2.0", + "bundled": true, + "requires": { + "es6-promisify": "^5.0.0" + }, + "dependencies": { + "es6-promisify": { + "version": "5.0.0", + "bundled": true, + "requires": { + "es6-promise": "^4.0.3" + }, + "dependencies": { + "es6-promise": { + "version": "4.2.4", + "bundled": true + } + } + } + } + }, + "debug": { + "version": "3.1.0", + "bundled": true, + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "bundled": true + } + } + } + } + }, + "node-fetch-npm": { + "version": "2.0.2", + "bundled": true, + "requires": { + "encoding": "^0.1.11", + "json-parse-better-errors": "^1.0.0", + "safe-buffer": "^5.1.1" + }, + "dependencies": { + "encoding": { + "version": "0.1.12", + "bundled": true, + "requires": { + "iconv-lite": "~0.4.13" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.21", + "bundled": true, + "requires": { + "safer-buffer": "^2.1.0" + }, + "dependencies": { + "safer-buffer": { + "version": "2.1.2", + "bundled": true + } + } + } + } + } + } + }, + "promise-retry": { + "version": "1.1.1", + "bundled": true, + "requires": { + "err-code": "^1.0.0", + "retry": "^0.10.0" + }, + "dependencies": { + "err-code": { + "version": "1.1.2", + "bundled": true + }, + "retry": { + "version": "0.10.1", + "bundled": true + } + } + }, + "socks-proxy-agent": { + "version": "3.0.1", + "bundled": true, + "requires": { + "agent-base": "^4.1.0", + "socks": "^1.1.10" + }, + "dependencies": { + "agent-base": { + "version": "4.2.0", + "bundled": true, + "requires": { + "es6-promisify": "^5.0.0" + }, + "dependencies": { + "es6-promisify": { + "version": "5.0.0", + "bundled": true, + "requires": { + "es6-promise": "^4.0.3" + }, + "dependencies": { + "es6-promise": { + "version": "4.2.4", + "bundled": true + } + } + } + } + }, + "socks": { + "version": "1.1.10", + "bundled": true, + "requires": { + "ip": "^1.1.4", + "smart-buffer": "^1.0.13" + }, + "dependencies": { + "ip": { + "version": "1.1.5", + "bundled": true + }, + "smart-buffer": { + "version": "1.1.15", + "bundled": true + } + } + } + } + } + } + } + } + }, + "npm-user-validate": { + "version": "1.0.0", + "bundled": true + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + }, + "dependencies": { + "are-we-there-yet": { + "version": "1.1.4", + "bundled": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + }, + "dependencies": { + "delegates": { + "version": "1.0.0", + "bundled": true + } + } + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "requires": { + "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" + }, + "dependencies": { + "object-assign": { + "version": "4.1.1", + "bundled": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + }, + "dependencies": { + "code-point-at": { + "version": "1.1.0", + "bundled": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "requires": { + "number-is-nan": "^1.0.0" + }, + "dependencies": { + "number-is-nan": { + "version": "1.0.1", + "bundled": true + } + } + } + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "requires": { + "ansi-regex": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "bundled": true + } + } + }, + "wide-align": { + "version": "1.1.2", + "bundled": true, + "requires": { + "string-width": "^1.0.2" + } + } + } + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true + } + } + }, + "once": { + "version": "1.4.0", + "bundled": true, + "requires": { + "wrappy": "1" + } + }, + "opener": { + "version": "1.4.3", + "bundled": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + }, + "dependencies": { + "os-homedir": { + "version": "1.0.2", + "bundled": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true + } + } + }, + "pacote": { + "version": "7.6.1", + "bundled": true, + "requires": { + "bluebird": "^3.5.1", + "cacache": "^10.0.4", + "get-stream": "^3.0.0", + "glob": "^7.1.2", + "lru-cache": "^4.1.1", + "make-fetch-happen": "^2.6.0", + "minimatch": "^3.0.4", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "normalize-package-data": "^2.4.0", + "npm-package-arg": "^6.0.0", + "npm-packlist": "^1.1.10", + "npm-pick-manifest": "^2.1.0", + "osenv": "^0.1.5", + "promise-inflight": "^1.0.1", + "promise-retry": "^1.1.1", + "protoduck": "^5.0.0", + "rimraf": "^2.6.2", + "safe-buffer": "^5.1.1", + "semver": "^5.5.0", + "ssri": "^5.2.4", + "tar": "^4.4.0", + "unique-filename": "^1.1.0", + "which": "^1.3.0" + }, + "dependencies": { + "get-stream": { + "version": "3.0.0", + "bundled": true + }, + "make-fetch-happen": { + "version": "2.6.0", + "bundled": true, + "requires": { + "agentkeepalive": "^3.3.0", + "cacache": "^10.0.0", + "http-cache-semantics": "^3.8.0", + "http-proxy-agent": "^2.0.0", + "https-proxy-agent": "^2.1.0", + "lru-cache": "^4.1.1", + "mississippi": "^1.2.0", + "node-fetch-npm": "^2.0.2", + "promise-retry": "^1.1.1", + "socks-proxy-agent": "^3.0.1", + "ssri": "^5.0.0" + }, + "dependencies": { + "agentkeepalive": { + "version": "3.4.0", + "bundled": true, + "requires": { + "humanize-ms": "^1.2.1" + }, + "dependencies": { + "humanize-ms": { + "version": "1.2.1", + "bundled": true, + "requires": { + "ms": "^2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.1.1", + "bundled": true + } + } + } + } + }, + "http-cache-semantics": { + "version": "3.8.1", + "bundled": true + }, + "http-proxy-agent": { + "version": "2.1.0", + "bundled": true, + "requires": { + "agent-base": "4", + "debug": "3.1.0" + }, + "dependencies": { + "agent-base": { + "version": "4.2.0", + "bundled": true, + "requires": { + "es6-promisify": "^5.0.0" + }, + "dependencies": { + "es6-promisify": { + "version": "5.0.0", + "bundled": true, + "requires": { + "es6-promise": "^4.0.3" + }, + "dependencies": { + "es6-promise": { + "version": "4.2.4", + "bundled": true + } + } + } + } + }, + "debug": { + "version": "3.1.0", + "bundled": true, + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "bundled": true + } + } + } + } + }, + "https-proxy-agent": { + "version": "2.2.0", + "bundled": true, + "requires": { + "agent-base": "^4.1.0", + "debug": "^3.1.0" + }, + "dependencies": { + "agent-base": { + "version": "4.2.0", + "bundled": true, + "requires": { + "es6-promisify": "^5.0.0" + }, + "dependencies": { + "es6-promisify": { + "version": "5.0.0", + "bundled": true, + "requires": { + "es6-promise": "^4.0.3" + }, + "dependencies": { + "es6-promise": { + "version": "4.2.4", + "bundled": true + } + } + } + } + }, + "debug": { + "version": "3.1.0", + "bundled": true, + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "bundled": true + } + } + } + } + }, + "mississippi": { + "version": "1.3.1", + "bundled": true, + "requires": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^1.0.0", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + }, + "dependencies": { + "concat-stream": { + "version": "1.6.1", + "bundled": true, + "requires": { + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + }, + "dependencies": { + "typedarray": { + "version": "0.0.6", + "bundled": true + } + } + }, + "duplexify": { + "version": "3.5.4", + "bundled": true, + "requires": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + }, + "dependencies": { + "stream-shift": { + "version": "1.0.0", + "bundled": true + } + } + }, + "end-of-stream": { + "version": "1.4.1", + "bundled": true, + "requires": { + "once": "^1.4.0" + } + }, + "flush-write-stream": { + "version": "1.0.2", + "bundled": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.4" + } + }, + "from2": { + "version": "2.3.0", + "bundled": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + } + }, + "parallel-transform": { + "version": "1.1.0", + "bundled": true, + "requires": { + "cyclist": "~0.2.2", + "inherits": "^2.0.3", + "readable-stream": "^2.1.5" + }, + "dependencies": { + "cyclist": { + "version": "0.2.2", + "bundled": true + } + } + }, + "pump": { + "version": "1.0.3", + "bundled": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "pumpify": { + "version": "1.4.0", + "bundled": true, + "requires": { + "duplexify": "^3.5.3", + "inherits": "^2.0.3", + "pump": "^2.0.0" + }, + "dependencies": { + "pump": { + "version": "2.0.1", + "bundled": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } + } + }, + "stream-each": { + "version": "1.2.2", + "bundled": true, + "requires": { + "end-of-stream": "^1.1.0", + "stream-shift": "^1.0.0" + }, + "dependencies": { + "stream-shift": { + "version": "1.0.0", + "bundled": true + } + } + }, + "through2": { + "version": "2.0.3", + "bundled": true, + "requires": { + "readable-stream": "^2.1.5", + "xtend": "~4.0.1" + }, + "dependencies": { + "xtend": { + "version": "4.0.1", + "bundled": true + } + } + } + } + }, + "node-fetch-npm": { + "version": "2.0.2", + "bundled": true, + "requires": { + "encoding": "^0.1.11", + "json-parse-better-errors": "^1.0.0", + "safe-buffer": "^5.1.1" + }, + "dependencies": { + "encoding": { + "version": "0.1.12", + "bundled": true, + "requires": { + "iconv-lite": "~0.4.13" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.19", + "bundled": true + } + } + }, + "json-parse-better-errors": { + "version": "1.0.1", + "bundled": true + } + } + }, + "socks-proxy-agent": { + "version": "3.0.1", + "bundled": true, + "requires": { + "agent-base": "^4.1.0", + "socks": "^1.1.10" + }, + "dependencies": { + "agent-base": { + "version": "4.2.0", + "bundled": true, + "requires": { + "es6-promisify": "^5.0.0" + }, + "dependencies": { + "es6-promisify": { + "version": "5.0.0", + "bundled": true, + "requires": { + "es6-promise": "^4.0.3" + }, + "dependencies": { + "es6-promise": { + "version": "4.2.4", + "bundled": true + } + } + } + } + }, + "socks": { + "version": "1.1.10", + "bundled": true, + "requires": { + "ip": "^1.1.4", + "smart-buffer": "^1.0.13" + }, + "dependencies": { + "ip": { + "version": "1.1.5", + "bundled": true + }, + "smart-buffer": { + "version": "1.1.15", + "bundled": true + } + } + } + } + } + } + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "requires": { + "brace-expansion": "^1.1.7" + }, + "dependencies": { + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + }, + "dependencies": { + "balanced-match": { + "version": "1.0.0", + "bundled": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true + } + } + } + } + }, + "npm-pick-manifest": { + "version": "2.1.0", + "bundled": true, + "requires": { + "npm-package-arg": "^6.0.0", + "semver": "^5.4.1" + } + }, + "promise-retry": { + "version": "1.1.1", + "bundled": true, + "requires": { + "err-code": "^1.0.0", + "retry": "^0.10.0" + }, + "dependencies": { + "err-code": { + "version": "1.1.2", + "bundled": true + }, + "retry": { + "version": "0.10.1", + "bundled": true + } + } + }, + "protoduck": { + "version": "5.0.0", + "bundled": true, + "requires": { + "genfun": "^4.0.1" + }, + "dependencies": { + "genfun": { + "version": "4.0.1", + "bundled": true + } + } + } + } + }, + "path-is-inside": { + "version": "1.0.2", + "bundled": true + }, + "promise-inflight": { + "version": "1.0.1", + "bundled": true + }, + "qrcode-terminal": { + "version": "0.12.0", + "bundled": true + }, + "query-string": { + "version": "6.1.0", + "bundled": true, + "requires": { + "decode-uri-component": "^0.2.0", + "strict-uri-encode": "^2.0.0" + }, + "dependencies": { + "decode-uri-component": { + "version": "0.2.0", + "bundled": true + }, + "strict-uri-encode": { + "version": "2.0.0", + "bundled": true + } + } + }, + "qw": { + "version": "1.0.1", + "bundled": true + }, + "read": { + "version": "1.0.7", + "bundled": true, + "requires": { + "mute-stream": "~0.0.4" + }, + "dependencies": { + "mute-stream": { + "version": "0.0.7", + "bundled": true + } + } + }, + "read-cmd-shim": { + "version": "1.0.1", + "bundled": true, + "requires": { + "graceful-fs": "^4.1.2" + } + }, + "read-installed": { + "version": "4.0.3", + "bundled": true, + "requires": { + "debuglog": "^1.0.1", + "graceful-fs": "^4.1.2", + "read-package-json": "^2.0.0", + "readdir-scoped-modules": "^1.0.0", + "semver": "2 || 3 || 4 || 5", + "slide": "~1.1.3", + "util-extend": "^1.0.1" + }, + "dependencies": { + "util-extend": { + "version": "1.0.3", + "bundled": true + } + } + }, + "read-package-json": { + "version": "2.0.13", + "bundled": true, + "requires": { + "glob": "^7.1.1", + "graceful-fs": "^4.1.2", + "json-parse-better-errors": "^1.0.1", + "normalize-package-data": "^2.0.0", + "slash": "^1.0.0" + }, + "dependencies": { + "json-parse-better-errors": { + "version": "1.0.1", + "bundled": true + }, + "slash": { + "version": "1.0.0", + "bundled": true + } + } + }, + "read-package-tree": { + "version": "5.2.1", + "bundled": true, + "requires": { + "debuglog": "^1.0.1", + "dezalgo": "^1.0.0", + "once": "^1.3.0", + "read-package-json": "^2.0.0", + "readdir-scoped-modules": "^1.0.0" + } + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "requires": { + "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" + }, + "dependencies": { + "core-util-is": { + "version": "1.0.2", + "bundled": true + }, + "isarray": { + "version": "1.0.0", + "bundled": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true + } + } + }, + "readdir-scoped-modules": { + "version": "1.0.2", + "bundled": true, + "requires": { + "debuglog": "^1.0.1", + "dezalgo": "^1.0.0", + "graceful-fs": "^4.1.2", + "once": "^1.3.0" + } + }, + "request": { + "version": "2.85.0", + "bundled": 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", + "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.17", + "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.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.1.0" + }, + "dependencies": { + "aws-sign2": { + "version": "0.7.0", + "bundled": true + }, + "aws4": { + "version": "1.6.0", + "bundled": true + }, + "caseless": { + "version": "0.12.0", + "bundled": true + }, + "combined-stream": { + "version": "1.0.6", + "bundled": true, + "requires": { + "delayed-stream": "~1.0.0" + }, + "dependencies": { + "delayed-stream": { + "version": "1.0.0", + "bundled": true + } + } + }, + "extend": { + "version": "3.0.1", + "bundled": true + }, + "forever-agent": { + "version": "0.6.1", + "bundled": true + }, + "form-data": { + "version": "2.3.2", + "bundled": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "1.0.6", + "mime-types": "^2.1.12" + }, + "dependencies": { + "asynckit": { + "version": "0.4.0", + "bundled": true + } + } + }, + "har-validator": { + "version": "5.0.3", + "bundled": true, + "requires": { + "ajv": "^5.1.0", + "har-schema": "^2.0.0" + }, + "dependencies": { + "ajv": { + "version": "5.5.2", + "bundled": true, + "requires": { + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" + }, + "dependencies": { + "co": { + "version": "4.6.0", + "bundled": true + }, + "fast-deep-equal": { + "version": "1.1.0", + "bundled": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "bundled": true + }, + "json-schema-traverse": { + "version": "0.3.1", + "bundled": true + } + } + }, + "har-schema": { + "version": "2.0.0", + "bundled": true + } + } + }, + "hawk": { + "version": "6.0.2", + "bundled": true, + "requires": { + "boom": "4.x.x", + "cryptiles": "3.x.x", + "hoek": "4.x.x", + "sntp": "2.x.x" + }, + "dependencies": { + "boom": { + "version": "4.3.1", + "bundled": true, + "requires": { + "hoek": "4.x.x" + } + }, + "cryptiles": { + "version": "3.1.2", + "bundled": true, + "requires": { + "boom": "5.x.x" + }, + "dependencies": { + "boom": { + "version": "5.2.0", + "bundled": true, + "requires": { + "hoek": "4.x.x" + } + } + } + }, + "hoek": { + "version": "4.2.1", + "bundled": true + }, + "sntp": { + "version": "2.1.0", + "bundled": true, + "requires": { + "hoek": "4.x.x" + } + } + } + }, + "http-signature": { + "version": "1.2.0", + "bundled": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true + }, + "jsprim": { + "version": "1.4.1", + "bundled": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + }, + "dependencies": { + "extsprintf": { + "version": "1.3.0", + "bundled": true + }, + "json-schema": { + "version": "0.2.3", + "bundled": true + }, + "verror": { + "version": "1.10.0", + "bundled": true, + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + }, + "dependencies": { + "core-util-is": { + "version": "1.0.2", + "bundled": true + } + } + } + } + }, + "sshpk": { + "version": "1.14.1", + "bundled": true, + "requires": { + "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", + "tweetnacl": "~0.14.0" + }, + "dependencies": { + "asn1": { + "version": "0.2.3", + "bundled": true + }, + "bcrypt-pbkdf": { + "version": "1.0.1", + "bundled": true, + "optional": true, + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "dashdash": { + "version": "1.14.1", + "bundled": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "ecc-jsbn": { + "version": "0.1.1", + "bundled": true, + "optional": true, + "requires": { + "jsbn": "~0.1.0" + } + }, + "getpass": { + "version": "0.1.7", + "bundled": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "jsbn": { + "version": "0.1.1", + "bundled": true, + "optional": true + }, + "tweetnacl": { + "version": "0.14.5", + "bundled": true, + "optional": true + } + } + } + } + }, + "is-typedarray": { + "version": "1.0.0", + "bundled": true + }, + "isstream": { + "version": "0.1.2", + "bundled": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "bundled": true + }, + "mime-types": { + "version": "2.1.18", + "bundled": true, + "requires": { + "mime-db": "~1.33.0" + }, + "dependencies": { + "mime-db": { + "version": "1.33.0", + "bundled": true + } + } + }, + "oauth-sign": { + "version": "0.8.2", + "bundled": true + }, + "performance-now": { + "version": "2.1.0", + "bundled": true + }, + "qs": { + "version": "6.5.1", + "bundled": true + }, + "stringstream": { + "version": "0.0.5", + "bundled": true + }, + "tough-cookie": { + "version": "2.3.4", + "bundled": true, + "requires": { + "punycode": "^1.4.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "bundled": true + } + } + }, + "tunnel-agent": { + "version": "0.6.0", + "bundled": true, + "requires": { + "safe-buffer": "^5.0.1" + } + } + } + }, + "retry": { + "version": "0.12.0", + "bundled": true + }, + "rimraf": { + "version": "2.6.2", + "bundled": true, + "requires": { + "glob": "^7.0.5" + } + }, + "safe-buffer": { + "version": "5.1.2", + "bundled": true + }, + "semver": { + "version": "5.5.0", + "bundled": true + }, + "sha": { + "version": "2.0.1", + "bundled": true, + "requires": { + "graceful-fs": "^4.1.2", + "readable-stream": "^2.0.2" + } + }, + "slide": { + "version": "1.1.6", + "bundled": true + }, + "sorted-object": { + "version": "2.0.1", + "bundled": true + }, + "sorted-union-stream": { + "version": "2.1.3", + "bundled": true, + "requires": { + "from2": "^1.3.0", + "stream-iterate": "^1.1.0" + }, + "dependencies": { + "from2": { + "version": "1.3.0", + "bundled": true, + "requires": { + "inherits": "~2.0.1", + "readable-stream": "~1.1.10" + }, + "dependencies": { + "readable-stream": { + "version": "1.1.14", + "bundled": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + }, + "dependencies": { + "core-util-is": { + "version": "1.0.2", + "bundled": true + }, + "isarray": { + "version": "0.0.1", + "bundled": true + }, + "string_decoder": { + "version": "0.10.31", + "bundled": true + } + } + } + } + }, + "stream-iterate": { + "version": "1.2.0", + "bundled": true, + "requires": { + "readable-stream": "^2.1.5", + "stream-shift": "^1.0.0" + }, + "dependencies": { + "stream-shift": { + "version": "1.0.0", + "bundled": true + } + } + } + } + }, + "ssri": { + "version": "5.3.0", + "bundled": true, + "requires": { + "safe-buffer": "^5.1.1" + } + }, + "strip-ansi": { + "version": "4.0.0", + "bundled": true, + "requires": { + "ansi-regex": "^3.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "bundled": true + } + } + }, + "tar": { + "version": "4.4.2", + "bundled": true, + "requires": { + "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.2", + "yallist": "^3.0.2" + }, + "dependencies": { + "fs-minipass": { + "version": "1.2.5", + "bundled": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "minipass": { + "version": "2.2.4", + "bundled": true, + "requires": { + "safe-buffer": "^5.1.1", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.1.0", + "bundled": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "bundled": true + }, + "yallist": { + "version": "3.0.2", + "bundled": true + } + } + }, + "text-table": { + "version": "0.2.0", + "bundled": true + }, + "tiny-relative-date": { + "version": "1.3.0", + "bundled": true + }, + "uid-number": { + "version": "0.0.6", + "bundled": true + }, + "umask": { + "version": "1.1.0", + "bundled": true + }, + "unique-filename": { + "version": "1.1.0", + "bundled": true, + "requires": { + "unique-slug": "^2.0.0" + }, + "dependencies": { + "unique-slug": { + "version": "2.0.0", + "bundled": true, + "requires": { + "imurmurhash": "^0.1.4" + } + } + } + }, + "unpipe": { + "version": "1.0.0", + "bundled": true + }, + "update-notifier": { + "version": "2.5.0", + "bundled": true, + "requires": { + "boxen": "^1.2.1", + "chalk": "^2.0.1", + "configstore": "^3.0.0", + "import-lazy": "^2.1.0", + "is-ci": "^1.0.10", + "is-installed-globally": "^0.1.0", + "is-npm": "^1.0.0", + "latest-version": "^3.0.0", + "semver-diff": "^2.0.0", + "xdg-basedir": "^3.0.0" + }, + "dependencies": { + "boxen": { + "version": "1.3.0", + "bundled": true, + "requires": { + "ansi-align": "^2.0.0", + "camelcase": "^4.0.0", + "chalk": "^2.0.1", + "cli-boxes": "^1.0.0", + "string-width": "^2.0.0", + "term-size": "^1.2.0", + "widest-line": "^2.0.0" + }, + "dependencies": { + "ansi-align": { + "version": "2.0.0", + "bundled": true, + "requires": { + "string-width": "^2.0.0" + } + }, + "camelcase": { + "version": "4.1.0", + "bundled": true + }, + "cli-boxes": { + "version": "1.0.0", + "bundled": true + }, + "string-width": { + "version": "2.1.1", + "bundled": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "2.0.0", + "bundled": true + } + } + }, + "term-size": { + "version": "1.2.0", + "bundled": true, + "requires": { + "execa": "^0.7.0" + }, + "dependencies": { + "execa": { + "version": "0.7.0", + "bundled": true, + "requires": { + "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": { + "version": "5.1.0", + "bundled": true, + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "dependencies": { + "shebang-command": { + "version": "1.2.0", + "bundled": true, + "requires": { + "shebang-regex": "^1.0.0" + }, + "dependencies": { + "shebang-regex": { + "version": "1.0.0", + "bundled": true + } + } + } + } + }, + "get-stream": { + "version": "3.0.0", + "bundled": true + }, + "is-stream": { + "version": "1.1.0", + "bundled": true + }, + "npm-run-path": { + "version": "2.0.2", + "bundled": true, + "requires": { + "path-key": "^2.0.0" + }, + "dependencies": { + "path-key": { + "version": "2.0.1", + "bundled": true + } + } + }, + "p-finally": { + "version": "1.0.0", + "bundled": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true + }, + "strip-eof": { + "version": "1.0.0", + "bundled": true + } + } + } + } + }, + "widest-line": { + "version": "2.0.0", + "bundled": true, + "requires": { + "string-width": "^2.1.1" + } + } + } + }, + "chalk": { + "version": "2.4.1", + "bundled": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "bundled": true, + "requires": { + "color-convert": "^1.9.0" + }, + "dependencies": { + "color-convert": { + "version": "1.9.1", + "bundled": true, + "requires": { + "color-name": "^1.1.1" + }, + "dependencies": { + "color-name": { + "version": "1.1.3", + "bundled": true + } + } + } + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "bundled": true + }, + "supports-color": { + "version": "5.4.0", + "bundled": true, + "requires": { + "has-flag": "^3.0.0" + }, + "dependencies": { + "has-flag": { + "version": "3.0.0", + "bundled": true + } + } + } + } + }, + "configstore": { + "version": "3.1.2", + "bundled": true, + "requires": { + "dot-prop": "^4.1.0", + "graceful-fs": "^4.1.2", + "make-dir": "^1.0.0", + "unique-string": "^1.0.0", + "write-file-atomic": "^2.0.0", + "xdg-basedir": "^3.0.0" + }, + "dependencies": { + "dot-prop": { + "version": "4.2.0", + "bundled": true, + "requires": { + "is-obj": "^1.0.0" + }, + "dependencies": { + "is-obj": { + "version": "1.0.1", + "bundled": true + } + } + }, + "make-dir": { + "version": "1.2.0", + "bundled": true, + "requires": { + "pify": "^3.0.0" + }, + "dependencies": { + "pify": { + "version": "3.0.0", + "bundled": true + } + } + }, + "unique-string": { + "version": "1.0.0", + "bundled": true, + "requires": { + "crypto-random-string": "^1.0.0" + }, + "dependencies": { + "crypto-random-string": { + "version": "1.0.0", + "bundled": true + } + } + } + } + }, + "import-lazy": { + "version": "2.1.0", + "bundled": true + }, + "is-ci": { + "version": "1.1.0", + "bundled": true, + "requires": { + "ci-info": "^1.0.0" + }, + "dependencies": { + "ci-info": { + "version": "1.1.3", + "bundled": true + } + } + }, + "is-installed-globally": { + "version": "0.1.0", + "bundled": true, + "requires": { + "global-dirs": "^0.1.0", + "is-path-inside": "^1.0.0" + }, + "dependencies": { + "global-dirs": { + "version": "0.1.1", + "bundled": true, + "requires": { + "ini": "^1.3.4" + } + }, + "is-path-inside": { + "version": "1.0.1", + "bundled": true, + "requires": { + "path-is-inside": "^1.0.1" + } + } + } + }, + "is-npm": { + "version": "1.0.0", + "bundled": true + }, + "latest-version": { + "version": "3.1.0", + "bundled": true, + "requires": { + "package-json": "^4.0.0" + }, + "dependencies": { + "package-json": { + "version": "4.0.1", + "bundled": true, + "requires": { + "got": "^6.7.1", + "registry-auth-token": "^3.0.1", + "registry-url": "^3.0.3", + "semver": "^5.1.0" + }, + "dependencies": { + "got": { + "version": "6.7.1", + "bundled": true, + "requires": { + "create-error-class": "^3.0.0", + "duplexer3": "^0.1.4", + "get-stream": "^3.0.0", + "is-redirect": "^1.0.0", + "is-retry-allowed": "^1.0.0", + "is-stream": "^1.0.0", + "lowercase-keys": "^1.0.0", + "safe-buffer": "^5.0.1", + "timed-out": "^4.0.0", + "unzip-response": "^2.0.1", + "url-parse-lax": "^1.0.0" + }, + "dependencies": { + "create-error-class": { + "version": "3.0.2", + "bundled": true, + "requires": { + "capture-stack-trace": "^1.0.0" + }, + "dependencies": { + "capture-stack-trace": { + "version": "1.0.0", + "bundled": true + } + } + }, + "duplexer3": { + "version": "0.1.4", + "bundled": true + }, + "get-stream": { + "version": "3.0.0", + "bundled": true + }, + "is-redirect": { + "version": "1.0.0", + "bundled": true + }, + "is-retry-allowed": { + "version": "1.1.0", + "bundled": true + }, + "is-stream": { + "version": "1.1.0", + "bundled": true + }, + "lowercase-keys": { + "version": "1.0.1", + "bundled": true + }, + "timed-out": { + "version": "4.0.1", + "bundled": true + }, + "unzip-response": { + "version": "2.0.1", + "bundled": true + }, + "url-parse-lax": { + "version": "1.0.0", + "bundled": true, + "requires": { + "prepend-http": "^1.0.1" + }, + "dependencies": { + "prepend-http": { + "version": "1.0.4", + "bundled": true + } + } + } + } + }, + "registry-auth-token": { + "version": "3.3.2", + "bundled": true, + "requires": { + "rc": "^1.1.6", + "safe-buffer": "^5.0.1" + }, + "dependencies": { + "rc": { + "version": "1.2.7", + "bundled": true, + "requires": { + "deep-extend": "^0.5.1", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "deep-extend": { + "version": "0.5.1", + "bundled": true + }, + "minimist": { + "version": "1.2.0", + "bundled": true + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true + } + } + } + } + }, + "registry-url": { + "version": "3.1.0", + "bundled": true, + "requires": { + "rc": "^1.0.1" + }, + "dependencies": { + "rc": { + "version": "1.2.7", + "bundled": true, + "requires": { + "deep-extend": "^0.5.1", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "deep-extend": { + "version": "0.5.1", + "bundled": true + }, + "minimist": { + "version": "1.2.0", + "bundled": true + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true + } + } + } + } + } + } + } + } + }, + "semver-diff": { + "version": "2.1.0", + "bundled": true, + "requires": { + "semver": "^5.0.3" + } + }, + "xdg-basedir": { + "version": "3.0.0", + "bundled": true + } + } + }, + "uuid": { + "version": "3.2.1", + "bundled": true + }, + "validate-npm-package-license": { + "version": "3.0.3", + "bundled": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + }, + "dependencies": { + "spdx-correct": { + "version": "3.0.0", + "bundled": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + }, + "dependencies": { + "spdx-license-ids": { + "version": "3.0.0", + "bundled": true + } + } + }, + "spdx-expression-parse": { + "version": "3.0.0", + "bundled": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + }, + "dependencies": { + "spdx-exceptions": { + "version": "2.1.0", + "bundled": true + }, + "spdx-license-ids": { + "version": "3.0.0", + "bundled": true + } + } + } + } + }, + "validate-npm-package-name": { + "version": "3.0.0", + "bundled": true, + "requires": { + "builtins": "^1.0.3" + }, + "dependencies": { + "builtins": { + "version": "1.0.3", + "bundled": true + } + } + }, + "which": { + "version": "1.3.0", + "bundled": true, + "requires": { + "isexe": "^2.0.0" + }, + "dependencies": { + "isexe": { + "version": "2.0.0", + "bundled": true + } + } + }, + "worker-farm": { + "version": "1.6.0", + "bundled": true, + "requires": { + "errno": "~0.1.7" + }, + "dependencies": { + "errno": { + "version": "0.1.7", + "bundled": true, + "requires": { + "prr": "~1.0.1" + }, + "dependencies": { + "prr": { + "version": "1.0.1", + "bundled": true + } + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true + }, + "write-file-atomic": { + "version": "2.3.0", + "bundled": true, + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + }, + "dependencies": { + "signal-exit": { + "version": "3.0.2", + "bundled": true + } + } + } + } + }, + "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 + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "openzeppelin-solidity": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/openzeppelin-solidity/-/openzeppelin-solidity-2.0.0.tgz", + "integrity": "sha512-SolpxQFArtiYnlSNg3dZ9sz0WVlKtPqSOcJkXRllaZp4+Lpfqz3vxF0yoh7g75TszKPyadqoJmU7+GM/vwh9SA==" + }, + "optimist": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "dev": true, + "requires": { + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" + }, + "dependencies": { + "minimist": { + "version": "0.0.10", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", + "dev": true + }, + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", + "dev": true + } + } + }, + "optionator": { + "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.4", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "wordwrap": "~1.0.0" + } + }, + "os-locale": { + "version": "1.4.0", + "resolved": "http://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", + "dev": true, + "requires": { + "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.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.0" + } + }, + "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=" + }, + "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.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "pathval": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", + "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", + "dev": true + }, + "pegjs": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/pegjs/-/pegjs-0.10.0.tgz", + "integrity": "sha1-z4uvrm7d/0tafvsYUmnqr0YQ3b0=", + "dev": true + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "^2.0.0" + } + }, + "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 + }, + "psl": { + "version": "1.1.29", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.29.tgz", + "integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ==", + "dev": true + }, + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + }, + "qs": { + "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 + }, + "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.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.0.0", + "read-pkg": "^1.0.0" + } + }, + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "requires": { + "resolve": "^1.1.6" + } + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, + "req-cwd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/req-cwd/-/req-cwd-1.0.1.tgz", + "integrity": "sha1-DXOurpJm5penj3l2AZZ352rPD/8=", + "dev": true, + "requires": { + "req-from": "^1.0.1" + } + }, + "req-from": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/req-from/-/req-from-1.0.1.tgz", + "integrity": "sha1-v4HaUUeUfTLRO5R9wSpYrUWHNQ4=", + "dev": true, + "requires": { + "resolve-from": "^2.0.0" + } + }, + "request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "dev": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, + "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 + }, + "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 + }, + "resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=" + }, + "resolve-from": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", + "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=", + "dev": true + }, + "right-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", + "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", + "dev": true, + "optional": true, + "requires": { + "align-text": "^0.1.1" + } + }, + "ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "rlp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/rlp/-/rlp-2.1.0.tgz", + "integrity": "sha512-93U7IKH5j7nmXFVg19MeNBGzQW5uXW1pmCuKY8veeKIhYTE32C2d0mOegfiIAfXcHOKJjjPlJisn8iHDF5AezA==", + "requires": { + "safe-buffer": "^5.1.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "secp256k1": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-3.5.2.tgz", + "integrity": "sha512-iin3kojdybY6NArd+UFsoTuapOF7bnJNf2UbcWXaY3z+E1sJDipl60vtzB5hbO/uquBu7z0fd4VC4Irp+xoFVQ==", + "requires": { + "bindings": "^1.2.1", + "bip66": "^1.1.3", + "bn.js": "^4.11.3", + "create-hash": "^1.1.2", + "drbg.js": "^1.0.1", + "elliptic": "^6.2.3", + "nan": "^2.2.1", + "safe-buffer": "^5.1.0" + } + }, + "semver": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz", + "integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw==", + "dev": true + }, + "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 + }, + "sha.js": { + "version": "2.4.11", + "resolved": "http://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "sha3": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/sha3/-/sha3-1.2.2.tgz", + "integrity": "sha1-pmxQmN5MJbyIM27ItIF9AFvKe6k=", + "dev": true, + "requires": { + "nan": "2.10.0" + } + }, + "shelljs": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.3.tgz", + "integrity": "sha512-fc0BKlAWiLpwZljmOvAOTE/gXawtCoNrP5oaY7KIaQbbyHeQVg01pSEuEGvGh3HEdBU4baCD7wQBwADmM/7f7A==", + "requires": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + }, + "dependencies": { + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "requires": { + "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" + } + } + } + }, + "sol-explore": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/sol-explore/-/sol-explore-1.6.2.tgz", + "integrity": "sha1-Q66MQZ/TrAVqBfip0fsQIs1B7MI=", + "dev": true + }, + "solidity-coverage": { + "version": "0.5.11", + "resolved": "https://registry.npmjs.org/solidity-coverage/-/solidity-coverage-0.5.11.tgz", + "integrity": "sha512-qikdsSi6+9XbfvwA0aI7HUVpF9fIFNqRWTw23M89GMDY+b6Gj0wWU9IngJS0fimoZIAdEp3bfChxvpfVcrUesg==", + "dev": true, + "requires": { + "death": "^1.1.0", + "ethereumjs-testrpc-sc": "6.1.6", + "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.11", + "tree-kill": "^1.2.0", + "web3": "^0.18.4" + }, + "dependencies": { + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "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" + } + }, + "shelljs": { + "version": "0.7.8", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.8.tgz", + "integrity": "sha1-3svPh0sNHl+3LhSxZKloMEjprLM=", + "dev": true, + "requires": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + } + } + } + }, + "solidity-parser-sc": { + "version": "0.4.11", + "resolved": "https://registry.npmjs.org/solidity-parser-sc/-/solidity-parser-sc-0.4.11.tgz", + "integrity": "sha512-1kV5iC7m3CtMDfmHaVNwz2saSGQVIuF16rIxU417Al38MVCWHMQQ5vT6cmLsNwDe60S74auobWij9vNawSeOyw==", + "dev": true, + "requires": { + "mocha": "^4.1.0", + "pegjs": "^0.10.0", + "yargs": "^4.6.0" + }, + "dependencies": { + "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.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" + } + }, + "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.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" + } + } + } + }, + "solidity-rlp": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/solidity-rlp/-/solidity-rlp-1.1.0.tgz", + "integrity": "sha512-Om611ECelrLBdrNi/5Wfia7rZrUINlQVICI/Vnc8mkkziL3YkRA22jdHXbPkyc4RJMg46lzLcRm/WXd6n40htA==", + "requires": { + "rlp": "^2.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==", + "dev": true + }, + "source-map-support": { + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.9.tgz", + "integrity": "sha512-gR6Rw4MvUlYy83vP0vxoVNzM6t8MUXqNuRsuBmBHQDu1Fh6X015FrLdgoDKcNdkwGubozq0P4N0Q37UyFVr1EA==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "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, + "requires": { + "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 + }, + "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, + "requires": { + "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 + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "sshpk": { + "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.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" + } + }, + "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.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.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.0" + } + }, + "strip-hex-prefix": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-hex-prefix/-/strip-hex-prefix-1.0.0.tgz", + "integrity": "sha1-DF8VX+8RUTczd96du1iNoFUA428=", + "requires": { + "is-hex-prefixed": "1.0.0" + } + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "dev": true, + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + } + }, + "tree-kill": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.0.tgz", + "integrity": "sha512-DlX6dR0lOIRDFxI0mjL9IYg6OTncLm/Zt+JiBhE5OlFcAR8yc9S7FFXU9so0oda47frdM/JFsk7UjNt9vscKcg==", + "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.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true, + "optional": true + }, + "type-check": { + "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" + } + }, + "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==", + "dev": true + }, + "uglify-js": { + "version": "2.8.29", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", + "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", + "dev": true, + "optional": true, + "requires": { + "source-map": "~0.5.1", + "uglify-to-browserify": "~1.0.0", + "yargs": "~3.10.0" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true, + "optional": true + } + } + }, + "uglify-to-browserify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", + "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", + "dev": true, + "optional": true + }, + "utf8": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/utf8/-/utf8-2.1.2.tgz", + "integrity": "sha1-H6DZJw6b6FDZsFAn9jUZv0ZFfZY=", + "dev": true + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + "dev": true + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "web3": { + "version": "0.18.4", + "resolved": "https://registry.npmjs.org/web3/-/web3-0.18.4.tgz", + "integrity": "sha1-gewXhBRUkfLqqJVbMcBgSeB8Xn0=", + "dev": true, + "requires": { + "bignumber.js": "git+https://github.com/debris/bignumber.js.git#94d7146671b9719e00a09c29b01a691bc85048c2", + "crypto-js": "^3.1.4", + "utf8": "^2.1.1", + "xhr2": "*", + "xmlhttprequest": "*" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^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.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": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "xhr2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/xhr2/-/xhr2-0.1.4.tgz", + "integrity": "sha1-f4dliEdxbbUCYyOBL4GMras4el8=", + "dev": true + }, + "xmlhttprequest": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz", + "integrity": "sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw=", + "dev": true + }, + "y18n": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", + "dev": true + }, + "yargs": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", + "dev": true, + "optional": true, + "requires": { + "camelcase": "^1.0.2", + "cliui": "^2.1.0", + "decamelize": "^1.0.0", + "window-size": "0.1.0" + } + }, + "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.0.6" + }, + "dependencies": { + "camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", + "dev": true + } + } + } + } +} diff --git a/contracts/package.json b/contracts/package.json new file mode 100644 index 0000000..21d8633 --- /dev/null +++ b/contracts/package.json @@ -0,0 +1,25 @@ +{ + "name": "plasma-mvp-rootchain", + "version": "1.0.0", + "directories": { + "test": "test" + }, + "scripts": { + "clean": "rm -rf build wrappers abi", + "test": "rm -rf build && truffle test", + "coverage": "./node_modules/.bin/solidity-coverage" + }, + "devDependencies": { + "chai": "^4.2.0", + "coveralls": "^3.0.2", + "solidity-coverage": "^0.5.11" + }, + "dependencies": { + "ethereumjs-util": "^6.0.0", + "npm": "^5.10.0", + "openzeppelin-solidity": "^2.0.0", + "rlp": "^2.1.0", + "shelljs": "^0.8.3", + "solidity-rlp": "^1.1.0" + } +} diff --git a/contracts/test/libraries/TMSimpleMerkleTree.js b/contracts/test/libraries/TMSimpleMerkleTree.js new file mode 100644 index 0000000..20766a8 --- /dev/null +++ b/contracts/test/libraries/TMSimpleMerkleTree.js @@ -0,0 +1,92 @@ +let assert = require('chai').assert; + +let TMSimpleMerkleTree_Test = artifacts.require("TMSimpleMerkleTree_Test"); + +let { catchError, toHex } = require('../utilities.js'); +let { generateMerkleRootAndProof } = require('../plasmamvp/plasmamvp_helpers.js'); + +contract('TMSimpleMerkleTree', async (accounts) => { + let instance; + before(async () => { + instance = await TMSimpleMerkleTree_Test.new(); + }); + + it("Verifies the membership in a merkle tree with 7 leaves (hardcoded)", async () => { + // this utxo input and the merkle root of its block were generated + // by the side chain + // this side chain block contains 7 txns + let rootHash = "0x4edd08572735720e2769fa536c03e5d2ed29ff2ba97fb102cfe71fdae3c30428"; + let leaf = "0x714c0269d202e4302fadab4d62a4e9171fbf09508cb589616342cce45981d329"; + let proof = "0xfbec635c930057fdc76939052216ed2aed7af618109b983ee1ae7b13c909f2dd1a61753dc9eccd5506144081ba000a8e44c9262be17ab934dda9e4fa10495fccdbb108450dad46789d67f5cfb5ee4be7f505bd8835a0867410412f22cfda8ad5"; + let total = 7; // Merkle tree contains 7 leaf nodes (transactions) + let index = 2; // index of the leaf we want to prove + + // checking membership of 3rd leaf + assert.isTrue(await instance.checkMembership.call(toHex(leaf), index, toHex(rootHash), toHex(proof), total), "Didn't prove membership"); + + + // checking membership of 4th leaf + leaf = "0xfbec635c930057fdc76939052216ed2aed7af618109b983ee1ae7b13c909f2dd"; + proof = "0x714c0269d202e4302fadab4d62a4e9171fbf09508cb589616342cce45981d3291a61753dc9eccd5506144081ba000a8e44c9262be17ab934dda9e4fa10495fccdbb108450dad46789d67f5cfb5ee4be7f505bd8835a0867410412f22cfda8ad5"; + index = 3; + assert.isTrue(await instance.checkMembership.call(toHex(leaf), index, toHex(rootHash), toHex(proof), total), "Didn't prove membership"); + }); + + it("Verifies the membership in a merkle tree with only one transaction", async () => { + let leafHash = web3.sha3("inputSeed"); + + let root, proof; + [root, proof] = generateMerkleRootAndProof([leafHash], 0); + + assert.isTrue(await instance.checkMembership.call(toHex(leafHash), 0, toHex(root), toHex(proof), 1), "Didn't prove membership"); + }); + + it("Catches bad input on checkMembership", async () => { + let leafHash1 = web3.sha3("inputSeed1"); + let leafHash2 = web3.sha3("inputSeed2"); + let leafHash3 = web3.sha3("inputSeed3"); + + let root, proof; + [root, proof] = generateMerkleRootAndProof([leafHash1, leafHash2, leafHash3], 0); + + let badLeafHash = web3.sha3("wrongInputSeed", {encoding: 'hex'}); + assert.isFalse(await instance.checkMembership.call(toHex(badLeafHash), 0, toHex(root), toHex(proof), 3), "Returned true on wrong leaf"); + + assert.isFalse(await instance.checkMembership.call(toHex(leafHash1), 1, toHex(root), toHex(proof), 3), "Returned true on wrong index"); + + let badRoot = web3.sha3("wrongRoot", {encoding: 'hex'}); + assert.isFalse(await instance.checkMembership.call(toHex(leafHash1), 0, toHex(badRoot), toHex(proof), 3), "Returned true on wrong root"); + + let badProof = "a".repeat(proof.length - 2); + assert.isFalse(await instance.checkMembership.call(toHex(leafHash1), 0, toHex(root), toHex(badProof), 3), "Returned true on wrong proof"); + + let err; + [err] = await catchError(instance.checkMembership.call(toHex(leafHash1), 0, toHex(root), toHex(proof + "0000"), 3)); + if (!err) + assert.fail("Didn't revert on an proof with the bad size"); + }); + + it("Verifies membership in a merkle tree with multiple transactions", async () => { + let leafHash1 = web3.sha3("inputSeed1"); + let leafHash2 = web3.sha3("inputSeed2"); + let leafHash3 = web3.sha3("inputSeed3"); + let leafHash4 = web3.sha3("inputSeed4"); + let leafHash5 = web3.sha3("inputSeed5"); + + let root, proof; + [root, proof] = generateMerkleRootAndProof([leafHash1, leafHash2, leafHash3, leafHash4, leafHash5], 0); + assert.isTrue(await instance.checkMembership.call(toHex(leafHash1), 0, toHex(root), toHex(proof), 5), "Didn't prove membership"); + + [root, proof] = generateMerkleRootAndProof([leafHash1, leafHash2, leafHash3, leafHash4, leafHash5], 1); + assert.isTrue(await instance.checkMembership.call(toHex(leafHash2), 1, toHex(root), toHex(proof), 5), "Didn't prove membership"); + + [root, proof] = generateMerkleRootAndProof([leafHash1, leafHash2, leafHash3, leafHash4, leafHash5], 2); + assert.isTrue(await instance.checkMembership.call(toHex(leafHash3), 2, toHex(root), toHex(proof), 5), "Didn't prove membership"); + + [root, proof] = generateMerkleRootAndProof([leafHash1, leafHash2, leafHash3, leafHash4, leafHash5], 3); + assert.isTrue(await instance.checkMembership.call(toHex(leafHash4), 3, toHex(root), toHex(proof), 5), "Didn't prove membership"); + + [root, proof] = generateMerkleRootAndProof([leafHash1, leafHash2, leafHash3, leafHash4, leafHash5], 4); + assert.isTrue(await instance.checkMembership.call(toHex(leafHash5), 4, toHex(root), toHex(proof), 5), "Didn't prove membership"); + }); +}); diff --git a/contracts/test/libraries/bytesUtil.js b/contracts/test/libraries/bytesUtil.js new file mode 100644 index 0000000..53113e1 --- /dev/null +++ b/contracts/test/libraries/bytesUtil.js @@ -0,0 +1,43 @@ +let assert = require('chai').assert; + +let BytesUtil_Test = artifacts.require("BytesUtil_Test"); + +let { catchError, toHex } = require('../utilities.js'); + +contract('BytesUtil', async (accounts) => { + let instance; + before(async () => { + instance = await BytesUtil_Test.new(); + }); + + it("Slices bytes correctly", async () => { + let inputHash = web3.sha3("inputSeed"); + + assert.equal((await instance.slice.call(toHex(inputHash), 0, 32)).toString(), inputHash, "Slice didn't get entire substring"); + + assert.equal((await instance.slice.call(toHex(inputHash), 0, 16)).toString(), toHex(inputHash.substring(2,34)), "Didn't get first half of the hash"); + assert.equal((await instance.slice.call(toHex(inputHash), 16, 16)).toString(), toHex(inputHash.substring(34)), "Didn't get second half of the hash"); + + assert.equal((await instance.slice.call(toHex(inputHash), 0, 8)).toString(), toHex(inputHash.substring(2,18)), "Didn't get first quarter of the hash"); + assert.equal((await instance.slice.call(toHex(inputHash), 8, 24)).toString(), toHex(inputHash.substring(18)), "Didn't get rest of the hash"); + }); + + it("Reverts if trying to slice out of range", async () => { + let inputHash = web3.sha3("inputSeed"); + + // sha3 maps input to a 32 byte hash (64 charac + let err; + [err] = await catchError(instance.slice.call(toHex(inputHash), 1, 32)); + if (!err) + assert.fail("slice did not revert when inputs produce an out of bounds error"); + }); + + it("Can slice bytes larger than a evm word size", async () => { + let input = "0x"; + for (let i = 0; i < 100; i++) { // 50 bytes + input += Math.floor(Math.random()*10) // include a random hex digit from 0-9 + } + + assert.equal((await instance.slice.call(toHex(input), 1, 40)).toString(), toHex(input.substring(4, 84)), "Didn't copy over a whole word size then left over bytes"); + }); +}); diff --git a/contracts/test/libraries/priorityQueue.js b/contracts/test/libraries/priorityQueue.js new file mode 100644 index 0000000..b363b3c --- /dev/null +++ b/contracts/test/libraries/priorityQueue.js @@ -0,0 +1,132 @@ +let assert = require('chai').assert; + +let PriorityQueue_Test = artifacts.require("PriorityQueue_Test"); +let { catchError } = require('../utilities.js'); + +contract('PriorityQueue', async (accounts) => { + let instance; + beforeEach(async () => { + instance = await PriorityQueue_Test.new(); + }); + + it("Reverts when deleting on an empty queue", async() => { + let err; + [err] = await catchError(instance.getMin.call()); + if (!err) + assert.fail("Didn't revert on getting min of an empty queue."); + + [err] = await catchError(instance.delMin()); + if (!err) + assert.fail("Didn't revert on deleting min of an empty queue."); + }); + + it("Correctly adds then remove elements", async () => { + await instance.insert(2); + await instance.insert(1); + await instance.insert(3); + + assert.equal((await instance.getMin.call()).toNumber(), 1, "Did not delete correct minimum"); + + await instance.delMin(); + assert.equal((await instance.getMin.call()).toNumber(), 2, "Did not delete correct minimum"); + + await instance.delMin(); + assert.equal((await instance.getMin.call()).toNumber(), 3, "Did not delete correct minimum"); + + await instance.delMin(); + assert.equal((await instance.currentSize.call()).toNumber(), 0, "Size is not zero"); + }); + + it("Handles ascending inserts", async () => { + let currSize = (await instance.currentSize.call()).toNumber(); + for (i = 1; i < 6; i++) { + await instance.insert(i); + } + + currSize = (await instance.currentSize.call()).toNumber(); + assert.equal(currSize, 5, "currentSize did not increment"); + + let min = (await instance.getMin.call()).toNumber(); + assert.equal(1, min, "getMin did not return the minimum"); + + for (i = 0; i < 3; i++) { + await instance.delMin(); + } + + min = (await instance.getMin.call()).toNumber(); + currSize = (await instance.currentSize.call()).toNumber(); + assert.equal(min, 4, "delMin deleted priorities out of order"); + assert.equal(currSize, 2, "currSize did not decrement"); + + // Clear the queue + for (i = 0; i < 2; i++) { + await instance.delMin(); + } + currSize = (await instance.currentSize.call()).toNumber(); + assert.equal(currSize, 0, "The priority queue has not been emptied"); + }); + + it("Can insert, delete min, then insert again", async () => { + for (i = 1; i < 6; i++) { + await instance.insert(i); + let min = (await instance.getMin.call()).toNumber(); + assert.equal(min, 1, "getMin does not return minimum element in pq."); + } + + // partially clear the pq + for (i = 0; i < 3; i++) { + await instance.delMin(); + } + min = (await instance.getMin.call()).toNumber(); + assert.equal(min, 4, "delMin deleted priorities out of order"); + + // insert to pq after partial delete + for (i = 2; i < 4; i++) { + await instance.insert(i); + let min = (await instance.getMin.call()).toNumber(); + assert.equal(min, 2, "getMin does not return minimum element in pq."); + } + // clear the pq + for (i = 0; i < 4; i++) { + await instance.delMin(); + } + + currSize = (await instance.currentSize.call()).toNumber(); + assert.equal(currSize, 0, "The priority queue has not been emptied"); + }); + + it("Handles duplicate entries", async () => { + let currentSize = (await instance.currentSize.call()).toNumber(); + assert.equal(currentSize, 0, "The size is not 0"); + + await instance.insert(10); + let min = (await instance.getMin.call()).toNumber(); + currentSize = (await instance.currentSize.call()).toNumber(); + assert.equal(currentSize, 1, "The size is not 0"); + + // Breaks here - has min as 0 + assert.equal(min, 10, "First insert did not work"); + + await instance.insert(10); + min = (await instance.getMin.call()).toNumber(); + assert.equal(min, 10, "Second insert of same priority did not work"); + await instance.insert(5); + await instance.insert(5); + + currentSize = (await instance.currentSize.call()).toNumber(); + assert.equal(currentSize, 4, "The currentSize is incorrect") + + await instance.delMin(); + min = (await instance.getMin.call()).toNumber(); + assert.equal(min, 5, "PriorityQueue did not handle same priorities correctly"); + + await instance.delMin(); + await instance.delMin(); + + min = (await instance.getMin.call()).toNumber(); + assert.equal(min, 10, "PriorityQueue did not delete duplicate correctly") + + await instance.delMin(); + assert.equal(await instance.currentSize.call(), 0); + }); +}); diff --git a/contracts/test/libraries/validator.js b/contracts/test/libraries/validator.js new file mode 100644 index 0000000..96cd00e --- /dev/null +++ b/contracts/test/libraries/validator.js @@ -0,0 +1,167 @@ +let assert = require('chai').assert; + +let Validator_Test = artifacts.require("Validator_Test"); +let { catchError, toHex } = require('../utilities.js'); +let { generateMerkleRootAndProof } = require('../plasmamvp/plasmamvp_helpers.js'); + +contract('Validator', async (accounts) => { + let instance; + before(async () => { + instance = await Validator_Test.new(); + }); + + it("Correctly recovers the signee of a signature", async () => { + // create tx hash + let txHash = web3.sha3("inputSeed"); + + let signer1 = accounts[1]; + // create tx sigs + let txSigs1 = await web3.eth.sign(signer1, txHash); + + let signer2 = accounts[2]; + // create tx sigs + let txSigs2 = await web3.eth.sign(signer2, txHash); + + assert.equal((await instance.recover.call(txHash, txSigs1)).toString(), signer1, "Recovered incorrect address"); + assert.equal((await instance.recover.call(txHash, txSigs2)).toString(), signer2, "Recovered incorrect address"); + assert.notEqual((await instance.recover.call(txHash, txSigs1)).toString(), (await instance.recover.call(txHash, txSigs2)).toString(), + "Recovered the same address"); + }); + + it("Correctly checks signatures", async () => { + let signer = accounts[5]; + let invalidSigner = accounts[6]; + + let txHash = web3.sha3("tx bytes to be hashed"); + let sig0 = await web3.eth.sign(signer, txHash); + let sig1 = Buffer.alloc(65).toString('hex'); + + let confirmationHash = web3.sha3("merkle leaf hash concat with root hash"); + + let confirmSignatures = await web3.eth.sign(signer, confirmationHash); + + let invalidConfirmSignatures = await web3.eth.sign(invalidSigner, confirmationHash); + + // assert valid confirmSignatures will pass checkSignatures + assert.isTrue(await instance.checkSignatures.call(txHash, toHex(confirmationHash), false, toHex(sig0), toHex(sig1), toHex(confirmSignatures)), + "checkSignatures should pass"); + + // assert invalid confirmSignatures will not pass checkSignatures + assert.isFalse(await instance.checkSignatures.call(txHash, toHex(confirmationHash), false, toHex(sig0), toHex(sig1), toHex(invalidConfirmSignatures)), + "checkSignatures should not pass given invalid confirmSignatures"); + }); + + it("Correctly handles empty signatures", async () => { + let singleEmptyConfirmSig = Buffer.alloc(65).toString('hex'); + let doubleEmptyConfirmSigs = Buffer.alloc(130).toString('hex'); + let emptySig0 = Buffer.alloc(65).toString('hex'); + let emptySig1 = Buffer.alloc(65).toString('hex'); + + let txHash = web3.sha3(Buffer.alloc(65).toString('hex'), {encoding: 'hex'}); + let confirmationHash = web3.sha3(Buffer.alloc(65).toString('hex'), {encoding: 'hex'}); + + assert.isFalse(await instance.checkSignatures.call(txHash, toHex(confirmationHash), false, toHex(emptySig0), toHex(emptySig1), toHex(singleEmptyConfirmSig)), + "checkSignatures should not pass given empty tx sigs and confirm signatures"); + + assert.isFalse(await instance.checkSignatures.call(txHash, toHex(confirmationHash), true, toHex(emptySig0), toHex(emptySig1), toHex(doubleEmptyConfirmSigs)), + "checkSignatures should not pass given empty tx sigs and confirm signatures"); + }); + + it("Checks incorrect signature lengths", async () => { + let confirmSignatures = Buffer.alloc(65).toString('hex'); + let sig0 = Buffer.alloc(65).toString('hex'); + let emptySig1 = Buffer.alloc(65).toString('hex'); + + let txHash = web3.sha3(Buffer.alloc(65).toString('hex'), {encoding: 'hex'}); + let confirmationHash = web3.sha3(Buffer.alloc(65).toString('hex'), {encoding: 'hex'}); + + let err; + [err] = await catchError(instance.checkSignatures.call(txHash, toHex(confirmationHash), false, toHex(sig0 + "0000"), toHex(emptySig1), toHex(confirmSignatures))); + if (!err) + assert.fail("Didn't revert on signature of wrong size"); + + [err] = await catchError(instance.checkSignatures.call(txHash, toHex(confirmationHash), false, toHex(sig0), toHex(emptySig1), toHex(confirmSignatures + "0000"))); + if (!err) + assert.fail("Didn't revert on confirm signature of wrong size"); + }); + + it("Allows for only the first signature to be present", async () => { + // create txHash + let txBytes = web3.sha3("inputSeed"); + let txHash = web3.sha3(txBytes.toString('hex'), {encoding: 'hex'}); + + // create sigs + let signer = accounts[4]; + let sigOverTxHash = await web3.eth.sign(signer, txHash); + let sig1 = Buffer.alloc(65).toString('hex'); + + // create confirmationHash + let merkleHash = web3.sha3(txHash.slice(2) + sigOverTxHash.slice(2), {encoding: 'hex'}); + let rootHash = generateMerkleRootAndProof([merkleHash], 0)[0]; + let confirmationHash = web3.sha3(merkleHash.slice(2) + rootHash, {encoding: 'hex'}); + + // create confirmSignatures + let confirmSignatures = await web3.eth.sign(signer, confirmationHash); + + assert.isTrue(await instance.checkSignatures.call(txHash, toHex(confirmationHash), false, toHex(sigOverTxHash), toHex(sig1), toHex(confirmSignatures)), + "checkSignatures should pass"); + }); + + it("Asserts that the first input cannot be empty", async () => { + // create txHash + let txBytes = web3.sha3("inputSeed"); + let txHash = web3.sha3(txBytes.toString('hex'), {encoding: 'hex'}); + + // create sigs + let signer = accounts[4]; + let sig0 = Buffer.alloc(65).toString('hex'); + let sigOverTxHash = (await web3.eth.sign(signer, txHash)).slice(2); + + // create confirmationHash + let merkleHash = web3.sha3(txHash.slice(2) + sigOverTxHash, {encoding: 'hex'}); + let rootHash = generateMerkleRootAndProof([merkleHash], 0)[0]; + let confirmationHash = web3.sha3(merkleHash.slice(2) + rootHash, {encoding: 'hex'}); + + // create confirmSignatures + let confirmSignatures = Buffer.alloc(65).toString('hex'); + confirmSignatures += (await web3.eth.sign(signer, confirmationHash)).slice(2); + + assert.isFalse(await instance.checkSignatures.call(txHash, toHex(confirmationHash), true, toHex(sig0), toHex(sigOverTxHash), toHex(confirmSignatures)), + "checkSignatures should not pass given an empty first confirmsig and non-empty second confirmsig"); + }); + + it("Handles incorrect transaction signatures", async () => { + // create txHash + let txBytes = web3.sha3("inputSeed"); + let txHash = web3.sha3(txBytes.toString('hex'), {encoding: 'hex'}); + + // create sigs + let signer0 = accounts[4]; + let signer1 = accounts[5]; + let invalidSigner = accounts[6]; + let invalidSigner2 = accounts[7]; + + // second tx sig is invalid + let sig0 = await web3.eth.sign(signer0, txHash); + let validSig = await web3.eth.sign(signer1, txHash).slice(2); + let invalidSig = await web3.eth.sign(invalidSigner, txHash).slice(2); + + // create confirmationHash + let merkleHash = web3.sha3(txHash.slice(2) + validSig.slice(2), {encoding: 'hex'}); + let rootHash = generateMerkleRootAndProof([merkleHash], 0)[0]; + let confirmationHash = web3.sha3(merkleHash.slice(2) + rootHash, {encoding: 'hex'}); + // create confirmSignatures + let confirmSignatures = await web3.eth.sign(signer0, confirmationHash); + confirmSignatures += await web3.eth.sign(signer1, confirmationHash).slice(2); + // create invalid confirmSignatures + let invalidConfirmSignatures = await web3.eth.sign(invalidSigner, confirmationHash); + invalidConfirmSignatures += await web3.eth.sign(invalidSigner2, confirmationHash).slice(2); + + assert.isFalse(await instance.checkSignatures.call(txHash, toHex(confirmationHash), true, toHex(sig0), toHex(invalidSig), toHex(confirmSignatures)), + "checkSignatures should not pass given invalid transaction sigs"); + assert.isFalse(await instance.checkSignatures.call(txHash, toHex(confirmationHash), true, toHex(sig0), toHex(validSig), toHex(invalidConfirmSignatures)), + "checkSignatures should not pass given invalid transaction sigs"); + assert.isTrue(await instance.checkSignatures.call(txHash, toHex(confirmationHash), true, toHex(sig0), toHex(validSig), toHex(confirmSignatures)), + "checkSignatures should pass for valid transaction sigs"); + }); +}); diff --git a/contracts/test/plasmamvp/blockSubmissions.js b/contracts/test/plasmamvp/blockSubmissions.js new file mode 100644 index 0000000..7ee52d7 --- /dev/null +++ b/contracts/test/plasmamvp/blockSubmissions.js @@ -0,0 +1,92 @@ +let assert = require('chai').assert; + +let PlasmaMVP = artifacts.require("PlasmaMVP"); + +let { toHex, catchError } = require('../utilities.js'); + +contract('[PlasmaMVP] Block Submissions', async (accounts) => { + let instance; + let authority = accounts[0]; + let minExitBond = 10000; + beforeEach(async () => { + instance = await PlasmaMVP.new({from: authority}); + }); + + it("Submit block from authority", async () => { + let root = web3.sha3('1234'); + let tx = await instance.submitBlock([root], [1], [0], 1, {from: authority}); + + // BlockSubmitted event + assert.equal(tx.logs[0].args.root, root, "incorrect block root in BlockSubmitted event"); + assert.equal(tx.logs[0].args.blockNumber.toNumber(), 1, "incorrect block number in BlockSubmitted event"); + assert.equal(tx.logs[0].args.numTxns.toNumber(), 1, "incorrect block size in BlockSubmitted event"); + assert.equal(tx.logs[0].args.feeAmount.toNumber(), 0, "incorrect block fee amount in BlockSubmitted event"); + + assert.equal((await instance.childChain.call(1))[0], root, 'Child block merkle root does not match submitted merkle root.'); + }); + + it("Submit block from someone other than authority", async () => { + let prev = (await instance.lastCommittedBlock.call()).toNumber(); + + let [err] = await catchError(instance.submitBlock([web3.sha3('578484785954')], [1], [0], 1, {from: accounts[1]})); + if (!err) + assert.fail("Submitted blocked without being the authority"); + + let curr = (await instance.lastCommittedBlock.call()).toNumber(); + assert.equal(prev, curr, "Child blocknum incorrectly changed"); + }); + + it("Can submit more than one merkle root", async () => { + let root1 = web3.sha3("root1").slice(2); + let root2 = web3.sha3("root2").slice(2); + + await instance.submitBlock([toHex(root1), toHex(root2)], [1, 2], [0, 0], 1, {from: authority}); + + assert.equal((await instance.lastCommittedBlock.call()).toNumber(), 2, "blocknum incremented incorrectly"); + assert.equal((await instance.childChain.call(1))[0], toHex(root1), "mismatch in block root"); + assert.equal((await instance.childChain.call(2))[0], toHex(root2), "mismatch in block root"); + }); + + it("Can submit fees for a block", async () => { + let root1 = web3.sha3("root1").slice(2); + let root2 = web3.sha3("root2").slice(2); + + let fees = [100, 200]; + + await instance.submitBlock([toHex(root1), toHex(root2)], [1, 2], fees, 1, {from: authority}); + + assert.equal((await instance.lastCommittedBlock.call()).toNumber(), 2, "blocknum incremented incorrectly"); + assert.equal((await instance.childChain.call(1))[0], toHex(root1), "mismatch in block root"); + assert.equal((await instance.childChain.call(2))[0], toHex(root2), "mismatch in block root"); + assert.equal((await instance.childChain.call(1))[2], fees[0], "mismatch in block fees"); + assert.equal((await instance.childChain.call(2))[2], fees[1], "mismatch in block fees"); + }); + + it("Cannot exceed size limits for a block", async () => { + let root = web3.sha3("root1").slice(2); + + let fees = [100]; + let numTxns0 = [65535] + let numTxns1 = [65536]; + + let err; + // numTxns1 exceeds block size limit + [err] = await catchError(instance.submitBlock([toHex(root)], numTxns1, fees, 1, {from: authority})); + if (!err) + assert.fail("Allowed submission of a block that exceeds block size limit"); + + // numTxns0 does not exceed block size limit + await instance.submitBlock([toHex(root)], numTxns0, fees, 1, {from: authority}); + }); + + it("Enforces block number ordering", async () => { + let root1 = web3.sha3("root1").slice(2) + let root3 = web3.sha3("root3").slice(2) + + await instance.submitBlock([toHex(root1)], [1], [0], 1); + let err; + [err] = await catchError(instance.submitBlock([toHex(root3)], [1], [0],3)); + if (!err) + assert.fail("Allowed block submission with inconsistent ordering"); + }); +}); diff --git a/contracts/test/plasmamvp/deposits.js b/contracts/test/plasmamvp/deposits.js new file mode 100644 index 0000000..aa25a90 --- /dev/null +++ b/contracts/test/plasmamvp/deposits.js @@ -0,0 +1,265 @@ +let RLP = require('rlp'); +let assert = require('chai').assert; + +let PlasmaMVP = artifacts.require("PlasmaMVP"); + +let { fastForward, proof, zeroHashes, sha256String, generateMerkleRootAndProof } = require('./plasmamvp_helpers.js'); +let { catchError, toHex } = require('../utilities.js'); + +contract('[PlasmaMVP] Deposits', async (accounts) => { + let instance; + let one_week = 604800; // in seconds + let minExitBond = 10000; + + let authority = accounts[0]; + beforeEach(async () => { + instance = await PlasmaMVP.new({from: authority}); + }); + + it("Catches Deposit event", async () => { + let nonce = (await instance.depositNonce.call()).toNumber(); + let tx = await instance.deposit(accounts[1], {from: accounts[1], value: 100}); + // check Deposit event + assert.equal(tx.logs[0].args.depositor, accounts[1], "incorrect deposit owner"); + assert.equal(tx.logs[0].args.amount.toNumber(), 100, "incorrect deposit amount"); + assert.equal(tx.logs[0].args.depositNonce, nonce, "incorrect deposit nonce"); + }); + + it("Allows deposits of funds into a different address", async () => { + let nonce = (await instance.depositNonce.call()).toNumber(); + let tx = await instance.deposit(accounts[2], {from: accounts[1], value: 100}); + + // check Deposit event + assert.equal(tx.logs[0].args.depositor, accounts[2], "incorrect deposit owner"); + assert.equal(tx.logs[0].args.amount.toNumber(), 100, "incorrect deposit amount"); + assert.equal(tx.logs[0].args.depositNonce, nonce, "incorrect deposit nonce"); + + // check instance deposit mapping + let deposit = await instance.deposits.call(nonce); + assert.equal(deposit[0], accounts[2], "incorrect deposit owner"); + assert.equal(deposit[1], 100, "incorrect deposit amount"); + }); + + it("Only allows deposit owner to start a deposit exit", async () => { + let nonce = (await instance.depositNonce.call()).toNumber(); + await instance.deposit(accounts[2], {from: accounts[1], value: 100}); + let err; + + // accounts[1] cannot start exit because it's not the owner + [err] = await catchError(instance.startDepositExit(nonce, 0, {from: accounts[1], value: minExitBond})); + if (!err) + assert.fail("Non deposit owner allowed to start an exit"); + + //accounts[2] should be able to start exit + await instance.startDepositExit(nonce, 0, {from: accounts[2], value: minExitBond}); + }); + + it("Rejects exiting a deposit twice", async () => { + let nonce = (await instance.depositNonce.call()).toNumber(); + await instance.deposit(accounts[2], {from: accounts[2], value: 100}); + await instance.startDepositExit(nonce, 0, {from: accounts[2], value: minExitBond}); + + let err; + [err] = await catchError(instance.startDepositExit(nonce, 0, {from: accounts[2], value: minExitBond})); + if (!err) + assert.fail("Started an exit for the same deposit twice."); + }); + + it("Catches StartedDepositExit event", async () => { + let nonce = (await instance.depositNonce.call()).toNumber(); + await instance.deposit(accounts[2], {from: accounts[2], value: 100}); + let tx = await instance.startDepositExit(nonce, 0, {from: accounts[2], value: minExitBond}); + + assert.equal(tx.logs[0].args.nonce.toNumber(), nonce, "StartedDepositExit event emits incorrect nonce"); + assert.equal(tx.logs[0].args.owner, accounts[2], "StartedDepositExit event emits incorrect owner"); + assert.equal(tx.logs[0].args.amount.toNumber(), 100, "StartedDepositExit event emits incorrect amount"); + }); + + it("Requires sufficient bond and refunds excess if overpayed", async () => { + let nonce = (await instance.depositNonce.call()).toNumber(); + await instance.deposit(accounts[2], {from: accounts[2], value: 100}); + + let err; + [err] = await catchError(instance.startDepositExit(nonce, 0, {from: accounts[2], value: minExitBond-10})); + if (!err) + assert.fail("started exit with insufficient bond"); + + await instance.startDepositExit(nonce, 0, {from: accounts[2], value: minExitBond+10}); + + let balance = (await instance.balanceOf.call(accounts[2])).toNumber(); + assert.equal(balance, 10, "excess for overpayed bond not refunded to sender"); + }); + + it("Can start and finalize a deposit exit. Child chain balance should reflect accordingly", async () => { + let nonce = (await instance.depositNonce.call()).toNumber(); + await instance.deposit(accounts[2], {from: accounts[2], value: 100}); + + let childChainBalance = (await instance.childChainBalance.call()).toNumber(); + assert.equal(childChainBalance, 100); + + await instance.startDepositExit(nonce, 0, {from: accounts[2], value: minExitBond}); + await fastForward(one_week + 100); + await instance.finalizeDepositExits(); + + childChainBalance = (await instance.childChainBalance.call()).toNumber(); + assert.equal(childChainBalance, 0); + + let balance = (await instance.balanceOf.call(accounts[2])).toNumber(); + assert.equal(balance, 100 + minExitBond, "deposit exit not finalized after a week"); + + let exit = await instance.depositExits.call(nonce); + assert.equal(exit[4], 3, "exit's state not set to finalized"); + }); + + it("Cannot reopen a finalized deposit exit", async () => { + let nonce = (await instance.depositNonce.call()).toNumber(); + await instance.deposit(accounts[2], {from: accounts[2], value: 100}); + await instance.startDepositExit(nonce, 0, {from: accounts[2], value: minExitBond}); + + await fastForward(one_week + 100); + + await instance.finalizeDepositExits(); + let err; + [err] = await catchError(instance.startDepositExit(nonce, 0, {from: accounts[2], value: minExitBond})); + if (!err) + assert.fail("reopened a finalized deposit exit"); + }); + + it("Correctly challenge a spent deposit", async () => { + let nonce = (await instance.depositNonce.call()).toNumber(); + await instance.deposit(accounts[2], {from: accounts[2], value: 100}); + + // construct transcation with first input as the deposit + let msg = Array(17).fill(0); + msg[3] = nonce; msg[12] = accounts[1]; msg[13] = 100; + let encodedMsg = RLP.encode(msg); + let hashedEncodedMsg = web3.sha3(encodedMsg.toString('hex'), {encoding: 'hex'}); + + // create signature by deposit owner. Second signature should be zero + let sigList = Array(2).fill(0); + sigList[0] = (await web3.eth.sign(accounts[2], hashedEncodedMsg)); + + let txBytes = Array(2).fill(0); + txBytes[0] = msg; txBytes[1] = sigList; + txBytes = RLP.encode(txBytes); + + // create signature by deposit owner. Second signature should be zero + let sigs = (await web3.eth.sign(accounts[2], hashedEncodedMsg)); + sigs = sigs + Buffer.alloc(65).toString('hex'); + + let merkleHash = sha256String(txBytes.toString('hex')); + + // include this transaction in the next block + let root; + [root, proof] = generateMerkleRootAndProof([merkleHash], 0); + + let blockNum = (await instance.lastCommittedBlock.call()).toNumber() + 1; + await instance.submitBlock([toHex(root)], [1], [0], blockNum, {from: authority}); + + // create the confirm sig + let confirmHash = sha256String(merkleHash + root.slice(2)); + let confirmSig = await web3.eth.sign(accounts[2], confirmHash); + + // start the malicious exit + await instance.startDepositExit(nonce, 0, {from: accounts[2], value: minExitBond}); + + // checks matching inputs + let err; + [err] = await catchError(instance.challengeExit([0,0,0,nonce-1], [blockNum, 0], + toHex(txBytes), toHex(sigs), toHex(proof), toHex(confirmSig), {from: accounts[3]})); + if (!err) + assert.fail("did not check against matching inputs"); + + // correctly challenge + await instance.challengeExit([0,0,0,nonce], [blockNum, 0], + toHex(txBytes), toHex(proof), toHex(confirmSig), {from: accounts[3]}); + + let balance = (await instance.balanceOf.call(accounts[3])).toNumber(); + assert.equal(balance, minExitBond, "challenger not awarded exit bond"); + + let exit = await instance.depositExits.call(nonce); + assert.equal(exit[4], 2, "exit state not changed to challenged"); + + // Cannot challenge twice + [err] = await catchError(instance.challengeExit([0,0,0,nonce], [blockNum, 0], + toHex(txBytes), toHex(sigs), toHex(proof), toHex(confirmSig), {from: accounts[3]})); + if (!err) + assert.fail("Allowed a challenge for an exit already challenged"); + }); + + it("Allows operator to challenge a deposit spend committing to an incorrect fee", async () => { + let nonce = (await instance.depositNonce.call()).toNumber(); + await instance.deposit(accounts[2], {from: accounts[2], value: 100}); + + let txList = Array(17).fill(0); + txList[3] = nonce; txList[16] = 5; // fee + txList[12] = accounts[1]; txList[13] = 100; + let txHash = web3.sha3(RLP.encode(txList).toString('hex'), {encoding: 'hex'}); + let sigs = [toHex(await web3.eth.sign(accounts[2], txHash)), toHex(Buffer.alloc(65).toString('hex'))]; + + let txBytes = [txList, sigs]; + txBytes = RLP.encode(txBytes).toString('hex'); + + // submit the block + let merkleHash = sha256String(txBytes); + let [merkleRoot, proof] = generateMerkleRootAndProof([merkleHash], 0); + let blockNum = (await instance.lastCommittedBlock.call()).toNumber() + 1; + await instance.submitBlock([toHex(merkleRoot)], [1], [5], blockNum, {from: authority}); + + // exit the deposit not committing to the fee + await instance.startDepositExit(nonce, 1, {from: accounts[2], value: minExitBond}); + + // challenge the exit + await instance.challengeFeeMismatch([0,0,0,nonce], [blockNum, 0], toHex(txBytes), toHex(proof)); + + let exit = await instance.depositExits.call(nonce); + assert.equal(exit[4].toNumber(), 0, "exit state not changed to non existent"); + + // start the exit again with the correct committed fee + await instance.startDepositExit(nonce, 5, {from: accounts[2], value: minExitBond}); + + // try challenge the exit + let err; + [err] = await catchError(instance.challengeFeeMismatch([0,0,0,nonce], [blockNum, 0], toHex(txBytes), toHex(proof))); + if (!err) + assert.fail("operator challenged exit with correct committed fee"); + }); + + it("Attempts a withdrawal delay attack on exiting deposits", async () => { + + /* 1. Start exit for nonce_2 (newest) + * 2. Start exit for nonce_1, nonce_0 in the same eth block + * 3. Check exit ordering: nonce_2, nonce_0, nonce_1 + */ + + let nonce_0 = (await instance.depositNonce.call()).toNumber(); + await instance.deposit(accounts[2], {from: accounts[2], value: 100}); + + let nonce_1 = (await instance.depositNonce.call()).toNumber(); + await instance.deposit(accounts[2], {from: accounts[2], value: 100}); + + let nonce_2 = (await instance.depositNonce.call()).toNumber(); + await instance.deposit(accounts[2], {from: accounts[2], value: 100}); + + // exit nonce_2 + await instance.startDepositExit(nonce_2, 0, {from: accounts[2], value: minExitBond}); + + // first exit should be in a different eth block + fastForward(10); + + // exit nonce_1 then nonce_0 in the same ethereum block + async function exits(nonce_1, nonce_0) { + let p1 = instance.startDepositExit(nonce_1, 0, {from: accounts[2], value: minExitBond}); + let p2 = instance.startDepositExit(nonce_0, 0, {from: accounts[2], value: minExitBond}); + return Promise.all([p1, p2]); + } + await exits(nonce_1, nonce_0); + + fastForward(one_week); + let depositExits = await instance.finalizeDepositExits({from: authority}); + assert.equal(depositExits.logs[0].args.position.toString(), [0, 0, 0, nonce_2].toString(), "nonce_2 was not finalized first"); + + assert.equal(depositExits.logs[2].args.position.toString(), [0, 0, 0, nonce_0].toString(), "nonce_0 was not finalized second"); + assert.equal(depositExits.logs[4].args.position.toString(), [0, 0, 0, nonce_1].toString(), "nonce_1 was not finalized last"); + }); +}); diff --git a/contracts/test/plasmamvp/plasmamvp_helpers.js b/contracts/test/plasmamvp/plasmamvp_helpers.js new file mode 100644 index 0000000..3754c8b --- /dev/null +++ b/contracts/test/plasmamvp/plasmamvp_helpers.js @@ -0,0 +1,87 @@ +let RLP = require('rlp'); +let ethjs_util = require('ethereumjs-util'); + +let { toHex } = require('../utilities.js'); + +// Fast forward 1 week +let fastForward = async function(time) { + await web3.currentProvider.send({jsonrpc: "2.0", method: "evm_mine", params: [], id: 0}); + let oldTime = (await web3.eth.getBlock(await web3.eth.blockNumber)).timestamp; + + // fast forward + await web3.currentProvider.send({jsonrpc: "2.0", method: "evm_increaseTime", params: [time], id: 0}); + + await web3.currentProvider.send({jsonrpc: "2.0", method: "evm_mine", params: [], id: 0}); + let currTime = (await web3.eth.getBlock(await web3.eth.blockNumber)).timestamp; + + assert.isAtLeast(currTime - oldTime, time, `Block time was not fast forwarded by at least ${time} seconds`); +} + +// SHA256 hash the input and returns it in string form. +// Expects a hex input. +let sha256String = function(input) { + return toHex(ethjs_util.sha256(toHex(input)).toString('hex')); +}; + +// SHA256 hashes together 2 inputs and returns it in string form. +// Expects hex inputs, and prepend each input with a 0x20 byte literal. +// Tendermint prefixes intermediate hashes with 0x20 bytes literals +// before hashing them. +let sha256StringMultiple = function(input1, input2) { + let toHash = "0x20" + input1.slice(2) + "20" + input2.slice(2); + return toHex(ethjs_util.sha256(toHash).toString('hex')); +}; + +// For a given list of leaves, this function constructs a simple merkle tree. +// It returns the merkle root and the merkle proof for the txn at index. +// @param leaves The leaves for which this function generates a merkle root and proof +// @param txIndex The leaf for which this function generates a merkle proof +// +// Simple Tree: https://tendermint.com/docs/spec/blockchain/encoding.html#merkle-trees +let generateMerkleRootAndProof = function(leaves, index) { + if (leaves.length == 0) { // If there are no leaves, then we can't generate anything + return ["", ""]; + } else if (leaves.length == 1) { // If there's only 1 leaf, return it with and empty proof + return [leaves[0], ""]; + } else { + let pivot = Math.floor((leaves.length + 1) / 2); + + let left, right; + let proof = ""; + + // If the index will be in the left subtree (index < pivot), then we + // need to generate the proof using the intermediary hash from the right + // side. Otherwise, do the reverse. + if (index < pivot) { + // recursively call the function on the leaves that will be in the + // left and right sub trees. + left = generateMerkleRootAndProof(leaves.slice(0, pivot), index); + right = generateMerkleRootAndProof(leaves.slice(pivot, leaves.length), -1); + + // add current level's right intermediary hash to the proof + if (index >= 0) { + proof = left[1] + right[0].slice(2); + } + } else { + // recursively call the function on the leaves that will be in the + // left and right sub trees. + // since the index will be in the right sub tree, we need to update + // it's value. + left = generateMerkleRootAndProof(leaves.slice(0, pivot), -1); + right = generateMerkleRootAndProof(leaves.slice(pivot, leaves.length), index - pivot); + + // add current level's left intermediary hash to the proof + if (index >= 0) { + proof = right[1] + left[0].slice(2); + } + } + return [sha256StringMultiple(left[0], right[0]), toHex(proof)]; + } +}; + + +module.exports = { + fastForward, + sha256String, + generateMerkleRootAndProof +}; diff --git a/contracts/test/plasmamvp/transactions.js b/contracts/test/plasmamvp/transactions.js new file mode 100644 index 0000000..8ac2082 --- /dev/null +++ b/contracts/test/plasmamvp/transactions.js @@ -0,0 +1,797 @@ +let RLP = require('rlp'); +let assert = require('chai').assert + +let PlasmaMVP = artifacts.require('PlasmaMVP'); + +let { + fastForward, + sha256String, + generateMerkleRootAndProof +} = require('./plasmamvp_helpers.js'); + +let { toHex, catchError } = require('../utilities.js'); + +contract('[PlasmaMVP] Transactions', async (accounts) => { + let instance; + let one_week = 604800; // in seconds + let authority = accounts[0]; + let minExitBond = 10000; + + // deploy the instance contract before each test. + // deposit from authority and mine the first block which + // includes a spend of that full deposit to account[1] (first input) + let amount = 100; + let depositNonce; + let txPos, txBytes; + let proof; + let sigs, confirmSignatures; + beforeEach(async () => { + instance = await PlasmaMVP.new({from: authority}); + + depositNonce = (await instance.depositNonce.call()).toNumber(); + await instance.deposit(authority, {from: authority, value: amount*2}); + + // deposit is the first input. authority creates two outputs. half to accounts[1], rest back to itself + let txList = Array(17).fill(0); + txList[3] = depositNonce; + txList[12] = accounts[1]; txList[13] = amount; + txList[14] = authority; txList[15] = amount; + let txHash = web3.sha3(RLP.encode(txList).toString('hex'), {encoding: 'hex'}); + + let sigs = [toHex(await web3.eth.sign(authority, txHash)), toHex(Buffer.alloc(65).toString('hex'))]; + + txBytes = [txList, sigs]; + txBytes = RLP.encode(txBytes).toString('hex'); + + // submit the block + let merkleHash = sha256String(txBytes); + let merkleRoot; + [merkleRoot, proof] = generateMerkleRootAndProof([merkleHash], 0); + let blockNum = (await instance.lastCommittedBlock.call()).toNumber() + 1; + await instance.submitBlock([toHex(merkleRoot)], [1], [0], blockNum, {from: authority}); + + // construct the confirm signature + let confirmHash = sha256String(merkleHash + merkleRoot.slice(2)); + confirmSignatures = await web3.eth.sign(authority, confirmHash); + + txPos = [blockNum, 0, 0]; + }); + + it("Will not revert finalizeExit with an empty queue", async () => { + await instance.finalizeDepositExits(); + await instance.finalizeTransactionExits(); + }); + + it("Allows only the utxo owner to start an exit (hardcoded)", async () => { + instance = await PlasmaMVP.new({from: authority}); + + // utxo information + // this utxo input and the merkle root of its block were generated + // by the side chain + let txPos = [2, 1, 0]; + let txBytes = "0xf8ebf86180808002940e02ce999156cf4e5a30d91b79329e1f01d61379c080808080940000000000000000000000000000000000000000c09453bb5e06573dbd3baeff3710c860f09f06c4c8a4329400000000000000000000000000000000000000008032f886b841288caa04324245958feb44f9ef5d483618b2cfea74622af8a1075a4089be513001cca34ed1230a20849b5c2b4ae33b3e24f4b36cf1d2d2dc45f1485c6f2c03a600b8410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; + let proof = "0xf17d0ac90940e6055a992ac3f76742a2ab47c504b495c6b1accdf839ac018814"; + let confirmSigs = "0xff05e0519b90b7b3f0d9d8a73a2792d55413f0f5626901d45ab0c8adedb668b638d1eac62cd88cd0719d8bfe0c47357c27463677c0997ad3337a0baed7fd6d6600"; + let total = 2; + + // submit block roots + let root1 = web3.sha3('1234').slice(2); + // this side chain block contains 2 txns + let root2 = "783842a0f2aacc2f988d0d9736aac13a0530f1c78d55ab468a1debcd6b42f109"; + + await instance.submitBlock([toHex(root1), toHex(root2)], [1, total], [0, 0], 1, {from: authority}); + + let newOwner = "0x53bB5E06573dbD3baEFF3710c860F09F06C4C8A4"; + + // attempt to start an transaction exit + await instance.startTransactionExit(txPos, + toHex(txBytes), toHex(proof), toHex(confirmSigs), 0, {from: newOwner, value: minExitBond}); + }); + + it("Can challenge a spend of a utxo (hardcoded)", async () => { + instance = await PlasmaMVP.new({from: authority}); + + // utxo information + // this utxo input and the merkle root of its block were generated + // by the side chain + let txPos = [2, 1, 0]; + let txBytes = "0xf8ebf86180808002940e02ce999156cf4e5a30d91b79329e1f01d61379c080808080940000000000000000000000000000000000000000c09453bb5e06573dbd3baeff3710c860f09f06c4c8a4329400000000000000000000000000000000000000008032f886b841288caa04324245958feb44f9ef5d483618b2cfea74622af8a1075a4089be513001cca34ed1230a20849b5c2b4ae33b3e24f4b36cf1d2d2dc45f1485c6f2c03a600b8410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; + let proof = "0xf17d0ac90940e6055a992ac3f76742a2ab47c504b495c6b1accdf839ac018814"; + let confirmSigs = "0xff05e0519b90b7b3f0d9d8a73a2792d55413f0f5626901d45ab0c8adedb668b638d1eac62cd88cd0719d8bfe0c47357c27463677c0997ad3337a0baed7fd6d6600"; + let total = 2; + + // spend the utxo + // this utxo input and the merkle root of its block were generated + // by the side chain + let newTxPostxPos = [3, 0, 0]; + let newTxBytes = "0xf9012ff8a5020180809453bb5e06573dbd3baeff3710c860f09f06c4c8a4f843b841ff05e0519b90b7b3f0d9d8a73a2792d55413f0f5626901d45ab0c8adedb668b638d1eac62cd88cd0719d8bfe0c47357c27463677c0997ad3337a0baed7fd6d660080808080940000000000000000000000000000000000000000c0940e02ce999156cf4e5a30d91b79329e1f01d61379199400000000000000000000000000000000000000008019f886b841ff4bafce58d0752731eec71617c68d256058b518e015bb7b0f85a053e491868964be5cd62b744362cb744e20de55f7123ca80115058ff9c1b76d7f31589bf99b01b8410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; + let newProof = ""; + let newConfirmSigs = "0x3dd7595c79ddbf6ea25ccb64f96218a80ecb435369fedcb865e54a87bd8464282210148512342574a6bf8e6f7a916bf4f0e65e1601cbe0b8124718b85860bb7e01"; + let newTotal = 1; + + // submit block roots + let root1 = web3.sha3('1234').slice(2); + // this side chain block contains 2 txns + let root2 = "783842a0f2aacc2f988d0d9736aac13a0530f1c78d55ab468a1debcd6b42f109"; + // this side chain block contains 1 txn + let root3 = "0501f4b09300d277cdfedb8c6d4747919bbbf454ef6ba9d300796e2703bf444c"; + + let blockNum = (await instance.lastCommittedBlock.call()).toNumber() + 1; + await instance.submitBlock([toHex(root1), toHex(root2), toHex(root3)], [1, total, newTotal], [0, 0, 0], blockNum, {from: authority}); + + let newOwner = "0x53bB5E06573dbD3baEFF3710c860F09F06C4C8A4"; + + // attempt to start an transaction exit + let tx = await instance.startTransactionExit(txPos, + toHex(txBytes), toHex(proof), toHex(confirmSigs), 0, {from: newOwner, value: minExitBond}); + + // challenge the exit above + await instance.challengeExit([...txPos, 0], [newTxPostxPos[0], newTxPostxPos[1]], + toHex(newTxBytes), toHex(newProof), toHex(newConfirmSigs), + {from: accounts[2]}); + + // check that the bond has been rewarded to the challenger + let balance = (await instance.balanceOf.call(accounts[2])).toNumber(); + assert.equal(balance, minExitBond, "exit bond not rewarded to challenger"); + + let position = 1000000*txPos[0] + 10*txPos[1]; + let exit = await instance.txExits.call(position); + assert.equal(exit[4].toNumber(), 2, "Fee exit state is not Challenged"); + }); + + it("Catches StartedTransactionExit event", async () => { + let tx = await instance.startTransactionExit(txPos, + toHex(txBytes), toHex(proof), toHex(confirmSignatures), 0, + {from: accounts[1], value: minExitBond}); + + assert.equal(tx.logs[0].args.position.toString(), txPos.toString(), "StartedTransactionExit event emits incorrect priority"); + assert.equal(tx.logs[0].args.owner, accounts[1], "StartedTransactionExit event emits incorrect owner"); + assert.equal(tx.logs[0].args.amount.toNumber(), amount, "StartedTransactionExit event emits incorrect amount"); + assert.equal(tx.logs[0].args.confirmSignatures, toHex(confirmSignatures), "StartedTransactionExit event does not emit confirm signatures"); + }); + + it("Can start and finalize a transaction exit", async () => { + await instance.startTransactionExit(txPos, + toHex(txBytes), toHex(proof), toHex(confirmSignatures), 0, + {from: accounts[1], value: minExitBond}); + + fastForward(one_week + 1000); + + await instance.finalizeTransactionExits(); + + let balance = (await instance.balanceOf.call(accounts[1])).toNumber(); + assert.equal(balance, amount + minExitBond); + + let position = 1000000*txPos[0]; + let exit = await instance.txExits.call(position); + assert.equal(exit[4].toNumber(), 3, "exit's state not set to finalized"); + }); + + it("Allows authority to start a fee withdrawal exit", async () => { + // non-authoritys cannot start fee exits + let err; + [err] = await catchError(instance.startFeeExit(txPos[0], {from: accounts[1], value: minExitBond})); + if (!err) + assert.fail("fee exit start from non-authority"); + + // authority cannot start a fee exit without putting a sufficient exit bond + [err] = await catchError(instance.startFeeExit(txPos[0], {from: authority, value: minExitBond - 100})); + if (!err) + assert.fail("started fee exit with insufficient bond"); + + // cannot start a fee exit for a non-existent block + let nonExistentBlockNum = txPos[0] + 100; + [err] = await catchError(instance.startFeeExit(nonExistentBlockNum, {from: authority, value: minExitBond})); + if (!err) + assert.fail("started fee exit for non-existent block"); + + // authority can start a fee exit with sufficient exit bond + let tx = await instance.startFeeExit(txPos[0], {from: authority, value: minExitBond}); + + let position = 1000000*txPos[0] + 10*(Math.pow(2, 16) - 1); + let feeExit = await instance.txExits.call(position); + assert.equal(feeExit[0].toNumber(), 0, "Incorrect fee exit amount"); + assert.equal(feeExit[3], authority, "Incorrect fee exit owner"); + assert.equal(feeExit[4].toNumber(), 1, "Incorrect exit state."); + + // can only start a fee exit for any particular block once + [err] = await catchError(instance.startFeeExit(txPos[0], {from: authority, value: minExitBond})); + if (!err) + assert.fail("attempted the same exit while a pending one existed"); + }); + + it("Allows authority to start and finalize a fee withdrawal exit", async () => { + let depositAmount = 1000; + let feeAmount = 10; + + let depositNonce = (await instance.depositNonce.call()).toNumber(); + await instance.deposit(authority, {from: authority, value: depositAmount}); + + // deposit is the first input. authority sends entire deposit to accounts[1] + // fee is 10 for this tx + let txList = Array(17).fill(0); + txList[3] = depositNonce; + txList[12] = accounts[1]; + txList[13] = depositAmount - feeAmount; + txList[16] = feeAmount; + + let txHash = web3.sha3(RLP.encode(txList).toString('hex'), {encoding: 'hex'}); + let sigs = [toHex(await web3.eth.sign(authority, txHash)), toHex(Buffer.alloc(65).toString('hex'))]; + let txBytes = [txList, sigs]; + txBytes = RLP.encode(txBytes).toString('hex'); + + // submit the block + let merkleHash = sha256String(txBytes); + let merkleRoot = generateMerkleRootAndProof([merkleHash], 0)[0]; + let blockNum = (await instance.lastCommittedBlock.call()).toNumber() + 1; + await instance.submitBlock([toHex(merkleRoot)], [1], [feeAmount], blockNum, {from: authority}); + + await instance.startFeeExit(blockNum, {from: authority, value: minExitBond}); + + let position = 1000000*blockNum + 10*(Math.pow(2, 16) - 1); + let feeExit = await instance.txExits.call(position); + assert.equal(feeExit[0].toNumber(), feeAmount, "Incorrect fee exit amount"); + assert.equal(feeExit[3], authority, "Incorrect fee exit owner"); + assert.equal(feeExit[4].toNumber(), 1, "Fee exit state is not Pending"); + + fastForward(one_week + 1000); + + await instance.finalizeTransactionExits(); + + let balance = (await instance.balanceOf.call(authority)).toNumber(); + assert.equal(balance, feeAmount + minExitBond, "authority has incorrect balance"); + + feeExit = await instance.txExits.call(position); + assert.equal(feeExit[4].toNumber(), 3, "Fee exit state is not Finalized"); + }); + + it("Allows authority to challenge only a incorrect committed fee", async () => { + // spend accounts[1]/authority (2 different inputs!!) => accounts[2] with a fee. fee should only come from the first input + // Both outputs spend total of 2*amount + let txList2 = Array(17).fill(0); + txList2[16] = 5; // fee + txList2[0] = txPos[0]; txList2[1] = txPos[1]; txList2[2] = txPos[2]; + txList2[6] = txPos[0]; txList2[7] = txPos[1]; txList2[8] = 1; + txList2[12] = accounts[2]; txList2[13] = amount - 5; + txList2[14] = accounts[1]; txList2[15] = amount; + let txHash2 = web3.sha3(RLP.encode(txList2).toString('hex'), {encoding: 'hex'}); + + let sigs2 = [toHex(await web3.eth.sign(accounts[1], txHash2)), toHex(await web3.eth.sign(authority, txHash2))]; + let txBytes2 = [txList2, sigs2]; + txBytes2 = RLP.encode(txBytes2).toString('hex'); + + // submit the block + let merkleHash2 = sha256String(txBytes2); + let merkleRoot2; + [merkleRoot2, proof2] = generateMerkleRootAndProof([merkleHash2], 0); + let blockNum2 = (await instance.lastCommittedBlock.call()).toNumber() + 1; + // 5 in fee + await instance.submitBlock([toHex(merkleRoot2)], [1], [5], blockNum2, {from: authority}); + + let txPos2 = [blockNum2, 0]; + + // accounts[1] will start an exit not committing to the fee + await instance.startTransactionExit(txPos, toHex(txBytes), toHex(proof), + toHex(confirmSignatures), 1, {from: accounts[1], value: minExitBond}); + + // authority will exit the second output. Second outputs do not commit fees + let secondOutput = [txPos[0], txPos[1], 1, 0]; + await instance.startTransactionExit(secondOutput, toHex(txBytes), toHex(proof), + toHex(confirmSignatures), 0, {from: authority, value: minExitBond}); + + // operator will challenge with second output + let err; + [err] = await catchError(instance.challengeFeeMismatch(secondOutput, txPos2, toHex(txBytes2), proof2)); + if (!err) + assert.fail("operator challenged with the second input"); + + // operator will challenge the exit + await instance.challengeFeeMismatch([txPos[0], txPos[1], txPos[2], 0], txPos2, toHex(txBytes2), proof2); + + let exit = await instance.txExits.call(1000000*txPos[0] + 10*txPos[1]); + assert.equal(exit[4].toNumber(), 0, "exit with incorrect fee not deleted"); + + // should not be able to challenge an exit which does not exist + [err] = await catchError(instance.challengeFeeMismatch([txPos[0], txPos[1], txPos[2], 0], txPos2, toHex(txBytes2), proof2)); + if (!err) + assert.fail("operator challenged an exit which does not exist"); + + // accounts[1] will exit with the correct committed fee + await instance.startTransactionExit(txPos, toHex(txBytes), toHex(proof), + toHex(confirmSignatures), 5, {from: accounts[1], value: minExitBond}); + + // operator will challenge the exit and will fail + [err] = await catchError(instance.challengeFeeMismatch([txPos[0], txPos[1], txPos[2], 0], txPos2, toHex(txBytes2), proof2)); + if (!err) + assert.fail("operator challenged an exit with the correct committed fee"); + }); + + it("Can challenge a fee withdrawal exit", async () => { + let depositAmount = 1000; + let feeAmount = 10; + + let depositNonce = (await instance.depositNonce.call()).toNumber(); + await instance.deposit(authority, {from: authority, value: depositAmount}); + + // deposit is the first input. authority sends entire deposit to accounts[1] + // fee is 10 for this tx + let txList = Array(17).fill(0); + txList[3] = depositNonce; + txList[12] = accounts[1]; + txList[13] = depositAmount - feeAmount; + txList[16] = feeAmount; + + let txHash = web3.sha3(RLP.encode(txList).toString('hex'), {encoding: 'hex'}); + let sigs = [toHex(await web3.eth.sign(authority, txHash)), toHex(Buffer.alloc(65).toString('hex'))]; + let txBytes = [txList, sigs]; + txBytes = RLP.encode(txBytes).toString('hex'); + + // submit the block + let merkleHash = sha256String(txBytes); + let merkleRoot = generateMerkleRootAndProof([merkleHash], 0)[0]; + let blockNum = (await instance.lastCommittedBlock.call()).toNumber() + 1; + await instance.submitBlock([toHex(merkleRoot)], [1], [feeAmount], blockNum, {from: authority}); + + // spend all fees to account[2] and mine the block + let txList2 = Array(17).fill(0); + txList2[0] = blockNum; txList2[1] = Math.pow(2, 16) - 1; txList2[2] = 0; // first input + txList2[12] = accounts[2]; txList2[13] = feeAmount; // first output + + // create signature by deposit owner. Second signature should be zero + let txHash2 = web3.sha3(RLP.encode(txList2).toString('hex'), {encoding: 'hex'}); + let sigs2 = [toHex(await web3.eth.sign(authority, txHash2)), toHex(Buffer.alloc(65).toString('hex'))] + + let newTxBytes2 = [txList2, sigs2]; + newTxBytes2 = RLP.encode(newTxBytes2).toString('hex'); + + // include this transaction in the next block + let merkleHash2 = sha256String(newTxBytes2); + let root2, proof2; + [root2, proof2] = generateMerkleRootAndProof([merkleHash2], 0); + let blockNum2 = (await instance.lastCommittedBlock.call()).toNumber() + 1; + await instance.submitBlock([toHex(root2)], [1], [0], blockNum2, {from: authority}); + + // create the confirm sig + let confirmHash2 = sha256String(merkleHash2 + root2.slice(2)); + let newConfirmSignatures2 = await web3.eth.sign(authority, confirmHash2); + + // start fee exit + await instance.startFeeExit(blockNum, {from: authority, value: minExitBond}); + + let position = 1000000*blockNum + 10*(Math.pow(2, 16) - 1); + let feeExit = await instance.txExits.call(position); + assert.equal(feeExit[0].toNumber(), feeAmount, "Incorrect fee exit amount"); + assert.equal(feeExit[3], authority, "Incorrect fee exit owner"); + assert.equal(feeExit[4].toNumber(), 1, "Fee exit state is not Pending"); + + // challenge fee exit + await instance.challengeExit([blockNum, Math.pow(2, 16) - 1, 0, 0], [blockNum2, 0], + toHex(newTxBytes2), toHex(proof2), toHex(newConfirmSignatures2), + {from: accounts[2]}); + + let balance = (await instance.balanceOf.call(accounts[2])).toNumber(); + assert.equal(balance, minExitBond, "exit bond not rewarded to challenger"); + + feeExit = await instance.txExits.call(position); + assert.equal(feeExit[4].toNumber(), 2, "Fee exit state is not Challenged"); + }); + + it("Requires sufficient bond and refunds excess if overpayed", async () => { + let err; + [err] = await catchError(instance.startTransactionExit(txPos, + toHex(txBytes), toHex(proof), toHex(confirmSignatures), 0, + {from: accounts[1], value: minExitBond - 100})); + if (!err) + assert.fail("started exit with insufficient bond"); + + await instance.startTransactionExit(txPos, + toHex(txBytes), toHex(proof), toHex(confirmSignatures), 0, + {from: accounts[1], value: minExitBond + 100}); + + let balance = (await instance.balanceOf(accounts[1])).toNumber(); + assert.equal(balance, 100, "excess funds not repayed back to caller"); + }); + + it("Only allows exiting a utxo once", async () => { + await instance.startTransactionExit(txPos, + toHex(txBytes), toHex(proof), toHex(confirmSignatures), 0, + {from: accounts[1], value: minExitBond}); + + let err; + [err] = await catchError(instance.startTransactionExit(txPos, + toHex(txBytes), toHex(proof), toHex(confirmSignatures), 0, + {from: accounts[1], value: minExitBond})); + + if (!err) + assert.fail("reopened the same exit while already a pending one existed"); + + fastForward(one_week + 100); + + [err] = await catchError(instance.startTransactionExit(txPos, + toHex(txBytes), toHex(proof), toHex(confirmSignatures), 0, + {from: accounts[1], value: minExitBond})); + + if (!err) + assert.fail("reopened the same exit after already finalized"); + }); + + it("Cannot exit a utxo with an input pending an exit", async () => { + await instance.startDepositExit(depositNonce, 0, {from: authority, value: minExitBond}); + + let err; + [err] = await catchError(instance.startTransactionExit(txPos, + toHex(txBytes), toHex(proof), toHex(confirmSignatures), 0, + {from: accounts[1], value: minExitBond})); + + if (!err) + assert.fail("started an exit with an input who has a pending exit state"); + }); + + it("Can challenge a spend of a utxo", async () => { + // spend all funds to account[2] and mine the block + // deposit is the first input. spending entire deposit to accounts[1] + let txList2 = Array(17).fill(0); + txList2[0] = txPos[0]; txList2[1] = txPos[1]; txList2[2] = txPos[2]; // first input + txList2[12] = accounts[2]; txList2[13] = amount; // first output + + // create signature by deposit owner. Second signature should be zero + let txHash = web3.sha3(RLP.encode(txList2).toString('hex'), {encoding: 'hex'}); + let sigs = [toHex(await web3.eth.sign(accounts[1], txHash)), toHex(Buffer.alloc(65).toString('hex'))] + + let newTxBytes = [txList2, sigs]; + newTxBytes = RLP.encode(newTxBytes).toString('hex'); + + // include this transaction in the next block + let merkleHash = sha256String(newTxBytes); + let root, proof2; + [root, proof2] = generateMerkleRootAndProof([merkleHash], 0); + let blockNum = (await instance.lastCommittedBlock.call()).toNumber() + 1; + await instance.submitBlock([toHex(root)], [1], [0], blockNum, {from: authority}); + + // create the confirm sig + let confirmHash = sha256String(merkleHash + root.slice(2)); + let newConfirmSignatures = await web3.eth.sign(accounts[1], confirmHash); + + // start an exit of the original utxo + await instance.startTransactionExit(txPos, + toHex(txBytes), toHex(proof), toHex(confirmSignatures), 0, + {from: accounts[1], value: minExitBond}); + + // try to exit this new utxo and realize it cannot. child has a pending exit + let err; + [err] = await catchError(instance.startTransactionExit([blockNum, 0, 0], + toHex(newTxBytes), toHex(proof2), toHex(newConfirmSignatures), 0, + {from: accounts[2], value: minExitBond})); + if (!err) + assert.fail("started exit when the child has a pending exit"); + + // matching input required + [err] = await catchError(instance.challengeExit([txPos[0], 0, 1, 0], [blockNum, 0], + toHex(newTxBytes), toHex(proof2), toHex(newConfirmSignatures.substring(0,65), + {from: accounts[2]}))); + if (!err) + assert.fail("challenged with transaction that is not a direct child"); + + // challenge + await instance.challengeExit([...txPos, 0], [blockNum, 0], + toHex(newTxBytes), toHex(proof2), toHex(newConfirmSignatures), + {from: accounts[2]}); + + let balance = (await instance.balanceOf.call(accounts[2])).toNumber(); + assert.equal(balance, minExitBond, "exit bond not rewarded to challenger"); + + let position = 1000000*txPos[0]; + let exit = await instance.txExits.call(position); + assert.equal(exit[4].toNumber(), 2, "fee exit state is not challenged"); + + // start an exit of the new utxo after successfully challenging + await instance.startTransactionExit([blockNum, 0, 0], + toHex(newTxBytes), toHex(proof2), toHex(newConfirmSignatures), 0, + {from: accounts[2], value: minExitBond}); + }); + + it("Rejects exiting a transaction whose sole input is the second", async () => { + let nonce = (await instance.depositNonce.call()).toNumber(); + await instance.deposit(accounts[2], {from: accounts[2], value: 100}); + + // construct transcation with second input as the deposit + let txList2 = Array(17).fill(0); + txList2[9] = nonce; txList2[12] = accounts[1]; txList2[13] = 100; + let txHash = web3.sha3(RLP.encode(txList2, {encoding: 'hex'})); + + // create signature by deposit owner. Second signature should be zero + let sigs = [toHex(Buffer.alloc(65).toString('hex')), toHex(await web3.eth.sign(accounts[2], txHash))]; + + let newTxBytes = RLP.encode([txList2, sigs]).toString('hex'); + + let merkleHash = sha256String(newTxBytes); + + // include this transaction in the next block + let root, proof2; + [root, proof2] = generateMerkleRootAndProof([merkleHash], 0); + let blockNum = (await instance.lastCommittedBlock.call()).toNumber() + 1; + await instance.submitBlock([toHex(root)], [1], [0], blockNum, {from: authority}); + + // create the confirm sig + let confirmHash = sha256String(merkleHash + root.slice(2)); + let confirmSig = await web3.eth.sign(accounts[2], confirmHash); + + let err; + [err] = await catchError(instance.startTransactionExit([blockNum, 0, 0], + toHex(newTxBytes), toHex(proof2), toHex(confirmSig), 0, {from: accounts[1], value: minExitBond})); + if (!err) + assert.fail("With a nonzero second input, two confirm signatures should have been required"); + + }); + + it("Cannot challenge with an incorrect transaction", async () => { + // account[1] spends deposit and creates two utxos for themselves + let txList1 = Array(17).fill(0); + txList1[0] = txPos[0]; txList1[1] = txPos[1]; txList1[2] = txPos[2]; // first input + txList1[12] = accounts[1]; txList1[13] = amount/2; // first utxo + txList1[14] = accounts[1]; txList1[15] = amount/2; // second utxo + + // include this tx the next block + let txHash1 = web3.sha3(RLP.encode(txList1).toString('hex'), {encoding: 'hex'}); + let sigs1 = [toHex(await web3.eth.sign(accounts[1], txHash1)), toHex(Buffer.alloc(65).toString('hex'))]; + + let txBytes1 = RLP.encode([txList1, sigs1]).toString('hex'); + + let merkleHash1 = sha256String(txBytes1); + let root1, proof1; + [root1, proof1] = generateMerkleRootAndProof([merkleHash1], 0); + let blockNum1 = (await instance.lastCommittedBlock.call()).toNumber() + 1; + await instance.submitBlock([toHex(root1)], [1], [0], blockNum1, {from: authority}); + + // create confirmation signature + let confirmationHash1 = sha256String(merkleHash1.slice(2) + root1.slice(2)); + let confirmSigs1 = await web3.eth.sign(accounts[1], confirmationHash1); + + // accounts[1] spends the first output to accounts[2] + let txList2 = Array(17).fill(0); + txList2[0] = blockNum1; txList2[12] = accounts[2]; txList2[12] = amount/2; + let txHash2 = web3.sha3(RLP.encode(txList2).toString('hex'), {encoding: 'hex'}); + + // include this tx the next block + let sigs2 = [toHex(await web3.eth.sign(accounts[1], txHash2)), toHex(Buffer.alloc(65).toString('hex'))]; + + let txBytes2 = RLP.encode([txList2, sigs2]).toString('hex'); + + let merkleHash2 = sha256String(txBytes2); + let root2, proof2; + [root2, proof2] = generateMerkleRootAndProof([merkleHash2], 0); + let blockNum2 = (await instance.lastCommittedBlock.call()).toNumber() + 1; + await instance.submitBlock([toHex(root2)], [1], [0], blockNum2, {from: authority}); + + // create confirmation signature + let confirmationHash2 = sha256String(merkleHash2.slice(2) + root2.slice(2)); + let confirmSigs2 = await web3.eth.sign(accounts[2], confirmationHash2); + + // accounts[1] exits the second output + await instance.startTransactionExit([blockNum1, 0, 1], toHex(txBytes1), + toHex(proof1), toHex(confirmSigs1), 0, {from: accounts[1], value: minExitBond}); + + // try to challenge with the spend of the first output + let err; + [err] = await catchError(instance.challengeExit([blockNum1, 0, 1, 0], [blockNum2, 0], + toHex(txBytes2), toHex(proof2), toHex(confirmSigs2))) + if (!err) + assert.fail("Challenged with incorrect transaction") + }); + + it("Attempt a withdrawal delay attack", async () => { + let five_days = 432000 // in seconds + // accounts[1] spends deposit and creates 2 new utxos for themself + let txList1 = Array(17).fill(0); + txList1[0] = txPos[0]; txList1[1] = txPos[1]; txList1[2] = txPos[2]; // first input + txList1[12] = accounts[1]; txList1[13] = amount/2; // first output + txList1[14] = accounts[1]; txList1[15] = amount/2; // second output + let txHash1 = web3.sha3(RLP.encode(txList1).toString('hex'), {encoding: 'hex'}); + let sigs1 = [toHex(await web3.eth.sign(accounts[1], txHash1)), toHex(Buffer.alloc(65).toString('hex'))]; + let txBytes1 = RLP.encode([txList1, sigs1]).toString('hex'); + + let merkleHash1 = sha256String(txBytes1); + let root1, proof1; + [root1, proof1] = generateMerkleRootAndProof([merkleHash1], 0); + let blockNum1 = (await instance.lastCommittedBlock.call()).toNumber() + 1; + await instance.submitBlock([toHex(root1)], [1], [0], blockNum1, {from: authority}); + + // create confirmation signature + let confirmationHash1 = sha256String(merkleHash1.slice(2) + root1.slice(2)); + let confirmSigs1 = await web3.eth.sign(accounts[1], confirmationHash1); + + // accounts[1] spends (blockNum1, 0, 1) utxo, sends 1 utxo to themself and the other to accounts[2] + let txList2 = Array(17).fill(0); + txList2[0] = blockNum1; txList2[2] = 1; // first input + txList2[12] = accounts[1]; txList2[13] = amount / 4; // first output + txList2[14] = accounts[2]; txList2[15] = amount / 4; // second output + let txHash2 = web3.sha3(RLP.encode(txList2).toString('hex'), {encoding: 'hex'}); + let sigs2 = [toHex(await web3.eth.sign(accounts[1], txHash2)), toHex(Buffer.alloc(65).toString('hex'))]; + let txBytes2 = RLP.encode([txList2, sigs2]).toString('hex'); + + let merkleHash2 = sha256String(txBytes2); + let root2, proof2; + [root2, proof2] = generateMerkleRootAndProof([merkleHash2], 0); + let blockNum2 = (await instance.lastCommittedBlock.call()).toNumber() + 1; + await instance.submitBlock([toHex(root2)], [1], [0], blockNum2, {from: authority}); + + // create confirmation signature + let confirmationHash2 = sha256String(merkleHash2.slice(2) + root2.slice(2)); + let confirmSigs2 = await web3.eth.sign(accounts[1], confirmationHash2); + + // make utxos > 1 week old + fastForward(one_week + 100); + + // start exit for accounts[2], last utxo to be created + await instance.startTransactionExit([blockNum2, 0, 1], + toHex(txBytes2), toHex(proof2), toHex(confirmSigs2), 0, {from: accounts[2], value: minExitBond}); + + // increase time slightly, so exit by accounts[2] has better priority than accounts[1] + fastForward(10); + + // start exit for accounts[1] utxo + await instance.startTransactionExit([blockNum2, 0, 0], + toHex(txBytes2), toHex(proof2), toHex(confirmSigs2), 0, {from: accounts[1], value: minExitBond}); + + // Fast Forward ~5 days + fastForward(five_days); + + // Check to make sure challenge period has not ended + let position = 1000000 * blockNum2 + 1; + let currExit = await instance.txExits.call(position); + assert.ok((currExit[2] + 604800) > (await web3.eth.getBlock(await web3.eth.blockNumber)).timestamp); + + // start exit for accounts[1], oldest utxo avaliable + await instance.startTransactionExit([blockNum1, 0, 0], + toHex(txBytes1), toHex(proof1), toHex(confirmSigs1), 0, {from: accounts[1], value: minExitBond}); + + // Fast Forward < 1 week + fastForward(five_days); + + // finalize exits should finalize accounts[2] then accounts[1] + let finalizedExits = await instance.finalizeTransactionExits({from: authority}); + let finalizedExit = await instance.txExits.call(position); + assert.equal(finalizedExits.logs[0].args.position.toString(), [blockNum2, 0, 1, 0].toString(), "Incorrect position for finalized tx"); + assert.equal(finalizedExits.logs[0].args.owner, accounts[2], "Incorrect finalized exit owner"); + assert.equal(finalizedExits.logs[0].args.amount.toNumber(), 25 + minExitBond, "Incorrect finalized exit amount."); + assert.equal(finalizedExit[4].toNumber(), 3, "Incorrect finalized exit state."); + + // Check other exits + position = 1000000 * blockNum2; + finalizedExit = await instance.txExits.call(position); + assert.equal(finalizedExits.logs[2].args.position.toString(), [blockNum2, 0, 0, 0].toString(), "Incorrect position for finalized tx"); + assert.equal(finalizedExits.logs[2].args.owner, accounts[1], "Incorrect finalized exit owner"); + assert.equal(finalizedExits.logs[2].args.amount.toNumber(), 25 + minExitBond, "Incorrect finalized exit amount."); + assert.equal(finalizedExit[4].toNumber(), 3, "Incorrect finalized exit state."); + + // Last exit should still be pending + position = 1000000 * blockNum1; + let pendingExit = await instance.txExits.call(position); + assert.equal(pendingExit[3], accounts[1], "Incorrect pending exit owner"); + assert.equal(pendingExit[0], 50, "Incorrect pending exit amount"); + assert.equal(pendingExit[4].toNumber(), 1, "Incorrect pending exit state."); + + // Fast Forward rest of challenge period + fastForward(one_week); + await instance.finalizeTransactionExits({from: authority}); + // Check that last exit was processed + finalizedExit = await instance.txExits.call(position); + assert.equal(finalizedExit[3], accounts[1], "Incorrect finalized exit owner"); + assert.equal(finalizedExit[0], 50, "Incorrect finalized exit amount"); + assert.equal(finalizedExit[4].toNumber(), 3, "Incorrect finalized exit state."); + }); + + it("Does not finalize a transaction exit if not enough gas", async () => { + depositNonce = (await instance.depositNonce.call()).toNumber(); + await instance.deposit(authority, {from: authority, value: amount}); + + // deposit is the first input. authority sends entire deposit to accounts[1] + let txList2 = Array(17).fill(0); + txList2[3] = depositNonce; txList2[12] = accounts[1]; txList2[13] = amount; + let txHash2 = web3.sha3(RLP.encode(txList2).toString('hex'), {encoding: 'hex'}); + + let sigs2 = [toHex(await web3.eth.sign(authority, txHash2)), toHex(Buffer.alloc(65).toString('hex'))]; + + txBytes2 = [txList2, sigs2]; + txBytes2 = RLP.encode(txBytes2).toString('hex'); + + // submit the block + let merkleHash2 = sha256String(txBytes2); + let merkleRoot2, proof2; + [merkleRoot2, proof2] = generateMerkleRootAndProof([merkleHash2], 0); + let blockNum2 = (await instance.lastCommittedBlock.call()).toNumber() + 1; + await instance.submitBlock([toHex(merkleRoot2)], [1], [0], blockNum2, {from: authority}); + + // construct the confirm signature + let confirmHash2 = sha256String(merkleHash2 + merkleRoot2.slice(2)); + confirmSignatures2 = await web3.eth.sign(authority, confirmHash2); + + txPos2 = [blockNum2, 0, 0]; + + // Start txn exit on both utxos + await instance.startTransactionExit(txPos, + toHex(txBytes), toHex(proof), toHex(confirmSignatures), 0, + {from: accounts[1], value: minExitBond}); + + await instance.startTransactionExit(txPos2, + toHex(txBytes2), toHex(proof2), toHex(confirmSignatures2), 0, + {from: accounts[1], value: minExitBond}); + + fastForward(one_week + 1000); + + // Only provide enough gas for 1 txn to be finalized + await instance.finalizeTransactionExits({gas: 120000}); + + // The first utxo should have been exited correctly + let balance = (await instance.balanceOf.call(accounts[1])).toNumber(); + assert.equal(balance, amount + minExitBond); + + let position = 1000000*txPos[0]; + let exit = await instance.txExits.call(position); + assert.equal(exit[4].toNumber(), 3, "exit's state not set to finalized"); + + // The second utxo exit should still be pending + balance = (await instance.balanceOf.call(accounts[1])).toNumber(); + assert.equal(balance, amount + minExitBond); + + position = 1000000*txPos2[0]; + exit = await instance.txExits.call(position); + assert.equal(exit[4].toNumber(), 1, "exit has been challenged or finalized"); + }); + + it("Does finalize two transaction exits if given enough gas", async () => { + depositNonce = (await instance.depositNonce.call()).toNumber(); + await instance.deposit(authority, {from: authority, value: amount}); + + // deposit is the first input. authority sends entire deposit to accounts[1] + let txList2 = Array(17).fill(0); + txList2[3] = depositNonce; txList2[12] = accounts[1]; txList2[13] = amount; + let txHash2 = web3.sha3(RLP.encode(txList2).toString('hex'), {encoding: 'hex'}); + + let sigs2 = [toHex(await web3.eth.sign(authority, txHash2)), toHex(Buffer.alloc(65).toString('hex'))]; + + txBytes2 = [txList2, sigs2]; + txBytes2 = RLP.encode(txBytes2).toString('hex'); + + // submit the block + let merkleHash2 = sha256String(txBytes2); + let merkleRoot2, proof2; + [merkleRoot2, proof2] = generateMerkleRootAndProof([merkleHash2], 0); + let blockNum2 = (await instance.lastCommittedBlock.call()).toNumber() + 1; + await instance.submitBlock([toHex(merkleRoot2)], [1], [0], blockNum2, {from: authority}); + + // construct the confirm signature + let confirmHash2 = sha256String(merkleHash2 + merkleRoot2.slice(2)); + confirmSignatures2 = await web3.eth.sign(authority, confirmHash2); + + txPos2 = [blockNum2, 0, 0]; + + // Start txn exit on both utxos + await instance.startTransactionExit(txPos, + toHex(txBytes), toHex(proof), toHex(confirmSignatures), 0, + {from: accounts[1], value: minExitBond}); + + await instance.startTransactionExit(txPos2, + toHex(txBytes2), toHex(proof2), toHex(confirmSignatures2), 0, + {from: accounts[1], value: minExitBond}); + + fastForward(one_week + 1000); + + // Provide enough gas for both txns to be finalized + await instance.finalizeTransactionExits({gas: 200000}); + + // The both utxo should have been exited correctly + let balance = (await instance.balanceOf.call(accounts[1])).toNumber(); + assert.equal(balance, 2 * (amount + minExitBond)); + + // Verify that txn1 has been exited + let position = 1000000*txPos[0]; + let exit = await instance.txExits.call(position); + assert.equal(exit[4].toNumber(), 3, "exit's state not set to finalized"); + + // Verify that txn2 has been exited + position = 1000000*txPos2[0]; + exit = await instance.txExits.call(position); + assert.equal(exit[4].toNumber(), 3, "exit's state not set to finalized"); + }); +}); diff --git a/contracts/test/utilities.js b/contracts/test/utilities.js new file mode 100644 index 0000000..aa0bd75 --- /dev/null +++ b/contracts/test/utilities.js @@ -0,0 +1,20 @@ +/* + How to avoid using try/catch blocks with promises' that could fail using async/await + - https://blog.grossman.io/how-to-write-async-await-without-try-catch-blocks-in-javascript/ + */ +let catchError = function(promise) { + return promise.then(result => [null, result]) + .catch(err => [err]); +}; + +let toHex = function(buffer) { + buffer = buffer.toString('hex'); + if (buffer.substring(0, 2) == '0x') + return buffer; + return '0x' + buffer; +}; + +module.exports = { + catchError, + toHex +}; diff --git a/contracts/truffle-config.js b/contracts/truffle-config.js new file mode 100644 index 0000000..a6330d6 --- /dev/null +++ b/contracts/truffle-config.js @@ -0,0 +1,4 @@ +module.exports = { + // See + // to customize your Truffle configuration! +}; diff --git a/contracts/truffle.js b/contracts/truffle.js new file mode 100644 index 0000000..96e4126 --- /dev/null +++ b/contracts/truffle.js @@ -0,0 +1,11 @@ +module.exports = { + // See + // to customize your Truffle configuration! + networks: { + development: { + host: "localhost", + port: 8545, + network_id: "*" // Match any network id + } + } +}; diff --git a/contracts/wrappers/plasma_mvp.go b/contracts/wrappers/plasma_mvp.go new file mode 100644 index 0000000..5c71f90 --- /dev/null +++ b/contracts/wrappers/plasma_mvp.go @@ -0,0 +1,1672 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package wrappers + +import ( + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = abi.U256 + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription +) + +// PlasmaMVPABI is the input ABI used to generate the binding from. +const PlasmaMVPABI = "[{\"constant\":true,\"inputs\":[],\"name\":\"txIndexFactor\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"address\"}],\"name\":\"balances\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"maxTxnsPerBLock\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"lastCommittedBlock\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"txExits\",\"outputs\":[{\"name\":\"amount\",\"type\":\"uint256\"},{\"name\":\"committedFee\",\"type\":\"uint256\"},{\"name\":\"createdAt\",\"type\":\"uint256\"},{\"name\":\"owner\",\"type\":\"address\"},{\"name\":\"state\",\"type\":\"uint8\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"blockIndexFactor\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"deposits\",\"outputs\":[{\"name\":\"owner\",\"type\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\"},{\"name\":\"createdAt\",\"type\":\"uint256\"},{\"name\":\"ethBlockNum\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"totalWithdrawBalance\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"depositExits\",\"outputs\":[{\"name\":\"amount\",\"type\":\"uint256\"},{\"name\":\"committedFee\",\"type\":\"uint256\"},{\"name\":\"createdAt\",\"type\":\"uint256\"},{\"name\":\"owner\",\"type\":\"address\"},{\"name\":\"state\",\"type\":\"uint8\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"depositNonce\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"childChain\",\"outputs\":[{\"name\":\"root\",\"type\":\"bytes32\"},{\"name\":\"numTxns\",\"type\":\"uint256\"},{\"name\":\"feeAmount\",\"type\":\"uint256\"},{\"name\":\"createdAt\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"AddedToBalances\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"root\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"blockNumber\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"numTxns\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"feeAmount\",\"type\":\"uint256\"}],\"name\":\"BlockSubmitted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"depositor\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"amount\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"depositNonce\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"ethBlockNum\",\"type\":\"uint256\"}],\"name\":\"Deposit\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"position\",\"type\":\"uint256[3]\"},{\"indexed\":false,\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"amount\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"confirmSignatures\",\"type\":\"bytes\"},{\"indexed\":false,\"name\":\"committedFee\",\"type\":\"uint256\"}],\"name\":\"StartedTransactionExit\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"nonce\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"StartedDepositExit\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"position\",\"type\":\"uint256[4]\"},{\"indexed\":false,\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"ChallengedExit\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"position\",\"type\":\"uint256[4]\"},{\"indexed\":false,\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"FinalizedExit\",\"type\":\"event\"},{\"constant\":false,\"inputs\":[{\"name\":\"headers\",\"type\":\"bytes32[]\"},{\"name\":\"txnsPerBlock\",\"type\":\"uint256[]\"},{\"name\":\"feesPerBlock\",\"type\":\"uint256[]\"},{\"name\":\"blockNum\",\"type\":\"uint256\"}],\"name\":\"submitBlock\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"deposit\",\"outputs\":[],\"payable\":true,\"stateMutability\":\"payable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"nonce\",\"type\":\"uint256\"},{\"name\":\"committedFee\",\"type\":\"uint256\"}],\"name\":\"startDepositExit\",\"outputs\":[],\"payable\":true,\"stateMutability\":\"payable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"txPos\",\"type\":\"uint256[3]\"},{\"name\":\"txBytes\",\"type\":\"bytes\"},{\"name\":\"proof\",\"type\":\"bytes\"},{\"name\":\"confirmSignatures\",\"type\":\"bytes\"},{\"name\":\"committedFee\",\"type\":\"uint256\"}],\"name\":\"startTransactionExit\",\"outputs\":[],\"payable\":true,\"stateMutability\":\"payable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"blockNumber\",\"type\":\"uint256\"}],\"name\":\"startFeeExit\",\"outputs\":[],\"payable\":true,\"stateMutability\":\"payable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"exitingTxPos\",\"type\":\"uint256[4]\"},{\"name\":\"challengingTxPos\",\"type\":\"uint256[2]\"},{\"name\":\"txBytes\",\"type\":\"bytes\"},{\"name\":\"proof\",\"type\":\"bytes\"}],\"name\":\"challengeFeeMismatch\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"exitingTxPos\",\"type\":\"uint256[4]\"},{\"name\":\"challengingTxPos\",\"type\":\"uint256[2]\"},{\"name\":\"txBytes\",\"type\":\"bytes\"},{\"name\":\"proof\",\"type\":\"bytes\"},{\"name\":\"confirmSignature\",\"type\":\"bytes\"}],\"name\":\"challengeExit\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"finalizeDepositExits\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"finalizeTransactionExits\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"withdraw\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"childChainBalance\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_address\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"}]" + +// PlasmaMVP is an auto generated Go binding around an Ethereum contract. +type PlasmaMVP struct { + PlasmaMVPCaller // Read-only binding to the contract + PlasmaMVPTransactor // Write-only binding to the contract + PlasmaMVPFilterer // Log filterer for contract events +} + +// PlasmaMVPCaller is an auto generated read-only Go binding around an Ethereum contract. +type PlasmaMVPCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// PlasmaMVPTransactor is an auto generated write-only Go binding around an Ethereum contract. +type PlasmaMVPTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// PlasmaMVPFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type PlasmaMVPFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// PlasmaMVPSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type PlasmaMVPSession struct { + Contract *PlasmaMVP // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// PlasmaMVPCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type PlasmaMVPCallerSession struct { + Contract *PlasmaMVPCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// PlasmaMVPTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type PlasmaMVPTransactorSession struct { + Contract *PlasmaMVPTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// PlasmaMVPRaw is an auto generated low-level Go binding around an Ethereum contract. +type PlasmaMVPRaw struct { + Contract *PlasmaMVP // Generic contract binding to access the raw methods on +} + +// PlasmaMVPCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type PlasmaMVPCallerRaw struct { + Contract *PlasmaMVPCaller // Generic read-only contract binding to access the raw methods on +} + +// PlasmaMVPTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type PlasmaMVPTransactorRaw struct { + Contract *PlasmaMVPTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewPlasmaMVP creates a new instance of PlasmaMVP, bound to a specific deployed contract. +func NewPlasmaMVP(address common.Address, backend bind.ContractBackend) (*PlasmaMVP, error) { + contract, err := bindPlasmaMVP(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &PlasmaMVP{PlasmaMVPCaller: PlasmaMVPCaller{contract: contract}, PlasmaMVPTransactor: PlasmaMVPTransactor{contract: contract}, PlasmaMVPFilterer: PlasmaMVPFilterer{contract: contract}}, nil +} + +// NewPlasmaMVPCaller creates a new read-only instance of PlasmaMVP, bound to a specific deployed contract. +func NewPlasmaMVPCaller(address common.Address, caller bind.ContractCaller) (*PlasmaMVPCaller, error) { + contract, err := bindPlasmaMVP(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &PlasmaMVPCaller{contract: contract}, nil +} + +// NewPlasmaMVPTransactor creates a new write-only instance of PlasmaMVP, bound to a specific deployed contract. +func NewPlasmaMVPTransactor(address common.Address, transactor bind.ContractTransactor) (*PlasmaMVPTransactor, error) { + contract, err := bindPlasmaMVP(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &PlasmaMVPTransactor{contract: contract}, nil +} + +// NewPlasmaMVPFilterer creates a new log filterer instance of PlasmaMVP, bound to a specific deployed contract. +func NewPlasmaMVPFilterer(address common.Address, filterer bind.ContractFilterer) (*PlasmaMVPFilterer, error) { + contract, err := bindPlasmaMVP(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &PlasmaMVPFilterer{contract: contract}, nil +} + +// bindPlasmaMVP binds a generic wrapper to an already deployed contract. +func bindPlasmaMVP(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(PlasmaMVPABI)) + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_PlasmaMVP *PlasmaMVPRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _PlasmaMVP.Contract.PlasmaMVPCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_PlasmaMVP *PlasmaMVPRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _PlasmaMVP.Contract.PlasmaMVPTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_PlasmaMVP *PlasmaMVPRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _PlasmaMVP.Contract.PlasmaMVPTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_PlasmaMVP *PlasmaMVPCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _PlasmaMVP.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_PlasmaMVP *PlasmaMVPTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _PlasmaMVP.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_PlasmaMVP *PlasmaMVPTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _PlasmaMVP.Contract.contract.Transact(opts, method, params...) +} + +// BalanceOf is a free data retrieval call binding the contract method 0x70a08231. +// +// Solidity: function balanceOf(_address address) constant returns(uint256) +func (_PlasmaMVP *PlasmaMVPCaller) BalanceOf(opts *bind.CallOpts, _address common.Address) (*big.Int, error) { + var ( + ret0 = new(*big.Int) + ) + out := ret0 + err := _PlasmaMVP.contract.Call(opts, out, "balanceOf", _address) + return *ret0, err +} + +// BalanceOf is a free data retrieval call binding the contract method 0x70a08231. +// +// Solidity: function balanceOf(_address address) constant returns(uint256) +func (_PlasmaMVP *PlasmaMVPSession) BalanceOf(_address common.Address) (*big.Int, error) { + return _PlasmaMVP.Contract.BalanceOf(&_PlasmaMVP.CallOpts, _address) +} + +// BalanceOf is a free data retrieval call binding the contract method 0x70a08231. +// +// Solidity: function balanceOf(_address address) constant returns(uint256) +func (_PlasmaMVP *PlasmaMVPCallerSession) BalanceOf(_address common.Address) (*big.Int, error) { + return _PlasmaMVP.Contract.BalanceOf(&_PlasmaMVP.CallOpts, _address) +} + +// Balances is a free data retrieval call binding the contract method 0x27e235e3. +// +// Solidity: function balances( address) constant returns(uint256) +func (_PlasmaMVP *PlasmaMVPCaller) Balances(opts *bind.CallOpts, arg0 common.Address) (*big.Int, error) { + var ( + ret0 = new(*big.Int) + ) + out := ret0 + err := _PlasmaMVP.contract.Call(opts, out, "balances", arg0) + return *ret0, err +} + +// Balances is a free data retrieval call binding the contract method 0x27e235e3. +// +// Solidity: function balances( address) constant returns(uint256) +func (_PlasmaMVP *PlasmaMVPSession) Balances(arg0 common.Address) (*big.Int, error) { + return _PlasmaMVP.Contract.Balances(&_PlasmaMVP.CallOpts, arg0) +} + +// Balances is a free data retrieval call binding the contract method 0x27e235e3. +// +// Solidity: function balances( address) constant returns(uint256) +func (_PlasmaMVP *PlasmaMVPCallerSession) Balances(arg0 common.Address) (*big.Int, error) { + return _PlasmaMVP.Contract.Balances(&_PlasmaMVP.CallOpts, arg0) +} + +// BlockIndexFactor is a free data retrieval call binding the contract method 0x89609149. +// +// Solidity: function blockIndexFactor() constant returns(uint256) +func (_PlasmaMVP *PlasmaMVPCaller) BlockIndexFactor(opts *bind.CallOpts) (*big.Int, error) { + var ( + ret0 = new(*big.Int) + ) + out := ret0 + err := _PlasmaMVP.contract.Call(opts, out, "blockIndexFactor") + return *ret0, err +} + +// BlockIndexFactor is a free data retrieval call binding the contract method 0x89609149. +// +// Solidity: function blockIndexFactor() constant returns(uint256) +func (_PlasmaMVP *PlasmaMVPSession) BlockIndexFactor() (*big.Int, error) { + return _PlasmaMVP.Contract.BlockIndexFactor(&_PlasmaMVP.CallOpts) +} + +// BlockIndexFactor is a free data retrieval call binding the contract method 0x89609149. +// +// Solidity: function blockIndexFactor() constant returns(uint256) +func (_PlasmaMVP *PlasmaMVPCallerSession) BlockIndexFactor() (*big.Int, error) { + return _PlasmaMVP.Contract.BlockIndexFactor(&_PlasmaMVP.CallOpts) +} + +// ChildChain is a free data retrieval call binding the contract method 0xf95643b1. +// +// Solidity: function childChain( uint256) constant returns(root bytes32, numTxns uint256, feeAmount uint256, createdAt uint256) +func (_PlasmaMVP *PlasmaMVPCaller) ChildChain(opts *bind.CallOpts, arg0 *big.Int) (struct { + Root [32]byte + NumTxns *big.Int + FeeAmount *big.Int + CreatedAt *big.Int +}, error) { + ret := new(struct { + Root [32]byte + NumTxns *big.Int + FeeAmount *big.Int + CreatedAt *big.Int + }) + out := ret + err := _PlasmaMVP.contract.Call(opts, out, "childChain", arg0) + return *ret, err +} + +// ChildChain is a free data retrieval call binding the contract method 0xf95643b1. +// +// Solidity: function childChain( uint256) constant returns(root bytes32, numTxns uint256, feeAmount uint256, createdAt uint256) +func (_PlasmaMVP *PlasmaMVPSession) ChildChain(arg0 *big.Int) (struct { + Root [32]byte + NumTxns *big.Int + FeeAmount *big.Int + CreatedAt *big.Int +}, error) { + return _PlasmaMVP.Contract.ChildChain(&_PlasmaMVP.CallOpts, arg0) +} + +// ChildChain is a free data retrieval call binding the contract method 0xf95643b1. +// +// Solidity: function childChain( uint256) constant returns(root bytes32, numTxns uint256, feeAmount uint256, createdAt uint256) +func (_PlasmaMVP *PlasmaMVPCallerSession) ChildChain(arg0 *big.Int) (struct { + Root [32]byte + NumTxns *big.Int + FeeAmount *big.Int + CreatedAt *big.Int +}, error) { + return _PlasmaMVP.Contract.ChildChain(&_PlasmaMVP.CallOpts, arg0) +} + +// ChildChainBalance is a free data retrieval call binding the contract method 0x385e2fd3. +// +// Solidity: function childChainBalance() constant returns(uint256) +func (_PlasmaMVP *PlasmaMVPCaller) ChildChainBalance(opts *bind.CallOpts) (*big.Int, error) { + var ( + ret0 = new(*big.Int) + ) + out := ret0 + err := _PlasmaMVP.contract.Call(opts, out, "childChainBalance") + return *ret0, err +} + +// ChildChainBalance is a free data retrieval call binding the contract method 0x385e2fd3. +// +// Solidity: function childChainBalance() constant returns(uint256) +func (_PlasmaMVP *PlasmaMVPSession) ChildChainBalance() (*big.Int, error) { + return _PlasmaMVP.Contract.ChildChainBalance(&_PlasmaMVP.CallOpts) +} + +// ChildChainBalance is a free data retrieval call binding the contract method 0x385e2fd3. +// +// Solidity: function childChainBalance() constant returns(uint256) +func (_PlasmaMVP *PlasmaMVPCallerSession) ChildChainBalance() (*big.Int, error) { + return _PlasmaMVP.Contract.ChildChainBalance(&_PlasmaMVP.CallOpts) +} + +// DepositExits is a free data retrieval call binding the contract method 0xce84f906. +// +// Solidity: function depositExits( uint256) constant returns(amount uint256, committedFee uint256, createdAt uint256, owner address, state uint8) +func (_PlasmaMVP *PlasmaMVPCaller) DepositExits(opts *bind.CallOpts, arg0 *big.Int) (struct { + Amount *big.Int + CommittedFee *big.Int + CreatedAt *big.Int + Owner common.Address + State uint8 +}, error) { + ret := new(struct { + Amount *big.Int + CommittedFee *big.Int + CreatedAt *big.Int + Owner common.Address + State uint8 + }) + out := ret + err := _PlasmaMVP.contract.Call(opts, out, "depositExits", arg0) + return *ret, err +} + +// DepositExits is a free data retrieval call binding the contract method 0xce84f906. +// +// Solidity: function depositExits( uint256) constant returns(amount uint256, committedFee uint256, createdAt uint256, owner address, state uint8) +func (_PlasmaMVP *PlasmaMVPSession) DepositExits(arg0 *big.Int) (struct { + Amount *big.Int + CommittedFee *big.Int + CreatedAt *big.Int + Owner common.Address + State uint8 +}, error) { + return _PlasmaMVP.Contract.DepositExits(&_PlasmaMVP.CallOpts, arg0) +} + +// DepositExits is a free data retrieval call binding the contract method 0xce84f906. +// +// Solidity: function depositExits( uint256) constant returns(amount uint256, committedFee uint256, createdAt uint256, owner address, state uint8) +func (_PlasmaMVP *PlasmaMVPCallerSession) DepositExits(arg0 *big.Int) (struct { + Amount *big.Int + CommittedFee *big.Int + CreatedAt *big.Int + Owner common.Address + State uint8 +}, error) { + return _PlasmaMVP.Contract.DepositExits(&_PlasmaMVP.CallOpts, arg0) +} + +// DepositNonce is a free data retrieval call binding the contract method 0xde35f5cb. +// +// Solidity: function depositNonce() constant returns(uint256) +func (_PlasmaMVP *PlasmaMVPCaller) DepositNonce(opts *bind.CallOpts) (*big.Int, error) { + var ( + ret0 = new(*big.Int) + ) + out := ret0 + err := _PlasmaMVP.contract.Call(opts, out, "depositNonce") + return *ret0, err +} + +// DepositNonce is a free data retrieval call binding the contract method 0xde35f5cb. +// +// Solidity: function depositNonce() constant returns(uint256) +func (_PlasmaMVP *PlasmaMVPSession) DepositNonce() (*big.Int, error) { + return _PlasmaMVP.Contract.DepositNonce(&_PlasmaMVP.CallOpts) +} + +// DepositNonce is a free data retrieval call binding the contract method 0xde35f5cb. +// +// Solidity: function depositNonce() constant returns(uint256) +func (_PlasmaMVP *PlasmaMVPCallerSession) DepositNonce() (*big.Int, error) { + return _PlasmaMVP.Contract.DepositNonce(&_PlasmaMVP.CallOpts) +} + +// Deposits is a free data retrieval call binding the contract method 0xb02c43d0. +// +// Solidity: function deposits( uint256) constant returns(owner address, amount uint256, createdAt uint256, ethBlockNum uint256) +func (_PlasmaMVP *PlasmaMVPCaller) Deposits(opts *bind.CallOpts, arg0 *big.Int) (struct { + Owner common.Address + Amount *big.Int + CreatedAt *big.Int + EthBlockNum *big.Int +}, error) { + ret := new(struct { + Owner common.Address + Amount *big.Int + CreatedAt *big.Int + EthBlockNum *big.Int + }) + out := ret + err := _PlasmaMVP.contract.Call(opts, out, "deposits", arg0) + return *ret, err +} + +// Deposits is a free data retrieval call binding the contract method 0xb02c43d0. +// +// Solidity: function deposits( uint256) constant returns(owner address, amount uint256, createdAt uint256, ethBlockNum uint256) +func (_PlasmaMVP *PlasmaMVPSession) Deposits(arg0 *big.Int) (struct { + Owner common.Address + Amount *big.Int + CreatedAt *big.Int + EthBlockNum *big.Int +}, error) { + return _PlasmaMVP.Contract.Deposits(&_PlasmaMVP.CallOpts, arg0) +} + +// Deposits is a free data retrieval call binding the contract method 0xb02c43d0. +// +// Solidity: function deposits( uint256) constant returns(owner address, amount uint256, createdAt uint256, ethBlockNum uint256) +func (_PlasmaMVP *PlasmaMVPCallerSession) Deposits(arg0 *big.Int) (struct { + Owner common.Address + Amount *big.Int + CreatedAt *big.Int + EthBlockNum *big.Int +}, error) { + return _PlasmaMVP.Contract.Deposits(&_PlasmaMVP.CallOpts, arg0) +} + +// LastCommittedBlock is a free data retrieval call binding the contract method 0x3acb097a. +// +// Solidity: function lastCommittedBlock() constant returns(uint256) +func (_PlasmaMVP *PlasmaMVPCaller) LastCommittedBlock(opts *bind.CallOpts) (*big.Int, error) { + var ( + ret0 = new(*big.Int) + ) + out := ret0 + err := _PlasmaMVP.contract.Call(opts, out, "lastCommittedBlock") + return *ret0, err +} + +// LastCommittedBlock is a free data retrieval call binding the contract method 0x3acb097a. +// +// Solidity: function lastCommittedBlock() constant returns(uint256) +func (_PlasmaMVP *PlasmaMVPSession) LastCommittedBlock() (*big.Int, error) { + return _PlasmaMVP.Contract.LastCommittedBlock(&_PlasmaMVP.CallOpts) +} + +// LastCommittedBlock is a free data retrieval call binding the contract method 0x3acb097a. +// +// Solidity: function lastCommittedBlock() constant returns(uint256) +func (_PlasmaMVP *PlasmaMVPCallerSession) LastCommittedBlock() (*big.Int, error) { + return _PlasmaMVP.Contract.LastCommittedBlock(&_PlasmaMVP.CallOpts) +} + +// MaxTxnsPerBLock is a free data retrieval call binding the contract method 0x338b881c. +// +// Solidity: function maxTxnsPerBLock() constant returns(uint256) +func (_PlasmaMVP *PlasmaMVPCaller) MaxTxnsPerBLock(opts *bind.CallOpts) (*big.Int, error) { + var ( + ret0 = new(*big.Int) + ) + out := ret0 + err := _PlasmaMVP.contract.Call(opts, out, "maxTxnsPerBLock") + return *ret0, err +} + +// MaxTxnsPerBLock is a free data retrieval call binding the contract method 0x338b881c. +// +// Solidity: function maxTxnsPerBLock() constant returns(uint256) +func (_PlasmaMVP *PlasmaMVPSession) MaxTxnsPerBLock() (*big.Int, error) { + return _PlasmaMVP.Contract.MaxTxnsPerBLock(&_PlasmaMVP.CallOpts) +} + +// MaxTxnsPerBLock is a free data retrieval call binding the contract method 0x338b881c. +// +// Solidity: function maxTxnsPerBLock() constant returns(uint256) +func (_PlasmaMVP *PlasmaMVPCallerSession) MaxTxnsPerBLock() (*big.Int, error) { + return _PlasmaMVP.Contract.MaxTxnsPerBLock(&_PlasmaMVP.CallOpts) +} + +// TotalWithdrawBalance is a free data retrieval call binding the contract method 0xc430c438. +// +// Solidity: function totalWithdrawBalance() constant returns(uint256) +func (_PlasmaMVP *PlasmaMVPCaller) TotalWithdrawBalance(opts *bind.CallOpts) (*big.Int, error) { + var ( + ret0 = new(*big.Int) + ) + out := ret0 + err := _PlasmaMVP.contract.Call(opts, out, "totalWithdrawBalance") + return *ret0, err +} + +// TotalWithdrawBalance is a free data retrieval call binding the contract method 0xc430c438. +// +// Solidity: function totalWithdrawBalance() constant returns(uint256) +func (_PlasmaMVP *PlasmaMVPSession) TotalWithdrawBalance() (*big.Int, error) { + return _PlasmaMVP.Contract.TotalWithdrawBalance(&_PlasmaMVP.CallOpts) +} + +// TotalWithdrawBalance is a free data retrieval call binding the contract method 0xc430c438. +// +// Solidity: function totalWithdrawBalance() constant returns(uint256) +func (_PlasmaMVP *PlasmaMVPCallerSession) TotalWithdrawBalance() (*big.Int, error) { + return _PlasmaMVP.Contract.TotalWithdrawBalance(&_PlasmaMVP.CallOpts) +} + +// TxExits is a free data retrieval call binding the contract method 0x6d3d8b1a. +// +// Solidity: function txExits( uint256) constant returns(amount uint256, committedFee uint256, createdAt uint256, owner address, state uint8) +func (_PlasmaMVP *PlasmaMVPCaller) TxExits(opts *bind.CallOpts, arg0 *big.Int) (struct { + Amount *big.Int + CommittedFee *big.Int + CreatedAt *big.Int + Owner common.Address + State uint8 +}, error) { + ret := new(struct { + Amount *big.Int + CommittedFee *big.Int + CreatedAt *big.Int + Owner common.Address + State uint8 + }) + out := ret + err := _PlasmaMVP.contract.Call(opts, out, "txExits", arg0) + return *ret, err +} + +// TxExits is a free data retrieval call binding the contract method 0x6d3d8b1a. +// +// Solidity: function txExits( uint256) constant returns(amount uint256, committedFee uint256, createdAt uint256, owner address, state uint8) +func (_PlasmaMVP *PlasmaMVPSession) TxExits(arg0 *big.Int) (struct { + Amount *big.Int + CommittedFee *big.Int + CreatedAt *big.Int + Owner common.Address + State uint8 +}, error) { + return _PlasmaMVP.Contract.TxExits(&_PlasmaMVP.CallOpts, arg0) +} + +// TxExits is a free data retrieval call binding the contract method 0x6d3d8b1a. +// +// Solidity: function txExits( uint256) constant returns(amount uint256, committedFee uint256, createdAt uint256, owner address, state uint8) +func (_PlasmaMVP *PlasmaMVPCallerSession) TxExits(arg0 *big.Int) (struct { + Amount *big.Int + CommittedFee *big.Int + CreatedAt *big.Int + Owner common.Address + State uint8 +}, error) { + return _PlasmaMVP.Contract.TxExits(&_PlasmaMVP.CallOpts, arg0) +} + +// TxIndexFactor is a free data retrieval call binding the contract method 0x00d2980a. +// +// Solidity: function txIndexFactor() constant returns(uint256) +func (_PlasmaMVP *PlasmaMVPCaller) TxIndexFactor(opts *bind.CallOpts) (*big.Int, error) { + var ( + ret0 = new(*big.Int) + ) + out := ret0 + err := _PlasmaMVP.contract.Call(opts, out, "txIndexFactor") + return *ret0, err +} + +// TxIndexFactor is a free data retrieval call binding the contract method 0x00d2980a. +// +// Solidity: function txIndexFactor() constant returns(uint256) +func (_PlasmaMVP *PlasmaMVPSession) TxIndexFactor() (*big.Int, error) { + return _PlasmaMVP.Contract.TxIndexFactor(&_PlasmaMVP.CallOpts) +} + +// TxIndexFactor is a free data retrieval call binding the contract method 0x00d2980a. +// +// Solidity: function txIndexFactor() constant returns(uint256) +func (_PlasmaMVP *PlasmaMVPCallerSession) TxIndexFactor() (*big.Int, error) { + return _PlasmaMVP.Contract.TxIndexFactor(&_PlasmaMVP.CallOpts) +} + +// ChallengeExit is a paid mutator transaction binding the contract method 0xd344e8e4. +// +// Solidity: function challengeExit(exitingTxPos uint256[4], challengingTxPos uint256[2], txBytes bytes, proof bytes, confirmSignature bytes) returns() +func (_PlasmaMVP *PlasmaMVPTransactor) ChallengeExit(opts *bind.TransactOpts, exitingTxPos [4]*big.Int, challengingTxPos [2]*big.Int, txBytes []byte, proof []byte, confirmSignature []byte) (*types.Transaction, error) { + return _PlasmaMVP.contract.Transact(opts, "challengeExit", exitingTxPos, challengingTxPos, txBytes, proof, confirmSignature) +} + +// ChallengeExit is a paid mutator transaction binding the contract method 0xd344e8e4. +// +// Solidity: function challengeExit(exitingTxPos uint256[4], challengingTxPos uint256[2], txBytes bytes, proof bytes, confirmSignature bytes) returns() +func (_PlasmaMVP *PlasmaMVPSession) ChallengeExit(exitingTxPos [4]*big.Int, challengingTxPos [2]*big.Int, txBytes []byte, proof []byte, confirmSignature []byte) (*types.Transaction, error) { + return _PlasmaMVP.Contract.ChallengeExit(&_PlasmaMVP.TransactOpts, exitingTxPos, challengingTxPos, txBytes, proof, confirmSignature) +} + +// ChallengeExit is a paid mutator transaction binding the contract method 0xd344e8e4. +// +// Solidity: function challengeExit(exitingTxPos uint256[4], challengingTxPos uint256[2], txBytes bytes, proof bytes, confirmSignature bytes) returns() +func (_PlasmaMVP *PlasmaMVPTransactorSession) ChallengeExit(exitingTxPos [4]*big.Int, challengingTxPos [2]*big.Int, txBytes []byte, proof []byte, confirmSignature []byte) (*types.Transaction, error) { + return _PlasmaMVP.Contract.ChallengeExit(&_PlasmaMVP.TransactOpts, exitingTxPos, challengingTxPos, txBytes, proof, confirmSignature) +} + +// ChallengeFeeMismatch is a paid mutator transaction binding the contract method 0x82033e4c. +// +// Solidity: function challengeFeeMismatch(exitingTxPos uint256[4], challengingTxPos uint256[2], txBytes bytes, proof bytes) returns() +func (_PlasmaMVP *PlasmaMVPTransactor) ChallengeFeeMismatch(opts *bind.TransactOpts, exitingTxPos [4]*big.Int, challengingTxPos [2]*big.Int, txBytes []byte, proof []byte) (*types.Transaction, error) { + return _PlasmaMVP.contract.Transact(opts, "challengeFeeMismatch", exitingTxPos, challengingTxPos, txBytes, proof) +} + +// ChallengeFeeMismatch is a paid mutator transaction binding the contract method 0x82033e4c. +// +// Solidity: function challengeFeeMismatch(exitingTxPos uint256[4], challengingTxPos uint256[2], txBytes bytes, proof bytes) returns() +func (_PlasmaMVP *PlasmaMVPSession) ChallengeFeeMismatch(exitingTxPos [4]*big.Int, challengingTxPos [2]*big.Int, txBytes []byte, proof []byte) (*types.Transaction, error) { + return _PlasmaMVP.Contract.ChallengeFeeMismatch(&_PlasmaMVP.TransactOpts, exitingTxPos, challengingTxPos, txBytes, proof) +} + +// ChallengeFeeMismatch is a paid mutator transaction binding the contract method 0x82033e4c. +// +// Solidity: function challengeFeeMismatch(exitingTxPos uint256[4], challengingTxPos uint256[2], txBytes bytes, proof bytes) returns() +func (_PlasmaMVP *PlasmaMVPTransactorSession) ChallengeFeeMismatch(exitingTxPos [4]*big.Int, challengingTxPos [2]*big.Int, txBytes []byte, proof []byte) (*types.Transaction, error) { + return _PlasmaMVP.Contract.ChallengeFeeMismatch(&_PlasmaMVP.TransactOpts, exitingTxPos, challengingTxPos, txBytes, proof) +} + +// Deposit is a paid mutator transaction binding the contract method 0xf340fa01. +// +// Solidity: function deposit(owner address) returns() +func (_PlasmaMVP *PlasmaMVPTransactor) Deposit(opts *bind.TransactOpts, owner common.Address) (*types.Transaction, error) { + return _PlasmaMVP.contract.Transact(opts, "deposit", owner) +} + +// Deposit is a paid mutator transaction binding the contract method 0xf340fa01. +// +// Solidity: function deposit(owner address) returns() +func (_PlasmaMVP *PlasmaMVPSession) Deposit(owner common.Address) (*types.Transaction, error) { + return _PlasmaMVP.Contract.Deposit(&_PlasmaMVP.TransactOpts, owner) +} + +// Deposit is a paid mutator transaction binding the contract method 0xf340fa01. +// +// Solidity: function deposit(owner address) returns() +func (_PlasmaMVP *PlasmaMVPTransactorSession) Deposit(owner common.Address) (*types.Transaction, error) { + return _PlasmaMVP.Contract.Deposit(&_PlasmaMVP.TransactOpts, owner) +} + +// FinalizeDepositExits is a paid mutator transaction binding the contract method 0xfcf5f9eb. +// +// Solidity: function finalizeDepositExits() returns() +func (_PlasmaMVP *PlasmaMVPTransactor) FinalizeDepositExits(opts *bind.TransactOpts) (*types.Transaction, error) { + return _PlasmaMVP.contract.Transact(opts, "finalizeDepositExits") +} + +// FinalizeDepositExits is a paid mutator transaction binding the contract method 0xfcf5f9eb. +// +// Solidity: function finalizeDepositExits() returns() +func (_PlasmaMVP *PlasmaMVPSession) FinalizeDepositExits() (*types.Transaction, error) { + return _PlasmaMVP.Contract.FinalizeDepositExits(&_PlasmaMVP.TransactOpts) +} + +// FinalizeDepositExits is a paid mutator transaction binding the contract method 0xfcf5f9eb. +// +// Solidity: function finalizeDepositExits() returns() +func (_PlasmaMVP *PlasmaMVPTransactorSession) FinalizeDepositExits() (*types.Transaction, error) { + return _PlasmaMVP.Contract.FinalizeDepositExits(&_PlasmaMVP.TransactOpts) +} + +// FinalizeTransactionExits is a paid mutator transaction binding the contract method 0x884fc7d6. +// +// Solidity: function finalizeTransactionExits() returns() +func (_PlasmaMVP *PlasmaMVPTransactor) FinalizeTransactionExits(opts *bind.TransactOpts) (*types.Transaction, error) { + return _PlasmaMVP.contract.Transact(opts, "finalizeTransactionExits") +} + +// FinalizeTransactionExits is a paid mutator transaction binding the contract method 0x884fc7d6. +// +// Solidity: function finalizeTransactionExits() returns() +func (_PlasmaMVP *PlasmaMVPSession) FinalizeTransactionExits() (*types.Transaction, error) { + return _PlasmaMVP.Contract.FinalizeTransactionExits(&_PlasmaMVP.TransactOpts) +} + +// FinalizeTransactionExits is a paid mutator transaction binding the contract method 0x884fc7d6. +// +// Solidity: function finalizeTransactionExits() returns() +func (_PlasmaMVP *PlasmaMVPTransactorSession) FinalizeTransactionExits() (*types.Transaction, error) { + return _PlasmaMVP.Contract.FinalizeTransactionExits(&_PlasmaMVP.TransactOpts) +} + +// StartDepositExit is a paid mutator transaction binding the contract method 0x70e4abf6. +// +// Solidity: function startDepositExit(nonce uint256, committedFee uint256) returns() +func (_PlasmaMVP *PlasmaMVPTransactor) StartDepositExit(opts *bind.TransactOpts, nonce *big.Int, committedFee *big.Int) (*types.Transaction, error) { + return _PlasmaMVP.contract.Transact(opts, "startDepositExit", nonce, committedFee) +} + +// StartDepositExit is a paid mutator transaction binding the contract method 0x70e4abf6. +// +// Solidity: function startDepositExit(nonce uint256, committedFee uint256) returns() +func (_PlasmaMVP *PlasmaMVPSession) StartDepositExit(nonce *big.Int, committedFee *big.Int) (*types.Transaction, error) { + return _PlasmaMVP.Contract.StartDepositExit(&_PlasmaMVP.TransactOpts, nonce, committedFee) +} + +// StartDepositExit is a paid mutator transaction binding the contract method 0x70e4abf6. +// +// Solidity: function startDepositExit(nonce uint256, committedFee uint256) returns() +func (_PlasmaMVP *PlasmaMVPTransactorSession) StartDepositExit(nonce *big.Int, committedFee *big.Int) (*types.Transaction, error) { + return _PlasmaMVP.Contract.StartDepositExit(&_PlasmaMVP.TransactOpts, nonce, committedFee) +} + +// StartFeeExit is a paid mutator transaction binding the contract method 0xae80d8c8. +// +// Solidity: function startFeeExit(blockNumber uint256) returns() +func (_PlasmaMVP *PlasmaMVPTransactor) StartFeeExit(opts *bind.TransactOpts, blockNumber *big.Int) (*types.Transaction, error) { + return _PlasmaMVP.contract.Transact(opts, "startFeeExit", blockNumber) +} + +// StartFeeExit is a paid mutator transaction binding the contract method 0xae80d8c8. +// +// Solidity: function startFeeExit(blockNumber uint256) returns() +func (_PlasmaMVP *PlasmaMVPSession) StartFeeExit(blockNumber *big.Int) (*types.Transaction, error) { + return _PlasmaMVP.Contract.StartFeeExit(&_PlasmaMVP.TransactOpts, blockNumber) +} + +// StartFeeExit is a paid mutator transaction binding the contract method 0xae80d8c8. +// +// Solidity: function startFeeExit(blockNumber uint256) returns() +func (_PlasmaMVP *PlasmaMVPTransactorSession) StartFeeExit(blockNumber *big.Int) (*types.Transaction, error) { + return _PlasmaMVP.Contract.StartFeeExit(&_PlasmaMVP.TransactOpts, blockNumber) +} + +// StartTransactionExit is a paid mutator transaction binding the contract method 0xcf024ea6. +// +// Solidity: function startTransactionExit(txPos uint256[3], txBytes bytes, proof bytes, confirmSignatures bytes, committedFee uint256) returns() +func (_PlasmaMVP *PlasmaMVPTransactor) StartTransactionExit(opts *bind.TransactOpts, txPos [3]*big.Int, txBytes []byte, proof []byte, confirmSignatures []byte, committedFee *big.Int) (*types.Transaction, error) { + return _PlasmaMVP.contract.Transact(opts, "startTransactionExit", txPos, txBytes, proof, confirmSignatures, committedFee) +} + +// StartTransactionExit is a paid mutator transaction binding the contract method 0xcf024ea6. +// +// Solidity: function startTransactionExit(txPos uint256[3], txBytes bytes, proof bytes, confirmSignatures bytes, committedFee uint256) returns() +func (_PlasmaMVP *PlasmaMVPSession) StartTransactionExit(txPos [3]*big.Int, txBytes []byte, proof []byte, confirmSignatures []byte, committedFee *big.Int) (*types.Transaction, error) { + return _PlasmaMVP.Contract.StartTransactionExit(&_PlasmaMVP.TransactOpts, txPos, txBytes, proof, confirmSignatures, committedFee) +} + +// StartTransactionExit is a paid mutator transaction binding the contract method 0xcf024ea6. +// +// Solidity: function startTransactionExit(txPos uint256[3], txBytes bytes, proof bytes, confirmSignatures bytes, committedFee uint256) returns() +func (_PlasmaMVP *PlasmaMVPTransactorSession) StartTransactionExit(txPos [3]*big.Int, txBytes []byte, proof []byte, confirmSignatures []byte, committedFee *big.Int) (*types.Transaction, error) { + return _PlasmaMVP.Contract.StartTransactionExit(&_PlasmaMVP.TransactOpts, txPos, txBytes, proof, confirmSignatures, committedFee) +} + +// SubmitBlock is a paid mutator transaction binding the contract method 0xd84ba62f. +// +// Solidity: function submitBlock(headers bytes32[], txnsPerBlock uint256[], feesPerBlock uint256[], blockNum uint256) returns() +func (_PlasmaMVP *PlasmaMVPTransactor) SubmitBlock(opts *bind.TransactOpts, headers [][32]byte, txnsPerBlock []*big.Int, feesPerBlock []*big.Int, blockNum *big.Int) (*types.Transaction, error) { + return _PlasmaMVP.contract.Transact(opts, "submitBlock", headers, txnsPerBlock, feesPerBlock, blockNum) +} + +// SubmitBlock is a paid mutator transaction binding the contract method 0xd84ba62f. +// +// Solidity: function submitBlock(headers bytes32[], txnsPerBlock uint256[], feesPerBlock uint256[], blockNum uint256) returns() +func (_PlasmaMVP *PlasmaMVPSession) SubmitBlock(headers [][32]byte, txnsPerBlock []*big.Int, feesPerBlock []*big.Int, blockNum *big.Int) (*types.Transaction, error) { + return _PlasmaMVP.Contract.SubmitBlock(&_PlasmaMVP.TransactOpts, headers, txnsPerBlock, feesPerBlock, blockNum) +} + +// SubmitBlock is a paid mutator transaction binding the contract method 0xd84ba62f. +// +// Solidity: function submitBlock(headers bytes32[], txnsPerBlock uint256[], feesPerBlock uint256[], blockNum uint256) returns() +func (_PlasmaMVP *PlasmaMVPTransactorSession) SubmitBlock(headers [][32]byte, txnsPerBlock []*big.Int, feesPerBlock []*big.Int, blockNum *big.Int) (*types.Transaction, error) { + return _PlasmaMVP.Contract.SubmitBlock(&_PlasmaMVP.TransactOpts, headers, txnsPerBlock, feesPerBlock, blockNum) +} + +// Withdraw is a paid mutator transaction binding the contract method 0x3ccfd60b. +// +// Solidity: function withdraw() returns(uint256) +func (_PlasmaMVP *PlasmaMVPTransactor) Withdraw(opts *bind.TransactOpts) (*types.Transaction, error) { + return _PlasmaMVP.contract.Transact(opts, "withdraw") +} + +// Withdraw is a paid mutator transaction binding the contract method 0x3ccfd60b. +// +// Solidity: function withdraw() returns(uint256) +func (_PlasmaMVP *PlasmaMVPSession) Withdraw() (*types.Transaction, error) { + return _PlasmaMVP.Contract.Withdraw(&_PlasmaMVP.TransactOpts) +} + +// Withdraw is a paid mutator transaction binding the contract method 0x3ccfd60b. +// +// Solidity: function withdraw() returns(uint256) +func (_PlasmaMVP *PlasmaMVPTransactorSession) Withdraw() (*types.Transaction, error) { + return _PlasmaMVP.Contract.Withdraw(&_PlasmaMVP.TransactOpts) +} + +// PlasmaMVPAddedToBalancesIterator is returned from FilterAddedToBalances and is used to iterate over the raw logs and unpacked data for AddedToBalances events raised by the PlasmaMVP contract. +type PlasmaMVPAddedToBalancesIterator struct { + Event *PlasmaMVPAddedToBalances // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *PlasmaMVPAddedToBalancesIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(PlasmaMVPAddedToBalances) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(PlasmaMVPAddedToBalances) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *PlasmaMVPAddedToBalancesIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *PlasmaMVPAddedToBalancesIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// PlasmaMVPAddedToBalances represents a AddedToBalances event raised by the PlasmaMVP contract. +type PlasmaMVPAddedToBalances struct { + Owner common.Address + Amount *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterAddedToBalances is a free log retrieval operation binding the contract event 0xf8552a24c7d58fd05114f6fc9db7b3a354db64d5fc758184af1696ccd8f158f3. +// +// Solidity: e AddedToBalances(owner address, amount uint256) +func (_PlasmaMVP *PlasmaMVPFilterer) FilterAddedToBalances(opts *bind.FilterOpts) (*PlasmaMVPAddedToBalancesIterator, error) { + + logs, sub, err := _PlasmaMVP.contract.FilterLogs(opts, "AddedToBalances") + if err != nil { + return nil, err + } + return &PlasmaMVPAddedToBalancesIterator{contract: _PlasmaMVP.contract, event: "AddedToBalances", logs: logs, sub: sub}, nil +} + +// WatchAddedToBalances is a free log subscription operation binding the contract event 0xf8552a24c7d58fd05114f6fc9db7b3a354db64d5fc758184af1696ccd8f158f3. +// +// Solidity: e AddedToBalances(owner address, amount uint256) +func (_PlasmaMVP *PlasmaMVPFilterer) WatchAddedToBalances(opts *bind.WatchOpts, sink chan<- *PlasmaMVPAddedToBalances) (event.Subscription, error) { + + logs, sub, err := _PlasmaMVP.contract.WatchLogs(opts, "AddedToBalances") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(PlasmaMVPAddedToBalances) + if err := _PlasmaMVP.contract.UnpackLog(event, "AddedToBalances", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// PlasmaMVPBlockSubmittedIterator is returned from FilterBlockSubmitted and is used to iterate over the raw logs and unpacked data for BlockSubmitted events raised by the PlasmaMVP contract. +type PlasmaMVPBlockSubmittedIterator struct { + Event *PlasmaMVPBlockSubmitted // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *PlasmaMVPBlockSubmittedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(PlasmaMVPBlockSubmitted) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(PlasmaMVPBlockSubmitted) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *PlasmaMVPBlockSubmittedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *PlasmaMVPBlockSubmittedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// PlasmaMVPBlockSubmitted represents a BlockSubmitted event raised by the PlasmaMVP contract. +type PlasmaMVPBlockSubmitted struct { + Root [32]byte + BlockNumber *big.Int + NumTxns *big.Int + FeeAmount *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterBlockSubmitted is a free log retrieval operation binding the contract event 0x044ff3798f9b3ad55d1155cea9a40508c71b4c64335f5dae87e8e11551515a06. +// +// Solidity: e BlockSubmitted(root bytes32, blockNumber uint256, numTxns uint256, feeAmount uint256) +func (_PlasmaMVP *PlasmaMVPFilterer) FilterBlockSubmitted(opts *bind.FilterOpts) (*PlasmaMVPBlockSubmittedIterator, error) { + + logs, sub, err := _PlasmaMVP.contract.FilterLogs(opts, "BlockSubmitted") + if err != nil { + return nil, err + } + return &PlasmaMVPBlockSubmittedIterator{contract: _PlasmaMVP.contract, event: "BlockSubmitted", logs: logs, sub: sub}, nil +} + +// WatchBlockSubmitted is a free log subscription operation binding the contract event 0x044ff3798f9b3ad55d1155cea9a40508c71b4c64335f5dae87e8e11551515a06. +// +// Solidity: e BlockSubmitted(root bytes32, blockNumber uint256, numTxns uint256, feeAmount uint256) +func (_PlasmaMVP *PlasmaMVPFilterer) WatchBlockSubmitted(opts *bind.WatchOpts, sink chan<- *PlasmaMVPBlockSubmitted) (event.Subscription, error) { + + logs, sub, err := _PlasmaMVP.contract.WatchLogs(opts, "BlockSubmitted") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(PlasmaMVPBlockSubmitted) + if err := _PlasmaMVP.contract.UnpackLog(event, "BlockSubmitted", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// PlasmaMVPChallengedExitIterator is returned from FilterChallengedExit and is used to iterate over the raw logs and unpacked data for ChallengedExit events raised by the PlasmaMVP contract. +type PlasmaMVPChallengedExitIterator struct { + Event *PlasmaMVPChallengedExit // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *PlasmaMVPChallengedExitIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(PlasmaMVPChallengedExit) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(PlasmaMVPChallengedExit) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *PlasmaMVPChallengedExitIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *PlasmaMVPChallengedExitIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// PlasmaMVPChallengedExit represents a ChallengedExit event raised by the PlasmaMVP contract. +type PlasmaMVPChallengedExit struct { + Position [4]*big.Int + Owner common.Address + Amount *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterChallengedExit is a free log retrieval operation binding the contract event 0xe1289dafb1083e540206bcd7d95a9705ba2590d6a9229c35a1c4c4c5efbda901. +// +// Solidity: e ChallengedExit(position uint256[4], owner address, amount uint256) +func (_PlasmaMVP *PlasmaMVPFilterer) FilterChallengedExit(opts *bind.FilterOpts) (*PlasmaMVPChallengedExitIterator, error) { + + logs, sub, err := _PlasmaMVP.contract.FilterLogs(opts, "ChallengedExit") + if err != nil { + return nil, err + } + return &PlasmaMVPChallengedExitIterator{contract: _PlasmaMVP.contract, event: "ChallengedExit", logs: logs, sub: sub}, nil +} + +// WatchChallengedExit is a free log subscription operation binding the contract event 0xe1289dafb1083e540206bcd7d95a9705ba2590d6a9229c35a1c4c4c5efbda901. +// +// Solidity: e ChallengedExit(position uint256[4], owner address, amount uint256) +func (_PlasmaMVP *PlasmaMVPFilterer) WatchChallengedExit(opts *bind.WatchOpts, sink chan<- *PlasmaMVPChallengedExit) (event.Subscription, error) { + + logs, sub, err := _PlasmaMVP.contract.WatchLogs(opts, "ChallengedExit") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(PlasmaMVPChallengedExit) + if err := _PlasmaMVP.contract.UnpackLog(event, "ChallengedExit", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// PlasmaMVPDepositIterator is returned from FilterDeposit and is used to iterate over the raw logs and unpacked data for Deposit events raised by the PlasmaMVP contract. +type PlasmaMVPDepositIterator struct { + Event *PlasmaMVPDeposit // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *PlasmaMVPDepositIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(PlasmaMVPDeposit) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(PlasmaMVPDeposit) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *PlasmaMVPDepositIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *PlasmaMVPDepositIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// PlasmaMVPDeposit represents a Deposit event raised by the PlasmaMVP contract. +type PlasmaMVPDeposit struct { + Depositor common.Address + Amount *big.Int + DepositNonce *big.Int + EthBlockNum *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterDeposit is a free log retrieval operation binding the contract event 0x36af321ec8d3c75236829c5317affd40ddb308863a1236d2d277a4025cccee1e. +// +// Solidity: e Deposit(depositor address, amount uint256, depositNonce uint256, ethBlockNum uint256) +func (_PlasmaMVP *PlasmaMVPFilterer) FilterDeposit(opts *bind.FilterOpts) (*PlasmaMVPDepositIterator, error) { + + logs, sub, err := _PlasmaMVP.contract.FilterLogs(opts, "Deposit") + if err != nil { + return nil, err + } + return &PlasmaMVPDepositIterator{contract: _PlasmaMVP.contract, event: "Deposit", logs: logs, sub: sub}, nil +} + +// WatchDeposit is a free log subscription operation binding the contract event 0x36af321ec8d3c75236829c5317affd40ddb308863a1236d2d277a4025cccee1e. +// +// Solidity: e Deposit(depositor address, amount uint256, depositNonce uint256, ethBlockNum uint256) +func (_PlasmaMVP *PlasmaMVPFilterer) WatchDeposit(opts *bind.WatchOpts, sink chan<- *PlasmaMVPDeposit) (event.Subscription, error) { + + logs, sub, err := _PlasmaMVP.contract.WatchLogs(opts, "Deposit") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(PlasmaMVPDeposit) + if err := _PlasmaMVP.contract.UnpackLog(event, "Deposit", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// PlasmaMVPFinalizedExitIterator is returned from FilterFinalizedExit and is used to iterate over the raw logs and unpacked data for FinalizedExit events raised by the PlasmaMVP contract. +type PlasmaMVPFinalizedExitIterator struct { + Event *PlasmaMVPFinalizedExit // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *PlasmaMVPFinalizedExitIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(PlasmaMVPFinalizedExit) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(PlasmaMVPFinalizedExit) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *PlasmaMVPFinalizedExitIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *PlasmaMVPFinalizedExitIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// PlasmaMVPFinalizedExit represents a FinalizedExit event raised by the PlasmaMVP contract. +type PlasmaMVPFinalizedExit struct { + Position [4]*big.Int + Owner common.Address + Amount *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterFinalizedExit is a free log retrieval operation binding the contract event 0xb5083a27a38f8a9aa999efb3306b7be96dc3f42010a968dd86627880ba7fdbe2. +// +// Solidity: e FinalizedExit(position uint256[4], owner address, amount uint256) +func (_PlasmaMVP *PlasmaMVPFilterer) FilterFinalizedExit(opts *bind.FilterOpts) (*PlasmaMVPFinalizedExitIterator, error) { + + logs, sub, err := _PlasmaMVP.contract.FilterLogs(opts, "FinalizedExit") + if err != nil { + return nil, err + } + return &PlasmaMVPFinalizedExitIterator{contract: _PlasmaMVP.contract, event: "FinalizedExit", logs: logs, sub: sub}, nil +} + +// WatchFinalizedExit is a free log subscription operation binding the contract event 0xb5083a27a38f8a9aa999efb3306b7be96dc3f42010a968dd86627880ba7fdbe2. +// +// Solidity: e FinalizedExit(position uint256[4], owner address, amount uint256) +func (_PlasmaMVP *PlasmaMVPFilterer) WatchFinalizedExit(opts *bind.WatchOpts, sink chan<- *PlasmaMVPFinalizedExit) (event.Subscription, error) { + + logs, sub, err := _PlasmaMVP.contract.WatchLogs(opts, "FinalizedExit") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(PlasmaMVPFinalizedExit) + if err := _PlasmaMVP.contract.UnpackLog(event, "FinalizedExit", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// PlasmaMVPStartedDepositExitIterator is returned from FilterStartedDepositExit and is used to iterate over the raw logs and unpacked data for StartedDepositExit events raised by the PlasmaMVP contract. +type PlasmaMVPStartedDepositExitIterator struct { + Event *PlasmaMVPStartedDepositExit // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *PlasmaMVPStartedDepositExitIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(PlasmaMVPStartedDepositExit) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(PlasmaMVPStartedDepositExit) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *PlasmaMVPStartedDepositExitIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *PlasmaMVPStartedDepositExitIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// PlasmaMVPStartedDepositExit represents a StartedDepositExit event raised by the PlasmaMVP contract. +type PlasmaMVPStartedDepositExit struct { + Nonce *big.Int + Owner common.Address + Amount *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterStartedDepositExit is a free log retrieval operation binding the contract event 0x0bdfdd54dc0a51ef460d31ddf95470493780afed2eee6046199b65c2b1d66b91. +// +// Solidity: e StartedDepositExit(nonce uint256, owner address, amount uint256) +func (_PlasmaMVP *PlasmaMVPFilterer) FilterStartedDepositExit(opts *bind.FilterOpts) (*PlasmaMVPStartedDepositExitIterator, error) { + + logs, sub, err := _PlasmaMVP.contract.FilterLogs(opts, "StartedDepositExit") + if err != nil { + return nil, err + } + return &PlasmaMVPStartedDepositExitIterator{contract: _PlasmaMVP.contract, event: "StartedDepositExit", logs: logs, sub: sub}, nil +} + +// WatchStartedDepositExit is a free log subscription operation binding the contract event 0x0bdfdd54dc0a51ef460d31ddf95470493780afed2eee6046199b65c2b1d66b91. +// +// Solidity: e StartedDepositExit(nonce uint256, owner address, amount uint256) +func (_PlasmaMVP *PlasmaMVPFilterer) WatchStartedDepositExit(opts *bind.WatchOpts, sink chan<- *PlasmaMVPStartedDepositExit) (event.Subscription, error) { + + logs, sub, err := _PlasmaMVP.contract.WatchLogs(opts, "StartedDepositExit") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(PlasmaMVPStartedDepositExit) + if err := _PlasmaMVP.contract.UnpackLog(event, "StartedDepositExit", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// PlasmaMVPStartedTransactionExitIterator is returned from FilterStartedTransactionExit and is used to iterate over the raw logs and unpacked data for StartedTransactionExit events raised by the PlasmaMVP contract. +type PlasmaMVPStartedTransactionExitIterator struct { + Event *PlasmaMVPStartedTransactionExit // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *PlasmaMVPStartedTransactionExitIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(PlasmaMVPStartedTransactionExit) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(PlasmaMVPStartedTransactionExit) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *PlasmaMVPStartedTransactionExitIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *PlasmaMVPStartedTransactionExitIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// PlasmaMVPStartedTransactionExit represents a StartedTransactionExit event raised by the PlasmaMVP contract. +type PlasmaMVPStartedTransactionExit struct { + Position [3]*big.Int + Owner common.Address + Amount *big.Int + ConfirmSignatures []byte + CommittedFee *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterStartedTransactionExit is a free log retrieval operation binding the contract event 0x20d695720ae96d3511520c6f51d6ab23aa19a3796da77024ad027b344bb72530. +// +// Solidity: e StartedTransactionExit(position uint256[3], owner address, amount uint256, confirmSignatures bytes, committedFee uint256) +func (_PlasmaMVP *PlasmaMVPFilterer) FilterStartedTransactionExit(opts *bind.FilterOpts) (*PlasmaMVPStartedTransactionExitIterator, error) { + + logs, sub, err := _PlasmaMVP.contract.FilterLogs(opts, "StartedTransactionExit") + if err != nil { + return nil, err + } + return &PlasmaMVPStartedTransactionExitIterator{contract: _PlasmaMVP.contract, event: "StartedTransactionExit", logs: logs, sub: sub}, nil +} + +// WatchStartedTransactionExit is a free log subscription operation binding the contract event 0x20d695720ae96d3511520c6f51d6ab23aa19a3796da77024ad027b344bb72530. +// +// Solidity: e StartedTransactionExit(position uint256[3], owner address, amount uint256, confirmSignatures bytes, committedFee uint256) +func (_PlasmaMVP *PlasmaMVPFilterer) WatchStartedTransactionExit(opts *bind.WatchOpts, sink chan<- *PlasmaMVPStartedTransactionExit) (event.Subscription, error) { + + logs, sub, err := _PlasmaMVP.contract.WatchLogs(opts, "StartedTransactionExit") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(PlasmaMVPStartedTransactionExit) + if err := _PlasmaMVP.contract.UnpackLog(event, "StartedTransactionExit", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} diff --git a/eth/main.go b/eth/main.go new file mode 100644 index 0000000..66a1c40 --- /dev/null +++ b/eth/main.go @@ -0,0 +1,96 @@ +package eth + +import ( + "context" + "encoding/hex" + "encoding/json" + "fmt" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/rpc" + "github.com/tendermint/tendermint/libs/log" + "math/big" +) + +// Client defines wrappers to a remote endpoint +type Client struct { + rpc *rpc.Client + ec *ethclient.Client + logger log.Logger +} + +// Instantiate a connection and bind the go plasma contract wrapper with this client +func InitEthConn(nodeUrl string, logger log.Logger) (*Client, error) { + // Connect to a remote etheruem client + // + // Ethclient wraps around the underlying rpc module and provides convenient functions. We still keep reference + // to the underlying rpc module to make calls that the wrapper does not support + c, err := rpc.Dial(nodeUrl) + if err != nil { + return nil, err + } + ec := ethclient.NewClient(c) + + return &Client{c, ec, logger}, nil +} + +// SubscribeToHeads returns a channel that funnels new ethereum headers to the returned channel +func (client *Client) SubscribeToHeads() (<-chan *types.Header, error) { + c := make(chan *types.Header) + + sub, err := client.ec.SubscribeNewHead(context.Background(), c) + if err != nil { + return nil, err + } + + // close the channel if an error arises in the subscription + go func() { + for { + err = <-sub.Err() + client.logger.Error("Etheruem client header subscription error -", err) + close(c) + } + }() + + return c, nil +} + +func (client *Client) CurrentBlockNum() (*big.Int, error) { + var res json.RawMessage + err := client.rpc.Call(&res, "eth_blockNumber") + + var hexStr string + if err = json.Unmarshal(res, &hexStr); err != nil { + return nil, fmt.Errorf("Error unmarshaling blockNumber response - %s", err) + } + + bytes, err := hex.DecodeString(hexStr) + if err != nil { + return nil, fmt.Errorf("Error converting hex string to bytes - %x", hexStr) + } + + return new(big.Int).SetBytes(bytes), nil +} + +// used for testing when running against a local client like ganache +func (client *Client) accounts() ([]common.Address, error) { + var res json.RawMessage + err := client.rpc.Call(&res, "eth_accounts") + if err != nil { + return nil, err + } + + var addrs []string + if err := json.Unmarshal(res, &addrs); err != nil { + return nil, err + } + + // convert to the correct type + result := make([]common.Address, len(addrs)) + for i, addr := range addrs { + result[i] = common.HexToAddress(addr) + } + + return result, nil +} diff --git a/eth/plasma.go b/eth/plasma.go new file mode 100644 index 0000000..beac948 --- /dev/null +++ b/eth/plasma.go @@ -0,0 +1,295 @@ +package eth + +import ( + "context" + "crypto/ecdsa" + "encoding/json" + "fmt" + contracts "github.com/FourthState/plasma-mvp-sidechain/contracts/wrappers" + plasmaTypes "github.com/FourthState/plasma-mvp-sidechain/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/syndtr/goleveldb/leveldb" + "github.com/syndtr/goleveldb/leveldb/comparer" + "github.com/syndtr/goleveldb/leveldb/memdb" + "github.com/tendermint/tendermint/libs/log" + "math/big" + "sync" +) + +// Plasma holds related unexported members +type Plasma struct { + session *contracts.PlasmaMVPSession + client *Client + logger log.Logger + + memdb *memdb.DB + db *leveldb.DB + + blockNum *big.Int + ethBlockNum *big.Int + finalityBound uint64 + + lock *sync.Mutex +} + +// InitPlasma binds the go wrapper to the deployed contract. This private key provides authentication for the operator +func InitPlasma(contractAddr common.Address, privateKey *ecdsa.PrivateKey, client *Client, logger log.Logger, finalityBound uint64) (*Plasma, error) { + plasmaContract, err := contracts.NewPlasmaMVP(contractAddr, client.ec) + if err != nil { + return nil, err + } + + // Create a session with the contract and operator account + auth := bind.NewKeyedTransactor(privateKey) + plasmaSession := &contracts.PlasmaMVPSession{ + Contract: plasmaContract, + CallOpts: bind.CallOpts{ + Pending: true, + }, + TransactOpts: bind.TransactOpts{ + From: auth.From, + Signer: auth.Signer, + GasLimit: 3141592, // aribitrary. TODO: check this + }, + } + + // TODO: deal with syncing issues + lastCommittedBlock, err := plasmaSession.LastCommittedBlock() + if err != nil { + return nil, fmt.Errorf("Contract session not correctly established - %s", err) + } + + plasma := &Plasma{ + session: plasmaSession, + client: client, + // capacity argument is advisory and not enforced in the memdb implementation + // TODO: flush the in-memory DB to a local one to bound memory consumption + memdb: memdb.New(comparer.DefaultComparer, 1), + logger: logger, + + ethBlockNum: big.NewInt(-1), + blockNum: lastCommittedBlock, + finalityBound: finalityBound, + + lock: &sync.Mutex{}, + } + + // listen to new ethereum block headers + ethCh, err := client.SubscribeToHeads() + if err != nil { + logger.Error("Could not successfully subscribe to heads: %v", err) + return nil, err + } + + // start listeners + go watchEthBlocks(plasma, ethCh) + go watchDeposits(plasma) + go watchExits(plasma) + + return plasma, nil +} + +// SubmitBlock proxy. TODO: handle batching with a timmer interrupt +func (plasma *Plasma) SubmitBlock(header [32]byte, numTxns *big.Int, fee *big.Int) (*types.Transaction, error) { + plasma.blockNum = plasma.blockNum.Add(plasma.blockNum, big.NewInt(1)) + + tx, err := plasma.session.SubmitBlock( + [][32]byte{header}, + []*big.Int{numTxns}, + []*big.Int{fee}, + plasma.blockNum) + + if err != nil { + return nil, err + } + + return tx, nil +} + +// GetDeposit checks the existence of a deposit nonce +func (plasma *Plasma) GetDeposit(nonce *big.Int) (*plasmaTypes.Deposit, error) { + key := prefixKey(depositPrefix, nonce.Bytes()) + data, err := plasma.memdb.Get(key) + + var deposit plasmaTypes.Deposit + // check against the contract if the deposit is not in the cache or decoding fails + if err != nil || json.Unmarshal(data, &deposit) != nil { + if plasma.memdb.Contains(key) { + plasma.logger.Info("corrupted deposit found within db") + plasma.memdb.Delete(key) + } + + d, err := plasma.session.Deposits(nonce) + if err != nil { + plasma.logger.Error("contract call, deposits, failed") + return nil, err + } + + if d.CreatedAt.Sign() == 0 { + return nil, fmt.Errorf("deposit does not exist") + } + + deposit = plasmaTypes.Deposit{ + Owner: d.Owner, + Amount: sdk.NewUintFromBigInt(d.Amount), + BlockNum: sdk.NewUintFromBigInt(d.EthBlockNum), + } + + // save to the db + data, err = json.Marshal(deposit) + if err != nil { + plasma.logger.Error("error encoding deposit. will not be cached") + } else { + plasma.memdb.Put(key, data) + } + } + + // check finality bound for the deposit + plasma.lock.Lock() + ethBlockNum := plasma.ethBlockNum + plasma.lock.Unlock() + if ethBlockNum.Sign() < 0 { + return nil, fmt.Errorf("not subscribed to ethereum block headers") + } + + if new(big.Int).Sub(ethBlockNum, deposit.BlockNum.BigInt()).Uint64() < plasma.finalityBound { + return nil, fmt.Errorf("deposit not finalized") + } + + return &deposit, nil +} + +// HasTXBeenExited indicates if the position has ever been exited +func (plasma *Plasma) HasTXBeenExited(position [4]*big.Int) bool { + var key []byte + var priority *big.Int + if position[3].Sign() == 0 { // utxo exit + txPos := [3]*big.Int{position[0], position[1], position[3]} + priority = calcPriority(txPos) + key = prefixKey(transactionExitPrefix, priority.Bytes()) + } else { // deposit exit + priority = position[3] + key = prefixKey(depositExitPrefix, priority.Bytes()) + } + + type exit struct { + Amount *big.Int + CommittedFee *big.Int + CreatedAt *big.Int + Owner common.Address + State uint8 + } + + if !plasma.memdb.Contains(key) { + var e exit + var err error + if position[3].Sign() == 0 { + e, err = plasma.session.TxExits(priority) + } else { + e, err = plasma.session.DepositExits(priority) + } + + // default to true if the contract cannot be queried. Nothing should be spent + if err != nil { + plasma.logger.Error(fmt.Sprintf("Error querying contract %s", err)) + return true + } + + if e.State == 1 || e.State == 3 { + return true + } else { + return false + } + } + + return true +} + +func watchDeposits(plasma *Plasma) { + // suscribe to future deposits + deposits := make(chan *contracts.PlasmaMVPDeposit) + opts := &bind.WatchOpts{ + Start: nil, // latest block + Context: context.Background(), + } + plasma.session.Contract.WatchDeposit(opts, deposits) + + for deposit := range deposits { + key := prefixKey(depositPrefix, deposit.DepositNonce.Bytes()) + + // remove the nonce, encode, and store + data, err := json.Marshal(plasmaTypes.Deposit{ + Owner: deposit.Depositor, + Amount: sdk.NewUintFromBigInt(deposit.Amount), + BlockNum: sdk.NewUintFromBigInt(deposit.EthBlockNum), + }) + + if err != nil { + plasma.logger.Error("Error encoding deposit event from contract -", deposit) + } else { + plasma.memdb.Put(key, data) + } + } +} + +func watchExits(plasma *Plasma) { + startedDepositExits := make(chan *contracts.PlasmaMVPStartedDepositExit) + startedTransactionExits := make(chan *contracts.PlasmaMVPStartedTransactionExit) + challengedExits := make(chan *contracts.PlasmaMVPChallengedExit) + + opts := &bind.WatchOpts{ + Start: nil, // latest block + Context: context.Background(), + } + plasma.session.Contract.WatchStartedDepositExit(opts, startedDepositExits) + plasma.session.Contract.WatchStartedTransactionExit(opts, startedTransactionExits) + plasma.session.Contract.WatchChallengedExit(opts, challengedExits) + + go func() { + for depositExit := range startedDepositExits { + nonce := depositExit.Nonce.Bytes() + key := prefixKey(depositExitPrefix, nonce) + plasma.memdb.Put(key, nil) + } + + plasma.logger.Info("stopped watching for deposit exits") + }() + + go func() { + for transactionExit := range startedTransactionExits { + priority := calcPriority(transactionExit.Position).Bytes() + key := prefixKey(transactionExitPrefix, priority) + plasma.memdb.Put(key, nil) + } + + plasma.logger.Info("stopped watching for transaction exits") + }() + + go func() { + for challengedExit := range challengedExits { + if challengedExit.Position[3].Sign() == 0 { + position := [3]*big.Int{challengedExit.Position[0], challengedExit.Position[1], challengedExit.Position[2]} + key := prefixKey(transactionExitPrefix, calcPriority(position).Bytes()) + plasma.memdb.Delete(key) + } else { + key := prefixKey(depositExitPrefix, challengedExit.Position[3].Bytes()) + plasma.memdb.Delete(key) + } + } + + plasma.logger.Info("stopped watching for challenged exit") + }() +} + +func watchEthBlocks(plasma *Plasma, ch <-chan *types.Header) { + for header := range ch { + plasma.lock.Lock() + plasma.ethBlockNum = header.Number + plasma.lock.Unlock() + } + + plasma.logger.Info("Block subscription closed.") +} diff --git a/eth/plasma_test.go b/eth/plasma_test.go new file mode 100644 index 0000000..0f19e6b --- /dev/null +++ b/eth/plasma_test.go @@ -0,0 +1,318 @@ +package eth + +import ( + "bytes" + "crypto/sha256" + "encoding/json" + plasmaTypes "github.com/FourthState/plasma-mvp-sidechain/types" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/rlp" + "github.com/tendermint/tendermint/libs/log" + "math/big" + "os" + "reflect" + "testing" + "time" +) + +// private/public keys using the `plasma` mnemonic with ganache-cli +// `ganache-cli -m=plasma` +// plasmaContractAddr will be deterministic. `truffle migrate` immediately after `ganache-cli -m=plasma` +const ( + clientAddr = "ws://127.0.0.1:8545" + plasmaContractAddr = "5cae340fb2c2bb0a2f194a95cda8a1ffdc9d2f85" + operatorPrivKey = "9cd69f009ac86203e54ec50e3686de95ff6126d3b30a19f926a0fe9323c17181" + + minExitBond = 10000 +) + +func TestConnection(t *testing.T) { + logger := log.NewTMLogger(os.Stderr) + + t.Logf("Connecting to remote client: %s", clientAddr) + client, err := InitEthConn(clientAddr, logger) + if err != nil { + t.Fatal("Connection Error -", err) + } + + _, err = client.accounts() + if err != nil { + t.Error("Error Retrieving Accounts -", err) + } +} + +func TestPlasmaInit(t *testing.T) { + logger := log.NewTMLogger(os.Stderr) + client, err := InitEthConn(clientAddr, logger) + + privKey, _ := crypto.HexToECDSA(operatorPrivKey) + _, err = InitPlasma(common.HexToAddress(plasmaContractAddr), privKey, client, logger, 1) + if err != nil { + t.Fatal("Could not bind contract -", err) + } +} + +func TestSubmitBlock(t *testing.T) { + logger := log.NewTMLogger(os.Stderr) + client, _ := InitEthConn(clientAddr, logger) + + privKey, _ := crypto.HexToECDSA(operatorPrivKey) + plasma, _ := InitPlasma(common.HexToAddress(plasmaContractAddr), privKey, client, logger, 1) + + header := crypto.Keccak256([]byte("blah")) + var root [32]byte + copy(root[:], header) + _, err := plasma.SubmitBlock(root, big.NewInt(0), big.NewInt(0)) + if err != nil { + t.Fatal("Failed block submission -", err) + } + + blockNum, err := plasma.session.LastCommittedBlock() + if err != nil { + t.Fatal("Failed query for the last committed block -", err) + } + + result, err := plasma.session.ChildChain(blockNum) + if err != nil { + t.Fatal("Failed query for the child chain - ", err) + } + + if bytes.Compare(result.Root[:], header) != 0 { + t.Errorf("Mismatch in block headers. Got: %x. Expected: %x", result, header) + } +} + +func TestEthBlockWatching(t *testing.T) { + logger := log.NewTMLogger(os.Stderr) + client, _ := InitEthConn(clientAddr, logger) + + privKey, _ := crypto.HexToECDSA(operatorPrivKey) + plasma, _ := InitPlasma(common.HexToAddress(plasmaContractAddr), privKey, client, logger, 1) + + // mine a block so that `ethBlockNum` within plasma gets set + // sleep after an rpc call to deal with the asynchrony + if err := client.rpc.Call(nil, "evm_mine"); err != nil { + t.Fatal("Could not mine a block -", err) + } + time.Sleep(1 * time.Second) + + plasma.lock.Lock() + lastEthBlockNum := plasma.ethBlockNum.Uint64() + plasma.lock.Unlock() + + // mine another block that should get caught + if err := client.rpc.Call(nil, "evm_mine"); err != nil { + t.Fatal("Could not mine a block -", err) + } + time.Sleep(1 * time.Second) + + plasma.lock.Lock() + currEthBlockNum := plasma.ethBlockNum.Uint64() + plasma.lock.Unlock() + if currEthBlockNum != lastEthBlockNum+1 { + t.Fatalf("EthBlockNum not incremented. Expected: %d, Got: %d", + lastEthBlockNum+1, currEthBlockNum) + } +} + +func TestDepositWatching(t *testing.T) { + logger := log.NewTMLogger(os.Stderr) + client, _ := InitEthConn(clientAddr, logger) + + privKey, _ := crypto.HexToECDSA(operatorPrivKey) + plasma, _ := InitPlasma(common.HexToAddress(plasmaContractAddr), privKey, client, logger, 0) + + nonce, err := plasma.session.DepositNonce() + if err != nil { + t.Fatalf("Could not query for the next deposit nonce") + } + + // Deposit 10 eth from the operator + plasma.session.TransactOpts.Value = big.NewInt(10) + operatorAddress := crypto.PubkeyToAddress(privKey.PublicKey) + _, err = plasma.session.Deposit(operatorAddress) + if err != nil { + t.Fatalf("Error sending a deposit tx") + } + time.Sleep(500 * time.Millisecond) + + deposit, err := plasma.GetDeposit(nonce) + if err != nil { + t.Fatalf("Deposit not caught - %s", err) + } + + if deposit.Amount.Uint64() != uint64(10) { + t.Errorf("Deposit amount incorrect. Expected 10, Got %d", deposit.Amount.Uint64()) + } + + if !bytes.Equal(operatorAddress[:], deposit.Owner[:]) { + t.Errorf("Deposit owner incorrect. Expected %x, Got %x", operatorAddress, deposit.Owner) + } + + // check persistence in the db + key := prefixKey(depositPrefix, nonce.Bytes()) + data, err := plasma.memdb.Get(key) + if err != nil { + t.Fatalf("Deposit not persisted - %s", err) + } + + var d plasmaTypes.Deposit + err = json.Unmarshal(data, &d) + if err != nil { + t.Fatalf("Error unmarshaling cache'd deposit - %s", err) + } + + if !reflect.DeepEqual(*deposit, d) { + t.Fatalf("Mismatch in the persisted deposit and `GetDeposit`") + } +} + +func TestDepositExitWatching(t *testing.T) { + logger := log.NewTMLogger(os.Stderr) + client, _ := InitEthConn(clientAddr, logger) + + privKey, _ := crypto.HexToECDSA(operatorPrivKey) + plasma, _ := InitPlasma(common.HexToAddress(plasmaContractAddr), privKey, client, logger, 0) + + // deposit and exit + nonce, _ := plasma.session.DepositNonce() + plasma.session.TransactOpts.Value = big.NewInt(10) + _, err := plasma.session.Deposit(crypto.PubkeyToAddress(privKey.PublicKey)) + if err != nil { + t.Fatal("Failed deposit -", err) + } + + plasma.session.TransactOpts.Value = big.NewInt(minExitBond) + _, err = plasma.session.StartDepositExit(nonce, big.NewInt(0)) + if err != nil { + t.Fatal("Error starting deposit exit -", err) + } + time.Sleep(500 * time.Millisecond) + + zero := big.NewInt(0) + position := [4]*big.Int{zero, zero, zero, nonce} + exited := plasma.HasTXBeenExited(position) + + if !exited { + t.Errorf("Deposit nonce not marked as exited") + } +} + +type SpendMsg struct { + Blknum0 uint64 + Txindex0 uint16 + Oindex0 uint8 + DepositNum0 uint64 + Owner0 common.Address + Input0ConfirmSigs [][65]byte + Blknum1 uint64 + Txindex1 uint16 + Oindex1 uint8 + DepositNum1 uint64 + OWner1 common.Address + Input1ConfirmSigs [][65]byte + Newowner0 common.Address + Amount0 uint64 + Newowner1 common.Address + Amount1 uint64 + FeeAmount uint64 +} + +type tx struct { + TxList SpendMsg + Sigs [2][]byte +} + +func toEthSignedMessageHash(msg []byte) []byte { + buffer := new(bytes.Buffer) + buffer.Write([]byte("\x19Ethereum Signed Message:\n32")) + buffer.Write(msg) + return crypto.Keccak256(buffer.Bytes()) +} + +func TestTxExitWatchingAndChallenge(t *testing.T) { + logger := log.NewTMLogger(os.Stderr) + client, _ := InitEthConn(clientAddr, logger) + + privKey, _ := crypto.HexToECDSA(operatorPrivKey) + plasma, _ := InitPlasma(common.HexToAddress(plasmaContractAddr), privKey, client, logger, 0) + zero := big.NewInt(0) + + // deposit and spend + nonce, _ := plasma.session.DepositNonce() + plasma.session.TransactOpts.Value = big.NewInt(10) + _, err := plasma.session.Deposit(crypto.PubkeyToAddress(privKey.PublicKey)) + if err != nil { + t.Fatal("Failed deposit -", err) + } + + // generate tx + var msg SpendMsg + msg.DepositNum0 = nonce.Uint64() + msg.Newowner0 = crypto.PubkeyToAddress(privKey.PublicKey) + msg.Amount0 = 10 + txList, _ := rlp.EncodeToBytes(msg) + + txHash := crypto.Keccak256(txList) + txHash = toEthSignedMessageHash(txHash) + sig0, _ := crypto.Sign(txHash, privKey) + sigs := [2][]byte{sig0, make([]byte, 65)} + + txBytes, _ := rlp.EncodeToBytes(tx{msg, sigs}) + + // submit header. header == merklehash + header := sha256.Sum256(txBytes) + plasma.session.TransactOpts.Value = nil + _, err = plasma.SubmitBlock(header, big.NewInt(1), zero) + if err != nil { + t.Fatal("Error submitting block -", err) + } + + time.Sleep(500 * time.Millisecond) + + // merkleHash == header + var data []byte + data = append(data, header[:]...) + data = append(data, header[:]...) + confirmationHash := sha256.Sum256(data) + confHash := toEthSignedMessageHash(confirmationHash[:]) + confirmSignature, _ := crypto.Sign(confHash, privKey) + + plasma.session.TransactOpts.Value = big.NewInt(minExitBond) + _, err = plasma.session.StartTransactionExit([3]*big.Int{plasma.blockNum, zero, zero}, txBytes, []byte{}, confirmSignature, zero) + if err != nil { + t.Fatal("Error starting tx exit -", err) + } + time.Sleep(500 * time.Millisecond) + + txPos := [4]*big.Int{plasma.blockNum, zero, zero, zero} + exited := plasma.HasTXBeenExited(txPos) + if !exited { + t.Errorf("Transaction not marked as exited") + } + + // attempt to exit the deposit & challenge + depositPos := [4]*big.Int{zero, zero, zero, nonce} + _, err = plasma.session.StartDepositExit(nonce, zero) + if err != nil { + t.Fatal("Error exiting deposit -", err) + } + time.Sleep(500 * time.Millisecond) + + exited = plasma.HasTXBeenExited(depositPos) + if !exited { + t.Errorf("Deposit not marked as exited after exiting") + } + plasma.session.TransactOpts.Value = nil + _, err = plasma.session.ChallengeExit(depositPos, [2]*big.Int{plasma.blockNum, zero}, txBytes, []byte{}, confirmSignature) + if err != nil { + t.Fatal("Error challenging exit -", err) + } + time.Sleep(500 * time.Millisecond) + + exited = plasma.HasTXBeenExited(depositPos) + if exited { + t.Errorf("Deposit marked as exited after being challenged") + } +} diff --git a/eth/util.go b/eth/util.go new file mode 100644 index 0000000..b19a739 --- /dev/null +++ b/eth/util.go @@ -0,0 +1,38 @@ +package eth + +import ( + "bytes" + "math/big" +) + +const ( + // prefixes + prefixSeperator = "::" + depositPrefix = "deposit" + transactionExitPrefix = "txExit" + depositExitPrefix = "depositExit" + + // constants + blockIndexFactor = 1000000 + txIndexFactor = 10 +) + +func prefixKey(prefix string, key []byte) []byte { + buffer := new(bytes.Buffer) + buffer.Write([]byte(prefix)) + buffer.Write([]byte(prefixSeperator)) + buffer.Write(key) + return buffer.Bytes() +} + +// [blockNumber, txIndex, outputIndex] +func calcPriority(position [3]*big.Int) *big.Int { + bFactor := big.NewInt(blockIndexFactor) + tFactor := big.NewInt(txIndexFactor) + + bFactor = bFactor.Mul(bFactor, position[0]) + tFactor = tFactor.Mul(tFactor, position[1]) + + temp := new(big.Int).Add(bFactor, tFactor) + return temp.Add(temp, position[2]) +} diff --git a/eth/util_test.go b/eth/util_test.go new file mode 100644 index 0000000..86d667c --- /dev/null +++ b/eth/util_test.go @@ -0,0 +1,53 @@ +package eth + +import ( + "bytes" + "math/big" + "testing" +) + +func TestPriorityCalc(t *testing.T) { + position := [3]*big.Int{big.NewInt(1), big.NewInt(2), big.NewInt(3)} + expected := uint64(1000000*1 + 10*2 + 3) + + priority := calcPriority(position).Uint64() + if priority != expected { + t.Fatalf("Position [1,2,3] yielded priority %d. Expected %d", + priority, expected) + } + + position = [3]*big.Int{big.NewInt(0), big.NewInt(2), big.NewInt(3)} + expected = uint64(10*2 + 3) + + priority = calcPriority(position).Uint64() + if priority != expected { + t.Fatalf("Position [0,2,3] yielded priority %d. Expected %d", + priority, expected) + } + + position = [3]*big.Int{big.NewInt(1), big.NewInt(0), big.NewInt(3)} + expected = uint64(1000000*1 + 3) + + priority = calcPriority(position).Uint64() + if priority != expected { + t.Fatalf("Position [1,0,3] yielded priority %d. Expected %d", + priority, expected) + } + + position = [3]*big.Int{big.NewInt(1), big.NewInt(2), big.NewInt(0)} + expected = uint64(1000000*1 + 10*2) + + priority = calcPriority(position).Uint64() + if priority != expected { + t.Fatalf("Position [1,2,0] yielded priority %d. Expected %d", + priority, expected) + } +} + +func TestPrefixKey(t *testing.T) { + expectedKey := []byte("prefix::key") + key := prefixKey("prefix", []byte("key")) + if !bytes.Equal(key, expectedKey) { + t.Fatalf("Actual: %s, Got: %s", string(expectedKey), string(key)) + } +} diff --git a/types/utxo.go b/types/utxo.go index cb75945..6d55585 100644 --- a/types/utxo.go +++ b/types/utxo.go @@ -1,10 +1,10 @@ package types import ( - amino "github.com/tendermint/go-amino" - "github.com/FourthState/plasma-mvp-sidechain/x/utxo" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/common" + amino "github.com/tendermint/go-amino" ) const ( @@ -51,6 +51,12 @@ func (position PlasmaPosition) IsValid() bool { } } +type Deposit struct { + Owner common.Address + Amount sdk.Uint + BlockNum sdk.Uint +} + //------------------------------------------------------- // misc func RegisterAmino(cdc *amino.Codec) {