From 6c3dd90567d50a0d96a8219e27efac794bcc2eb5 Mon Sep 17 00:00:00 2001 From: David Murdoch Date: Tue, 12 Jan 2021 14:12:07 -0500 Subject: [PATCH] refactor: rewrite into a TypeScript monorepo, partial (#657) --- .dockerignore | 2 - .gitattributes | 3 + .github/ganache-logo-dark.svg | 1 + .github/truffle-logo-dark.svg | 1 + .github/workflows/CI.yml | 52 + .gitignore | 15 +- .npmignore | 14 - .npmrc | 6 + .nvmrc | 1 + .nycrc | 3 + .prettierignore | 8 + .prettierrc | 4 + .travis.yml | 38 - CONTRIBUTING.md | 145 + Dockerfile | 12 - ISSUE_TEMPLATE.md | 40 - LICENSE | 28 +- README.md | 213 +- completions.sh | 38 + docs/launch.json | 34 + index.js | 36 - lerna.json | 4 + lib/block_tracker.js | 71 - lib/blockchain_double.js | 1261 ------ lib/database.js | 77 - lib/database/blocklogsserializer.js | 61 - lib/database/blockserializer.js | 49 - lib/database/bufferserializer.js | 12 - lib/database/filedown.js | 186 - lib/database/leveluparrayadapter.js | 140 - lib/database/levelupobjectadapter.js | 119 - lib/database/levelupvalueadapter.js | 50 - lib/database/receiptserializer.js | 60 - lib/database/txserializer.js | 79 - lib/forking/forked_blockchain.js | 1004 ----- lib/forking/forked_storage_trie.js | 165 - lib/httpServer.js | 127 - lib/provider.js | 267 -- lib/server.js | 89 - lib/statemanager.js | 1066 ----- lib/subproviders/delayedblockfilter.js | 153 - lib/subproviders/gethdefaults.js | 39 - lib/subproviders/reactiveblocktracker.js | 52 - lib/subproviders/requestfunnel.js | 62 - lib/utils/block_helper.js | 34 - lib/utils/errorhelper.js | 19 - lib/utils/gas/binSearch.js | 38 - lib/utils/gas/estimateGas.js | 14 - lib/utils/log.js | 39 - lib/utils/random.js | 28 - lib/utils/receipt.js | 49 - lib/utils/runtimeerror.js | 103 - lib/utils/to.js | 145 - lib/utils/transaction.js | 331 -- lib/utils/txrejectederror.js | 14 - package.json | 206 +- patches/keccak+3.0.1.patch | 11 - patches/web3-provider-engine+14.2.1.patch | 13 - perf/transactions.js | 94 - public-exports.js | 16 - scripts/create.ts | 279 ++ scripts/postinstall.ts | 7 + src/chains/ethereum/.npmignore | 8 + src/chains/ethereum/LICENSE | 21 + src/chains/ethereum/README.md | 3 + src/chains/ethereum/index.ts | 9 + src/chains/ethereum/npm-shrinkwrap.json | 3919 +++++++++++++++++ src/chains/ethereum/package.json | 92 + .../ethereum/scripts/post-process-docs.js | 23 + .../src/@types/ethereumjs-block/header.d.ts | 62 + .../src/@types/ethereumjs-block/index.d.ts | 43 + .../src/@types/ethereumjs-util/index.d.ts | 8 + .../ethereum/src/@types/hdkey/index.d.ts | 37 + .../ethereum/src/@types/levelup/index.d.ts | 128 + .../@types/merkle-patricia-tree/baseTrie.ts | 45 + .../@types/merkle-patricia-tree/index.d.ts | 38 + .../@types/merkle-patricia-tree/readStream.ts | 10 + .../@types/merkle-patricia-tree/trieNode.ts | 45 + src/chains/ethereum/src/api.ts | 2047 +++++++++ src/chains/ethereum/src/blockchain.ts | 821 ++++ src/chains/ethereum/src/connector.ts | 140 + .../src/data-managers/account-manager.ts | 71 + .../src/data-managers/block-manager.ts | 153 + .../src/data-managers/blocklog-manager.ts | 18 + .../ethereum/src/data-managers/manager.ts | 46 + .../src/data-managers/transaction-manager.ts | 104 + src/chains/ethereum/src/database.ts | 171 + src/chains/ethereum/src/errors/coded-error.ts | 63 + src/chains/ethereum/src/errors/errors.ts | 35 + .../ethereum/src/errors/runtime-error.ts | 69 + .../ethereum/src/helpers/assert-arg-length.ts | 27 + .../ethereum/src/helpers/filter-parsing.ts | 62 + .../ethereum/src/helpers/gas-estimator.ts | 193 +- src/chains/ethereum/src/miner/miner.ts | 476 ++ .../ethereum/src/miner/replace-from-heap.ts | 20 + .../ethereum/src/options/chain-options.ts | 169 + .../ethereum/src/options/database-options.ts | 45 + src/chains/ethereum/src/options/helpers.ts | 1 + src/chains/ethereum/src/options/index.ts | 80 + .../ethereum/src/options/legacy-options.ts | 25 + .../ethereum/src/options/logging-options.ts | 87 + .../ethereum/src/options/miner-options.ts | 183 + .../ethereum/src/options/wallet-options.ts | 225 + src/chains/ethereum/src/provider.ts | 302 ++ src/chains/ethereum/src/things/account.ts | 42 + src/chains/ethereum/src/things/address.ts | 15 + src/chains/ethereum/src/things/blocklogs.ts | 220 + src/chains/ethereum/src/things/params.ts | 28 + .../ethereum/src/things/runtime-block.ts | 230 + src/chains/ethereum/src/things/tags.ts | 29 + .../src/things/transaction-receipt.ts | 120 + src/chains/ethereum/src/things/transaction.ts | 532 +++ src/chains/ethereum/src/transaction-pool.ts | 319 ++ src/chains/ethereum/src/types/executables.ts | 7 + .../src/types/extract-values-from-types.ts | 1 + src/chains/ethereum/src/types/filters.ts | 24 + src/chains/ethereum/src/types/shh.ts | 1 + src/chains/ethereum/src/types/snapshots.ts | 24 + .../ethereum/src/types/subscriptions.ts | 6 + .../ethereum/src/types/tuple-from-union.ts | 36 + src/chains/ethereum/src/wallet.ts | 451 ++ .../ethereum/tests/@types/solc/index.d.ts | 158 + src/chains/ethereum/tests/api/bzz/bzz.test.ts | 28 + src/chains/ethereum/tests/api/db/db.test.ts | 32 + .../tests/api/eth/contracts/GetCode.sol | 4 + .../tests/api/eth/contracts/GetStorageAt.sol | 7 + .../ethereum/tests/api/eth/contracts/Logs.sol | 16 + .../tests/api/eth/contracts/Reverts.sol | 18 + src/chains/ethereum/tests/api/eth/eth.test.ts | 497 +++ .../ethereum/tests/api/eth/getCode.test.ts | 97 + .../tests/api/eth/getStorageAt.test.ts | 94 + .../tests/api/eth/legacyInstamining.test.ts | 81 + .../ethereum/tests/api/eth/logs.test.ts | 488 ++ .../tests/api/eth/sendRawTransaction.test.ts | 61 + .../tests/api/eth/sendTransaction.test.ts | 103 + .../ethereum/tests/api/eth/sign.test.ts | 91 + .../tests/api/eth/signTypedData.test.ts | 138 + .../ethereum/tests/api/eth/subscribe.test.ts | 201 + .../ethereum/tests/api/eth/uncles.test.ts | 89 + src/chains/ethereum/tests/api/evm/evm.test.ts | 245 ++ .../ethereum/tests/api/evm/snapshot.sol | 10 + .../ethereum/tests/api/evm/snapshot.test.ts | 561 +++ .../ethereum/tests/api/miner/miner.test.ts | 131 + src/chains/ethereum/tests/api/net/net.test.ts | 26 + .../tests/api/personal/personal.test.ts | 341 ++ .../ethereum/tests/api/rpc/modules.test.ts | 25 + src/chains/ethereum/tests/api/shh/shh.test.ts | 62 + .../ethereum/tests/api/web3/web3.test.ts | 31 + .../ethereum/tests/contracts/HelloWorld.sol | 21 + src/chains/ethereum/tests/helpers/compile.ts | 51 + .../ethereum/tests/helpers/getProvider.ts | 41 + src/chains/ethereum/tests/provider.test.ts | 55 + src/chains/ethereum/tests/temp-tests.test.ts | 315 ++ src/chains/ethereum/tests/tsconfig.json | 4 + src/chains/ethereum/tsconfig.json | 8 + src/chains/ethereum/typedoc.json | 13 + src/chains/tezos/.npmignore | 8 + src/chains/tezos/LICENSE | 21 + src/chains/tezos/README.md | 3 + src/chains/tezos/index.ts | 42 + src/chains/tezos/npm-shrinkwrap.json | 1931 ++++++++ src/chains/tezos/package.json | 54 + src/chains/tezos/scripts/post-process-docs.js | 21 + src/chains/tezos/src/api.ts | 9 + src/chains/tezos/src/provider.ts | 18 + src/chains/tezos/tests/index.test.ts | 7 + src/chains/tezos/tsconfig.json | 7 + src/chains/tezos/typedoc.json | 13 + src/packages/cli/.npmignore | 8 + src/packages/cli/LICENSE | 21 + src/packages/cli/README.md | 3 + src/packages/cli/args.ts | 258 ++ src/packages/cli/cli.ts | 261 ++ src/packages/cli/index.ts | 3 + src/packages/cli/npm-shrinkwrap.json | 618 +++ src/packages/cli/package.json | 58 + src/packages/cli/tests/index.test.ts | 6 + src/packages/cli/tsconfig.json | 7 + src/packages/core/.npmignore | 8 + src/packages/core/LICENSE | 21 + src/packages/core/README.md | 5 + src/packages/core/index.ts | 11 + src/packages/core/npm-shrinkwrap.json | 207 + src/packages/core/package.json | 61 + src/packages/core/src/@types/superagent.d.ts | 6 + .../core/src/@types/uWebsockets.js.ts | 23 + src/packages/core/src/connector.ts | 45 + src/packages/core/src/options/index.ts | 36 + .../core/src/options/server-options.ts | 67 + src/packages/core/src/server.ts | 172 + src/packages/core/src/servers/http-server.ts | 216 + .../core/src/servers/utils/content-types.ts | 5 + .../src/servers/utils/http-response-codes.ts | 9 + .../servers/utils/websocket-close-codes.ts | 11 + src/packages/core/src/servers/ws-server.ts | 123 + src/packages/core/tests/connector.test.ts | 148 + .../core/tests/helpers/getProvider.ts | 13 + src/packages/core/tests/interface.test.ts | 16 + src/packages/core/tests/server.test.ts | 881 ++++ ...a4012219db100ef0a340307869393f9df55ebd470c | 0 ...3d12ccdd1938364fb8e72dcdda25d6baa70992b80f | 0 ...a730bb0282bbaaadd8ac1b94510b69a499527c3505 | 0 ...243a1661770198770b14029eed74f0eb473c79cd83 | 0 src/packages/core/tsconfig.json | 7 + src/packages/flavors/.npmignore | 8 + src/packages/flavors/LICENSE | 21 + src/packages/flavors/README.md | 3 + src/packages/flavors/index.ts | 25 + src/packages/flavors/npm-shrinkwrap.json | 245 ++ src/packages/flavors/package.json | 51 + src/packages/flavors/tests/index.test.ts | 5 + src/packages/flavors/tsconfig.json | 7 + src/packages/ganache/.npmignore | 8 + src/packages/ganache/LICENSE | 21 + src/packages/ganache/README.md | 3 + src/packages/ganache/index.ts | 6 + src/packages/ganache/npm-shrinkwrap.json | 2362 ++++++++++ src/packages/ganache/package.json | 80 + src/packages/ganache/src/cli.ts | 3 + src/packages/ganache/tests/index.test.ts | 6 + src/packages/ganache/tsconfig.json | 7 + src/packages/ganache/webpack.config.ts | 5 + .../polyfills/browser-bigint-buffer.ts | 4 + .../webpack/polyfills/browser-tmp-promise.ts | 364 ++ .../ganache/webpack/webpack.browser.config.ts | 41 + .../ganache/webpack/webpack.cli.config.ts | 31 + .../ganache/webpack/webpack.common.config.ts | 41 + .../ganache/webpack/webpack.node.config.ts | 23 + src/packages/options/.npmignore | 8 + src/packages/options/LICENSE | 21 + src/packages/options/README.md | 3 + src/packages/options/index.ts | 11 + src/packages/options/npm-shrinkwrap.json | 387 ++ src/packages/options/package.json | 51 + src/packages/options/src/base.ts | 21 + src/packages/options/src/create.ts | 84 + src/packages/options/src/definition.ts | 44 + src/packages/options/src/exclusive.ts | 186 + src/packages/options/src/getters.ts | 55 + src/packages/options/tests/index.test.ts | 7 + src/packages/options/tsconfig.json | 7 + src/packages/promise-queue/.npmignore | 8 + src/packages/promise-queue/LICENSE | 21 + src/packages/promise-queue/README.md | 3 + src/packages/promise-queue/index.ts | 108 + .../promise-queue/npm-shrinkwrap.json | 13 + src/packages/promise-queue/package.json | 49 + src/packages/promise-queue/src/entry.ts | 25 + .../promise-queue/tests/index.test.ts | 6 + src/packages/promise-queue/tsconfig.json | 7 + src/packages/utils/.npmignore | 8 + src/packages/utils/LICENSE | 21 + src/packages/utils/README.md | 3 + src/packages/utils/index.ts | 7 + src/packages/utils/npm-shrinkwrap.json | 42 + src/packages/utils/package.json | 55 + .../utils/src/things/json-rpc/index.ts | 1 + .../things/json-rpc/json-rpc-base-types.ts | 108 + .../src/things/json-rpc/json-rpc-data.ts | 58 + .../src/things/json-rpc/json-rpc-quantity.ts | 89 + src/packages/utils/src/things/jsonrpc.ts | 77 + src/packages/utils/src/things/promievent.ts | 124 + src/packages/utils/src/types/api.ts | 13 + src/packages/utils/src/types/connector.ts | 53 + src/packages/utils/src/types/index.ts | 23 + src/packages/utils/src/types/provider.ts | 3 + .../utils/src/utils/bigint-to-buffer.ts | 54 + src/packages/utils/src/utils/constants.ts | 12 + src/packages/utils/src/utils/executor.ts | 54 + src/packages/utils/src/utils/has-own.ts | 17 + src/packages/utils/src/utils/heap.ts | 210 + src/packages/utils/src/utils/index.ts | 8 + .../utils/src/utils/request-coordinator.ts | 102 + .../utils/src/utils/uint-to-buffer.ts | 179 + src/packages/utils/src/utils/unref.ts | 16 + src/packages/utils/tests/utils.test.ts | 7 + src/packages/utils/tsconfig.json | 7 + test/.eslintrc | 5 - test/contracts/call/Call.sol | 14 - test/contracts/chainId/ChainId.sol | 11 - .../constantinople/ConstantinopleContract.sol | 10 - .../customContracts/LargeContract.sol | 8 - test/contracts/debug/DebugContract.sol | 27 - test/contracts/debug/DebugContractStorage.sol | 20 - test/contracts/events/EventTest.sol | 8 - test/contracts/examples/Example.sol | 20 - test/contracts/examples/Example2.sol | 6 - test/contracts/forking/Debug.sol | 19 - test/contracts/forking/IntraBlockCache.sol | 19 - test/contracts/forking/Snapshot.sol | 13 - test/contracts/forking/StorageDelete.sol | 18 - test/contracts/gas/ContractFactory.sol | 41 - test/contracts/gas/CreateTwo.sol | 15 - test/contracts/gas/Donation.sol | 63 - test/contracts/gas/EstimateGas.sol | 117 - test/contracts/gas/Fib.sol | 22 - test/contracts/gas/GasLeft.sol | 8 - test/contracts/gas/NonZero.sol | 19 - test/contracts/gas/SendContract.sol | 22 - test/contracts/gas/TestDepth.sol | 17 - test/contracts/misc/Oracle.sol | 15 - test/contracts/revert/Revert.sol | 13 - test/contracts/runtime/RuntimeError.sol | 22 - test/contracts/snapshotting/snapshot.sol | 9 - .../transaction-data/TransactionData.sol | 7 - test/helpers/contract/bootstrap.js | 27 - test/helpers/contract/compileAndDeploy.js | 123 - test/helpers/contract/singleFileCompile.js | 23 - test/helpers/utils/create-signed-tx.js | 18 - test/helpers/utils/generateRandomInteger.js | 10 - test/helpers/utils/rpc.js | 25 - test/helpers/utils/sleep.js | 12 - test/helpers/utils/toBytesHexString.js | 16 - test/helpers/web3/initializeTestProvider.js | 26 - test/local/CallLibrary.sol | 15 - test/local/LargeContract.sol | 8 - test/local/Library.sol | 14 - test/local/accounts.js | 363 -- test/local/bad_input.js | 213 - test/local/block-tags.js | 113 - test/local/call.js | 54 - test/local/call/undefined.js | 88 - test/local/chainId.js | 52 - test/local/cors.js | 131 - test/local/debug/debug.js | 288 -- test/local/debug/debugStorage.js | 104 - test/local/enable_constantinople_hardfork.js | 65 - test/local/ethereum.js | 11 - test/local/ethers.js | 66 - test/local/events.js | 369 -- test/local/forking/call.js | 62 - test/local/forking/caseSensitivity.js | 137 - test/local/forking/debug.js | 102 - test/local/forking/delete.js | 54 - test/local/forking/deployAfterFork.js | 141 - test/local/forking/forking.js | 735 ---- test/local/forking/forkingAsProvider.js | 60 - test/local/forking/snapshot.js | 126 - test/local/gas/customGasLimit.js | 14 - test/local/gas/customGasPrice.js | 33 - test/local/gas/gas.js | 945 ---- test/local/gas/gasLimit.js | 24 - test/local/gas/gasPrice.js | 51 - test/local/gas/lib/confirmGasPrice.js | 61 - test/local/gas/lib/transactionEstimate.js | 14 - test/local/hex.js | 40 - test/local/interval-mining.js | 222 - test/local/library.js | 98 - test/local/mining.js | 423 -- test/local/options/account_keys_path.js | 28 - test/local/options/keepAliveTimeout.js | 15 - test/local/options/lib/testTimeout.js | 48 - test/local/persistence.js | 222 - test/local/public-exports.js | 10 - test/local/runTimeErrors.js | 237 - test/local/server.js | 18 - test/local/snapshotting.js | 133 - test/local/stability.js | 131 - test/local/subscriptions.js | 103 - test/local/swarm.js | 17 - ...3b60dc2c5f1b1b0f82a762c869bfa1b15acf8597e6 | 1 - ...498478a5db4cc80792cd0a8b64821512a50802d4cd | 1 - ...310e7d4e128a396226ed4ce10a101b31262a88868c | 1 - test/local/testdb/!blockLogs!0 | 1 - test/local/testdb/!blockLogs!1 | 1 - test/local/testdb/!blockLogs!2 | 1 - test/local/testdb/!blockLogs!length | 1 - test/local/testdb/!blocks!0 | 1 - test/local/testdb/!blocks!1 | 1 - test/local/testdb/!blocks!2 | 1 - test/local/testdb/!blocks!length | 1 - ...318ea4882fe25e7d5fee8dfe6ac2ed4049c34a1883 | 1 - ...a9bd6a62fa371881f6e72690c178b22cda8e552ffb | 1 - ...318ea4882fe25e7d5fee8dfe6ac2ed4049c34a1883 | 1 - ...a9bd6a62fa371881f6e72690c178b22cda8e552ffb | 1 - ...2fe51d5677504f6867f7c6f31f55748f10544b64e3 | 1 - ...7613296e562cafa8a25a91deb88a47433ccc4e138e | 1 - ...7fc42b84d07ef56de983d48b4e631291c607a71e5e | 1 - ...36ee03f64eda5cbb14fb58cbeb3362709f510b2c0e | 1 - ...65fdfd3f12de1e1379c37b9753c5dbe62082ef8960 | 1 - ...30a86e532f7b442c08f6d7ed7d9a79601c93591386 | 1 - ...c8ac85d44e521490c8475507cc62da7583cb9723e1 | 1 - ...c251e958743b1c30db7bb63e7bbf247b8f78aeb1d7 | 1 - ...0f9afcff45ff959c6bacb6fb2276bac7bee70ed2ce | 1 - ...38658725d9067d9e85450d043946d439cae014547b | 1 - ...342734e6bd259a6bc6d9f3a527e2b06a597ba27cb0 | 1 - ...fa7481601253172b63d77e80278ab3d4895b20001b | 1 - ...8e1c8990e0f19a3fe5909007a6aefe8a9b04026899 | 1 - ...bc4dc46d54e7067320d7093781ce3b944ad84c8f3a | 1 - ...c6cf01c59e1150fdc7c456a31b4c42fe4a64064aca | 1 - ...c11e5400d677f12fb8da2a5650efa9b9f638385600 | 1 - ...379b31fbaf2ef6c71d2d8085ade2d58b55ab4ea2ac | 1 - ...55898ddcb997e6b8e624e09c5a3f4b200226c65383 | 1 - ...a6b70fab3c29d545df173e5798da1a3bef0efeb5d9 | 1 - ...35caf68673a43b7a13bb1fa82f2643bad3e4d1c253 | 1 - ...68b20e31952619100e01a31e619f91393e34d80ab5 | 1 - ...66b4f7c9d6a216d0ac795355860ac66dea8d88b767 | 1 - ...509cf31f345ea3265c2faf00b6dcb883876eb89cc1 | 1 - ...97c2d34d5d10e136757bf4cfff5fa41bfca219554a | 1 - ...092e2b6133f6826e045c391501e80374760de69320 | 1 - ...ab7dc65de14684038e90bfb07417c7ce7ef669c0eb | 1 - ...7fcdcfd5b561442347326a3ffca40ae225d0029d4c | 1 - ...7c88c00e44733094250664cbfc9d9bbf9eda3ca745 | 1 - ...48905e0e1410b8859b9fdfc1991e3860eecbcaa2da | 1 - ...e574a87e32492071f22d0a42e184892f6b225df9b3 | 1 - ...fa9a02f85475aaa5b109d41dc35f1893dc5bb3cf72 | 1 - ...64970c97eb75bbb1508b7a4570eda4b87ef76d7988 | 1 - ...15245c68de1b6642ace21159e538d0a96bf1254460 | 1 - ...0976490ab2c71a0643e9f16e16807811b7112ed032 | 1 - ...b3ad18415a1fc08d2ffab89a852e0067d6957b09d4 | 1 - ...5ac468fc88ab99335759bb47d22b0947229f0b170a | 1 - ...0018ded196413ef989c0062341ff886889f39ab41b | 1 - ...c4acc5c3d803aa7cb7818017520686266074832155 | 1 - ...dc6cccea1f3b4b170d53b9b92f92bde5573b585bfc | 1 - ...4d9a639e60fad01fac4634d78c7203c6916e3f17fc | 1 - ...3e87ce2f1b38b12409f5c65078b201783d6280e764 | 1 - ...487d91f45503cb88cf28b802043c5acd7304f6bb45 | 1 - ...62c3d06a54045f4f69e8e1c0cacb05e11a7d2a23af | 1 - ...3dde2f88f4eb01aa731d5495387f414c8e471c7b18 | 1 - ...bab1ab2419a5e09b9aede2d9e29b8d45535217abf1 | 1 - ...126fd0b2eb33764bdb3ec5763b93968f205314944c | 1 - ...a59915a1b661eb90e0ad6b61bc8135e9e14af02608 | 1 - ...2ea6ffcd436f6c6d4fe14a771042f7d4cea5cfa92f | 1 - ...20dfd8b00714aaf2cfcce46aa43a023e0e8fa65bfd | 1 - ...5a83b70e89208877a149724ace5df5eb9ad355259e | 1 - ...e36725c555afc7fdfc4855bd86add2cdcdec4a4de3 | 1 - ...f256f7f21226617577101caae5e93f9ce5eb2bc082 | 1 - ...eadfc3d2affdb420d73dc4ecf07c5a21b22a221a98 | 1 - ...c6b7b65d87b1089d447cde9cd46fad00d658f2e7c8 | 1 - ...62b8a367e2c9ea4065f6fea7229b9484d17b6192be | 1 - ...1730301b377f5c238369dfc2d598b6aa9d14004ccb | 1 - ...edac6345402a902e385a2829b9bded7697113e9836 | 1 - ...41752157c205a391ec9a1b730031bccc77a9276b02 | 1 - test/local/time-adjust.js | 100 - test/local/transaction-data.js | 52 - test/local/transaction-rejection.js | 143 - test/local/transaction.js | 55 - test/local/transasction-ordering.js | 68 - test/local/unlimitedContractSize.js | 68 - test/local/vm.js | 37 - test/local/whisper.js | 10 - test/smoke/forking/infura/simple.js | 155 - tsconfig.json | 34 + typings/index.d.ts | 79 - webpack/base.webpack.config.js | 72 - webpack/node/core.webpack.config.js | 17 - webpack/node/provider.webpack.config.js | 17 - webpack/node/server.webpack.config.js | 17 - .../web-experimental/core.webpack.config.js | 17 - .../provider.webpack.config.js | 17 - .../web-experimental/server.webpack.config.js | 17 - .../webbase.webpack.config.js | 16 - 452 files changed, 28504 insertions(+), 14679 deletions(-) delete mode 100644 .dockerignore create mode 100644 .gitattributes create mode 100644 .github/ganache-logo-dark.svg create mode 100644 .github/truffle-logo-dark.svg create mode 100644 .github/workflows/CI.yml delete mode 100644 .npmignore create mode 100644 .npmrc create mode 100644 .nvmrc create mode 100644 .nycrc create mode 100644 .prettierignore create mode 100644 .prettierrc delete mode 100644 .travis.yml create mode 100644 CONTRIBUTING.md delete mode 100644 Dockerfile delete mode 100644 ISSUE_TEMPLATE.md create mode 100644 completions.sh create mode 100644 docs/launch.json delete mode 100644 index.js create mode 100644 lerna.json delete mode 100644 lib/block_tracker.js delete mode 100644 lib/blockchain_double.js delete mode 100644 lib/database.js delete mode 100644 lib/database/blocklogsserializer.js delete mode 100644 lib/database/blockserializer.js delete mode 100644 lib/database/bufferserializer.js delete mode 100644 lib/database/filedown.js delete mode 100644 lib/database/leveluparrayadapter.js delete mode 100644 lib/database/levelupobjectadapter.js delete mode 100644 lib/database/levelupvalueadapter.js delete mode 100644 lib/database/receiptserializer.js delete mode 100644 lib/database/txserializer.js delete mode 100644 lib/forking/forked_blockchain.js delete mode 100644 lib/forking/forked_storage_trie.js delete mode 100644 lib/httpServer.js delete mode 100644 lib/provider.js delete mode 100644 lib/server.js delete mode 100644 lib/statemanager.js delete mode 100644 lib/subproviders/delayedblockfilter.js delete mode 100644 lib/subproviders/gethdefaults.js delete mode 100644 lib/subproviders/reactiveblocktracker.js delete mode 100644 lib/subproviders/requestfunnel.js delete mode 100644 lib/utils/block_helper.js delete mode 100644 lib/utils/errorhelper.js delete mode 100644 lib/utils/gas/binSearch.js delete mode 100644 lib/utils/gas/estimateGas.js delete mode 100644 lib/utils/log.js delete mode 100644 lib/utils/random.js delete mode 100644 lib/utils/receipt.js delete mode 100644 lib/utils/runtimeerror.js delete mode 100644 lib/utils/to.js delete mode 100644 lib/utils/transaction.js delete mode 100644 lib/utils/txrejectederror.js delete mode 100644 patches/keccak+3.0.1.patch delete mode 100644 patches/web3-provider-engine+14.2.1.patch delete mode 100755 perf/transactions.js delete mode 100644 public-exports.js create mode 100644 scripts/create.ts create mode 100644 scripts/postinstall.ts create mode 100644 src/chains/ethereum/.npmignore create mode 100644 src/chains/ethereum/LICENSE create mode 100644 src/chains/ethereum/README.md create mode 100644 src/chains/ethereum/index.ts create mode 100644 src/chains/ethereum/npm-shrinkwrap.json create mode 100644 src/chains/ethereum/package.json create mode 100644 src/chains/ethereum/scripts/post-process-docs.js create mode 100644 src/chains/ethereum/src/@types/ethereumjs-block/header.d.ts create mode 100644 src/chains/ethereum/src/@types/ethereumjs-block/index.d.ts create mode 100644 src/chains/ethereum/src/@types/ethereumjs-util/index.d.ts create mode 100644 src/chains/ethereum/src/@types/hdkey/index.d.ts create mode 100644 src/chains/ethereum/src/@types/levelup/index.d.ts create mode 100644 src/chains/ethereum/src/@types/merkle-patricia-tree/baseTrie.ts create mode 100644 src/chains/ethereum/src/@types/merkle-patricia-tree/index.d.ts create mode 100644 src/chains/ethereum/src/@types/merkle-patricia-tree/readStream.ts create mode 100644 src/chains/ethereum/src/@types/merkle-patricia-tree/trieNode.ts create mode 100644 src/chains/ethereum/src/api.ts create mode 100644 src/chains/ethereum/src/blockchain.ts create mode 100644 src/chains/ethereum/src/connector.ts create mode 100644 src/chains/ethereum/src/data-managers/account-manager.ts create mode 100644 src/chains/ethereum/src/data-managers/block-manager.ts create mode 100644 src/chains/ethereum/src/data-managers/blocklog-manager.ts create mode 100644 src/chains/ethereum/src/data-managers/manager.ts create mode 100644 src/chains/ethereum/src/data-managers/transaction-manager.ts create mode 100644 src/chains/ethereum/src/database.ts create mode 100644 src/chains/ethereum/src/errors/coded-error.ts create mode 100644 src/chains/ethereum/src/errors/errors.ts create mode 100644 src/chains/ethereum/src/errors/runtime-error.ts create mode 100644 src/chains/ethereum/src/helpers/assert-arg-length.ts create mode 100644 src/chains/ethereum/src/helpers/filter-parsing.ts rename lib/utils/gas/guestimation.js => src/chains/ethereum/src/helpers/gas-estimator.ts (57%) create mode 100644 src/chains/ethereum/src/miner/miner.ts create mode 100644 src/chains/ethereum/src/miner/replace-from-heap.ts create mode 100644 src/chains/ethereum/src/options/chain-options.ts create mode 100644 src/chains/ethereum/src/options/database-options.ts create mode 100644 src/chains/ethereum/src/options/helpers.ts create mode 100644 src/chains/ethereum/src/options/index.ts create mode 100644 src/chains/ethereum/src/options/legacy-options.ts create mode 100644 src/chains/ethereum/src/options/logging-options.ts create mode 100644 src/chains/ethereum/src/options/miner-options.ts create mode 100644 src/chains/ethereum/src/options/wallet-options.ts create mode 100644 src/chains/ethereum/src/provider.ts create mode 100644 src/chains/ethereum/src/things/account.ts create mode 100644 src/chains/ethereum/src/things/address.ts create mode 100644 src/chains/ethereum/src/things/blocklogs.ts create mode 100644 src/chains/ethereum/src/things/params.ts create mode 100644 src/chains/ethereum/src/things/runtime-block.ts create mode 100644 src/chains/ethereum/src/things/tags.ts create mode 100644 src/chains/ethereum/src/things/transaction-receipt.ts create mode 100644 src/chains/ethereum/src/things/transaction.ts create mode 100644 src/chains/ethereum/src/transaction-pool.ts create mode 100644 src/chains/ethereum/src/types/executables.ts create mode 100644 src/chains/ethereum/src/types/extract-values-from-types.ts create mode 100644 src/chains/ethereum/src/types/filters.ts create mode 100644 src/chains/ethereum/src/types/shh.ts create mode 100644 src/chains/ethereum/src/types/snapshots.ts create mode 100644 src/chains/ethereum/src/types/subscriptions.ts create mode 100644 src/chains/ethereum/src/types/tuple-from-union.ts create mode 100644 src/chains/ethereum/src/wallet.ts create mode 100644 src/chains/ethereum/tests/@types/solc/index.d.ts create mode 100644 src/chains/ethereum/tests/api/bzz/bzz.test.ts create mode 100644 src/chains/ethereum/tests/api/db/db.test.ts create mode 100644 src/chains/ethereum/tests/api/eth/contracts/GetCode.sol create mode 100644 src/chains/ethereum/tests/api/eth/contracts/GetStorageAt.sol create mode 100644 src/chains/ethereum/tests/api/eth/contracts/Logs.sol create mode 100644 src/chains/ethereum/tests/api/eth/contracts/Reverts.sol create mode 100644 src/chains/ethereum/tests/api/eth/eth.test.ts create mode 100644 src/chains/ethereum/tests/api/eth/getCode.test.ts create mode 100644 src/chains/ethereum/tests/api/eth/getStorageAt.test.ts create mode 100644 src/chains/ethereum/tests/api/eth/legacyInstamining.test.ts create mode 100644 src/chains/ethereum/tests/api/eth/logs.test.ts create mode 100644 src/chains/ethereum/tests/api/eth/sendRawTransaction.test.ts create mode 100644 src/chains/ethereum/tests/api/eth/sendTransaction.test.ts create mode 100644 src/chains/ethereum/tests/api/eth/sign.test.ts create mode 100644 src/chains/ethereum/tests/api/eth/signTypedData.test.ts create mode 100644 src/chains/ethereum/tests/api/eth/subscribe.test.ts create mode 100644 src/chains/ethereum/tests/api/eth/uncles.test.ts create mode 100644 src/chains/ethereum/tests/api/evm/evm.test.ts create mode 100644 src/chains/ethereum/tests/api/evm/snapshot.sol create mode 100644 src/chains/ethereum/tests/api/evm/snapshot.test.ts create mode 100644 src/chains/ethereum/tests/api/miner/miner.test.ts create mode 100644 src/chains/ethereum/tests/api/net/net.test.ts create mode 100644 src/chains/ethereum/tests/api/personal/personal.test.ts create mode 100644 src/chains/ethereum/tests/api/rpc/modules.test.ts create mode 100644 src/chains/ethereum/tests/api/shh/shh.test.ts create mode 100644 src/chains/ethereum/tests/api/web3/web3.test.ts create mode 100644 src/chains/ethereum/tests/contracts/HelloWorld.sol create mode 100644 src/chains/ethereum/tests/helpers/compile.ts create mode 100644 src/chains/ethereum/tests/helpers/getProvider.ts create mode 100644 src/chains/ethereum/tests/provider.test.ts create mode 100644 src/chains/ethereum/tests/temp-tests.test.ts create mode 100644 src/chains/ethereum/tests/tsconfig.json create mode 100644 src/chains/ethereum/tsconfig.json create mode 100644 src/chains/ethereum/typedoc.json create mode 100644 src/chains/tezos/.npmignore create mode 100644 src/chains/tezos/LICENSE create mode 100644 src/chains/tezos/README.md create mode 100644 src/chains/tezos/index.ts create mode 100644 src/chains/tezos/npm-shrinkwrap.json create mode 100644 src/chains/tezos/package.json create mode 100644 src/chains/tezos/scripts/post-process-docs.js create mode 100644 src/chains/tezos/src/api.ts create mode 100644 src/chains/tezos/src/provider.ts create mode 100644 src/chains/tezos/tests/index.test.ts create mode 100644 src/chains/tezos/tsconfig.json create mode 100644 src/chains/tezos/typedoc.json create mode 100644 src/packages/cli/.npmignore create mode 100644 src/packages/cli/LICENSE create mode 100644 src/packages/cli/README.md create mode 100644 src/packages/cli/args.ts create mode 100644 src/packages/cli/cli.ts create mode 100644 src/packages/cli/index.ts create mode 100644 src/packages/cli/npm-shrinkwrap.json create mode 100644 src/packages/cli/package.json create mode 100644 src/packages/cli/tests/index.test.ts create mode 100644 src/packages/cli/tsconfig.json create mode 100644 src/packages/core/.npmignore create mode 100644 src/packages/core/LICENSE create mode 100644 src/packages/core/README.md create mode 100644 src/packages/core/index.ts create mode 100644 src/packages/core/npm-shrinkwrap.json create mode 100644 src/packages/core/package.json create mode 100644 src/packages/core/src/@types/superagent.d.ts create mode 100644 src/packages/core/src/@types/uWebsockets.js.ts create mode 100644 src/packages/core/src/connector.ts create mode 100644 src/packages/core/src/options/index.ts create mode 100644 src/packages/core/src/options/server-options.ts create mode 100644 src/packages/core/src/server.ts create mode 100644 src/packages/core/src/servers/http-server.ts create mode 100644 src/packages/core/src/servers/utils/content-types.ts create mode 100644 src/packages/core/src/servers/utils/http-response-codes.ts create mode 100644 src/packages/core/src/servers/utils/websocket-close-codes.ts create mode 100644 src/packages/core/src/servers/ws-server.ts create mode 100644 src/packages/core/tests/connector.test.ts create mode 100644 src/packages/core/tests/helpers/getProvider.ts create mode 100644 src/packages/core/tests/interface.test.ts create mode 100644 src/packages/core/tests/server.test.ts rename {test/local => src/packages/core/tests}/testdb/!trie_db!0x08ac839d755e4a25bcbc47a4012219db100ef0a340307869393f9df55ebd470c (100%) rename {test/local => src/packages/core/tests}/testdb/!trie_db!0x6990c157721aea0e000dc63d12ccdd1938364fb8e72dcdda25d6baa70992b80f (100%) rename {test/local => src/packages/core/tests}/testdb/!trie_db!0xb0108c95b74533d6862f59a730bb0282bbaaadd8ac1b94510b69a499527c3505 (100%) rename {test/local => src/packages/core/tests}/testdb/!trie_db!0xf173aa08e820d7e4b4bb0c243a1661770198770b14029eed74f0eb473c79cd83 (100%) create mode 100644 src/packages/core/tsconfig.json create mode 100644 src/packages/flavors/.npmignore create mode 100644 src/packages/flavors/LICENSE create mode 100644 src/packages/flavors/README.md create mode 100644 src/packages/flavors/index.ts create mode 100644 src/packages/flavors/npm-shrinkwrap.json create mode 100644 src/packages/flavors/package.json create mode 100644 src/packages/flavors/tests/index.test.ts create mode 100644 src/packages/flavors/tsconfig.json create mode 100644 src/packages/ganache/.npmignore create mode 100644 src/packages/ganache/LICENSE create mode 100644 src/packages/ganache/README.md create mode 100644 src/packages/ganache/index.ts create mode 100644 src/packages/ganache/npm-shrinkwrap.json create mode 100644 src/packages/ganache/package.json create mode 100644 src/packages/ganache/src/cli.ts create mode 100644 src/packages/ganache/tests/index.test.ts create mode 100644 src/packages/ganache/tsconfig.json create mode 100644 src/packages/ganache/webpack.config.ts create mode 100644 src/packages/ganache/webpack/polyfills/browser-bigint-buffer.ts create mode 100644 src/packages/ganache/webpack/polyfills/browser-tmp-promise.ts create mode 100644 src/packages/ganache/webpack/webpack.browser.config.ts create mode 100644 src/packages/ganache/webpack/webpack.cli.config.ts create mode 100644 src/packages/ganache/webpack/webpack.common.config.ts create mode 100644 src/packages/ganache/webpack/webpack.node.config.ts create mode 100644 src/packages/options/.npmignore create mode 100644 src/packages/options/LICENSE create mode 100644 src/packages/options/README.md create mode 100644 src/packages/options/index.ts create mode 100644 src/packages/options/npm-shrinkwrap.json create mode 100644 src/packages/options/package.json create mode 100644 src/packages/options/src/base.ts create mode 100644 src/packages/options/src/create.ts create mode 100644 src/packages/options/src/definition.ts create mode 100644 src/packages/options/src/exclusive.ts create mode 100644 src/packages/options/src/getters.ts create mode 100644 src/packages/options/tests/index.test.ts create mode 100644 src/packages/options/tsconfig.json create mode 100644 src/packages/promise-queue/.npmignore create mode 100644 src/packages/promise-queue/LICENSE create mode 100644 src/packages/promise-queue/README.md create mode 100644 src/packages/promise-queue/index.ts create mode 100644 src/packages/promise-queue/npm-shrinkwrap.json create mode 100644 src/packages/promise-queue/package.json create mode 100644 src/packages/promise-queue/src/entry.ts create mode 100644 src/packages/promise-queue/tests/index.test.ts create mode 100644 src/packages/promise-queue/tsconfig.json create mode 100644 src/packages/utils/.npmignore create mode 100644 src/packages/utils/LICENSE create mode 100644 src/packages/utils/README.md create mode 100644 src/packages/utils/index.ts create mode 100644 src/packages/utils/npm-shrinkwrap.json create mode 100644 src/packages/utils/package.json create mode 100644 src/packages/utils/src/things/json-rpc/index.ts create mode 100644 src/packages/utils/src/things/json-rpc/json-rpc-base-types.ts create mode 100644 src/packages/utils/src/things/json-rpc/json-rpc-data.ts create mode 100644 src/packages/utils/src/things/json-rpc/json-rpc-quantity.ts create mode 100644 src/packages/utils/src/things/jsonrpc.ts create mode 100644 src/packages/utils/src/things/promievent.ts create mode 100644 src/packages/utils/src/types/api.ts create mode 100644 src/packages/utils/src/types/connector.ts create mode 100644 src/packages/utils/src/types/index.ts create mode 100644 src/packages/utils/src/types/provider.ts create mode 100644 src/packages/utils/src/utils/bigint-to-buffer.ts create mode 100644 src/packages/utils/src/utils/constants.ts create mode 100644 src/packages/utils/src/utils/executor.ts create mode 100644 src/packages/utils/src/utils/has-own.ts create mode 100644 src/packages/utils/src/utils/heap.ts create mode 100644 src/packages/utils/src/utils/index.ts create mode 100644 src/packages/utils/src/utils/request-coordinator.ts create mode 100644 src/packages/utils/src/utils/uint-to-buffer.ts create mode 100644 src/packages/utils/src/utils/unref.ts create mode 100644 src/packages/utils/tests/utils.test.ts create mode 100644 src/packages/utils/tsconfig.json delete mode 100644 test/.eslintrc delete mode 100644 test/contracts/call/Call.sol delete mode 100644 test/contracts/chainId/ChainId.sol delete mode 100644 test/contracts/constantinople/ConstantinopleContract.sol delete mode 100644 test/contracts/customContracts/LargeContract.sol delete mode 100644 test/contracts/debug/DebugContract.sol delete mode 100644 test/contracts/debug/DebugContractStorage.sol delete mode 100644 test/contracts/events/EventTest.sol delete mode 100644 test/contracts/examples/Example.sol delete mode 100644 test/contracts/examples/Example2.sol delete mode 100644 test/contracts/forking/Debug.sol delete mode 100644 test/contracts/forking/IntraBlockCache.sol delete mode 100644 test/contracts/forking/Snapshot.sol delete mode 100644 test/contracts/forking/StorageDelete.sol delete mode 100644 test/contracts/gas/ContractFactory.sol delete mode 100644 test/contracts/gas/CreateTwo.sol delete mode 100644 test/contracts/gas/Donation.sol delete mode 100644 test/contracts/gas/EstimateGas.sol delete mode 100644 test/contracts/gas/Fib.sol delete mode 100644 test/contracts/gas/GasLeft.sol delete mode 100644 test/contracts/gas/NonZero.sol delete mode 100644 test/contracts/gas/SendContract.sol delete mode 100644 test/contracts/gas/TestDepth.sol delete mode 100644 test/contracts/misc/Oracle.sol delete mode 100644 test/contracts/revert/Revert.sol delete mode 100644 test/contracts/runtime/RuntimeError.sol delete mode 100644 test/contracts/snapshotting/snapshot.sol delete mode 100644 test/contracts/transaction-data/TransactionData.sol delete mode 100644 test/helpers/contract/bootstrap.js delete mode 100644 test/helpers/contract/compileAndDeploy.js delete mode 100644 test/helpers/contract/singleFileCompile.js delete mode 100644 test/helpers/utils/create-signed-tx.js delete mode 100644 test/helpers/utils/generateRandomInteger.js delete mode 100644 test/helpers/utils/rpc.js delete mode 100644 test/helpers/utils/sleep.js delete mode 100644 test/helpers/utils/toBytesHexString.js delete mode 100644 test/helpers/web3/initializeTestProvider.js delete mode 100644 test/local/CallLibrary.sol delete mode 100644 test/local/LargeContract.sol delete mode 100644 test/local/Library.sol delete mode 100644 test/local/accounts.js delete mode 100644 test/local/bad_input.js delete mode 100644 test/local/block-tags.js delete mode 100644 test/local/call.js delete mode 100644 test/local/call/undefined.js delete mode 100644 test/local/chainId.js delete mode 100644 test/local/cors.js delete mode 100644 test/local/debug/debug.js delete mode 100644 test/local/debug/debugStorage.js delete mode 100644 test/local/enable_constantinople_hardfork.js delete mode 100644 test/local/ethereum.js delete mode 100644 test/local/ethers.js delete mode 100644 test/local/events.js delete mode 100644 test/local/forking/call.js delete mode 100644 test/local/forking/caseSensitivity.js delete mode 100644 test/local/forking/debug.js delete mode 100644 test/local/forking/delete.js delete mode 100644 test/local/forking/deployAfterFork.js delete mode 100644 test/local/forking/forking.js delete mode 100644 test/local/forking/forkingAsProvider.js delete mode 100644 test/local/forking/snapshot.js delete mode 100644 test/local/gas/customGasLimit.js delete mode 100644 test/local/gas/customGasPrice.js delete mode 100644 test/local/gas/gas.js delete mode 100644 test/local/gas/gasLimit.js delete mode 100644 test/local/gas/gasPrice.js delete mode 100644 test/local/gas/lib/confirmGasPrice.js delete mode 100644 test/local/gas/lib/transactionEstimate.js delete mode 100644 test/local/hex.js delete mode 100644 test/local/interval-mining.js delete mode 100644 test/local/library.js delete mode 100644 test/local/mining.js delete mode 100644 test/local/options/account_keys_path.js delete mode 100644 test/local/options/keepAliveTimeout.js delete mode 100644 test/local/options/lib/testTimeout.js delete mode 100644 test/local/persistence.js delete mode 100644 test/local/public-exports.js delete mode 100644 test/local/runTimeErrors.js delete mode 100644 test/local/server.js delete mode 100644 test/local/snapshotting.js delete mode 100644 test/local/stability.js delete mode 100644 test/local/subscriptions.js delete mode 100644 test/local/swarm.js delete mode 100644 test/local/testdb/!blockHashes!0x4b6de53cdbc759a655d98b3b60dc2c5f1b1b0f82a762c869bfa1b15acf8597e6 delete mode 100644 test/local/testdb/!blockHashes!0xa339ce48c9eb2335ba87f9498478a5db4cc80792cd0a8b64821512a50802d4cd delete mode 100644 test/local/testdb/!blockHashes!0xc02e0ee40051c3d4a5a9be310e7d4e128a396226ed4ce10a101b31262a88868c delete mode 100644 test/local/testdb/!blockLogs!0 delete mode 100644 test/local/testdb/!blockLogs!1 delete mode 100644 test/local/testdb/!blockLogs!2 delete mode 100644 test/local/testdb/!blockLogs!length delete mode 100644 test/local/testdb/!blocks!0 delete mode 100644 test/local/testdb/!blocks!1 delete mode 100644 test/local/testdb/!blocks!2 delete mode 100644 test/local/testdb/!blocks!length delete mode 100644 test/local/testdb/!transactionReceipts!0x1b677be476665ed6c357c9318ea4882fe25e7d5fee8dfe6ac2ed4049c34a1883 delete mode 100644 test/local/testdb/!transactionReceipts!0x59214a31f0a1aec159a239a9bd6a62fa371881f6e72690c178b22cda8e552ffb delete mode 100644 test/local/testdb/!transactions!0x1b677be476665ed6c357c9318ea4882fe25e7d5fee8dfe6ac2ed4049c34a1883 delete mode 100644 test/local/testdb/!transactions!0x59214a31f0a1aec159a239a9bd6a62fa371881f6e72690c178b22cda8e552ffb delete mode 100644 test/local/testdb/!trie_db!0x0279439031111a3c85d5a32fe51d5677504f6867f7c6f31f55748f10544b64e3 delete mode 100644 test/local/testdb/!trie_db!0x03a0f12083f4cdb767e0f47613296e562cafa8a25a91deb88a47433ccc4e138e delete mode 100644 test/local/testdb/!trie_db!0x06de23a6d2ecbd78afda8c7fc42b84d07ef56de983d48b4e631291c607a71e5e delete mode 100644 test/local/testdb/!trie_db!0x0c94b6c2b893b998e5e67d36ee03f64eda5cbb14fb58cbeb3362709f510b2c0e delete mode 100644 test/local/testdb/!trie_db!0x17e16fa1786714b7d5f86765fdfd3f12de1e1379c37b9753c5dbe62082ef8960 delete mode 100644 test/local/testdb/!trie_db!0x1883479dfb5eded4883af730a86e532f7b442c08f6d7ed7d9a79601c93591386 delete mode 100644 test/local/testdb/!trie_db!0x21cf3512b27750f536562dc8ac85d44e521490c8475507cc62da7583cb9723e1 delete mode 100644 test/local/testdb/!trie_db!0x29611bb48aac94d8020c92c251e958743b1c30db7bb63e7bbf247b8f78aeb1d7 delete mode 100644 test/local/testdb/!trie_db!0x2f7ed65fe19e553cc2c9e20f9afcff45ff959c6bacb6fb2276bac7bee70ed2ce delete mode 100644 test/local/testdb/!trie_db!0x304356a9ff9f36e260c1ef38658725d9067d9e85450d043946d439cae014547b delete mode 100644 test/local/testdb/!trie_db!0x33bc209afd8d36f97af3e3342734e6bd259a6bc6d9f3a527e2b06a597ba27cb0 delete mode 100644 test/local/testdb/!trie_db!0x372afacecb5d397370430cfa7481601253172b63d77e80278ab3d4895b20001b delete mode 100644 test/local/testdb/!trie_db!0x3781707048c5d3acf2d0a78e1c8990e0f19a3fe5909007a6aefe8a9b04026899 delete mode 100644 test/local/testdb/!trie_db!0x3a6bcf8171ae73adaff761bc4dc46d54e7067320d7093781ce3b944ad84c8f3a delete mode 100644 test/local/testdb/!trie_db!0x3b6a18a4c0691fbd7c2090c6cf01c59e1150fdc7c456a31b4c42fe4a64064aca delete mode 100644 test/local/testdb/!trie_db!0x4172dc20058c99d8b96489c11e5400d677f12fb8da2a5650efa9b9f638385600 delete mode 100644 test/local/testdb/!trie_db!0x47fcc5864e5a790d538706379b31fbaf2ef6c71d2d8085ade2d58b55ab4ea2ac delete mode 100644 test/local/testdb/!trie_db!0x4a00bea2712cfd525de12555898ddcb997e6b8e624e09c5a3f4b200226c65383 delete mode 100644 test/local/testdb/!trie_db!0x4ef305cd67b97b743669faa6b70fab3c29d545df173e5798da1a3bef0efeb5d9 delete mode 100644 test/local/testdb/!trie_db!0x5072a6c1e2e016ff4e5b6b35caf68673a43b7a13bb1fa82f2643bad3e4d1c253 delete mode 100644 test/local/testdb/!trie_db!0x54350d30b48d6984395cf168b20e31952619100e01a31e619f91393e34d80ab5 delete mode 100644 test/local/testdb/!trie_db!0x575dc2642e9386612af4e066b4f7c9d6a216d0ac795355860ac66dea8d88b767 delete mode 100644 test/local/testdb/!trie_db!0x57de8e0787e5eb6e29bb1f509cf31f345ea3265c2faf00b6dcb883876eb89cc1 delete mode 100644 test/local/testdb/!trie_db!0x5911f24d96912350de50f297c2d34d5d10e136757bf4cfff5fa41bfca219554a delete mode 100644 test/local/testdb/!trie_db!0x5e287c9213b0dcabfdf484092e2b6133f6826e045c391501e80374760de69320 delete mode 100644 test/local/testdb/!trie_db!0x607f7405b317ab741f6607ab7dc65de14684038e90bfb07417c7ce7ef669c0eb delete mode 100644 test/local/testdb/!trie_db!0x62ab8b4e7f7569eb032e637fcdcfd5b561442347326a3ffca40ae225d0029d4c delete mode 100644 test/local/testdb/!trie_db!0x67bcaf1d60f75a22d49b037c88c00e44733094250664cbfc9d9bbf9eda3ca745 delete mode 100644 test/local/testdb/!trie_db!0x69b7a8125fdd1dac0b872648905e0e1410b8859b9fdfc1991e3860eecbcaa2da delete mode 100644 test/local/testdb/!trie_db!0x7559bb4901892880aec39ee574a87e32492071f22d0a42e184892f6b225df9b3 delete mode 100644 test/local/testdb/!trie_db!0x77ecddca419c697a6aae21fa9a02f85475aaa5b109d41dc35f1893dc5bb3cf72 delete mode 100644 test/local/testdb/!trie_db!0x7ad6cee5cd3acb548b1af064970c97eb75bbb1508b7a4570eda4b87ef76d7988 delete mode 100644 test/local/testdb/!trie_db!0x81f3d60fe1f6e52b48917d15245c68de1b6642ace21159e538d0a96bf1254460 delete mode 100644 test/local/testdb/!trie_db!0x83097864ebee0be81925b10976490ab2c71a0643e9f16e16807811b7112ed032 delete mode 100644 test/local/testdb/!trie_db!0x84893185cfd51214257490b3ad18415a1fc08d2ffab89a852e0067d6957b09d4 delete mode 100644 test/local/testdb/!trie_db!0x8a083a05cf77f2f36f6c055ac468fc88ab99335759bb47d22b0947229f0b170a delete mode 100644 test/local/testdb/!trie_db!0x98ad39fb8468cbcb93a0990018ded196413ef989c0062341ff886889f39ab41b delete mode 100644 test/local/testdb/!trie_db!0xa2616c2f24b3c29c29fedec4acc5c3d803aa7cb7818017520686266074832155 delete mode 100644 test/local/testdb/!trie_db!0xa6221bba43ca4638c5b4ffdc6cccea1f3b4b170d53b9b92f92bde5573b585bfc delete mode 100644 test/local/testdb/!trie_db!0xa692717e52ff2d01d314834d9a639e60fad01fac4634d78c7203c6916e3f17fc delete mode 100644 test/local/testdb/!trie_db!0xa9c678e4e10500df2d3c913e87ce2f1b38b12409f5c65078b201783d6280e764 delete mode 100644 test/local/testdb/!trie_db!0xae94d36357988098ed9618487d91f45503cb88cf28b802043c5acd7304f6bb45 delete mode 100644 test/local/testdb/!trie_db!0xb01c8885a7276794acae6562c3d06a54045f4f69e8e1c0cacb05e11a7d2a23af delete mode 100644 test/local/testdb/!trie_db!0xb84b045ddcae6d0562d0293dde2f88f4eb01aa731d5495387f414c8e471c7b18 delete mode 100644 test/local/testdb/!trie_db!0xbf2795b671509d37d9d2b1bab1ab2419a5e09b9aede2d9e29b8d45535217abf1 delete mode 100644 test/local/testdb/!trie_db!0xc4f6a1f0b681632674246c126fd0b2eb33764bdb3ec5763b93968f205314944c delete mode 100644 test/local/testdb/!trie_db!0xc72aa692983dcb3230af2fa59915a1b661eb90e0ad6b61bc8135e9e14af02608 delete mode 100644 test/local/testdb/!trie_db!0xc74b55ae25b522915216f62ea6ffcd436f6c6d4fe14a771042f7d4cea5cfa92f delete mode 100644 test/local/testdb/!trie_db!0xcdf6e1aa543f388bd6340e20dfd8b00714aaf2cfcce46aa43a023e0e8fa65bfd delete mode 100644 test/local/testdb/!trie_db!0xd9268c1bf3f59a12714b735a83b70e89208877a149724ace5df5eb9ad355259e delete mode 100644 test/local/testdb/!trie_db!0xde941f06d6f99270e651ece36725c555afc7fdfc4855bd86add2cdcdec4a4de3 delete mode 100644 test/local/testdb/!trie_db!0xdeb8f2fa902116659c4439f256f7f21226617577101caae5e93f9ce5eb2bc082 delete mode 100644 test/local/testdb/!trie_db!0xed57d93087e1a79ee9e3f6eadfc3d2affdb420d73dc4ecf07c5a21b22a221a98 delete mode 100644 test/local/testdb/!trie_db!0xed8c90ad5952c76a592f31c6b7b65d87b1089d447cde9cd46fad00d658f2e7c8 delete mode 100644 test/local/testdb/!trie_db!0xef7657bce0047464187afc62b8a367e2c9ea4065f6fea7229b9484d17b6192be delete mode 100644 test/local/testdb/!trie_db!0xf1ced711647c94bb97082b1730301b377f5c238369dfc2d598b6aa9d14004ccb delete mode 100644 test/local/testdb/!trie_db!0xfa71baff75cc869d687859edac6345402a902e385a2829b9bded7697113e9836 delete mode 100644 test/local/testdb/!trie_db!0xfe3d828c2803bd586bc34641752157c205a391ec9a1b730031bccc77a9276b02 delete mode 100644 test/local/time-adjust.js delete mode 100644 test/local/transaction-data.js delete mode 100644 test/local/transaction-rejection.js delete mode 100644 test/local/transaction.js delete mode 100644 test/local/transasction-ordering.js delete mode 100644 test/local/unlimitedContractSize.js delete mode 100644 test/local/vm.js delete mode 100644 test/local/whisper.js delete mode 100644 test/smoke/forking/infura/simple.js create mode 100644 tsconfig.json delete mode 100644 typings/index.d.ts delete mode 100644 webpack/base.webpack.config.js delete mode 100644 webpack/node/core.webpack.config.js delete mode 100644 webpack/node/provider.webpack.config.js delete mode 100644 webpack/node/server.webpack.config.js delete mode 100644 webpack/web-experimental/core.webpack.config.js delete mode 100644 webpack/web-experimental/provider.webpack.config.js delete mode 100644 webpack/web-experimental/server.webpack.config.js delete mode 100644 webpack/web-experimental/webbase.webpack.config.js diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index 85dcc16df6..0000000000 --- a/.dockerignore +++ /dev/null @@ -1,2 +0,0 @@ -.git -node_modules diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..39ac70e40f --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +docs/launch.json linguist-language=JSON5 +.nycrc linguist-language=JSON +.prettierrc linguist-language=JSON diff --git a/.github/ganache-logo-dark.svg b/.github/ganache-logo-dark.svg new file mode 100644 index 0000000000..3746549d85 --- /dev/null +++ b/.github/ganache-logo-dark.svg @@ -0,0 +1 @@ +ganache-logo-v-dark \ No newline at end of file diff --git a/.github/truffle-logo-dark.svg b/.github/truffle-logo-dark.svg new file mode 100644 index 0000000000..92900f899f --- /dev/null +++ b/.github/truffle-logo-dark.svg @@ -0,0 +1 @@ +truffle-logo-dark \ No newline at end of file diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml new file mode 100644 index 0000000000..64bddedb1b --- /dev/null +++ b/.github/workflows/CI.yml @@ -0,0 +1,52 @@ +# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions + +name: CI + +on: [push, pull_request] + +jobs: + build: + strategy: + fail-fast: false + matrix: + node: [10.7.0, 10.x, 11.x, 12.x, 13.x, 14.x] + os: + [ + windows-2016, + windows-2019, + ubuntu-16.04, + ubuntu-18.04, + ubuntu-20.04, + macos-11.0, + ] + + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node }} + # we need build tools for the `bigint-buffer` module + - name: Add msbuild to PATH + if: startsWith(matrix.os, 'windows-') + uses: microsoft/setup-msbuild@v1.0.2 + - name: install node tools + if: startsWith(matrix.os, 'windows-') + # windows-build-tools@5.2.2 failed to install, so we use 4.0.0 + run: npm install --global --production windows-build-tools@4.0.0 + - name: install node-gyp + if: startsWith(matrix.os, 'windows-') + run: npm install --global node-gyp@latest + - name: Set node config to use python2.7 + if: startsWith(matrix.os, 'windows-') + run: npm config set python python2.7 + - name: Set node config to set msvs_version to 2015 + if: startsWith(matrix.os, 'windows-') + run: npm config set msvs_version 2015 + - run: npm ci + - run: npm test + env: + FORCE_COLOR: 1 diff --git a/.gitignore b/.gitignore index 80f51edf25..84795d7c22 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,9 @@ node_modules -TODO -*.log -.eslintrc.js -.tern-project .DS_Store -.tern-port .vscode -build -npm-shrinkwrap.json.bak -test/testdb -.nyc_output/ +.nyc_output +lerna-debug.log +npm-debug.log +src/**/*/lib +coverage +dist/ diff --git a/.npmignore b/.npmignore deleted file mode 100644 index c2ecc8f333..0000000000 --- a/.npmignore +++ /dev/null @@ -1,14 +0,0 @@ -.npmignore -.tern-project -.tern-port -.dockerignore -.travis.yml -.eslintrc.js -*.log -Dockerfile -ISSUE_TEMPLATE.md -.vscode/ -test/ -webpack/ -perf/ -npm-shrinkwrap.json.bak diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000000..eb29e2d5fe --- /dev/null +++ b/.npmrc @@ -0,0 +1,6 @@ +engine-strict=true +save-exact=true +sign-git-commit=true +sign-git-tag=true +loglevel=error +fund=false diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000000..a63bb50634 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v10.7.0 diff --git a/.nycrc b/.nycrc new file mode 100644 index 0000000000..0395a09274 --- /dev/null +++ b/.nycrc @@ -0,0 +1,3 @@ +{ + "extends": "@istanbuljs/nyc-config-typescript" +} diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000000..43ecbd0792 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,8 @@ +.DS_Store +.vscode +.nyc_output +lerna-debug.log +npm-debug.log +src/**/*/lib +coverage +npm-shrinkwrap.json diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000000..1f5e5d140d --- /dev/null +++ b/.prettierrc @@ -0,0 +1,4 @@ +{ + "arrowParens": "avoid", + "trailingComma": "none" +} diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index a8b038297e..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,38 +0,0 @@ -version: ~> 1.0 -language: node_js -node_js: - - "10" - - "12" - - "14.13.0" -os: - - linux - - osx - - windows -# using xcode9.2 forces osx 10.12, which is WAAAYYYYYY faster than 10.13 -# in fact, we couldn't get osx 10.13 to ever finish the running the tests -osx_image: xcode9.2 -git: - autocrlf: false - -addons: - apt: - sources: - - ubuntu-toolchain-r-test - packages: - - gcc-5 - - g++-5 - -before_install: - # Apply workaround for git:// packages - - git config --global url."https://github.com/".insteadOf ssh://git@github.com/ - - if [ $TRAVIS_OS_NAME == "linux" ]; then - export CC="gcc-5"; - export CXX="g++-5"; - export LINK="gcc-5"; - export LINKXX="g++-5"; - fi - -script: - - npm run prepare - - npm run test - - npm run test-smoke diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000..b55bdff3f9 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,145 @@ +# Contributing to Ganache + +## Getting set up + +- Use Node.js v10.7.0, this is the earliest version we support. + - Why v10.7.0? Because this is the first version that supports BigInt literals (the `n` in `100n`). + - recommendation: use [nvm](https://github.com/nvm-sh/nvm) on Linux and macOS, and [nvm-windows](https://github.com/coreybutler/nvm-windows) on + Windows, to configure your node version. + - On Linux and macOS, if you have `nvm` installed, just run `nvm use` to switch to Node.js v10.7.0. +- `git clone git@github.com:trufflesuite/ganache-core.git` +- `cd ganache-core` +- `npm install` (use npm v6) +- On Linux and macOS: run `source completions.sh` to enable autocomplete for npm scripts. + +## Solving node-gyp issues + +If installation fails due to a `node-gyp` issue you may need to perform some additional system configuration. + +### on Linux (Ubuntu-based) + +- Determine if you have Python 2.7 installed + - example: `which python2.7` +- If you do not have Python 2.7 installed, you need to install it + - example: `sudo apt update && sudo apt install python2.7` +- Finally, run `npm config set python python2.7` + +### on Windows + +- Install [https://www.npmjs.com/package/windows-build-tools](Windows-Build-Tools) + - `npm install --global windows-build-tools` + +### On macOS + +- I have no idea. + +## Clean install + +- `npm run reinstall` + +Which just runs these commands for you: + +- `npm run clean` +- `npm install` + +This deletes all `node_modules` folders, as well as all generated `lib` +directories, then reinstalls all modules. + +## To build + +Builds all packages: + +- `npm run tsc` + +## To test + +Runs all tests: + +- `npm test` (or the shorthand, `npm t`) + +## To create a new package + +- `npm run create --location ` + +This will create a new package with Ganache defaults at `src//`. + +## To add a module to a package: + +- `npx lerna add [@version] -E [--dev] [--peer] --scope=` + +Where `` is the npm-module you want to add and `` is where you want to add it. See +[@lerna/add documentation](https://github.com/lerna/lerna/tree/master/commands/add) for more details. + +Example: + +```bash +npx lerna add @ganache/options -E --scope=@ganache/filecoin +``` + +will add our local `@ganache/options` package to the `@ganache/filecoin` package. + +## To remove a module from another package: + +`cd` to the package and then run `npm uninstall ` + +## Editor Integrations + +### Automated Code Formatting + +- See: https://prettier.io/docs/en/editors.html + +### VSCode On Windows (10) + +- Enable "Developer Mode" by going to Settings -> Developer Settings -> Then select Developer Mode. + +### To debug tests in VS Code + +- Copy the [`launch.json`](./launch.json) file into a folder named `.vscode` in root of the project. +- Set breakpoints by clicking the margin to the left of the line numbers (you can set conditional breakpoints or + logpoints by right-clicking instead) +- Press F5 (or select `Run` 🡺 `Start Debugging` from the menu bar) to automatically start debugging. + +To change which files are debugged update your `.vscode/launch.json` file glob to match your target files. Here is an +example to debug only test files in the `@ganache/ethereum` package: + +```diff +diff --git a/.vscode/launch.json b/.vscode/launch.json +index 2a2aa9e..57cbf21 100644 +--- a/.vscode/launch.json ++++ b/.vscode/launch.json +@@ -24,7 +24,7 @@ + "--colors", + "--require", + "ts-node/register", +- "${workspaceFolder}/src/**/tests/**/*.test.ts" ++ "${workspaceFolder}/src/chains/ethereum/tests/**/*.test.ts" + ], + "skipFiles": ["/**"], + "console": "integratedTerminal", +``` + +## Code Conventions + +These are guidelines, not rules. :-) + +- Use Node.js v10.7.0 for most local development. +- Use `bigint` literals, e.g., `123n`; if the number is externally configurable and/or could exceed + `Number.MAX_SAFE_INTEGER`. +- Write tests. +- Do not use "Optional Chaining" (`obj?.prop`). I'd love to enable this, but TypeScript makes it hard to use bigint + literals and optional chaining together. If you figure it out, delete this rule! +- Prefer using a single loop to functional chaining. +- Prefer performant code over your own developer experience. +- Document complex code. Explain why the code does what it does. +- Feel free to be clever, just document _why_ you're being clever. If it's hard to read, comment _what_ the code does, + too. +- Add JSDoc comments to public class members where it makes sense. +- Before adding an external dependency check its code for quality, its # of external dependencies, its node version + support, and make sure it's absolutely necessary. +- Pin all dependencies, even dev dependencies. +- Use npm; do not use yarn. +- Don't use web3, ethers, etc in ganache-core core code. (Tests are fine) +- Ensure a smooth development experience on Windows, Mac, and Linux. +- Do not use bash scripts for critical development or configuration. +- Do not use CLI commands in npm scripts or build scripts that aren't available by default on supported platforms. +- Push your code often (at least every-other day!), even broken WIP code (to your own branch, of course). diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index b621f54072..0000000000 --- a/Dockerfile +++ /dev/null @@ -1,12 +0,0 @@ -FROM mhart/alpine-node:7.9.0 - -RUN apk add --no-cache make gcc g++ python git bash -COPY package.json /src/package.json -WORKDIR /src -RUN npm install - -ADD . . - -EXPOSE 8545 - -ENTRYPOINT ["node", "./bin/testrpc"] diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md deleted file mode 100644 index 4d096d2025..0000000000 --- a/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - -## Expected Behavior - - - -## Current Behavior - - - -## Possible Solution - - - -## Steps to Reproduce (for bugs) - - -1. -2. -3. -4. - -## Context - - - -## Your Environment - -* Version used: -* Environment name and version (e.g. PHP 5.4 on nginx 1.9.1): -* Server type and version: -* Operating System and version: -* Link to your project: diff --git a/LICENSE b/LICENSE index 2189a2f2de..39f3b14498 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,21 @@ The MIT License (MIT) -Copyright (c) 2020 XGM Studio +Copyright (c) 2019-2020 Truffle Blockchain Group -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index e10a663e82..65d380f5d3 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,211 @@ -![GodMode Logo](https://godmode-public-assets.s3.amazonaws.com/godmode_logo.jpg) +

+ Ganache +

-# GodMode Ganache Core +

+ A tool for creating a local blockchain for fast Ethereum development. +

-This is a modification of the JavaScript [trufflesuite/ganache-core](https://github.com/trufflesuite/ganache-core/tree/master) library used for [xGodMode/godmode](https://github.com/xGodMode/godmode). +

+ + + +

-We keep this repository as close as possible to the __master__ branch of its upstream. +

+ Features • + Getting Started • + Documentation • + Community • + Contributing • + Related +

-## License -[MIT](https://tldrlegal.com/license/mit-license) +--- + +## Features + +Ganache is an Ethereum simulator that makes developing Ethereum applications faster, easier, and safer. It includes all popular RPC functions and features (like events) and can be run deterministically to make development a breeze. + +- Fork any Ethereum network without waiting to sync +- Ethereum json-rpc support +- Snapshot/revert state +- Mine blocks instantly, on demand, or at an interval +- Fast-forward time +- Impersonate any account (no private keys required!) +- Listens for JSON-RPC 2.0 requests over HTTP/WebSockets +- Programmatic use in Node.js +- Pending Transactions + +## Getting Started + +Ganache can be used from the command line or programmatically via Node.js. + +### Command line use + +You must first install [Node.js](https://nodejs.org/) >= v10.7.0 and npm >= 6.1.0. + +To install ganache-cli globally, run: + +```console +$ npm install ganache-cli --global +``` + +Once installed globally, you can start ganache-cli right from your command line: + +```console +$ ganache-cli +Ganache CLI v6.12.1 (ganache-core: 2.13.1) + +Available Accounts +================== +(0) 0x665a72A5A58c8ecD51dBC913f18286a104Ff6F8d (100 ETH) +(1) 0x990Ca50F8Ac586384594a98EDaB3F8b46CEd179c (100 ETH) +(2) 0x5c49b8831C81C4aa572d2733Ea7619e2fbaE7bb2 (100 ETH) +(3) 0xb3eFA990367077B0b74150B74E8D6520E692bD82 (100 ETH) +(4) 0xEb9D56915a83F7f2FEA6B18C702cD24D6a07fD62 (100 ETH) +(5) 0x8A199Adfd3D2fB10430f8D006cfd79b28D7D6562 (100 ETH) +(6) 0x2964eCA6615534E59b94FBf642d73Bcc09C7D835 (100 ETH) +(7) 0x255dE55cA7040D4ada06295F89Af5a3d7204f751 (100 ETH) +(8) 0x946790395bB4C0f6a6cEDD90D04D0023c3Bf256B (100 ETH) +(9) 0xddAeCA7f5d58539c9f78F64e8Be4bD437e6E085a (100 ETH) + +Private Keys +================== +(0) 0x3b1f1c5750edbda54702bcd70d2f0925f38c77269d606bd0faad2369aa834770 +(1) 0xdd09ee23ec00b5a6c24d954d9b333411c0ad830c1edc4dec0d625c532785e621 +(2) 0x71edfcf731f142526f2a9adee826775b2ae512d7a65de7ae65fd074e0c7053a9 +(3) 0x66ad9dd75f1fc73582a09c6da31ededec8df138db0acd02285792e9c29cd6711 +(4) 0xa0a728215cbf24a62edfef3ecfdc5b137b18b4a07fa2502d4f21f705f898c5a4 +(5) 0x9003b737d388eff793d308302c2e484f339c417e727e213cc46b7cd3f29dcef5 +(6) 0xe8de8c5ee8643699f344cefb0c502e6081422fc012bd50274007dd167147a4e6 +(7) 0xdf5df29263acd5b327db6870798856c2abab31262c83b5801ab4851297326266 +(8) 0x2bb1e1a8372370ac758795c26a6cf1f015b89d05079a8750616cd1b61d93d3bb +(9) 0xbaa9325adb6a75b1177700130aed2281b1eb1fcfac5203c14a8cabf0f82e71d3 + +HD Wallet +================== +Mnemonic: charge bamboo worry unaware rude drink congress mushroom exile federal typical couple +Base HD Path: m/44'/60'/0'/0/{account_index} + +Gas Price +================== +20000000000 + +Gas Limit +================== +6721975 + +Call Gas Limit +================== +9007199254740991 + +Listening on 127.0.0.1:8545 +``` + +To install ganache-cli into an npm project, run: + +```console +$ npm install ganache-cli +``` + +You can then add ganache-cli to your package.json scripts: + +```json +"scripts": { + "ganache": "ganache-cli --seed myCustomSeed" +} +``` + +_See [Documentation](#documentation) for additional command line options._ + +then start it: + +```console +$ npm run ganache +``` + +### Programmatic use + +You can use ganache-cli programmatically from Node.js. Install ganache-cli into your npm package: + +```console +$ npm install ganache-cli +``` + +then start ganache as an EIP-1193 provider only: + +```javascript +const ganache = require("ganache-cli"); + +const options = {}; +const provider = ganache.provider(options); +const accounts = await provider.request({ method: "eth_accounts", params: [] }); +``` + +or as an EIP-1193 provider _and_ JSON-RPC web server: + +```javascript +const ganache = require("ganache-cli"); + +const options = {}; +const server = ganache.server(options); +const PORT = 8545; +server.listen(PORT, err => { + if (err) throw err; + + console.log(`ganache-cli listening on port ${PORT}...`); + const provider = server.provider; + const accounts = await provider.request({ method: "eth_accounts", params:[] }); +}); +``` + +#### As a [web3.js](https://www.npmjs.com/package/web3) Provider + +To use ganache as a Web3 provider: + +```javascript +const Web3 = require("web3"); +const ganache = require("ganache-cli"); + +const web3 = new Web3(ganache.provider()); +``` + +NOTE: depending on your web3 version, you may need to set a number of confirmation blocks + +``` +const web3 = new Web3(ganache.provider(), null, { transactionConfirmationBlocks: 1 }); +``` + +#### As an [ethers.js]() provider: + +```javascript +const ganache = require("ganache-cli"); + +const provider = new ethers.providers.Web3Provider(ganache.provider()); +``` + +## Documentation + +TODO + +## Community + +TODO + +## Contributing + +See [CONTRIBUTING.md](CONTRIBUTING.md) for our guide to contributing to ganache. + +## Related + +- [Truffle](https://www.github.com/trufflesuite/truffle) +- [Drizzle](https://www.github.com/trufflesuite/drizzle) + +
+ +--- + +

+ Truffle +

diff --git a/completions.sh b/completions.sh new file mode 100644 index 0000000000..98b265cf29 --- /dev/null +++ b/completions.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +_npmScriptsCompletions() { + local cur_word args type_list first_arg + + cur_word="${COMP_WORDS[COMP_CWORD]}" + args=("${COMP_WORDS[@]}") + + if [ "${COMP_WORDS[1]}" == "run" ] && [ ${#COMP_WORDS[@]} == 3 ]; then + # get a list of all npm scripts and add them to the bash autocomplete reply + type_list=$(node -e "console.log(Object.keys(require('./package.json').scripts).join('\n'))") + COMPREPLY=($(compgen -W "${type_list}" -- ${cur_word})) + else + # if the command if the create command (npm run create) get its completion values + if [ "${COMP_WORDS[1]}" == "run" ] && [ "${COMP_WORDS[2]}" == "create" ]; then + while [[ "$#" -gt 0 ]]; do + case $1 in + -l | --location) + type_list=$(ls src) + shift + ;; + - | --l | --lo | --loc | --loca | --locat | --locati | --locatio) + # autocomplete "-l" or "--location" (but only when we don't already have the full word) + if [[ ! " ${COMP_WORDS[@]} " =~ " --location " ]] && [[ ! " ${COMP_WORDS[@]} " =~ " -l " ]]; then + type_list="--location" + fi + shift + ;; + *) shift ;; + esac + done + + COMPREPLY=($(compgen -W "${type_list}" -- ${cur_word})) + fi + fi + return 0 +} +complete -o default -F _npmScriptsCompletions npm diff --git a/docs/launch.json b/docs/launch.json new file mode 100644 index 0000000000..5022ee3196 --- /dev/null +++ b/docs/launch.json @@ -0,0 +1,34 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Typescript Mocha All", + "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", + "env": { + "TS_NODE_COMPILER": "ttypescript", + "TS_NODE_FILES": "true" + }, + // 10.7.0 is the earliest version of Node.js that we support, and is the first version to support BigInt literals. + "runtimeVersion": "10.7.0", + "args": [ + "${workspaceFolder}/node_modules/mocha/bin/_mocha", + "--throw-deprecation", + "--trace-warnings", + "--check-leaks", + "--no-timeout", + "--colors", + "--require", + "ts-node/register", + "${workspaceFolder}/src/**/tests/**/*.test.ts" + ], + "skipFiles": ["/**"], + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen" + } + ] +} diff --git a/index.js b/index.js deleted file mode 100644 index 7370c3d27e..0000000000 --- a/index.js +++ /dev/null @@ -1,36 +0,0 @@ -// make sourcemaps work! -require("source-map-support/register"); - -const debug = require("debug")("ganache"); - -// we use optional dependencies which may, or may not exist, so try native first -try { - // make sure these exist before we try to load ganache with native modules - const optionalDependencies = require("./package.json").optionalDependencies; - const wrongWeb3 = require("web3/package.json").version !== optionalDependencies.web3; - const wrongEthereumJs = - require("ethereumjs-wallet/package.json").version !== optionalDependencies["ethereumjs-wallet"]; - if (wrongWeb3 || wrongEthereumJs) { - useBundled(); - } else { - module.exports = require("./public-exports.js"); - module.exports._webpacked = false; - debug("Optional dependencies installed; exporting ganache-core with native optional dependencies."); - } -} catch (nativeError) { - debug(nativeError); - - // grabbing the native/optional deps failed, try using our webpacked build. - useBundled(); -} - -function useBundled() { - try { - module.exports = require("./build/ganache.core.node.js"); - module.exports._webpacked = true; - debug("Optional dependencies not installed; exporting ganache-core from `./build` directory."); - } catch (webpackError) { - debug("ganache-core could not be exported; optional dependencies nor webpack build available for export."); - throw webpackError; - } -} diff --git a/lerna.json b/lerna.json new file mode 100644 index 0000000000..f53127ee45 --- /dev/null +++ b/lerna.json @@ -0,0 +1,4 @@ +{ + "packages": ["src/packages/*", "src/chains/*"], + "version": "independent" +} diff --git a/lib/block_tracker.js b/lib/block_tracker.js deleted file mode 100644 index 8ba5854b5a..0000000000 --- a/lib/block_tracker.js +++ /dev/null @@ -1,71 +0,0 @@ -// this replaces `eth-block-tracker` in the provider-engine, as that block tracker is meant to work with -// an external provider instance - -const EventEmitter = require("events"); -var blockHelper = require("./utils/block_helper"); - -function GanacheBlockTracker(opts) { - opts = opts || {}; - EventEmitter.apply(this); - if (!opts.blockchain) { - throw new Error("RpcBlockTracker - no blockchain specified."); - } - if (!opts.blockchain.on) { - throw new Error("RpcBlockTracker - blockchain is not an EventEmitter."); - } - this._blockchain = opts.blockchain; - this.start = this.start.bind(this); - this.stop = this.stop.bind(this); - this.getTrackingBlock = this.getTrackingBlock.bind(this); - this.awaitCurrentBlock = this.awaitCurrentBlock.bind(this); - this._setCurrentBlock = this._setCurrentBlock.bind(this); -} - -GanacheBlockTracker.prototype = Object.create(EventEmitter.prototype); -GanacheBlockTracker.prototype.constructor = GanacheBlockTracker; - -GanacheBlockTracker.prototype.getTrackingBlock = function() { - return this._currentBlock; -}; - -GanacheBlockTracker.prototype.getCurrentBlock = function() { - return this._currentBlock; -}; - -GanacheBlockTracker.prototype.awaitCurrentBlock = function() { - const self = this; - // return if available - if (this._currentBlock) { - return this._currentBlock; - } - // wait for "sync" event - return new Promise((resolve) => this.once("block", resolve)).then(() => self._currentBlock); -}; - -GanacheBlockTracker.prototype.start = function(opts = {}) { - this._blockchain.on("block", this._setCurrentBlock); - return Promise.resolve(); -}; - -GanacheBlockTracker.prototype.stop = function() { - this._isRunning = false; - this._blockchain.removeListener("block", this._setCurrentBlock); -}; - -// -// private -// - -GanacheBlockTracker.prototype._setCurrentBlock = function(newBlock) { - const block = blockHelper.toJSON(newBlock, true); - if (this._currentBlock && this._currentBlock.hash === block.hash) { - return; - } - const oldBlock = this._currentBlock; - this._currentBlock = block; - this.emit("latest", block); - this.emit("sync", { block, oldBlock }); - this.emit("block", block); -}; - -module.exports = GanacheBlockTracker; diff --git a/lib/blockchain_double.js b/lib/blockchain_double.js deleted file mode 100644 index 301e7a046c..0000000000 --- a/lib/blockchain_double.js +++ /dev/null @@ -1,1261 +0,0 @@ -var to = require("./utils/to.js"); -var Account = require("ethereumjs-account").default; -var Block = require("ethereumjs-block"); -var Log = require("./utils/log"); -var Receipt = require("./utils/receipt"); -var VM = require("ethereumjs-vm").default; -var Common = require("ethereumjs-common").default; -var RuntimeError = require("./utils/runtimeerror"); -var Trie = require("merkle-patricia-tree"); -var utils = require("ethereumjs-util"); -var async = require("async"); -var Heap = require("heap"); -var Database = require("./database"); -var EventEmitter = require("events"); -var estimateGas = require("./utils/gas/estimateGas"); -var _ = require("lodash"); -var promisify = require("util").promisify; -const BN = utils.BN; - -function BlockchainDouble(options) { - var self = this; - EventEmitter.apply(self); - - this.options = options = this._applyDefaultOptions(options || {}); - - this.logger = options.logger || console; - - this.data = new Database(options); - - if (options.trie != null && options.db_path != null) { - throw new Error("Can't initialize a TestRPC with a db and a custom trie."); - } - - this.pending_transactions = []; - - // updated periodically to keep up with the times - this.blockGasLimit = options.gasLimit; - this.defaultTransactionGasLimit = options.defaultTransactionGasLimit; - this.timeAdjustment = 0; -} - -const defaultOptions = { - gasLimit: "0x6691b7", - defaultTransactionGasLimit: "0x15f90", - time: null, - debug: false, - hardfork: "muirGlacier", - allowUnlimitedContractSize: false -}; - -// inheritence w/ prototype chaining -BlockchainDouble.prototype = Object.create(EventEmitter.prototype); -BlockchainDouble.prototype.constructor = BlockchainDouble; - -BlockchainDouble.prototype._applyDefaultOptions = function(options) { - // We want this function to mutate the options object so that we can report - // our settings back to our consumer application (e.g., ganache) - return _.merge(options, defaultOptions, Object.assign({}, options)); -}; - -BlockchainDouble.prototype.initialize = function(accounts, callback) { - var self = this; - - this.data.initialize(function(err) { - if (err) { - return callback(err); - } - - self.latestBlock(function(err, block) { - if (err) { - return callback(err); - } - - var options = self.options; - - var root = null; - - if (block) { - root = block.header.stateRoot; - } - - // I haven't yet found a good way to do this. Getting the trie from the - // forked blockchain without going through the other setup is a little gross. - self.stateTrie = self.createStateTrie(self.data.trie_db, root); - - self.vm = options.vm || self.createVMFromStateTrie(self.stateTrie, true); - - if (options.time) { - self.setTime(options.time); - } - - // If we already have a block, then that means there's an existing chain. - // Don't create a genesis block. - if (block) { - self.emit("block", block); - return callback(); - } - - self.createGenesisBlock(function(err, block) { - if (err) { - return callback(err); - } - - accounts = accounts || []; - - self.vm.stateManager.checkpoint(() => { - async.eachSeries( - accounts, - function(accountData, finished) { - self.vm.stateManager.putAccount(utils.toBuffer(accountData.address), accountData.account, finished); - }, - function(err) { - if (err) { - return callback(err); - } - - self.vm.stateManager.commit(() => { - // Create first block - self.putBlock(block, [], [], callback); - }); - } - ); - }); - }); - }); - }); -}; - -BlockchainDouble.prototype.createVMFromStateTrie = function(state, activatePrecompiles) { - const self = this; - const common = Common.forCustomChain( - "mainnet", // TODO needs to match chain id - { - name: "ganache", - networkId: self.options.network_id || self.forkVersion, - chainId: self.options._chainId, - comment: "Local test network", - bootstrapNodes: [] - }, - self.options.hardfork - ); - - const vm = new VM({ - state: state, - common, - blockchain: { - // EthereumJS VM needs a blockchain object in order to get block information. - // When calling getBlock() it will pass a number that's of a Buffer type. - // Unfortunately, it uses a 64-character buffer (when converted to hex) to - // represent block numbers as well as block hashes. Since it's very unlikely - // any block number will get higher than the maximum safe Javascript integer, - // we can convert this buffer to a number ahead of time before calling our - // own getBlock(). If the conversion succeeds, we have a block number. - // If it doesn't, we have a block hash. (Note: Our implementation accepts both.) - getBlock: function(number, done) { - try { - number = to.number(number); - } catch (e) { - // Do nothing; must be a block hash. - } - - self.getBlock(number, done); - } - }, - activatePrecompiles: activatePrecompiles || false, - allowUnlimitedContractSize: self.options.allowUnlimitedContractSize - }); - - if (self.options.debug === true) { - // log executed opcodes, including args as hex - vm.on("step", function(info) { - var name = info.opcode.name; - var argsNum = info.opcode.in; - if (argsNum) { - var args = info.stack - .slice(-argsNum) - .map((arg) => to.hex(arg)) - .join(" "); - - self.logger.log(`${name} ${args}`); - } else { - self.logger.log(name); - } - }); - } - - return vm; -}; - -BlockchainDouble.prototype.createStateTrie = function(db, root) { - return new Trie(db, root); -}; - -// Overrideable so other implementations (forking) can edit it. -BlockchainDouble.prototype.createGenesisBlock = function(callback) { - this.createBlock(callback); -}; - -BlockchainDouble.prototype.latestBlock = function(callback) { - this.data.blocks.last(function(err, last) { - if (err) { - return callback(err); - } - callback(null, last); - }); -}; - -// number accepts number (integer, hex) or tag (e.g., "latest") -BlockchainDouble.prototype.getEffectiveBlockNumber = function(number, callback) { - if (typeof number !== "string") { - number = to.hex(number); - } - - // If we have a hex number - if (number.indexOf("0x") >= 0) { - return callback(null, to.number(number)); - } else { - if (number === "latest" || number === "pending") { - return this.getHeight(callback); - } else if (number === "earliest") { - return callback(null, 0); - } - } -}; - -// number accepts number (integer, hex), tag (e.g., "latest") or block hash -// This function is used by ethereumjs-vm -BlockchainDouble.prototype.getBlock = function(number, callback) { - var self = this; - - if (typeof number !== "string") { - number = to.hex(number); - } - - // If we have a hex number or a block hash - if (number.indexOf("0x") >= 0) { - var hash = number; - - // block hash - if (hash.length > 40) { - this.data.blockHashes.get(to.hex(hash), function(err, blockIndex) { - if (err) { - return callback(err); - } - return self.data.blocks.get(blockIndex, callback); - }); - } else { - // Block number - return this.data.blocks.get(to.number(hash), callback); - } - } else { - if (number === "latest" || number === "pending") { - return this.latestBlock(callback); - } else if (number === "earliest") { - return this.data.blocks.first(callback); - } else { - process.nextTick(callback, new Error("Invalid `blockNumber`: \"" + number + "\"")); - } - } -}; - -BlockchainDouble.prototype.putBlock = function(block, logs, receipts, callback) { - var self = this; - - // Lock in the state root for this block. - block.header.stateRoot = this.stateTrie.root; - - this.data.blocks.length(function(err, length) { - if (err) { - return callback(err); - } - - var requests = [ - self.data.blocks.push.bind(self.data.blocks, block), - self.data.blockLogs.push.bind(self.data.blockLogs, logs), - self.data.blockHashes.set.bind(self.data.blockHashes, to.hex(block.hash()), length) - ]; - - block.transactions.forEach(function(tx, index) { - var txHash = to.hex(tx.hash()); - requests.push( - self.data.transactions.set.bind(self.data.transactions, txHash, tx), - self.data.transactionReceipts.set.bind(self.data.transactionReceipts, txHash, receipts[index]) - ); - }); - - async.parallel(requests, (err, result) => { - if (!err) { - self.emit("block", block); - } - callback(err, result); - }); - }); -}; - -BlockchainDouble.prototype.popBlock = function(callback) { - var self = this; - - this.data.blocks.last(function(err, block) { - if (err) { - return callback(err); - } - if (block == null) { - return callback(null, null); - } - - var requests = []; - var blockHash = to.hex(block.hash()); - - block.transactions.forEach(function(tx) { - var txHash = to.hex(tx.hash()); - - requests.push( - self.data.transactions.del.bind(self.data.transactions, txHash), - self.data.transactionReceipts.del.bind(self.data.transactionReceipts, txHash) - ); - }); - - requests.push( - self.data.blockLogs.pop.bind(self.data.blockLogs), - self.data.blockHashes.del.bind(self.data.blockHashes, blockHash), - self.data.blocks.pop.bind(self.data.blocks) // Do this one last in case anything relies on it. - ); - - async.series(requests, function(err) { - if (err) { - return callback(err); - } - - // Set the root to the last available, which will "roll back" to the previous - // moment in time. Note that all the old data is still in the db, but it's now just junk data. - self.data.blocks.last(function(err, newLastBlock) { - if (err) { - return callback(err); - } - // using setStateRoot because in the future it will automatically take care - // of clearing the cache for us. - // note setStateRoot checks for checkpoints, and if there are any, it will fail. - // At time of writing this comment, the only time there could be a checkpoint - // is in the middle of a vm.runBlock call. Once asyncRequestProcessing is reenabled - // this will likely cause problems. - self.vm.stateManager.setStateRoot(newLastBlock.header.stateRoot, function(err) { - // Remember: Return block we popped off. - callback(err, block); - }); - }); - }); - }); -}; - -BlockchainDouble.prototype.clearPendingTransactions = function() { - this.pending_transactions = []; -}; - -/** - * createBlock - * - * Create a new block, where the parent's block is either the latest block - * on the chain or the parent block passed in. - * - * @param {Block} parent The block meant to be the parent block (optional) - * @param {Function} callback Callback function called after block is created - * @return Block The block created. - */ -BlockchainDouble.prototype.createBlock = function(parent, emulateParent, callback) { - var self = this; - - if (typeof parent === "function") { - callback = parent; - parent = null; - emulateParent = false; - } else if (typeof emulateParent === "function") { - callback = emulateParent; - emulateParent = false; - } - - var block = new Block(); - - function getParent(callback) { - if (parent) { - return callback(null, parent); - } else { - self.latestBlock(callback); - } - } - - getParent(function(err, parent) { - if (err) { - return callback(err); - } - - block.header.gasLimit = self.blockGasLimit; - - if (parent != null && emulateParent) { - block.header.number = parent.header.number; - block.header.timestamp = parent.header.timestamp; - block.header.parentHash = parent.header.parentHash; - } else { - var parentNumber = parent != null ? to.number(parent.header.number) : -1; - - // Ensure we have the right block number for the VM. - block.header.number = to.hex(parentNumber + 1); - - // Set the timestamp before processing txs - block.header.timestamp = to.hex(self.currentTime()); - - if (parent != null) { - block.header.parentHash = to.hex(parent.hash()); - } - } - - callback(null, block); - }); -}; - -BlockchainDouble.prototype.getQueuedNonce = function(address, callback) { - var nonce = null; - var addressBuffer = to.buffer(address); - this.pending_transactions.forEach(function(tx) { - if (!tx.from.equals(addressBuffer)) { - return; - } - - var pendingNonce = new BN(tx.nonce); - // If this is the first queued nonce for this address we found, - // or it's higher than the previous highest, note it. - if (nonce === null || pendingNonce.gt(nonce)) { - nonce = pendingNonce; - } - }); - - // If we found a queued transaction nonce, return one higher - // than the highest we found - if (nonce != null) { - return callback(null, nonce.iaddn(1).toArrayLike(Buffer)); - } - this.stateTrie.get(addressBuffer, function(err, val) { - if (err) { - return callback(err); - } - - var account = new Account(val); - // nonces are initiallized as an empty buffer, which isn't what we want. - callback(null, account.nonce.length === 0 ? Buffer.from([0]) : account.nonce); - }); -}; - -BlockchainDouble.prototype.queueTransaction = function(tx) { - this.pending_transactions.push(tx); -}; - -BlockchainDouble.prototype.sortByPriceAndNonce = function() { - // Sorts transactions like I believe geth does. - // See the description of 'SortByPriceAndNonce' at - // https://github.com/ethereum/go-ethereum/blob/290e851f57f5d27a1d5f0f7ad784c836e017c337/core/types/transaction.go - var self = this; - var sortedByNonce = {}; - - self.pending_transactions.forEach((tx) => { - const from = tx.from.toString("hex"); - const arr = sortedByNonce[from]; - if (arr) { - arr.push(tx); - } else { - sortedByNonce[from] = [tx]; - } - }); - - var priceSort = function(a, b) { - return parseInt(to.hex(b.gasPrice), 16) - parseInt(to.hex(a.gasPrice), 16); - }; - var nonceSort = function(a, b) { - return parseInt(to.hex(a.nonce), 16) - parseInt(to.hex(b.nonce), 16); - }; - - // Now sort each address by nonce - Object.keys(sortedByNonce).forEach((address) => { - sortedByNonce[address].sort(nonceSort); - }); - - // Initialise a heap, sorted by price, for the head transaction from each account. - var heap = new Heap(priceSort); - Object.keys(sortedByNonce).forEach((address) => { - heap.push(sortedByNonce[address].shift()); - }); - - // Now reorder our transactions. Compare the next transactions from each account, and choose - // the one with the highest gas price. - const sortedTransactions = []; - while (heap.size() > 0) { - const best = heap.pop(); - const address = best.from.toString("hex"); - if (sortedByNonce[address].length > 0) { - // Push on the next transaction from this account - heap.push(sortedByNonce[address].shift()); - } - sortedTransactions.push(best); - } - self.pending_transactions = sortedTransactions; -}; - -BlockchainDouble.prototype.getReadyCall = function(tx, emulateParent, blockNumber, callback) { - const readyCall = (tx, err, parentBlock) => { - if (err) { - return callback(err); - } - // create a fake block with this fake transaction - this.createBlock(parentBlock, emulateParent, (err, newBlock) => { - if (err) { - return callback(err); - } - - newBlock.transactions.push(tx); - - // gas estimates and eth_calls aren't subject to block gas limits - newBlock.header.gasLimit = tx.gasLimit; - - const runArgs = { - tx: tx, - block: newBlock, - skipBalance: true, - skipNonce: true - }; - - callback(null, parentBlock.header.stateRoot, runArgs); - }); - }; - // Delegate block selection - if (blockNumber === "latest") { - this.latestBlock(readyCall.bind(null, tx)); - } else { - this.getBlock(blockNumber, readyCall.bind(null, tx)); - } -}; - -BlockchainDouble.prototype.readyCall = function(tx, emulateParent, blockNumber, callback) { - this.getReadyCall(tx, emulateParent, blockNumber, (err, stateRoot, runArgs) => { - if (err) { - callback(err); - return; - } - const stateTrie = this.createStateTrie(this.data.trie_db, stateRoot, { persist: false }); - const vm = this.createVMFromStateTrie(stateTrie); - callback(null, vm, runArgs); - }); -}; - -BlockchainDouble.prototype.processCall = function(tx, blockNumber, callback) { - this.readyCall(tx, true, blockNumber, async(err, vm, runArgs) => { - if (err) { - callback(err); - return; - } - - const result = await vm.runTx(runArgs).catch((vmerr) => ({ vmerr })); - let vmerr = result.vmerr; - // This is a check that has been in there for awhile. I'm unsure if it's required, but it can't hurt. - if (vmerr && vmerr instanceof Error === false) { - vmerr = new Error("VM error: " + vmerr); - } - - // If we're given an error back directly, it's worse than a runtime error. Expose it and get out. - if (vmerr) { - return callback(vmerr, err); - } - - // If no error, check for a runtime error. This can return null if no runtime error. - vmerr = RuntimeError.fromResults([tx], { results: [result] }); - - callback(vmerr, result); - }); -}; - -BlockchainDouble.prototype.estimateGas = function(tx, blockNumber, callback) { - this.getReadyCall(tx, false, blockNumber, (err, stateRoot, runArgs) => { - if (err) { - callback(err); - return; - } - const generateVM = () => { - const stateTrie = this.createStateTrie(this.data.trie_db, stateRoot); - return this.createVMFromStateTrie(stateTrie); - }; - estimateGas(generateVM, runArgs, callback); - }); -}; - -/** - * processBlock - * - * Process the passed in block and included transactions - * - * @param {VM} vm the vm to use when running the block - * @param {Block} block block to process - * @param {Boolean} commit Whether or not changes should be committed to the state - * trie and the block appended to the end of the chain. - * @param {Function} callback Callback function when transaction processing is completed. - * @return [type] [description] - */ -BlockchainDouble.prototype.processBlock = async function(vm, block, commit, callback) { - var self = this; - - if (typeof commit === "function") { - callback = commit; - commit = true; - } - - const results = await vm - .runBlock({ - block: block, - generate: true, - skipBlockValidation: true - }) - .catch((vmerr) => ({ vmerr })); - let vmerr = results.vmerr; - // This is a check that has been in there for awhile. I'm unsure if it's required, but it can't hurt. - if (vmerr && vmerr instanceof Error === false) { - vmerr = new Error("VM error: " + vmerr); - } - - // If we're given an error back directly, it's worse than a runtime error. Expose it and get out. - if (vmerr) { - callback(vmerr); - return; - } - // If no error, check for a runtime error. This can return null if no runtime error. - vmerr = RuntimeError.fromResults(block.transactions, results); - - // Note, even if we have an error, some transactions may still have succeeded. - // Process their logs if so, returning the error at the end. - - var logs = []; - var receipts = []; - - var totalBlockGasUsage = 0; - - results.results.forEach(function(result) { - totalBlockGasUsage += to.number(result.gasUsed); - }); - - block.header.gasUsed = utils.toBuffer(to.hex(totalBlockGasUsage)); - - const txTrie = new Trie(); - const rcptTrie = new Trie(); - const promises = []; - const putInTrie = (trie, key, val) => promisify(trie.put.bind(trie))(key, val); - - for (var v = 0; v < results.receipts.length; v++) { - var result = results.results[v]; - var receipt = results.receipts[v]; - var tx = block.transactions[v]; - var txHash = tx.hash(); - var txLogs = []; - - // Only process the transaction's logs if it didn't error. - if (result.execResult.exceptionError === undefined) { - for (var i = 0; i < receipt.logs.length; i++) { - var receiptLog = receipt.logs[i]; - var address = to.hex(receiptLog[0]); - var topics = []; - - for (var j = 0; j < receiptLog[1].length; j++) { - topics.push(to.hex(receiptLog[1][j])); - } - - var data = to.hex(receiptLog[2]); - - var log = new Log({ - logIndex: to.hex(i), - transactionIndex: to.hex(v), - transactionHash: txHash, - block: block, - address: address, - data: data, - topics: topics, - type: "mined" - }); - - logs.push(log); - txLogs.push(log); - } - } - - const rcpt = new Receipt( - tx, - block, - txLogs, - result.gasUsed.toArrayLike(Buffer), - receipt.gasUsed, - result.createdAddress, - receipt.status, - to.hex(receipt.bitvector) - ); - receipts.push(rcpt); - - const rawReceipt = [receipt.status, receipt.gasUsed, receipt.bitvector, receipt.logs]; - const rcptBuffer = utils.rlp.encode(rawReceipt); - const key = utils.rlp.encode(v); - promises.push(putInTrie(txTrie, key, tx.serialize())); - promises.push(putInTrie(rcptTrie, key, rcptBuffer)); - } - await Promise.all(promises); - - block.header.transactionsTrie = utils.toBuffer(txTrie.root); - block.header.receiptTrie = utils.toBuffer(rcptTrie.root); - - if (commit) { - // Put that block on the end of the chain - self.putBlock(block, logs, receipts, done); - } else { - done(); - } - - function done(e) { - if (e) { - return callback(e); - } - // Note we return the vm err here too, if it exists. - callback(vmerr, block.transactions, results); - } -}; - -/** - * processNextBlock - * - * Process the next block like a normal blockchain, pulling from the list of - * pending transactions. - * - * @param {number} timestamp at which the block is mined - * @param {Function} callback Callback when transaction processing is finished. - * @return [type] [description] - */ -BlockchainDouble.prototype.processNextBlock = function(timestamp, callback) { - var self = this; - - if (typeof timestamp === "function") { - callback = timestamp; - timestamp = undefined; - } - - self.sortByPriceAndNonce(); - - // Grab only the transactions that can fit within the block - var currentTransactions = []; - var totalGasLimit = 0; - var maxGasLimit = to.number(self.blockGasLimit); - - while (self.pending_transactions.length > 0) { - var tx = self.pending_transactions[0]; - var gasLimit = to.number(tx.gasLimit); - - if (totalGasLimit + gasLimit <= maxGasLimit) { - totalGasLimit += gasLimit; - self.pending_transactions.shift(); - currentTransactions.push(tx); - } else { - // Next one won't fit. Break. - break; - } - } - - // Remember, we ensured transactions had a valid gas limit when they were queued (in the state manager). - // If we run into a case where we can't process any because one is higher than the gas limit, - // then it's a serious issue. This should never happen, but let's check anyway. - if (currentTransactions.length === 0 && self.pending_transactions.length > 0) { - // Error like geth. - var error = "Unexpected error condition: next transaction exceeds block gas limit"; - return callback(error); - } - - // Create a new block meant to be the end of the chain - this.createBlock(function(err, block) { - if (err) { - return callback(err); - } - - // Overwrite block timestamp - if (timestamp) { - self.data.blocks.last(function(err, last) { - if (err) { - // it is safe to ignore this error as we only use the result - // to log a warning to the console. - return; - } - if (last && to.number(last.header.timestamp) > timestamp) { - self.logger.log( - "Waring: Setting the block timestamp (" + timestamp + ") that is earlier than the parent block one." - ); - } - }); - block.header.timestamp = to.hex(timestamp); - self.setTime(new Date(timestamp * 1000)); - } - // Add transactions to the block. - Array.prototype.push.apply(block.transactions, currentTransactions); - - self.processBlock(self.vm, block, true, callback); - }); -}; - -/** - * processTransactionTrace - * - * Run a previously-run transaction in the same state in which it occurred at the time it was run. - * This will return the vm-level trace output for debugging purposes. - * - * Strategy: - * - * 1. Find block where transaction occurred - * 2. Set state root of that block - * 3. Rerun every transaction in that block prior to and including the requested transaction - * 4. Send trace results back. - * - * @param {[type]} tx [description] - * @param {Function} callback [description] - * @return [type] [description] - */ -BlockchainDouble.prototype.processTransactionTrace = async function(hash, params, callback) { - const self = this; - const targetHash = to.hex(hash); - let txHashCurrentlyProcessing = ""; - let txCurrentlyProcessing = null; - let vm; - - const storageStack = { - currentDepth: -1, - stack: [] - }; - - const returnVal = { - gas: 0, - returnValue: "", - structLogs: [] - }; - - function stepListener(event, next) { - // See these docs: - // https://github.com/ethereum/go-ethereum/wiki/Management-APIs - - const gasLeft = to.number(event.gasLeft); - const totalGasUsedAfterThisStep = to.number(txCurrentlyProcessing.gasLimit) - gasLeft; - const gasUsedPreviousStep = totalGasUsedAfterThisStep - returnVal.gas; - returnVal.gas += gasUsedPreviousStep; - - let memory = null; - if (!params.disableMemory) { - // Get memory and break it up into 32-byte words. - // Note we may possibly have to pad the final word. - memory = Buffer.from(event.memory).toString("hex"); - memory = memory.match(/.{1,64}/g) || []; - - if (memory.length > 0) { - const lastItem = memory[memory.length - 1]; - if (lastItem.length < 64) { - memory[memory.length - 1] = lastItem + new Array(64 - lastItem.length + 1).join("0"); - } - } - } - - let stack = null; - if (!params.disableStack) { - stack = event.stack.map((item) => { - return to.rpcDataHexString(item, 64).replace("0x", ""); // non-0x prefixed. - }); - } - let structLog = { - depth: event.depth, - error: "", - gas: gasLeft, - gasCost: 0, - memory, - op: event.opcode.name, - pc: event.pc, - stack, - storage: null - }; - - // The gas difference calculated for each step is indicative of gas consumed in - // the previous step. Gas consumption in the final step will always be zero. - if (returnVal.structLogs.length) { - returnVal.structLogs[returnVal.structLogs.length - 1].gasCost = gasUsedPreviousStep; - } - - if (params.disableStorage) { - returnVal.structLogs.push(structLog); - next(); - } else { - structLog = self.processStorageTrace(structLog, storageStack, event, vm, function(err, structLog) { - if (err) { - return next(err); - } - returnVal.structLogs.push(structLog); - next(); - }); - } - } - - // #1 - get block via transaction receipt - this.getTransactionReceipt(targetHash, function(err, receipt) { - if (err) { - return callback(err); - } - - if (!receipt) { - return callback(new Error("Unknown transaction " + targetHash)); - } - - const targetBlock = receipt.block; - - // Get the parent of the target block - self.getBlock(targetBlock.header.parentHash, function(err, parent) { - if (err) { - return callback(err); - } - - // #2 - Set state root of original block - var stateTrie = self.createStateTrie(self.data.trie_db, parent.header.stateRoot, { - // when forking we need to make sure we also copy over the forkBlockNumber, - // otherwise some operations will request data from the main chain at the - // latest block - forkBlockNumber: to.number(parent.header.number) - }); - vm = self.createVMFromStateTrie(stateTrie); - - // Prepare the "next" block with necessary transactions - self.createBlock(parent, false, function(err, block) { - if (err) { - return callback(err); - } - - // make sure we use the same timestamp as the target block - block.header.timestamp = targetBlock.header.timestamp; - - for (var i = 0; i < targetBlock.transactions.length; i++) { - var tx = targetBlock.transactions[i]; - block.transactions.push(tx); - - // After including the target transaction, that's all we need to do. - if (to.hex(tx.hash()) === targetHash) { - break; - } - } - - function beforeTxListener(tx) { - txCurrentlyProcessing = tx; - txHashCurrentlyProcessing = to.hex(tx.hash()); - if (txHashCurrentlyProcessing === targetHash) { - vm.on("step", stepListener); - } - } - - // afterTxListener cleans up everything. - function afterTxListener() { - if (txHashCurrentlyProcessing === targetHash) { - removeListeners(); - } - } - - function removeListeners() { - vm.removeListener("step", stepListener); - vm.removeListener("beforeTx", beforeTxListener); - vm.removeListener("afterTx", afterTxListener); - } - - // Listen to beforeTx and afterTx so we know when our target transaction - // is processing. These events will add the event listener for getting the trace data. - vm.on("beforeTx", beforeTxListener); - vm.on("afterTx", afterTxListener); - - // Don't even let the vm try to flush the block's _cache to the stateTrie. - // When forking some of the data that the traced function may request will - // exist only on the main chain. Because we pretty much lie to the VM by - // telling it we DO have data in our Trie, when we really don't, it gets - // lost during the commit phase when it traverses the "borrowed" data's - // trie (as it may not have a valid root). Because this is a trace, and we - // don't need to commit the data, duck punching the `flush` method (the - // simplest method I could find) is fine. - // Remove this and you may see the infamous - // `Uncaught TypeError: Cannot read property 'pop' of undefined` error! - vm.stateManager._cache.flush = (cb) => cb(); - - // #3 - Process the block without committing the data. - self.processBlock(vm, block, false, function(err) { - // Ignore runtime errors, or else erroneous transactions can't be traced. - if (err && err.message.indexOf("VM Exception") === 0) { - err = null; - } - - // Just to be safe - removeListeners(); - - // #4 - send state results back - callback(err, returnVal); - }); - }); - }); - }); -}; - -BlockchainDouble.prototype.processStorageTrace = function(structLog, storageStack, event, vm, callback) { - var name = event.opcode.name; - - if (storageStack.currentDepth > event.depth) { - storageStack.stack.pop(); - } - if (storageStack.currentDepth < event.depth) { - storageStack.stack.push({}); - } - - storageStack.currentDepth = event.depth; - - var key; - var value; - switch (name) { - case "SSTORE": - { - const stack = event.stack; - const stackLength = stack.length; - key = to.rpcDataHexString(stack[stackLength - 1], 64).replace("0x", ""); - value = to.rpcDataHexString(stack[stackLength - 2], 64).replace("0x", ""); - // use Object.assign to prevent future steps from overwriting this step's storage values - structLog.storage = Object.assign({}, storageStack.stack[storageStack.currentDepth]); - - callback(null, structLog); - // assign after callback because this storage change actually takes - // effect _after_ this opcode executes - storageStack.stack[storageStack.currentDepth][key] = value; - } - break; - case "SLOAD": - { - const stack = event.stack; - // this one's more fun, we need to get the value the contract is loading from current storage - key = to.rpcDataHexString(stack[stack.length - 1], 64).replace("0x", ""); - - vm.stateManager.getContractStorage(event.address, Buffer.from(key, "hex"), function(err, result) { - if (err) { - return callback(err); - } - - value = to.rpcDataHexString(result, 64).replace("0x", ""); - storageStack.stack[storageStack.currentDepth][key] = value; - // use Object.assign to prevent future steps from overwriting this step's storage values - structLog.storage = Object.assign({}, storageStack.stack[storageStack.currentDepth]); - callback(null, structLog); - }); - } - break; - default: - // use Object.assign to prevent future steps from overwriting this step's storage values - structLog.storage = Object.assign({}, storageStack.stack[storageStack.currentDepth]); - callback(null, structLog); - } -}; - -BlockchainDouble.prototype.getAccount = function(address, number, callback) { - var self = this; - - this.getBlock(number, function(err, block) { - if (err) { - return callback(err); - } - - var trie = self.stateTrie; - - // Manipulate the state root in place to maintain checkpoints - var currentStateRoot = trie.root; - self.stateTrie.root = block.header.stateRoot; - - trie.get(utils.toBuffer(address), function(err, data) { - // Finally, put the stateRoot back for good - trie.root = currentStateRoot; - - if (err) { - return callback(err); - } - - var account = new Account(data); - - callback(null, account); - }); - }); -}; - -BlockchainDouble.prototype.getNonce = function(address, number, callback) { - this.getAccount(address, number, function(err, account) { - if (err) { - return callback(err); - } - callback(null, account.nonce); - }); -}; - -BlockchainDouble.prototype.getBalance = function(address, number, callback) { - this.getAccount(address, number, function(err, account) { - if (err) { - return callback(err); - } - - callback(null, account.balance); - }); -}; - -// Note! Storage values are returned RLP encoded! -BlockchainDouble.prototype.getStorage = function(address, position, number, callback) { - var self = this; - - this.getBlock(number, function(err, block) { - if (err) { - return callback(err); - } - - var trie = self.stateTrie; - - // Manipulate the state root in place to maintain checkpoints - var currentStateRoot = trie.root; - self.stateTrie.root = block.header.stateRoot; - - trie.get(utils.toBuffer(address), function(err, data) { - if (err != null) { - // Put the stateRoot back if there's an error - trie.root = currentStateRoot; - return callback(err); - } - - var account = new Account(data); - - trie.root = account.stateRoot; - - trie.get(utils.setLengthLeft(utils.toBuffer(position), 32), function(err, value) { - // Finally, put the stateRoot back for good - trie.root = currentStateRoot; - - if (err != null) { - return callback(err); - } - - callback(null, value); - }); - }); - }); -}; - -BlockchainDouble.prototype.getCode = function(address, number, callback) { - var self = this; - - this.getBlock(number, function(err, block) { - if (err) { - return callback(err); - } - - var trie = self.stateTrie; - - // Manipulate the state root in place to maintain checkpoints - var currentStateRoot = trie.root; - self.stateTrie.root = block.header.stateRoot; - - trie.get(utils.toBuffer(address), function(err, data) { - if (err != null) { - // Put the stateRoot back if there's an error - trie.root = currentStateRoot; - return callback(err); - } - - var account = new Account(data); - - account.getCode(trie, function(err, code) { - // Finally, put the stateRoot back for good - trie.root = currentStateRoot; - - if (err) { - return callback(err); - } - - callback(null, code); - }); - }); - }); -}; - -BlockchainDouble.prototype.getTransaction = function(hash, callback) { - hash = to.hex(hash); - - this.data.transactions.get(hash, function(err, tx) { - if (err) { - if (err.notFound) { - return callback(null, null); - } else { - return callback(err); - } - } - callback(null, tx); - }); -}; - -BlockchainDouble.prototype.getTransactionReceipt = function(hash, callback) { - hash = to.hex(hash); - const pendingTxs = this.pending_transactions; - - for (var i = 0; i < pendingTxs.length; i++) { - const pendingTxHash = to.hex(pendingTxs[i].hash()); - if (hash === pendingTxHash) { - return callback(null, { tx: pendingTxs[i] }); - } - } - - this.data.transactionReceipts.get(hash, function(err, receipt) { - if (err) { - if (err.notFound) { - return callback(null, null); - } else { - return callback(err); - } - } - - callback(err, receipt); - }); -}; - -BlockchainDouble.prototype.getBlockLogs = function(number, callback) { - var self = this; - this.getEffectiveBlockNumber(number, function(err, effective) { - if (err) { - return callback(err); - } - self.data.blockLogs.get(effective, callback); - }); -}; - -BlockchainDouble.prototype.getHeight = function(callback) { - this.data.blocks.length(function(err, length) { - if (err) { - return callback(err); - } - callback(null, length - 1); - }); -}; - -BlockchainDouble.prototype.currentTime = function() { - return ((new Date().getTime() / 1000) | 0) + this.timeAdjustment; -}; - -BlockchainDouble.prototype.increaseTime = function(seconds) { - if (seconds < 0) { - seconds = 0; - } - this.timeAdjustment += seconds; - return this.timeAdjustment; -}; - -BlockchainDouble.prototype.setTime = function(date) { - var now = (new Date().getTime() / 1000) | 0; - var start = (date.getTime() / 1000) | 0; - this.timeAdjustment = start - now; -}; - -BlockchainDouble.prototype.close = function(callback) { - this.data.close(callback); -}; - -module.exports = BlockchainDouble; diff --git a/lib/database.js b/lib/database.js deleted file mode 100644 index 94a5e5d635..0000000000 --- a/lib/database.js +++ /dev/null @@ -1,77 +0,0 @@ -var LevelUpArrayAdapter = require("./database/leveluparrayadapter"); -var LevelUpObjectAdapter = require("./database/levelupobjectadapter"); -var levelup = require("levelup"); -var encode = require("encoding-down"); -var filedown = require("./database/filedown"); -var cachedown = require("cachedown"); -var txserializer = require("./database/txserializer"); -var blockserializer = require("./database/blockserializer"); -var bufferserializer = require("./database/bufferserializer"); -var BlockLogsSerializer = require("./database/blocklogsserializer"); -var ReceiptSerializer = require("./database/receiptserializer"); -var tmp = require("tmp"); - -function Database(options) { - this.options = options; - this.directory = null; -} - -Database.prototype.initialize = function(callback) { - var self = this; - - function getDir(cb) { - if (self.options.db_path) { - cb(null, self.options.db_path); - } else { - tmp.dir(cb); - } - } - - getDir(function(err, directory) { - if (err) { - return callback(err); - } - const levelupOptions = { valueEncoding: "json" }; - if (self.options.db) { - const store = self.options.db; - levelup(store, levelupOptions, finishInitializing); - } else { - self.directory = directory; - const store = encode(cachedown(directory, filedown).maxSize(100), levelupOptions); - levelup(store, {}, finishInitializing); - } - }); - - function finishInitializing(err, db) { - if (err) { - return callback(err); - } - - self.db = db; - - // Blocks, keyed by array index (not necessarily by block number) (0-based) - self.blocks = new LevelUpArrayAdapter("blocks", self.db, blockserializer); - - // Logs triggered in each block, keyed by block id (ids in the blocks array; not necessarily block number) (0-based) - self.blockLogs = new LevelUpArrayAdapter("blockLogs", self.db, new BlockLogsSerializer(self)); - - // Block hashes -> block ids (ids in the blocks array; not necessarily block number) for quick lookup - self.blockHashes = new LevelUpObjectAdapter("blockHashes", self.db); - - // Transaction hash -> transaction objects - self.transactions = new LevelUpObjectAdapter("transactions", self.db, txserializer); - - // Transaction hash -> transaction receipts - self.transactionReceipts = new LevelUpObjectAdapter("transactionReceipts", self.db, new ReceiptSerializer(self)); - - self.trie_db = new LevelUpObjectAdapter("trie_db", self.db, bufferserializer, bufferserializer); - - callback(); - } -}; - -Database.prototype.close = function(callback) { - callback(); -}; - -module.exports = Database; diff --git a/lib/database/blocklogsserializer.js b/lib/database/blocklogsserializer.js deleted file mode 100644 index 5d66ac3f64..0000000000 --- a/lib/database/blocklogsserializer.js +++ /dev/null @@ -1,61 +0,0 @@ -var Log = require("../utils/log"); -var async = require("async"); - -function BlockLogsSerializer(database) { - this.database = database; -} - -BlockLogsSerializer.prototype.encode = function(logs, done) { - logs = logs.map(function(log) { - return log.toJSON(); - }); - - done(null, logs); -}; - -BlockLogsSerializer.prototype.decode = function(json, done) { - var self = this; - - if (json.length === 0) { - return done(null, []); - } - - this.database.blockHashes.get(json[0].blockHash, function(err, blockIndex) { - if (err) { - return done(err); - } - - self.database.blocks.get(blockIndex, function(err, block) { - if (err) { - return done(err); - } - - async.map( - json, - function(log, finished) { - finished( - null, - new Log({ - block: block, - logIndex: log.logIndex, - transactionIndex: log.transactionIndex, - transactionHash: log.transactionHash, - address: log.address, - data: log.data, - topics: log.topics, - type: log.type - }) - ); - }, - function(err, logs) { - if (err) { - return done(err); - } - done(null, logs); - } - ); - }); - }); -}; - -module.exports = BlockLogsSerializer; diff --git a/lib/database/blockserializer.js b/lib/database/blockserializer.js deleted file mode 100644 index 649b2a59a9..0000000000 --- a/lib/database/blockserializer.js +++ /dev/null @@ -1,49 +0,0 @@ -var txserializer = require("./txserializer"); -var async = require("async"); -var Block = require("ethereumjs-block"); - -module.exports = { - encode: function(block, done) { - var encoded = block.toJSON(true); - - async.map( - block.transactions, - function(tx, finished) { - txserializer.encode(tx, finished); - }, - function(err, transactions) { - if (err) { - return done(err); - } - encoded.transactions = transactions; - done(null, encoded); - } - ); - }, - decode: function(json, done) { - var transactions = json.transactions; - json.transactions = []; - - var block = new Block(json); - - async.eachSeries( - transactions, - function(txJson, finished) { - txserializer.decode(txJson, function(err, tx) { - if (err) { - return finished(err); - } - block.transactions.push(tx); - finished(); - }); - }, - function(err) { - if (err) { - return done(err); - } - - done(null, block); - } - ); - } -}; diff --git a/lib/database/bufferserializer.js b/lib/database/bufferserializer.js deleted file mode 100644 index 0174c99e5b..0000000000 --- a/lib/database/bufferserializer.js +++ /dev/null @@ -1,12 +0,0 @@ -var utils = require("ethereumjs-util"); -var to = require("../utils/to"); - -module.exports = { - encode: function(val, cb) { - var hex = to.hex(val); - cb(null, hex); - }, - decode: function(json, cb) { - cb(null, utils.toBuffer(json)); - } -}; diff --git a/lib/database/filedown.js b/lib/database/filedown.js deleted file mode 100644 index 5c7f195a11..0000000000 --- a/lib/database/filedown.js +++ /dev/null @@ -1,186 +0,0 @@ -var util = require("util"); -var AbstractLevelDOWN = require("abstract-leveldown").AbstractLevelDOWN; -var async = require("async"); -var fs = require("fs"); -var path = require("path"); -var tmp = require("tmp"); - -util.inherits(FileDown, AbstractLevelDOWN); - -function FileDown(location) { - this.location = location; - const tmpDir = path.join(location, "_tmp"); - try { - // Fixes https://github.com/trufflesuite/ganache/issues/1617 - fs.mkdirSync(tmpDir, { recursive: true, mode: 0o1777 }); - } catch (e) { - // `recursive` doesn't throw if the file exists, but `recursive` doesn't - // exist in Node 8, so we catch the EEXISTS error in that case and ignore it. - // once we drop node 8 suport we can remove this try/catch - if (e.code !== "EEXIST") { - throw e; - } - } - this.tmpOptions = { keep: true, dir: tmpDir }; - AbstractLevelDOWN.call(this, location); -} - -FileDown.prototype._open = function(options, callback) { - var self = this; - callback(null, self); -}; -const accessQueue = { - next: (lKey) => { - const cont = accessQueue.cache[lKey].shift(); - if (cont) { - cont(); - } else { - delete accessQueue.cache[lKey]; - } - }, - execute: (lKey, callback) => { - const cache = accessQueue.cache[lKey]; - if (cache) { - cache.push(callback); - } else { - accessQueue.cache[lKey] = []; - callback(); - } - }, - cache: {} -}; - -const fds = new Set(); -const cleanup = (exit) => { - try { - fds.forEach((fdPath) => { - const [fd, path] = fdPath; - try { - fs.closeSync(fd); - } catch (e) { - // ignore - } finally { - try { - fs.unlinkSync(path); - } catch (e) { - // ignore - } - } - }); - fds.clear(); - } finally { - if (exit) { - process.exit(0); - } - } -}; -process.on("SIGINT", cleanup); -process.on("exit", () => cleanup(false)); - -FileDown.prototype._put = function(key, value, options, callback) { - const lKey = path.join(this.location, key); - // This fixes an issue caused by writing AND reading the same key multiple times - // simultaneously. Sometimes the read operation would end up reading the value as 0 bytes - // due to the way writes are performed in node. To fix this, we implemented a queue so only a - // single read or write operation can occur at a time for each key; basically an in-memory lock. - // Additionally, during testing we found that it was possible for a write operation to fail - // due to program termination. This failure would sometimes cause the key to _exist_ but contain - // 0 bytes (which is always invalid). To fix this we write to a temp file, and only if it works - // do we move this temp file to its correct key location. This prevents early termination from - // writing partial/empty values. - // Of course, all this will eventually be for nothing as we are migrating the db to actual an - // leveldb implementation that doesn't use a separate file for every key Soon(TM). - accessQueue.execute(lKey, () => { - // get a tmp file to write the contents to... - tmp.file(this.tmpOptions, (err, path, fd, cleanupTmpFile) => { - if (err) { - callback(err); - accessQueue.next(lKey); - return; - } - const pair = [fd, path]; - fds.add(pair); - const cleanupAndCallback = (err) => { - err && cleanupTmpFile(); - fds.delete(pair); - callback(err); - accessQueue.next(lKey); - }; - - // write the value to our temporary file - fs.writeFile(fd, value, "utf8", (err) => { - if (err) { - cleanupAndCallback(err); - return; - } - - // It worked! Move the temporary file to its final destination - fs.rename(path, lKey, (err) => { - if (err) { - cleanupAndCallback(err); - return; - } - - // make sure we close this file descriptor now that the file is no - // longer "temporary" (because we successfully moved it) - fs.close(fd, () => cleanupAndCallback()); - }); - }); - }); - }); -}; - -FileDown.prototype._get = function(key, options, callback) { - const lKey = path.join(this.location, key); - accessQueue.execute(lKey, () => { - fs.readFile(lKey, "utf8", (err, data) => { - if (err) { - callback(new Error("NotFound")); - } else { - callback(null, data); - } - accessQueue.next(lKey); - }); - }); -}; - -FileDown.prototype._del = function(key, options, callback) { - fs.unlink(path.join(this.location, key), function(err) { - // Ignore when we try to delete a file that doesn't exist. - // I'm not sure why this happens. Worth looking into. - if (err) { - if (err.message.indexOf("ENOENT") >= 0) { - return callback(); - } else { - return callback(err); - } - } - callback(); - }); -}; - -FileDown.prototype._batch = function(array, options, callback) { - var self = this; - async.each( - array, - function(item, finished) { - if (item.type === "put") { - self.put(item.key, item.value, options, finished); - } else if (item.type === "del") { - self.del(item.key, options, finished); - } else { - finished(new Error("Unknown batch type", item.type)); - } - }, - function(err) { - if (err) { - return callback(err); - } - callback(); - } - ); -}; - -module.exports = function(location) { - return new FileDown(location); -}; diff --git a/lib/database/leveluparrayadapter.js b/lib/database/leveluparrayadapter.js deleted file mode 100644 index d9c2533cdc..0000000000 --- a/lib/database/leveluparrayadapter.js +++ /dev/null @@ -1,140 +0,0 @@ -var Sublevel = require("level-sublevel"); -const { LevelUpOutOfRangeError, BlockOutOfRangeError } = require("../utils/errorhelper"); - -// Level up adapter that looks like an array. Doesn't support inserts. - -function LevelUpArrayAdapter(name, db, serializer) { - this.db = Sublevel(db); - this.db = this.db.sublevel(name); - this.name = name; - this.serializer = serializer || { - encode: function(val, callback) { - callback(null, val); - }, - decode: function(val, callback) { - callback(null, val); - } - }; -} - -LevelUpArrayAdapter.prototype.length = function(callback) { - this.db.get("length", function(err, result) { - if (err) { - if (err.notFound) { - return callback(null, 0); - } else { - return callback(err); - } - } - - callback(null, result); - }); -}; - -LevelUpArrayAdapter.prototype._get = function(key, callback) { - var self = this; - this.db.get(key, function(err, val) { - if (err) { - return callback(err); - } - self.serializer.decode(val, callback); - }); -}; - -LevelUpArrayAdapter.prototype._put = function(key, value, callback) { - var self = this; - this.serializer.encode(value, function(err, encoded) { - if (err) { - return callback(err); - } - self.db.put(key, encoded, callback); - }); -}; - -LevelUpArrayAdapter.prototype.get = function(index, callback) { - var self = this; - - this.length(function(err, length) { - if (err) { - return callback(err); - } - if (index >= length) { - // index out of range - const RangeError = - self.name === "blocks" - ? new BlockOutOfRangeError(index, length) - : new LevelUpOutOfRangeError(self.name, index, length); - return callback(RangeError); - } - self._get(index, callback); - }); -}; - -LevelUpArrayAdapter.prototype.push = function(val, callback) { - var self = this; - this.length(function(err, length) { - if (err) { - return callback(err); - } - - // TODO: Do this in atomic batch. - self._put(length + "", val, function(err) { - if (err) { - return callback(err); - } - self.db.put("length", length + 1, callback); - }); - }); -}; - -LevelUpArrayAdapter.prototype.pop = function(callback) { - var self = this; - - this.length(function(err, length) { - if (err) { - return callback(err); - } - - var newLength = length - 1; - - // TODO: Do this in atomic batch. - self._get(newLength + "", function(err, val) { - if (err) { - return callback(err); - } - self.db.del(newLength + "", function(err) { - if (err) { - return callback(err); - } - self.db.put("length", newLength, function(err) { - if (err) { - return callback(err); - } - - callback(null, val); - }); - }); - }); - }); -}; - -LevelUpArrayAdapter.prototype.last = function(callback) { - var self = this; - this.length(function(err, length) { - if (err) { - return callback(err); - } - - if (length === 0) { - return callback(null, null); - } - - self._get(length - 1 + "", callback); - }); -}; - -LevelUpArrayAdapter.prototype.first = function(callback) { - this._get("0", callback); -}; - -module.exports = LevelUpArrayAdapter; diff --git a/lib/database/levelupobjectadapter.js b/lib/database/levelupobjectadapter.js deleted file mode 100644 index fb569ab9fd..0000000000 --- a/lib/database/levelupobjectadapter.js +++ /dev/null @@ -1,119 +0,0 @@ -var Sublevel = require("level-sublevel"); -var async = require("async"); - -function LevelUpObjectAdapter(name, db, valueserializer, keyserializer, options) { - this.db = Sublevel(db, options); - this.db = this.db.sublevel(name); - this.name = name; - this.valueserializer = valueserializer || { - encode: function(val, callback) { - callback(null, val); - }, - decode: function(val, callback) { - callback(null, val); - } - }; - this.keyserializer = keyserializer || { - encode: function(val, callback) { - callback(null, val); - }, - decode: function(val, callback) { - callback(null, val); - } - }; -} - -LevelUpObjectAdapter.prototype.get = function(key, options, callback) { - var self = this; - - if (typeof options === "function") { - callback = options; - options = {}; - } - - this.keyserializer.encode(key, function(err, encodedKey) { - if (err) { - return callback(err); - } - - self.db.get(encodedKey, function(err, val) { - if (err) { - return callback(err); - } - - self.valueserializer.decode(val, function(err, decodedValue) { - if (err) { - return callback(err); - } - - callback(null, decodedValue); - }); - }); - }); -}; - -LevelUpObjectAdapter.prototype.put = function(key, value, options, callback) { - var self = this; - - if (typeof options === "function") { - callback = options; - options = {}; - } - - this.keyserializer.encode(key, function(err, encodedKey) { - if (err) { - return callback(err); - } - - self.valueserializer.encode(value, function(err, encoded) { - if (err) { - return callback(err); - } - - self.db.put(encodedKey, encoded, callback); - }); - }); -}; - -LevelUpObjectAdapter.prototype.set = LevelUpObjectAdapter.prototype.put; - -LevelUpObjectAdapter.prototype.del = function(key, callback) { - var self = this; - - this.keyserializer.encode(key, function(err, encodedKey) { - if (err) { - return callback(err); - } - - self.db.del(encodedKey, callback); - }); -}; - -LevelUpObjectAdapter.prototype.batch = function(array, options, callback) { - var self = this; - - async.each( - array, - function(item, finished) { - if (item.type === "put") { - self.put(item.key, item.value, options, finished); - } else if (item.type === "del") { - self.del(item.key, finished); - } else { - finished(new Error("Unknown batch type", item.type)); - } - }, - function(err) { - if (err) { - return callback(err); - } - callback(); - } - ); -}; - -LevelUpObjectAdapter.prototype.isOpen = function() { - return true; -}; - -module.exports = LevelUpObjectAdapter; diff --git a/lib/database/levelupvalueadapter.js b/lib/database/levelupvalueadapter.js deleted file mode 100644 index 3df284f4ff..0000000000 --- a/lib/database/levelupvalueadapter.js +++ /dev/null @@ -1,50 +0,0 @@ -// Warning: Wrote this because I wanted it, then didn't need it. -// May come in handy later. You've been warned. This might be bad/dead code. -var Sublevel = require("level-sublevel"); - -function LevelUpValueAdapter(name, db, serializer) { - this.db = Sublevel(db); - this.db = this.db.sublevel(name); - this.name = name; - this.serializer = serializer || { - encode: function(val, callback) { - callback(null, val); - }, - decode: function(val, callback) { - callback(null, val); - } - }; - this.value_key = "value"; -} - -LevelUpValueAdapter.prototype.get = function(callback) { - var self = this; - - this.db.get(this.value_key, function(err, val) { - if (err) { - if (err.notFound) { - return callback(null, null); - } else { - return callback(err); - } - } - - self.serializer.decode(val, callback); - }); -}; - -LevelUpValueAdapter.prototype.set = function(value, callback) { - var self = this; - this.serializer.encode(value, function(err, encoded) { - if (err) { - return callback(err); - } - self.db.put(self.value_key, encoded, callback); - }); -}; - -LevelUpValueAdapter.prototype.del = function(callback) { - this.db.del(this.value_key, callback); -}; - -module.exports = LevelUpValueAdapter; diff --git a/lib/database/receiptserializer.js b/lib/database/receiptserializer.js deleted file mode 100644 index ca385ebab8..0000000000 --- a/lib/database/receiptserializer.js +++ /dev/null @@ -1,60 +0,0 @@ -var Receipt = require("../utils/receipt"); -var async = require("async"); - -function ReceiptSerializer(database) { - this.database = database; -} - -ReceiptSerializer.prototype.encode = function(receipt, done) { - done(null, receipt.toJSON()); -}; - -ReceiptSerializer.prototype.decode = function(json, done) { - var self = this; - // Make sure we can handle mixed/upper-case transaction hashes - // it doesn't seem possible to record a transaction hash that isn't - // already lower case, as that's the way ganache generates them, however - // I don't think it will hurt anything to normalize here anyway. - // If you can figure out how to test this please feel free to add a test! - var txHash = json.transactionHash.toLowerCase(); - - this.database.transactions.get(json.transactionHash, function(err, tx) { - if (err) { - return done(err); - } - - self.database.blockHashes.get(json.blockHash, function(err, blockIndex) { - if (err) { - return done(err); - } - - async.parallel( - { - block: self.database.blocks.get.bind(self.database.blocks, blockIndex), - logs: self.database.blockLogs.get.bind(self.database.blockLogs, blockIndex) - }, - function(err, result) { - if (err) { - return done(err); - } - - done( - null, - new Receipt( - tx, - result.block, - result.logs.filter((log) => log.transactionHash.toLowerCase() === txHash), - json.gasUsed, - json.cumulativeGasUsed, - json.contractAddress, - json.status, - json.logsBloom - ) - ); - } - ); - }); - }); -}; - -module.exports = ReceiptSerializer; diff --git a/lib/database/txserializer.js b/lib/database/txserializer.js deleted file mode 100644 index 10de485572..0000000000 --- a/lib/database/txserializer.js +++ /dev/null @@ -1,79 +0,0 @@ -// var to = require("../utils/to"); -var Transaction = require("../utils/transaction"); -var Common = require("ethereumjs-common").default; -var ethUtil = require("ethereumjs-util"); - -const decode = function(json, done) { - const options = { - hash: json.hash, - nonce: json.nonce, - value: json.value, - to: json.to, - from: json.from, - gasLimit: json.gas || json.gasLimit, - gasPrice: json.gasPrice, - data: json.data, - v: json.v, - r: json.r, - s: json.s - }; - - const sigV = ethUtil.bufferToInt(options.v); - let chainId = Math.floor((sigV - 35) / 2); - if (chainId < 0) { - chainId = 0; - } - - const commonOptions = { - name: "ganache", - chainId, - networkId: 1, - comment: "Local test network" - }; - - let hardfork = "muirGlacier"; - if (json._options) { - hardfork = json._options.hardfork; - commonOptions.chainId = json._options.chainId; - commonOptions.networkId = json._options.networkId; - } - - const common = Common.forCustomChain( - "mainnet", // TODO needs to match chain id - commonOptions, - hardfork - ); - // databases generated before ganache-core@2.3.2 didn't have a `_type` and - // and were always fake signed. So if _type is undefined it is "fake" (even - // if we have a valid signature that can generate the tx's `from`). - const type = json._type === undefined ? Transaction.types.fake : json._type; - const tx = Transaction.fromJSON(options, type, common); - - // Commenting this out because we don't want to throw if the json.hash we - // put in is different that the tx.hash() calculation we now have. There - // may have been bug fixes to the way transactions are hashed in future - // versions of ganache-core, but we still want tobe able to read in - // transactions from previously saved databases! - // if (to.hex(tx.hash()) !== json.hash) { - // const e = new Error( - // "DB consistency check: Decoded transaction hash " + - // "didn't match encoded hash. Expected: " + - // json.hash + - // "; actual: " + - // to.hex(tx.hash()) - // ); - // return done(e); - // } - - done(null, tx); -}; - -const encode = function(tx, done) { - const encoded = tx.encode(); - done(null, encoded); -}; - -module.exports = { - encode, - decode -}; diff --git a/lib/forking/forked_blockchain.js b/lib/forking/forked_blockchain.js deleted file mode 100644 index d3a87cc8e7..0000000000 --- a/lib/forking/forked_blockchain.js +++ /dev/null @@ -1,1004 +0,0 @@ -var BlockchainDouble = require("../blockchain_double.js"); -var Account = require("ethereumjs-account").default; -var Block = require("ethereumjs-block"); -var Log = require("../utils/log.js"); -var Receipt = require("../utils/receipt.js"); -var utils = require("ethereumjs-util"); -var ForkedStorageTrie = require("./forked_storage_trie.js"); -var Web3 = require("web3"); -var to = require("../utils/to.js"); -var Transaction = require("../utils/transaction"); -var async = require("async"); -var LRUCache = require("lru-cache"); -const Sublevel = require("level-sublevel"); -const BN = utils.BN; - -var inherits = require("util").inherits; - -inherits(ForkedBlockchain, BlockchainDouble); - -const httpReg = /^https?:/i; -const protocolReg = /^[A-Za-z][A-Za-z0-9+\-.]*:/; -const validProtocolReg = /^(?:http|ws)s?:/i; -const blockNumberReg = /@([0-9]+)$/; - -function cloneWithoutId(obj) { - return Object.assign({}, obj, { id: null }); -} - -function ForkedBlockchain(options) { - this.options = options || {}; - - if (options.fork == null || (typeof options.fork === "string" && options.fork.trim().length === 0)) { - throw new Error("ForkedBlockchain must be passed a fork parameter."); - } - - this.forkVersion = null; - - if (typeof options.fork === "string") { - const blockNumber = blockNumberReg.exec(options.fork); - - if (blockNumber) { - options.fork = options.fork.slice(0, blockNumber.index); - options.fork_block_number = parseInt(blockNumber[1], 10); - } - - let fork; - if (!protocolReg.test(options.fork)) { - // we don't have a protocol at all, assume ws - options.fork = "ws://" + options.fork; - fork = new Web3.providers.WebsocketProvider(options.fork); - } else if (validProtocolReg.test(options.fork)) { - if (httpReg.test(options.fork)) { - fork = new Web3.providers.HttpProvider(options.fork); - } else { - fork = new Web3.providers.WebsocketProvider(options.fork); - } - } else { - throw new Error(`Invalid scheme for fork url: ${options.fork}. Supported schemes are: http, https, ws, and wss.`); - } - - this.fork = fork; - } else { - this.fork = options.fork; - } - - this.forkBlockNumber = options.fork_block_number; - this.forkCacheSize = parseInt(options.forkCacheSize); - - // if forkCacheSize is `0`, it means it is "off" - if (!isNaN(this.forkCacheSize) && this.forkCacheSize !== 0) { - const send = this.fork.send; - const cache = new LRUCache({ - // `-1` means `Infinity`, which is represented by `0` in LRUCache's options - max: this.forkCacheSize === -1 ? 0 : this.forkCacheSize, - length: (bufValue, strKey) => { - // compute the rough byte size of the stored key + value - return Buffer.byteLength(bufValue) + Buffer.byteLength(strKey, "utf8"); - } - }); - - // Patch the `send` method of the underlying fork provider. We can - // simply cache every non-error result because all requests to the - // fork should be deterministic. - const pendingRequests = new Map(); - this.fork.send = (payload, callback) => { - let payloads; - const sendArray = Array.isArray(payload); - if (sendArray) { - payloads = payload; - } else { - payloads = [payload]; - } - Promise.all( - payloads.map(async(payload) => { - const key = JSON.stringify(cloneWithoutId(payload)); - let pendingRequest = pendingRequests.get(key); - // if a request is in flight just wait for it instead of sending another - // note: web3 actually polls on `.send`, resending the `payload`, so don't wait - // if the new `payload` is the same as the `pendingRequest`. - if (pendingRequest && pendingRequest.payload !== payload) { - await pendingRequest.promise; - } - - const cachedItem = cache.get(key); - if (cachedItem) { - const result = JSON.parse(cachedItem.toString()); - result.id = payload.id; - return Promise.resolve({ error: null, result }); - } else { - const promise = new Promise((resolve) => { - send.call(this.fork, payload, (error, result) => { - if (!error) { - cache.set(key, Buffer.from(JSON.stringify(cloneWithoutId(result)))); - } - resolve({ error, result }); - }); - }); - pendingRequest = { - payload, - promise - }; - - pendingRequests.set(key, pendingRequest); - // Node 8 doesn't have Promise.finally - promise - .catch(() => {}) - .then(() => { - pendingRequests.delete(key); - }); - return promise; - } - }) - ).then((errResults) => { - if (!sendArray) { - const errResult = errResults[0]; - callback(errResult.error, errResult.result); - } else { - let hasError = false; - const errors = []; - const results = []; - errResults.forEach(({ error, result }) => { - if (error) { - hasError = true; - } - errors.push(error); - results.push(result); - }); - callback(hasError ? errors : null, results); - } - }); - }; - } - - this.time = options.time; - this.storageTrieCache = {}; - - BlockchainDouble.call(this, options); - - this.createVMFromStateTrie = function() { - var vm = BlockchainDouble.prototype.createVMFromStateTrie.apply(this, arguments); - this.patchVM(vm); - return vm; - }; - - this.web3 = new Web3(this.fork); - this._touchedKeys = []; -} - -ForkedBlockchain.prototype.initialize = async function(accounts, callback) { - try { - const forkVersion = await new Promise((resolve, reject) => { - this.web3.eth.net.getId((err, version) => { - if (err) { - if (this.options.network_id) { - resolve(this.options.network_id); - } else { - Error.captureStackTrace(err); - err.message = `The fork provider errored when checking net_version: ${err.message}`; - reject(err); - } - } else { - resolve(version); - } - }); - }); - - this.forkVersion = forkVersion; - - const forkBlock = (this.forkBlock = await new Promise((resolve, reject) => { - const queriedBlock = this.forkBlockNumber || "latest"; - this.web3.eth.getBlock(queriedBlock, (err, json) => { - if (err) { - Error.captureStackTrace(err); - err.message = - `The fork provider errored when checking for block '${ - queriedBlock - }': ${err.message}`; - reject(err); - } else { - resolve(json); - } - }); - })); - - // If no start time was passed, set the time to where we forked from. - // We only want to do this if a block was explicitly passed. If a block - // number wasn't passed, then we're using the last block and the current time. - if (!this.time && this.forkBlockNumber) { - this.time = this.options.time = new Date(to.number(forkBlock.timestamp) * 1000); - this.setTime(this.time); - } - - this.forkBlockNumber = this.options.fork_block_number = forkBlock.number; - this.forkBlockHash = forkBlock.hash; - - // Fetch the nonce for all the accounts before we prime them in our state manager. - // This is necessary to prevent conflicting contract deployments. - const nonces = await Promise.all( - accounts.map((account) => { - return new Promise((resolve, reject) => { - this.web3.eth.getTransactionCount(account.address, this.forkBlockNumber, (err, nonce) => { - if (err) { - Error.captureStackTrace(err); - err.message = - `The fork provider errored when checking the nonce for account ${ - account.address - }: ${err.message}`; - reject(err); - } else { - resolve(nonce); - } - }); - }); - }) - ); - - nonces.forEach((nonce, index) => { - accounts[index].account.nonce = nonce; - }); - - BlockchainDouble.prototype.initialize.call(this, accounts, callback); - } catch (err) { - callback(err); - } -}; - -ForkedBlockchain.prototype.patchVM = function(vm) { - const trie = vm.stateManager._trie; - const lookupAccount = this.getLookupAccount(trie); - // Unfortunately forking requires a bit of monkey patching, but it gets the job done. - vm.stateManager._cache._lookupAccount = lookupAccount; - vm.stateManager._lookupStorageTrie = this.getLookupStorageTrie(trie, lookupAccount); -}; - -/** - * @param db - * @param root - * @param options Allows overriding the options passed to the ForkedStorageTrie, - * like `forkBlockNumber` (required for tracing transactions) - */ -ForkedBlockchain.prototype.createStateTrie = function(db, root, options) { - options = Object.assign( - { - fork: this.fork, - forkBlockNumber: this.forkBlockNumber, - blockchain: this - }, - options - ); - // never allow the forkBlockNumber to go beyond our root forkBlockNumber - if (options.forkBlockNumber > this.forkBlockNumber) { - options.forkBlockNumber = this.forkBlockNumber; - } - return new ForkedStorageTrie(db, root, options); -}; - -ForkedBlockchain.prototype.createGenesisBlock = function(callback) { - const forkBlock = this.forkBlock; - - this.createBlock(function(err, block) { - if (err) { - return callback(err); - } - - block.header.number = forkBlock.number + 1; - block.header.parentHash = forkBlock.hash; - - callback(null, block); - }); -}; - -ForkedBlockchain.prototype.getLookupStorageTrie = function(stateTrie, lookupAccount) { - lookupAccount = lookupAccount || this.getLookupAccount(stateTrie); - return (address, callback) => { - const storageTrie = stateTrie.copy(); - storageTrie.address = address; - lookupAccount(address, (err, account) => { - if (err) { - return callback(err); - } - - storageTrie.root = account.stateRoot; - callback(null, storageTrie); - }); - }; -}; - -ForkedBlockchain.prototype.isFallbackBlock = function(value, callback) { - var self = this; - - self.getEffectiveBlockNumber(value, function(err, number) { - if (err) { - return callback(err); - } - - callback(null, number <= to.number(self.forkBlockNumber)); - }); -}; - -ForkedBlockchain.prototype.isBlockHash = function(value) { - const isHash = typeof value === "string" && value.indexOf("0x") === 0 && value.length > 42; - return isHash || (Buffer.isBuffer(value) && value.byteLength > 20); -}; - -ForkedBlockchain.prototype.isFallbackBlockHash = function(value, callback) { - var self = this; - - if (!this.isBlockHash(value)) { - return callback(null, false); - } - - if (Buffer.isBuffer(value)) { - value = to.hex(value); - } - - self.data.blockHashes.get(value, function(err, blockIndex) { - if (err) { - if (err.notFound) { - // If the block isn't found in our database, then it must be a fallback block. - return callback(null, true); - } else { - return callback(err); - } - } - callback(null, false); - }); -}; - -ForkedBlockchain.prototype.getFallbackBlock = function(numberOrHash, cb) { - var self = this; - - if (Buffer.isBuffer(numberOrHash)) { - // When tracing a transaction the VM sometimes ask for a block numbers as - // buffers instead of numbers. - numberOrHash = to.rpcDataHexString(numberOrHash); - } - if (typeof numberOrHash === "string" && numberOrHash.length !== 66) { - numberOrHash = to.number(numberOrHash); - } - - self.web3.eth.getBlock(numberOrHash, true, function(err, json) { - if (err) { - return cb(err); - } - - if (json == null) { - return cb(new Error("Block not found")); - } - - var block = new Block(); - - block.header.parentHash = utils.toBuffer(json.parentHash); - block.header.uncleHash = utils.toBuffer(json.sha3Uncles); - block.header.coinbase = utils.toBuffer(json.miner); - block.header.stateRoot = utils.toBuffer(json.stateRoot); // Should we include the following three? - block.header.transactionsTrie = utils.toBuffer(json.transactionsRoot); - block.header.receiptTrie = utils.toBuffer(json.receiptsRoot); - block.header.bloom = utils.toBuffer(json.logsBloom); - block.header.difficulty = utils.toBuffer("0x" + json.totalDifficulty.toString(16)); // BigNumber - block.header.number = utils.toBuffer(json.number); - block.header.gasLimit = utils.toBuffer(json.gasLimit); - block.header.gasUsed = utils.toBuffer(json.gasUsed); - block.header.timestamp = utils.toBuffer(json.timestamp); - block.header.extraData = utils.toBuffer(json.extraData); - - (json.transactions || []).forEach(function(txJson, index) { - block.transactions.push( - Transaction.fromJSON(txJson, Transaction.types.real, null, self.forkVersion, self.options.hardfork) - ); - }); - - // Fake block. Let's do the worst. - // TODO: Attempt to fill out all block data so as to produce the same hash! (can we?) - block.hash = function() { - return utils.toBuffer(json.hash); - }; - - cb(null, block); - }); -}; - -ForkedBlockchain.prototype.getBlock = function(number, callback) { - let checkFn; - const isBlockHash = this.isBlockHash(number); - if (isBlockHash) { - checkFn = this.isFallbackBlockHash; - } else { - checkFn = this.isFallbackBlock; - } - checkFn.call(this, number, (err, isFallback) => { - if (err) { - return callback(err); - } - if (isFallback) { - return this.getFallbackBlock(number, callback); - } - - const getBlock = BlockchainDouble.prototype.getBlock.bind(this); - if (isBlockHash) { - getBlock(number, callback); - } else { - this.getRelativeBlockNumber(number, (err, number) => { - if (err) { - return callback(err); - } - getBlock(number, callback); - }); - } - }); -}; - -ForkedBlockchain.prototype.getStorage = function(address, key, number, callback) { - var self = this; - - this.getEffectiveBlockNumber(number, (err, blockNumber) => { - if (err) { - return callback(err); - } - - if (blockNumber > self.forkBlockNumber) { - // we should have this block - - self.getBlock(blockNumber, function(err, block) { - if (err) { - return callback(err); - } - - const trie = self.stateTrie; - - // Manipulate the state root in place to maintain checkpoints - const currentStateRoot = trie.root; - self.stateTrie.root = block.header.stateRoot; - - self.getLookupStorageTrie(self.stateTrie)(address, (err, trie) => { - if (err) { - return callback(err); - } - - trie.get(utils.setLengthLeft(utils.toBuffer(key), 32), function(err, value) { - // Finally, put the stateRoot back for good - trie.root = currentStateRoot; - - if (err != null) { - return callback(err); - } - - callback(null, value); - }); - }); - }); - } else { - // we're looking for something prior to forking, so let's - // hit eth_getStorageAt - self.web3.eth.getStorageAt(to.rpcDataHexString(address), to.rpcDataHexString(key), blockNumber, function( - err, - value - ) { - if (err) { - return callback(err); - } - - value = utils.rlp.encode(value); - - callback(null, value); - }); - } - }); -}; - -ForkedBlockchain.prototype.getCode = function(address, number, callback) { - var self = this; - - if (typeof number === "function") { - callback = number; - number = "latest"; - } - - if (!number) { - number = "latest"; - } - - this.getEffectiveBlockNumber(number, function(err, effective) { - if (err) { - return callback(err); - } - number = effective; - - self.stateTrie.getTouchedAt(address, (err, touchedAt) => { - if (err) { - return callback(err); - } - - if (typeof touchedAt !== "undefined" && touchedAt <= number) { - BlockchainDouble.prototype.getCode.call(self, address, number, callback); - } else { - if (number > to.number(self.forkBlockNumber)) { - number = "latest"; - } - - self.fetchCodeFromFallback(address, number, function(err, code) { - if (code) { - code = utils.toBuffer(code); - } - callback(err, code); - }); - } - }); - }); -}; - -ForkedBlockchain.prototype.getLookupAccount = function(trie) { - return (address, callback) => { - // If the account doesn't exist in our state trie, get it off the wire. - trie.keyExists(address, (err, exists) => { - if (err) { - return callback(err); - } - if (exists) { - trie.get(address, (err, data) => { - if (err) { - return callback(err); - } - const account = new Account(data); - callback(null, account); - }); - } else { - this.fetchAccountFromFallback(address, to.number(trie.forkBlockNumber), callback); - } - }); - }; -}; - -ForkedBlockchain.prototype.getAccount = function(address, number, callback) { - var self = this; - - if (typeof number === "function") { - callback = number; - number = "latest"; - } - - this.getEffectiveBlockNumber(number, function(err, effective) { - if (err) { - return callback(err); - } - number = effective; - - // If the account doesn't exist in our state trie, get it off the wire. - self.stateTrie.keyExists(address, function(err, exists) { - if (err) { - return callback(err); - } - - if (exists && number > to.number(self.forkBlockNumber)) { - BlockchainDouble.prototype.getAccount.call(self, address, number, function(err, acc) { - if (err) { - return callback(err); - } - callback(null, acc); - }); - } else { - self.fetchAccountFromFallback(address, number, callback); - } - }); - }); -}; - -ForkedBlockchain.prototype.getTransaction = function(hash, callback) { - var self = this; - BlockchainDouble.prototype.getTransaction.call(this, hash, function(err, tx) { - if (err) { - return callback(err); - } - if (tx != null) { - return callback(null, tx); - } - - self.web3.eth.getTransaction(hash, function(err, result) { - if (err) { - return callback(err); - } - - if (result) { - result = Transaction.fromJSON(result, Transaction.types.signed, null, self.forkVersion, self.options.hardfork); - } - - callback(null, result); - }); - }); -}; - -ForkedBlockchain.prototype.getTransactionReceipt = function(hash, callback) { - var self = this; - BlockchainDouble.prototype.getTransactionReceipt.call(this, hash, function(err, receipt) { - if (err) { - return callback(err); - } - if (receipt) { - return callback(null, receipt); - } - - self.web3.eth.getTransactionReceipt(hash, function(err, receiptJson) { - if (err) { - return callback(err); - } - if (!receiptJson) { - return callback(); - } - - async.parallel( - { - tx: self.getTransaction.bind(self, hash), - block: self.getBlock.bind(self, receiptJson.blockNumber) - }, - function(err, result) { - if (err) { - return callback(err); - } - - var logs = receiptJson.logs.map(function(log) { - log.block = result.block; - return new Log(log); - }); - - var receipt = new Receipt( - result.tx, - result.block, - logs, - receiptJson.gasUsed, - receiptJson.cumulativeGasUsed, - receiptJson.contractAddress, - receiptJson.status, - to.hex(receiptJson.logsBloom) - ); - - callback(null, receipt); - } - ); - }); - }); -}; - -ForkedBlockchain.prototype.fetchAccountFromFallback = function(address, blockNumber, callback) { - var self = this; - address = to.hex(address); - - async.parallel( - { - code: this.fetchCodeFromFallback.bind(this, address, blockNumber), - balance: this.fetchBalanceFromFallback.bind(this, address, blockNumber), - nonce: this.fetchNonceFromFallback.bind(this, address, blockNumber) - }, - function(err, results) { - if (err) { - return callback(err); - } - - var code = results.code; - var balance = results.balance; - var nonce = results.nonce; - - var account = new Account({ - nonce: nonce, - balance: balance - }); - - // This puts the code on the trie, keyed by the hash of the code. - // It does not actually link an account to code in the trie. - account.setCode(self.stateTrie, utils.toBuffer(code), function(err) { - if (err) { - return callback(err); - } - callback(null, account); - }); - } - ); -}; - -ForkedBlockchain.prototype.fetchCodeFromFallback = function(address, blockNumber, callback) { - var self = this; - address = to.hex(address); - - // Allow an optional blockNumber - if (typeof blockNumber === "function") { - callback = blockNumber; - blockNumber = this.forkBlockNumber; - } - - this.getSafeFallbackBlockNumber(blockNumber, function(err, safeBlockNumber) { - if (err) { - return callback(err); - } - - self.web3.eth.getCode(address, safeBlockNumber, function(err, code) { - if (err) { - return callback(err); - } - - code = "0x" + utils.toBuffer(code).toString("hex"); - callback(null, code); - }); - }); -}; - -ForkedBlockchain.prototype.fetchBalanceFromFallback = function(address, blockNumber, callback) { - var self = this; - address = to.hex(address); - - // Allow an optional blockNumber - if (typeof blockNumber === "function") { - callback = blockNumber; - blockNumber = this.forkBlockNumber; - } - - this.getSafeFallbackBlockNumber(blockNumber, function(err, safeBlockNumber) { - if (err) { - return callback(err); - } - - self.web3.eth.getBalance(address, safeBlockNumber, function(err, balance) { - if (err) { - return callback(err); - } - - balance = "0x" + new BN(balance).toString(16); - callback(null, balance); - }); - }); -}; - -ForkedBlockchain.prototype.fetchNonceFromFallback = function(address, blockNumber, callback) { - var self = this; - address = to.hex(address); - - // Allow an optional blockNumber - if (typeof blockNumber === "function") { - callback = blockNumber; - blockNumber = this.forkBlockNumber; - } - - this.getSafeFallbackBlockNumber(blockNumber, function(err, safeBlockNumber) { - if (err) { - return callback(err); - } - - self.web3.eth.getTransactionCount(address, safeBlockNumber, function(err, nonce) { - if (err) { - return callback(err); - } - - nonce = "0x" + self.web3.utils.toBN(nonce).toString(16); - callback(null, nonce); - }); - }); -}; - -ForkedBlockchain.prototype.getHeight = function(callback) { - this.latestBlock(function(err, block) { - if (err) { - return callback(err); - } - callback(null, to.number(block.header.number)); - }); -}; - -ForkedBlockchain.prototype.getRelativeBlockNumber = function(number, callback) { - var self = this; - this.getEffectiveBlockNumber(number, function(err, effective) { - if (err) { - return callback(err); - } - callback(null, effective - to.number(self.forkBlockNumber) - 1); - }); -}; - -ForkedBlockchain.prototype.getSafeFallbackBlockNumber = function(blockNumber, callback) { - var forkBlockNumber = to.number(this.forkBlockNumber); - - if (blockNumber == null) { - return callback(null, forkBlockNumber); - } - - this.getEffectiveBlockNumber(blockNumber, function(err, effective) { - if (err) { - return callback(err); - } - if (effective > forkBlockNumber) { - effective = forkBlockNumber; - } - - callback(null, effective); - }); -}; - -ForkedBlockchain.prototype.getBlockLogs = function(number, callback) { - var self = this; - - this.getEffectiveBlockNumber(number, function(err, effective) { - if (err) { - return callback(err); - } - - self.getRelativeBlockNumber(effective, function(err, relative) { - if (err) { - return callback(err); - } - - if (relative < 0) { - self.getBlock(number, function(err, block) { - if (err) { - return callback(err); - } - - self.web3.currentProvider.send( - { - jsonrpc: "2.0", - method: "eth_getLogs", - params: [ - { - fromBlock: to.hex(number), - toBlock: to.hex(number) - } - ], - id: new Date().getTime() - }, - function(err, res) { - if (err) { - return callback(err); - } - - var logs = res.result.map(function(log) { - // To make this result masquerade as the right information. - log.block = block; - return new Log(log); - }); - - callback(null, logs); - } - ); - }); - } else { - BlockchainDouble.prototype.getBlockLogs.call(self, relative, callback); - } - }); - }); -}; - -ForkedBlockchain.prototype.getQueuedNonce = function(address, callback) { - var nonce = null; - var addressBuffer = to.buffer(address); - this.pending_transactions.forEach(function(tx) { - if (!tx.from.equals(addressBuffer)) { - return; - } - - var pendingNonce = new BN(tx.nonce); - // If this is the first queued nonce for this address we found, - // or it's higher than the previous highest, note it. - if (nonce === null || pendingNonce.gt(nonce)) { - nonce = pendingNonce; - } - }); - - // If we found a queued transaction nonce, return one higher - // than the highest we found - if (nonce != null) { - return callback(null, nonce.iaddn(1).toArrayLike(Buffer)); - } - this.getLookupAccount(this.stateTrie)(addressBuffer, function(err, account) { - if (err) { - return callback(err); - } - - // nonces are initialized as an empty buffer, which isn't what we want. - callback(null, account.nonce.length === 0 ? Buffer.from([0]) : account.nonce); - }); -}; - -ForkedBlockchain.prototype.processCall = function(tx, blockNumber, callback) { - const self = this; - - this.getEffectiveBlockNumber(blockNumber, function(err, effectiveBlockNumber) { - if (err) { - return callback(err); - } - - if (effectiveBlockNumber > self.forkBlockNumber) { - BlockchainDouble.prototype.processCall.call(self, tx, blockNumber, callback); - } else { - self.web3.eth.call({ - from: to.rpcDataHexString(tx.from), - to: to.nullableRpcDataHexString(tx.to), - data: to.rpcDataHexString(tx.data) - }, effectiveBlockNumber, function(err, result) { - if (err) { - return callback(err); - } - - callback(null, { - execResult: { - returnValue: result - } - }); - }); - } - }); -}; - -ForkedBlockchain.prototype.processBlock = async function(vm, block, commit, callback) { - const self = this; - - self._touchedKeys = []; - BlockchainDouble.prototype.processBlock.call(self, vm, block, commit, callback); -}; - -ForkedBlockchain.prototype.putBlock = function(block, logs, receipts, callback) { - const self = this; - const touched = Sublevel(self.data.trie_db).sublevel("touched"); - const blockKey = `block-${to.number(block.header.number)}`; - - BlockchainDouble.prototype.putBlock.call(self, block, logs, receipts, function(err, result) { - if (err) { - return callback(err); - } - - touched.put(blockKey, JSON.stringify(self._touchedKeys), (err) => { - if (err) { - return callback(err); - } - - callback(null, result); - }); - }); -}; - -ForkedBlockchain.prototype.popBlock = function(callback) { - const self = this; - const touched = Sublevel(this.data.trie_db).sublevel("touched"); - - this.data.blocks.last(function(err, block) { - if (err) { - return callback(err); - } - if (block == null) { - return callback(null, null); - } - - const blockKey = `block-${to.number(block.header.number)}`; - touched.get(blockKey, function(err, value) { - if (err) { - return callback(err); - } - - const touchedKeys = value ? JSON.parse(value) : []; - async.eachSeries( - touchedKeys, - function(touchedKey, finished) { - touched.del(touchedKey, finished); - }, - function(err) { - if (err) { - return callback(err); - } - - touched.del(blockKey, function(err) { - if (err) { - return callback(err); - } - - BlockchainDouble.prototype.popBlock.call(self, callback); - }); - } - ); - }); - }); -}; - -ForkedBlockchain.prototype.close = function(callback) { - if (this.fork.disconnect) { - this.fork.disconnect(); - } - BlockchainDouble.prototype.close.call(this, callback); -}; - -module.exports = ForkedBlockchain; diff --git a/lib/forking/forked_storage_trie.js b/lib/forking/forked_storage_trie.js deleted file mode 100644 index acdde00036..0000000000 --- a/lib/forking/forked_storage_trie.js +++ /dev/null @@ -1,165 +0,0 @@ -const Sublevel = require("level-sublevel"); -const MerklePatriciaTree = require("merkle-patricia-tree"); -const BaseTrie = require("merkle-patricia-tree/baseTrie"); -const checkpointInterface = require("merkle-patricia-tree/checkpoint-interface"); -var utils = require("ethereumjs-util"); -var inherits = require("util").inherits; -var Web3 = require("web3"); -var to = require("../utils/to.js"); - -inherits(ForkedStorageBaseTrie, BaseTrie); - -function ForkedStorageBaseTrie(db, root, options) { - BaseTrie.call(this, db, root); - this._touched = Sublevel(this.db).sublevel("touched"); - - this.options = options; - this.address = options.address; - this.forkBlockNumber = options.forkBlockNumber; - this.blockchain = options.blockchain; - this.fork = options.fork; - this.web3 = new Web3(this.fork); - this.persist = typeof options.persist === "undefined" ? true : options.persist; -} - -// Note: This overrides a standard method whereas the other methods do not. -ForkedStorageBaseTrie.prototype.get = function(key, callback) { - var self = this; - - key = utils.toBuffer(key); - - self.keyExists(key, function(err, keyExists) { - if (err) { - return callback(err); - } - - self.getTouchedAt(key, function(err, touchedAt) { - if (err) { - return callback(err); - } - - if (keyExists && typeof touchedAt !== "undefined") { - MerklePatriciaTree.prototype.get.call(self, key, function(err, r) { - callback(err, r); - }); - } else { - // If this is the main trie, get the whole account. - if (self.address == null) { - self.blockchain.fetchAccountFromFallback(key, self.forkBlockNumber, function(err, account) { - if (err) { - return callback(err); - } - - callback(null, account.serialize()); - }); - } else { - self.web3.eth.getStorageAt( - to.rpcDataHexString(self.address), - to.rpcDataHexString(key), - self.forkBlockNumber, - function(err, value) { - if (err) { - return callback(err); - } - - value = to.rpcQuantityBuffer(value); - - callback(null, value); - } - ); - } - } - }); - }); -}; - -ForkedStorageBaseTrie.prototype.keyExists = function(key, callback) { - key = utils.toBuffer(key); - this.findPath(key, (err, node, remainder, stack) => { - const exists = node && remainder.length === 0; - callback(err, exists); - }); -}; - -ForkedStorageBaseTrie.prototype.touch = function(key, callback) { - if (!this.persist) { - return callback(); - } - - const self = this; - let rpcKey = to.rpcDataHexString(key); - if (this.address) { - rpcKey = `${to.rpcDataHexString(this.address)};${rpcKey}`; - } - rpcKey = rpcKey.toLowerCase(); - - this._touched.get(rpcKey, (err, result) => { - if (err && err.type !== "NotFoundError") { - return callback(err); - } - - if (typeof result === "undefined") { - // key doesn't exist - this.blockchain.data.blocks.last((err, lastBlock) => { - if (err) { - return callback(err); - } - - const number = lastBlock === null ? self.forkBlockNumber : to.number(lastBlock.header.number); - this._touched.put(rpcKey, number + 1); - this.blockchain._touchedKeys.push(rpcKey); - callback(); - }); - } else { - callback(); - } - }); -}; - -const originalPut = ForkedStorageBaseTrie.prototype.put; -ForkedStorageBaseTrie.prototype.put = function(key, value, callback) { - const self = this; - this.touch(key, function(err) { - if (err) { - return callback(err); - } - - originalPut.call(self, key, value, callback); - }); -}; - -ForkedStorageBaseTrie.prototype.getTouchedAt = function(key, callback) { - let rpcKey = to.rpcDataHexString(key); - if (this.address) { - rpcKey = `${to.rpcDataHexString(this.address)};${rpcKey}`; - } - rpcKey = rpcKey.toLowerCase(); - - this._touched.get(rpcKey, function(err, result) { - if (err && err.type !== "NotFoundError") { - return callback(err); - } - - callback(null, result); - }); -}; - -ForkedStorageBaseTrie.prototype.del = function(key, callback) { - this.put(key, 0, callback); -}; - -ForkedStorageBaseTrie.prototype.copy = function() { - return new ForkedStorageBaseTrie(this.db, this.root, this.options); -}; - -inherits(ForkedStorageTrie, ForkedStorageBaseTrie); - -function ForkedStorageTrie(db, root, options) { - ForkedStorageBaseTrie.call(this, db, root, options); - checkpointInterface.call(this, this); -} - -ForkedStorageTrie.prove = MerklePatriciaTree.prove; -ForkedStorageTrie.verifyProof = MerklePatriciaTree.verifyProof; - -module.exports = ForkedStorageTrie; diff --git a/lib/httpServer.js b/lib/httpServer.js deleted file mode 100644 index fac6de2390..0000000000 --- a/lib/httpServer.js +++ /dev/null @@ -1,127 +0,0 @@ -const http = require("http"); -const { rpcError } = require("./utils/to"); - -const hasOwnProperty = Object.prototype.hasOwnProperty; - -function createCORSResponseHeaders(method, requestHeaders) { - // https://fetch.spec.whatwg.org/#http-requests - const headers = {}; - const isCORSRequest = hasOwnProperty.call(requestHeaders, "origin"); - if (isCORSRequest) { - // OPTIONS preflight requests need a little extra treatment - if (method === "OPTIONS") { - // we only allow POST requests, so it doesn't matter which method the request is asking for - headers["Access-Control-Allow-Methods"] = "POST"; - // echo all requested access-control-request-headers back to the response - if (hasOwnProperty.call(requestHeaders, "access-control-request-headers")) { - headers["Access-Control-Allow-Headers"] = requestHeaders["access-control-request-headers"]; - } - // Safari needs Content-Length = 0 for a 204 response otherwise it hangs forever - // https://github.com/expressjs/cors/pull/121#issue-130260174 - headers["Content-Length"] = 0; - - // Make browsers and compliant clients cache the OPTIONS preflight response for 10 - // minutes (this is the maximum time Chromium allows) - headers["Access-Control-Max-Age"] = 600; // seconds - } - - // From the spec: https://fetch.spec.whatwg.org/#http-responses - // "For a CORS-preflight request, request’s credentials mode is always "omit", - // but for any subsequent CORS requests it might not be. Support therefore - // needs to be indicated as part of the HTTP response to the CORS-preflight request as well.", so this - // header is added to all requests. - // Additionally, https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials, - // states that there aren't any HTTP Request headers that indicate you whether or not Request.withCredentials - // is set. Because web3@1.0.0-beta.35-? always sets `request.withCredentials = true` while Safari requires it be - // returned even when no credentials are set in the browser this header must always be return on all requests. - // (I've found that Chrome and Firefox don't actually require the header when credentials aren't set) - // Regression Commit: https://github.com/ethereum/web3.js/pull/1722 - // Open Web3 Issue: https://github.com/ethereum/web3.js/issues/1802 - headers["Access-Control-Allow-Credentials"] = true; - - // From the spec: "It cannot be reliably identified as participating in the CORS protocol - // as the `Origin` header is also included for all requests whose method is neither - // `GET` nor `HEAD`." - // Explicitly set the origin instead of using *, since credentials - // can't be used in conjunction with *. This will always be set - /// for valid preflight requests. - headers["Access-Control-Allow-Origin"] = requestHeaders.origin; - } - return headers; -} - -function sendResponse(response, statusCode, headers, data) { - response.writeHead(statusCode, headers); - response.end(data); -} - -module.exports = function(provider, logger) { - var server = http.createServer(function(request, response) { - var method = request.method; - var body = []; - - request - .on("data", function(chunk) { - body.push(chunk); - }) - .on("end", function() { - body = Buffer.concat(body).toString(); - // At this point, we have the headers, method, url and body, and can now - // do whatever we need to in order to respond to this request. - - const headers = createCORSResponseHeaders(method, request.headers); - - switch (method) { - case "POST": - var payload; - try { - payload = JSON.parse(body); - } catch (e) { - headers["Content-Type"] = "text/plain"; - sendResponse(response, 400, headers, "400 Bad Request"); - return; - } - - // Log messages that come into the TestRPC via http - if (payload instanceof Array) { - // Batch request - for (var i = 0; i < payload.length; i++) { - var item = payload[i]; - logger.log(item.method); - } - } else { - console.log(""); - console.log(""); - console.log("------------------"); - logger.log(payload.method); - console.log(payload); - console.log("------------------"); - } - - // http connections do not support subscriptions - if (payload.method === "eth_subscribe" || payload.method === "eth_unsubscribe") { - headers["Content-Type"] = "application/json"; - sendResponse(response, 400, headers, rpcError(payload.id, -32000, "notifications not supported")); - break; - } - - provider.send(payload, function(_, result) { - headers["Content-Type"] = "application/json"; - sendResponse(response, 200, headers, JSON.stringify(result)); - }); - - break; - case "OPTIONS": - sendResponse(response, 204, headers); - break; - default: - headers["Content-Type"] = "text/plain"; - sendResponse(response, 400, headers, "400 Bad Request"); - break; - } - }); - }); - - server.ganacheProvider = provider; - return server; -}; diff --git a/lib/provider.js b/lib/provider.js deleted file mode 100644 index 05882b9302..0000000000 --- a/lib/provider.js +++ /dev/null @@ -1,267 +0,0 @@ -// make sourcemaps work! -require("source-map-support/register"); - -const ProviderEngine = require("web3-provider-engine"); -const SubscriptionSubprovider = require("web3-provider-engine/subproviders/subscriptions"); - -const RequestFunnel = require("./subproviders/requestfunnel"); -const DelayedBlockFilter = require("./subproviders/delayedblockfilter"); -const GethDefaults = require("./subproviders/gethdefaults"); -const GethApiDouble = require("./subproviders/geth_api_double"); - -const BlockTracker = require("./block_tracker"); - -const RuntimeError = require("./utils/runtimeerror"); -const EventEmitter = require("events"); - -const _ = require("lodash"); - -function Provider(options) { - const self = this; - EventEmitter.call(this); - - this.options = options = self._applyDefaultOptions(options || {}); - - const gethApiDouble = new GethApiDouble(options, this); - - this.engine = new ProviderEngine({ - blockTracker: new BlockTracker({ blockchain: gethApiDouble.state.blockchain }) - }); - - const subscriptionSubprovider = new SubscriptionSubprovider(); - - this.engine.manager = gethApiDouble; - this.engine.addProvider(new RequestFunnel()); - this.engine.addProvider(new DelayedBlockFilter()); - this.engine.addProvider(subscriptionSubprovider); - this.engine.addProvider(new GethDefaults()); - this.engine.addProvider(gethApiDouble); - - this.engine.setMaxListeners(100); - this.engine.start(); - - this.manager = gethApiDouble; - this.sendAsync = this.send.bind(this); - this.send = this.send.bind(this); - this.close = this.close.bind(this); - this._queueRequest = this._queueRequest.bind(this); - this._processRequestQueue = this._processRequestQueue.bind(this); - - subscriptionSubprovider.on("data", function(err, notification) { - self.emit("data", err, notification); - }); -} - -const defaultOptions = { - _chainId: 1, - _chainIdRpc: 1337, - vmErrorsOnRPCResponse: true, - verbose: false, - asyncRequestProcessing: false, - logger: { - log: function() {} - } -}; - -Provider.prototype = Object.create(EventEmitter.prototype); -Provider.prototype.constructor = Provider; - -Provider.prototype._applyDefaultOptions = function(options) { - return _.merge({}, defaultOptions, options); -}; - -Provider.prototype.send = function(payload, callback) { - if (typeof callback !== "function") { - throw new Error( - "No callback provided to provider's send function. As of web3 1.0, provider.send " + - "is no longer synchronous and must be passed a callback as its final argument." - ); - } - - const self = this; - - const externalize = function(payload) { - return _.cloneDeep(payload); - }; - - if (Array.isArray(payload)) { - payload = payload.map(externalize); - } else { - payload = externalize(payload); - } - - const intermediary = function(err, result) { - // clone result so that we can mutate the response without worrying about - // that messing up assumptions the calling logic might have about us - // mutating things - result = _.cloneDeep(result); - let response; - if (Array.isArray(result)) { - response = []; - for (let i = 0; i < result.length; i++) { - response.push(self.reportErrorInResponse(payload[i], err, result[i])); - } - } else { - response = self.reportErrorInResponse(payload, err, result); - } - - if (self.options.verbose) { - self.options.logger.log( - " < " + - JSON.stringify(response, null, 2) - .split("\n") - .join("\n < ") - ); - } - process.nextTick(() => callback(response.error ? err : null, response)); - }; - - if (self.options.verbose) { - self.options.logger.log( - " > " + - JSON.stringify(payload, null, 2) - .split("\n") - .join("\n > ") - ); - } - - if (self.options.asyncRequestProcessing) { - self.engine.sendAsync(payload, intermediary); - } else { - self._queueRequest(payload, intermediary); - } -}; - -Provider.prototype.close = function(callback) { - // This is a little gross reaching, but... - this.manager.state.stopMining(); - this.manager.state.blockchain.close(callback); - this.engine.stop(); -}; - -Provider.prototype._queueRequest = function(payload, intermediary) { - if (!this._requestQueue) { - this._requestQueue = []; - } - - this._requestQueue.push({ - payload: payload, - callback: intermediary - }); - - setImmediate(this._processRequestQueue); -}; - -Provider.prototype._processRequestQueue = function() { - const self = this; - - if (self._requestInProgress) { - return; - } - - self._requestInProgress = true; - - const args = self._requestQueue.shift(); - - if (args) { - self.engine.sendAsync(args.payload, (err, result) => { - if (self._requestQueue.length > 0) { - setImmediate(self._processRequestQueue); - } - args.callback(err, result); - self._requestInProgress = false; - }); - } else { - // still need to free the lock - self._requestInProgress = false; - - if (self._requestQueue.length > 0) { - setImmediate(self._processRequestQueue); - } - } -}; - -Provider.prototype.cleanUpErrorObject = function(err, response) { - // Our response should already have an error field at this point, if it - // doesn't, this was likely intentional. If not, this is the wrong place to - // fix that problem. - if (!err || !response.error) { - return response; - } - - const errorObject = { - error: { - data: {} - } - }; - - if (err.message) { - // clean up the error reporting done by the provider engine so the error message isn't lost in the stack trace noise - errorObject.error.message = err.message; - errorObject.error.data.stack = err.stack; - errorObject.error.data.name = err.name; - if ("code" in err) { - errorObject.error.code = err.code; - } - } else if (!response.error) { - errorObject.error = { - message: err.toString() - }; - } - - return _.merge(response, errorObject); -}; - -// helper set of RPC methods which execute code and respond with a transaction hash as their result -const transactionMethods = new Set(["eth_sendTransaction", "eth_sendRawTransaction", "personal_sendTransaction"]); - -Provider.prototype._isTransactionRequest = function(request) { - return transactionMethods.has(request.method); -}; - -Provider.prototype.reportErrorInResponse = function(request, err, response) { - const self = this; - - if (!err) { - return response; - } - - // TODO: for next major release: move reporting of tx hash on error to error - // field to prevent poorly-written clients which assume that the existence of - // the "result" field implies no errors from breaking. - if (self._isTransactionRequest(request)) { - if (err instanceof RuntimeError) { - // Make sure we always return the transaction hash on failed transactions so - // the caller can get their tx receipt. This breaks JSONRPC 2.0, but it's how - // we've always done it. - response.result = err.hashes[0]; - - if (self.options.vmErrorsOnRPCResponse) { - if (!response.error.data) { - response.error.data = {}; - } - response.error.data[err.hashes[0]] = err.results[err.hashes[0]]; - } else { - delete response.error; - } - } - } - - if (request.method === "eth_call") { - if (err instanceof RuntimeError) { - if (self.options.vmErrorsOnRPCResponse) { - if (!response.error.data) { - response.error.data = {}; - } - response.error.data[err.hashes[0]] = err.results[err.hashes[0]]; - } else { - response.result = err.results[err.hashes[0]].return || "0x"; - delete response.error; - } - } - } - - return self.cleanUpErrorObject(err, response); -}; - -module.exports = Provider; diff --git a/lib/server.js b/lib/server.js deleted file mode 100644 index 121023917c..0000000000 --- a/lib/server.js +++ /dev/null @@ -1,89 +0,0 @@ -// make sourcemaps work! -require("source-map-support/register"); - -var Provider = require("./provider"); -var webSocketServer = require("./webSocketServer"); -var httpServer = require("./httpServer"); -var _ = require("lodash"); - -module.exports = { - create: function(options) { - options = _applyDefaultOptions(options || {}); - - var logger = options.logger; - var provider = new Provider(options); - - var server = httpServer(provider, logger); - server.keepAliveTimeout = options.keepAliveTimeout; - - let connectionCounter = 0; - const connections = {}; - server.on("connection", (conn) => { - const key = connectionCounter++; - connections[key] = conn; - conn.on("close", () => delete connections[key]); - }); - - var oldListen = server.listen; - - server.listen = function() { - var args = Array.prototype.slice.call(arguments); - var callback = function() {}; - if (args.length > 0) { - var last = args[args.length - 1]; - if (typeof last === "function") { - callback = args.pop(); - } - } - - var intermediary = function(err) { - if (err) { - return callback(err); - } - server.provider.manager.waitForInitialization(callback); - }; - - args.push(intermediary); - - oldListen.apply(server, args); - }; - - server.provider = provider; - - if (options.ws) { - webSocketServer(server, provider, logger); - } - - var oldClose = server.close; - - server.close = function(callback) { - var args = Array.prototype.slice.call(arguments); - oldClose.apply(server, args); - - server.provider.close(function(err) { - if (err) { - return callback(err); - } - Object.keys(connections).forEach((key) => { - try { - connections[key].destroy(); - } catch (error) {} - }); - }); - }; - - return server; - } -}; - -const defaultOptions = { - logger: { - log: function() {} - }, - ws: true, - keepAliveTimeout: 5000 -}; - -var _applyDefaultOptions = function(options) { - return _.merge({}, defaultOptions, options); -}; diff --git a/lib/statemanager.js b/lib/statemanager.js deleted file mode 100644 index e60965f2bf..0000000000 --- a/lib/statemanager.js +++ /dev/null @@ -1,1066 +0,0 @@ -var Account = require("ethereumjs-account").default; -var RuntimeError = require("./utils/runtimeerror"); -var Transaction = require("./utils/transaction"); -var utils = require("ethereumjs-util"); -var seedrandom = require("seedrandom"); -var bip39 = require("bip39"); -var wallet = require("ethereumjs-wallet"); -var hdkey = require("ethereumjs-wallet/hdkey"); -var async = require("async"); -var BlockchainDouble = require("./blockchain_double.js"); -var ForkedBlockchain = require("./forking/forked_blockchain.js"); -var Web3 = require("web3"); -var fs = require("fs"); -var sigUtil = require("eth-sig-util"); -var _ = require("lodash"); -const { BlockOutOfRangeError } = require("./utils/errorhelper"); -const BN = utils.BN; -const rlp = require("rlp"); -const Common = require("ethereumjs-common").default; - -const ZERO_BUFFER = Buffer.from([0]); - -var to = require("./utils/to"); -var random = require("./utils/random"); -var TXRejectedError = require("./utils/txrejectederror"); - -function StateManager(options, provider) { - this.options = options = this._applyDefaultOptions(options || {}); - - if (options.fork) { - this.blockchain = new ForkedBlockchain(options); - } else { - this.blockchain = new BlockchainDouble(options); - } - - this.vm = this.blockchain.vm; - this.stateTrie = this.blockchain.stateTrie; - - this.accounts = {}; - this.secure = !!options.secure; - this.account_passwords = {}; - this.personal_accounts = {}; - this.total_accounts = options.total_accounts; - this.coinbase = null; - - this.latest_filter_id = 1; - - // This queue manages actions that shouldn't be run in parallel. - // The action_processing flag ensures new actions are queued instead of - // run immediately. - this.action_queue = []; - this.action_processing = false; - - this.snapshots = []; - this.logger = options.logger; - this.net_version = options.network_id; - this.mnemonic = options.mnemonic; - this.wallet = hdkey.fromMasterSeed(bip39.mnemonicToSeed(this.mnemonic)); - this.wallet_hdpath = options.hd_path || options.hdPath; - - this.gasPriceVal = to.rpcQuantityHexString(options.gasPrice); - - this.is_mining = true; - this.blockTime = options.blockTime; - this.is_mining_on_interval = !!options.blockTime; - this.mining_interval_timeout = null; - - this._provider = provider; -} - -const defaultOptions = { - forkCacheSize: 1024 * 1024 * 1024, - total_accounts: 10, - gasPrice: "0x77359400", // 2 gwei - default_balance_ether: 100, - unlocked_accounts: [], - hdPath: "m/44'/60'/0'/0/" -}; - -StateManager.prototype._applyDefaultOptions = function(options) { - // do this so that we can use the same seed on our next run and get the same - // results without explicitly setting a seed up front - if (!options.seed) { - options.seed = random.randomAlphaNumericString(10, seedrandom()); - } - - // generate a randomized default mnemonic - if (!options.mnemonic) { - const randomBytes = random.randomBytes(16, seedrandom(options.seed)); - options.mnemonic = bip39.entropyToMnemonic(randomBytes.toString("hex")); - } - - if (!options.fork && !options.network_id) { - options.network_id = new Date().getTime(); - } - - // We want this function to mutate the options object so that we can report - // our settings back to our consumer application (e.g., ganache) - return _.merge(options, defaultOptions, Object.assign({}, options)); -}; - -StateManager.prototype.initialize = function(callback) { - var self = this; - - var accounts = []; - - const defaultBalanceWei = to.hex(Web3.utils.toWei(self.options.default_balance_ether.toString(), "ether")); - - if (self.options.accounts) { - accounts = self.options.accounts.map(self.createAccount.bind(self)); - } else { - if (!self.total_accounts) { - return callback( - new Error("Cannot initialize chain: either options.accounts or options.total_accounts must be specified") - ); - } - - for (var i = 0; i < self.total_accounts; i++) { - accounts.push( - self.createAccount({ - index: i, - balance: defaultBalanceWei - }) - ); - } - } - - self.coinbase = to.hex(accounts[0].address); - self.accounts = {}; - - accounts.forEach(function(data) { - self.accounts[data.address] = data; - self.personal_accounts[data.address.toLowerCase()] = true; - }); - - // Turn array into object, mostly for speed purposes. - // No need for caller to specify private keys. - self.unlocked_accounts = self.options.unlocked_accounts.reduce(function(obj, address) { - // If it doesn't have a hex prefix, must be a number (either a string or number type). - if ((address + "").indexOf("0x") !== 0) { - const idx = parseInt(address); - const account = accounts[idx]; - if (!account) { - throw new Error(`Account at index ${idx} not found. Max index available is ${accounts.length - 1}.`); - } - address = account.address.toLowerCase(); - } - - obj[address.toLowerCase()] = true; // can be any value - return obj; - }, {}); - - if (!self.secure) { - accounts.forEach(function(data) { - self.unlocked_accounts[data.address.toLowerCase()] = data; - }); - } - - if (self.options.account_keys_path) { - const fileData = { - addresses: {}, - private_keys: {} - }; - accounts.forEach(function(account) { - fileData.private_keys[account.address] = account.secretKey.toString("hex"); - fileData.addresses[account.address] = account; - }); - const json = JSON.stringify(fileData); - fs.writeFileSync(self.options.account_keys_path, json, "utf8"); - } - - self.blockchain.initialize(accounts, function(err) { - if (err) { - return callback(err); - } - - // If the user didn't pass a specific version id in, then use the - // forked blockchain's version (if it exists) or create our own. - if (!self.net_version) { - self.net_version = self.blockchain.forkVersion; - } - - if (self.is_mining_on_interval) { - self.mineOnInterval(); - } - callback(); - }); -}; - -StateManager.prototype._minerCancellationToken = null; -StateManager.prototype.mineOnInterval = function() { - // cancel the a previous miner's timeout - clearTimeout(this.mining_interval_timeout); - - // make sure a pending eth_mine doesn't come back and execute mineOnInterval - // again... - if (this._minerCancellationToken !== null) { - this._minerCancellationToken.cancelled = true; - } - - // if mining was stopped `mineOnInterval` shouldn't start mining again - if (!this.is_mining) { - this.logger.log("Warning: mineOnInterval called when miner was stopped"); - return; - } - - const cancellationToken = { cancelled: false }; - this._minerCancellationToken = cancellationToken; - - const timeout = (this.mining_interval_timeout = setTimeout( - this._provider.send.bind(this._provider), - this.blockTime * 1000, - { method: "evm_mine" }, - () => { - if (!cancellationToken.cancelled) { - this.mineOnInterval.bind(this)(); - } - } - )); - - // Ensure this won't keep a node process open. - if (typeof timeout.unref === "function") { - timeout.unref(); - } -}; - -StateManager.prototype.createAccount = function(opts, i) { - var secretKey; - - if (opts.generate) { - secretKey = wallet.generate().getPrivateKey(); - } else if (opts.secretKey) { - secretKey = utils.toBuffer(to.hex(opts.secretKey)); - } else { - var index = typeof opts.index === "undefined" ? i : opts.index; - var acct = this.wallet.derivePath(this.wallet_hdpath + index); // index is a number - secretKey = acct.getWallet().getPrivateKey(); // Buffer - } - - var publicKey = utils.privateToPublic(secretKey); - var address = utils.publicToAddress(publicKey); - - var account = new Account(); - - account.balance = to.hex(opts.balance); - - var data = { - secretKey: secretKey, - publicKey: publicKey, - address: to.hex(address).toLowerCase(), - account: account - }; - - return data; -}; - -StateManager.prototype.blockNumber = function(callback) { - return this.blockchain.getHeight(callback); -}; - -StateManager.prototype.gasPrice = function() { - return this.gasPriceVal; -}; - -StateManager.prototype.getBalance = function(address, number, callback) { - this.blockchain.getBalance(address, number, function(err, balance) { - if (balance) { - balance = to.rpcQuantityHexString(balance); - } - callback(err, balance); - }); -}; - -StateManager.prototype.getTransactionCount = function(address, number, callback) { - this.blockchain.getNonce(address, number, function(err, nonce) { - if (nonce) { - nonce = to.rpcQuantityHexString(nonce); - } - callback(err, nonce); - }); -}; - -StateManager.prototype.getCode = function(address, number, callback) { - this.blockchain.getCode(address, number, function(err, code) { - if (code) { - code = to.hex(code); - } - callback(err, code); - }); -}; - -StateManager.prototype.queueRawTransaction = function(data, callback) { - // a hack until we get chainid and networkId alignment - // in the next major (breaking_change) version: - // Reason: historically we didn't validate chain ids. - let chainId; - if (Buffer.isBuffer(data)) { - const decodedData = rlp.decode(data); - let v = decodedData[6]; - if (v !== undefined) { - v = utils.bufferToInt(v); - chainId = Math.floor((v - 35) / 2); - if (chainId < 0) { - chainId = 0; - } - } - } - const common = !chainId - ? this.blockchain.vm.opts.common - : Common.forCustomChain( - "mainnet", // TODO needs to match chain id - { - name: "ganache", - networkId: this.options.network_id || this.blockchain.forkVersion, - chainId, - comment: "Local test network", - bootstrapNodes: [] - }, - this.options.hardfork - ); - const tx = new Transaction(data, Transaction.types.signed, common); - // use toLowerCase() to properly handle from addresses meant to be validated. - const from = to.hex(tx.from).toLowerCase(); - this._queueTransaction("eth_sendRawTransaction", tx, from, null, callback); -}; - -StateManager.prototype.queueStorage = function(address, position, block, callback) { - this.action_queue.push({ - method: "eth_getStorageAt", - address: utils.addHexPrefix(address), - position: utils.addHexPrefix(position), - block: block, - callback: callback - }); - - // We know there's work, so get started. - this.processNextAction(); -}; - -StateManager.prototype.queueTransaction = function(method, txJsonRpc, blockNumber, callback) { - // use toLowerCase() to properly handle from addresses meant to be validated. - const from = txJsonRpc.from ? to.hex(txJsonRpc.from).toLowerCase() : null; - - if (from == null) { - callback(new TXRejectedError("from not found; is required")); - return; - } - - // Error checks. It's possible to JSON.stringify a Buffer to JSON. - // we actually now handle this "properly" (not sure about spec), but for - // legacy reasons we don't allow it. - if (txJsonRpc.to && typeof txJsonRpc.to !== "string") { - return callback(new TXRejectedError("Invalid to address")); - } - - const hasOwnProperty = Object.prototype.hasOwnProperty; - const isKnownAccount = hasOwnProperty.call(this.accounts, from); - - if (method === "eth_sendTransaction" && !hasOwnProperty.call(this.unlocked_accounts, from)) { - const msg = isKnownAccount ? "signer account is locked" : "sender account not recognized"; - return callback(new TXRejectedError(msg)); - } - - let type = Transaction.types.none; - if (!isKnownAccount || method === "eth_call" || method === "eth_estimateGas") { - type |= Transaction.types.fake; - } - - let tx; - try { - tx = Transaction.fromJSON(txJsonRpc, type, this.blockchain.vm.opts.common); - this._setTransactionDefaults(tx, method === "eth_sendTransaction"); - } catch (e) { - callback(e); - return; - } - this._queueTransaction(method, tx, from, blockNumber, callback); -}; - -StateManager.prototype._setTransactionDefaults = function(tx, isTransaction) { - if (isTransaction && tx.gasLimit.length === 0) { - tx.gasLimit = utils.toBuffer(this.blockchain.defaultTransactionGasLimit); - } - - if (tx.gasPrice.length === 0) { - tx.gasPrice = utils.toBuffer(this.gasPriceVal); - } - - if (tx.value.length === 0) { - tx.value = Buffer.from([0]); - } - - if (tx.to.length === 0 || tx.to.equals(ZERO_BUFFER)) { - tx.to = Buffer.allocUnsafe(0); - } -}; - -StateManager.prototype._queueTransaction = function(method, tx, from, blockNumber, callback) { - if (!(tx instanceof Transaction)) { - throw new TXRejectedError("tx must be of type Transaction"); - } - - // If the transaction has a higher gas limit than the block gas limit, error. - if ( - (method === "eth_sendRawTransaction" || method === "eth_sendTransaction") && - to.number(tx.gasLimit) > to.number(this.blockchain.blockGasLimit) - ) { - return callback(new TXRejectedError("Exceeds block gas limit")); - } - - this.action_queue.push({ - method, - from, - tx, - callback, - blockNumber - }); - - // We know there's work, so get started. - this.processNextAction(); -}; - -StateManager.prototype.queueTransactionTrace = function(txHash, params, callback) { - this.action_queue.push({ - method: "debug_traceTransaction", - hash: to.hex(txHash), - params: params, - callback: callback - }); - - // We know there's work, so get started. - this.processNextAction(); -}; - -StateManager.prototype.processNextAction = function(override) { - var self = this; - - if (override !== true) { - if (this.action_processing === true || this.action_queue.length === 0) { - return; - } - } - - var queued = this.action_queue.shift(); - - // Set the flag that we're currently processing something. - this.action_processing = true; - - var intermediary = function(err, result) { - queued.callback(err, result); - - if (self.action_queue.length > 0) { - self.processNextAction(true); - } else { - self.action_processing = false; - } - }; - - if (typeof queued.method === "function") { - var result = queued.method(); - return intermediary(null, result); - } else if (queued.method === "eth_getStorageAt") { - this.blockchain.getStorage(queued.address, queued.position, queued.block, function(err, result) { - if (err) { - return intermediary(err); - } - - if (result) { - result = utils.rlp.decode(result); - } - - result = to.hex(result || 0); - intermediary(null, result); - }); - } else if (queued.method === "debug_traceTransaction") { - this.blockchain.processTransactionTrace(queued.hash, queued.params, intermediary); - } else if (queued.method === "eth_sendTransaction" || queued.method === "eth_sendRawTransaction") { - this.processTransaction(queued.from, queued.tx, intermediary); - } else if (queued.method === "eth_call") { - this.processCall(queued.from, queued.tx, queued.blockNumber, intermediary); - } else if (queued.method === "eth_estimateGas") { - this.processGasEstimate(queued.from, queued.tx, queued.blockNumber, intermediary); - } -}; - -StateManager.prototype.sign = function(address, dataToSign) { - var account = this.accounts[to.hex(address).toLowerCase()]; - - if (!account) { - throw new Error("cannot sign data; no private key"); - } - - var secretKey = account.secretKey; - var msg = to.buffer(dataToSign, "hex"); - var msgHash = utils.hashPersonalMessage(msg); - var sgn = utils.ecsign(msgHash, Buffer.from(secretKey)); - // ethereumjs-utils changed the behavior of toRpcSig so that the `v` value - // output by `toRpcSig` is always `sgn.v - 27` (basically `0` or `1`). In - // order to avoid a breaking change in Ganache at this time we are calculating - // a chain ID that will always a) validate the v, and b) generate an output v - // of `0` or `1`, like it used to. - const v = sgn.v - 27; - const chainId = (v - 35) / 2; - return utils.toRpcSig(v, sgn.r, sgn.s, chainId); -}; - -StateManager.prototype.signTypedData = function(address, typedDataToSign) { - var account = this.accounts[to.hex(address).toLowerCase()]; - if (!account) { - throw new Error("cannot sign data; no private key"); - } - - if (!typedDataToSign.types) { - throw new Error("cannot sign data; types missing"); - } - - if (!typedDataToSign.types.EIP712Domain) { - throw new Error("cannot sign data; EIP712Domain definition missing"); - } - - if (!typedDataToSign.domain) { - throw new Error("cannot sign data; domain missing"); - } - - if (!typedDataToSign.primaryType) { - throw new Error("cannot sign data; primaryType missing"); - } - - if (!typedDataToSign.message) { - throw new Error("cannot sign data; message missing"); - } - - return sigUtil.signTypedData_v4(account.secretKey, { data: typedDataToSign }); -}; - -StateManager.prototype.printTransactionReceipt = function(txHash, error, callback) { - var self = this; - - self.blockchain.getTransactionReceipt(txHash, function(err, receipt) { - if (err) { - return callback(err); - } - - self.blockchain.latestBlock(function(err, block) { - if (err) { - return callback(err); - } - - receipt = receipt.toJSON(); - - self.logger.log(""); - self.logger.log(" Transaction: " + txHash); - - if (receipt.contractAddress != null) { - self.logger.log(" Contract created: " + receipt.contractAddress); - } - - self.logger.log(" Gas usage: " + parseInt(receipt.gasUsed, 16)); - self.logger.log(" Block Number: " + parseInt(receipt.blockNumber, 16)); - self.logger.log(" Block Time: " + new Date(to.number(block.header.timestamp) * 1000).toString()); - - if (error) { - self.logger.log(" Runtime Error: " + error.error); - if (error.reason) { - self.logger.log(" Revert reason: " + error.reason); - } - } - - self.logger.log(""); - - callback(null, txHash); - }); - }); -}; - -StateManager.prototype.processBlock = function(timestamp, callback) { - var self = this; - - if (typeof timestamp === "function") { - callback = timestamp; - timestamp = null; - } - - self.blockchain.processNextBlock(timestamp, function(runtimeError, transactions, vmOutput) { - if (runtimeError && runtimeError instanceof RuntimeError === false) { - // This is bad. Get out. - return callback(runtimeError, transactions, vmOutput); - } - - // TODO: Can we refactor printTransactionReceipt so it's synchronous? - // We technically have the raw vm receipts (though they're not full receipts here...). - async.eachSeries( - transactions, - function(tx, finishedPrinting) { - var hash = to.hex(tx.hash()); - var error = runtimeError == null ? { results: {} } : runtimeError; - self.printTransactionReceipt(hash, error.results[hash], finishedPrinting); - }, - callback(runtimeError, transactions, vmOutput) - ); - }); -}; - -StateManager.prototype.processBlocks = function(totalBlocks, callback) { - var self = this; - - if (typeof totalBlocks === "function") { - callback = totalBlocks; - totalBlocks = null; - } - - // Note: VM errors (errors that the VM directly returns) trump all runtime errors. - var runtimeError = null; - var amountProcessed = 0; - - async.whilst( - function() { - var shouldContinue; - - if (totalBlocks == null) { - shouldContinue = self.blockchain.pending_transactions.length > 0; - } else { - shouldContinue = amountProcessed < totalBlocks; - } - - return shouldContinue; - }, - function(done) { - self.processBlock(function(err, transactions, vmOutput) { - amountProcessed += 1; - - if (err) { - if (err instanceof RuntimeError === false) { - // This is bad. Get out. - return done(err); - } - - // We must have a RuntimeError. Merge results if we've found - // other runtime errors during this execution. - if (runtimeError == null) { - runtimeError = err; - } else { - runtimeError.combine(err); - } - } - - // Note we don't quit on runtime errors. We keep processing transactions. - done(); - }); - }, - function(err) { - // Remember: vm errors trump runtime errors - callback(err || runtimeError); - } - ); -}; - -StateManager.prototype.processCall = function(from, tx, blockNumber, callback) { - var self = this; - - function next(err, tx) { - if (err) { - return callback(err); - } - - self.blockchain.processCall(tx, blockNumber, function(err, results) { - if (err) { - if (err instanceof BlockOutOfRangeError) { - // block doesn't exist - return callback(null, null); - } - return callback(err); - } - - var result = "0x"; - if (!results.error && results.execResult.returnValue) { - result = to.hex(results.execResult.returnValue); - } else if (results.error) { - self.logger.log(`Error processing call: ${results.error}`); - } - - return callback(null, result); - }); - } - - // `eth_call` should never need to validate an existing nonce - if (tx.nonce.length === 0) { - self.createTransactionWithCorrectNonce(tx, from, next); - } else { - next(null, tx); - } -}; - -StateManager.prototype.processGasEstimate = function(from, tx, blockNumber, callback) { - var self = this; - - function next(err, tx) { - if (err) { - return callback(err); - } - - self.blockchain.estimateGas(tx, blockNumber, function(err, results) { - if (err) { - return callback(err); - } - var result = "0x"; - if (!results.error) { - result = to.hex(results.gasEstimate); - } else { - self.logger.log(`Error calculating gas estimate: ${results.error}`); - } - return callback(null, result); - }); - } - - // `eth_estimateGas` should never need to validate an existing nonce - if (tx.nonce.length === 0) { - self.createTransactionWithCorrectNonce(tx, from, next); - } else { - next(null, tx); - } -}; - -StateManager.prototype.processTransaction = function(from, tx, callback) { - var self = this; - - self.createTransactionWithCorrectNonce(tx, from, function(err, tx) { - if (err) { - return callback(err); - } - - self.blockchain.queueTransaction(tx); - - var txHash = to.hex(tx.hash()); - - // If we're not currently mining or we're mining on an interval, - // only queue the transaction, don't process it. - if (self.is_mining === false || self.is_mining_on_interval) { - return callback(null, txHash); - } - - self.processBlocks(function(err) { - if (err) { - return callback(err); - } - callback(null, txHash); - }); - }); -}; - -StateManager.prototype.getTransactionReceipt = function(hash, callback) { - this.blockchain.getTransactionReceipt(hash, function(err, receipt) { - if (err && err.notFound) { - // Return null if the receipt's not found. - return callback(null, null); - } - callback(err, receipt); - }); -}; - -StateManager.prototype.getBlock = function(hashOrNumber, callback) { - this.blockchain.getBlock(hashOrNumber, callback); -}; - -StateManager.prototype.getLogs = function(filter, callback) { - var self = this; - - // filter.address may be a single address or an array - // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getlogs - var expectedAddress = filter.address && (Array.isArray(filter.address) ? filter.address : [filter.address]); - expectedAddress = - expectedAddress && - expectedAddress.map(function(a) { - return a.toLowerCase(); - }); - var expectedTopics = filter.topics || []; - - async.parallel( - { - fromBlock: this.blockchain.getEffectiveBlockNumber.bind(this.blockchain, filter.fromBlock || "latest"), - toBlock: this.blockchain.getEffectiveBlockNumber.bind(this.blockchain, filter.toBlock || "latest"), - latestBlock: this.blockchain.getEffectiveBlockNumber.bind(this.blockchain, "latest"), - block: this.blockchain.getBlock.bind(this.blockchain, filter.blockHash || 0) - }, - function(err, results) { - if (err) { - return callback(err); - } - var fromBlock = results.fromBlock; - var toBlock = results.toBlock; - var latestBlock = results.latestBlock; - var block = results.block; - - if (toBlock > latestBlock) { - toBlock = latestBlock; - } - - if (filter.blockHash) { - fromBlock = to.number(block.header.number); - toBlock = to.number(block.header.number); - } - - var logs = []; - var current = fromBlock; - - async.whilst( - function() { - return current <= toBlock; - }, - function(finished) { - self.blockchain.getBlockLogs(current, function(err, blockLogs) { - if (err) { - return finished(err); - } - - // Filter logs that match the address - var filtered = !expectedAddress - ? blockLogs - : blockLogs.filter(function(log) { - return expectedAddress.indexOf(log.address.toLowerCase()) > -1; - }); - - // Now filter based on topics. - filtered = filtered.filter(function(log) { - var keep = true; - for (var i = 0; i < expectedTopics.length; i++) { - var expectedTopic = expectedTopics[i]; - var logTopic = log.topics[i]; - if (expectedTopic == null) { - continue; - } - var isMatch = Array.isArray(expectedTopic) - ? expectedTopic.includes(logTopic) - : expectedTopic === logTopic; - if (i >= log.topics.length || !isMatch) { - keep = false; - break; - } - } - return keep; - }); - - logs.push.apply(logs, filtered); - - current += 1; - finished(); - }); - }, - function(err) { - if (err) { - return callback(err); - } - - logs = logs.map(function(log) { - return log.toJSON(); - }); - - callback(err, logs); - } - ); - } - ); -}; - -// Note: Snapshots have 1-based ids. -StateManager.prototype.snapshot = function(callback) { - var self = this; - - this.blockchain.getHeight(function(err, blockNumber) { - if (err) { - return callback(err); - } - - self.snapshots.push({ - blockNumber: blockNumber, - timeAdjustment: self.blockchain.timeAdjustment - }); - - self.logger.log("Saved snapshot #" + self.snapshots.length); - - callback(null, to.hex(self.snapshots.length)); - }); -}; - -StateManager.prototype.revert = function(snapshotId, callback) { - var self = this; - - if (snapshotId === null || snapshotId === undefined) { - callback(new Error("invalid snapshotId")); - return; - } - // Convert from hex. - try { - snapshotId = utils.bufferToInt(snapshotId); - } catch (e) { - callback(e); - return; - } - - this.logger.log("Reverting to snapshot #" + snapshotId); - - if (snapshotId > this.snapshots.length || snapshotId <= 0) { - // the snapshot doesn't exist now, or it has already been reverted - callback(null, false); - return false; - } - - // Convert to zero based. - snapshotId = snapshotId - 1; - var timeAdjustment = this.snapshots[snapshotId].timeAdjustment; - - // Loop through each snapshot with a higher id than the current one. - async.whilst( - function() { - return self.snapshots.length > snapshotId; - }, - function(nextSnapshot) { - var snapshot = self.snapshots.pop(); - - // For each snapshot, asynchronously pop off the blocks it represents. - async.during( - function(doneWithTest) { - self.blockchain.getHeight(function(err, blockNumber) { - if (err) { - return doneWithTest(err); - } - - doneWithTest(null, blockNumber > snapshot.blockNumber); - }); - }, - function(nextBlock) { - self.blockchain.popBlock(function(err) { - if (err) { - return nextBlock(err); - } - nextBlock(); - }); - }, - nextSnapshot - ); - }, - function(err) { - if (err) { - return callback(err); - } - - // Pending transactions are removed when you revert. - self.blockchain.clearPendingTransactions(); - // The time adjustment is restored to its prior state - self.blockchain.timeAdjustment = timeAdjustment; - - callback(null, true); - } - ); -}; - -StateManager.prototype.hasContractCode = function(address, callback) { - this.vm.stateManager.getContractCode(address, function(err, result) { - if (err != null) { - callback(err, false); - } else { - callback(null, true); - } - }); -}; - -StateManager.prototype.startMining = function(callback) { - if (this.is_mining) { - process.nextTick(callback); - this.logger.log("Warning: startMining called when miner was already started"); - return; - } - - this.is_mining = true; - - if (this.is_mining_on_interval) { - this.mineOnInterval(); - process.nextTick(callback); - } else { - this.processBlocks(callback); - } -}; - -StateManager.prototype.stopMining = function(callback) { - if (this.is_mining) { - if (this._minerCancellationToken) { - this._minerCancellationToken.cancelled = true; - this._minerCancellationToken = null; - } - this.is_mining = false; - clearTimeout(this.mining_interval_timeout); - this.mining_interval_timeout = null; - } else { - this.logger.log("Warning: stopMining called when miner was already stopped"); - } - callback && process.nextTick(callback); -}; - -StateManager.prototype.isUnlocked = function(address) { - return this.unlocked_accounts[address.toLowerCase()] != null; -}; - -StateManager.prototype.createTransactionWithCorrectNonce = function(tx, from, callback) { - // account for transactions waiting in the tx queue - this.blockchain.getQueuedNonce(from, (err, expectedNonce) => { - if (err) { - return callback(err); - } - - const validateNonce = (tx, expectedNonce) => { - if (tx.validateNonce(expectedNonce)) { - return Promise.resolve(tx); - } else { - const expected = new BN(expectedNonce).toString(10); - const actual = new BN(tx.nonce).toString(10); - return Promise.reject( - new TXRejectedError( - `the tx doesn't have the correct nonce. account has nonce of: ${expected} tx has nonce of: ${actual}` - ) - ); - } - }; - const done = (tx) => callback(null, tx); - - if (tx.isSigned()) { - validateNonce(tx, expectedNonce) - .then(done) - .catch(callback); - } else { - const sign = (tx) => { - // we need to sign transactions if they haven't been signed already - // but we never sign fake transactions - if (!tx.isFake() && !tx.isSigned()) { - const account = this.accounts[from]; - if (account) { - tx.sign(account.secretKey); - } - } - return Promise.resolve(tx); - }; - - // Validate the tx's nonce and then sign the transaction. - // By signing this transaction now we ensure all future calls to tx.hash() - // return the same signed transaction hash. It's sort of an unintuitive - // quirk of etheremjs-tx that: - // tx.hash(includeSignature); - // tx.sign(secretKey); - // tx.hash(includeSignature); - // will produce different hashes. - if (tx.nonce.length === 0) { - // Since this transaction is unsigned and the nonce was not defined - // we can go ahead and change the nonce and not worry about - // invalidating the tx hash the client may be expecting (automatic nonce - // calculation should have been expected by the client in this case). - tx.nonce = expectedNonce; - sign(tx).then(done); - } else { - validateNonce(tx, expectedNonce) - .then(sign) - .then(done) - .catch(callback); - } - } - }); -}; -module.exports = StateManager; diff --git a/lib/subproviders/delayedblockfilter.js b/lib/subproviders/delayedblockfilter.js deleted file mode 100644 index c1fe1e5511..0000000000 --- a/lib/subproviders/delayedblockfilter.js +++ /dev/null @@ -1,153 +0,0 @@ -// It's unforutnate we have to have this subprovider, but it's because -// we instamine, and web3 isn't written in a way that supports instamining -// (i.e., it sets up the filter after the transaction has been processed). -// This block filter will ensure that each block filter will always see -// the change from the last block to the current block. -// -// Note: An added benefit of this is that it shaves off a signifcant -// amount of time from tests that use web3 and block filters. - -var Subprovider = require("web3-provider-engine/subproviders/subprovider.js"); -var inherits = require("util").inherits; -var async = require("async"); -var to = require("../utils/to"); - -inherits(DelayedBlockFilter, Subprovider); - -module.exports = DelayedBlockFilter; - -function DelayedBlockFilter() { - this.watching = {}; -} - -DelayedBlockFilter.prototype.handleRequest = function(payload, next, end) { - if (payload.method === "eth_newBlockFilter") { - return this.handleNewBlockFilter(payload, next, end); - } - if (payload.method === "eth_getFilterChanges") { - return this.handleGetFilterChanges(payload, next, end); - } - - next(); -}; - -DelayedBlockFilter.prototype.handleNewBlockFilter = function(payload, next, end) { - var self = this; - - // Let this filter process and add it to our watch list. - next(function(err, result, cb) { - if (err) { - return cb(); - } - self.watching[result] = true; - cb(); - }); -}; - -DelayedBlockFilter.prototype.handleGetFilterChanges = function(payload, next, end) { - var self = this; - var filterId = payload.params[0]; - - if (!this.watching[filterId]) { - return next(); - } - - // Get the changes, and then alter the result. - next(function(err, result, cb) { - if (err) { - return cb(); - } - - var currentBlockHash; - var previousBlockHash; - var blockNumber; - - async.series( - [ - function(c) { - // If we have a result, use it. - if (result.length !== 0) { - currentBlockHash = result[0]; - c(); - } else { - // Otherwise, get the current block number. - self.emitPayload( - { - method: "eth_blockNumber" - }, - function(err, res) { - if (err) { - return c(err); - } - blockNumber = to.number(res.result); - c(); - } - ); - } - }, - function(c) { - // If we got a block number above, meaning, we didn't get a block hash, - // skip this step. - if (blockNumber) { - return c(); - } - - // If not skipped, then we got a block hash, and we need to get a block number from it. - self.emitPayload( - { - method: "eth_getBlockByHash", - params: [currentBlockHash, false] - }, - function(err, res) { - if (err) { - return c(err); - } - blockNumber = to.number(res.result.number); - c(); - } - ); - }, - function(c) { - // If we're at block 0, return no changes. See final function below. - blockNumber = to.number(blockNumber); - if (blockNumber === 0) { - previousBlockHash = undefined; - return c(); - } - - // If at this point, we do have a block number, so let's subtract one - // from it and get the block hash of the block before it. - blockNumber = blockNumber - 1; - self.emitPayload( - { - method: "eth_getBlockByNumber", - params: [blockNumber, false] - }, - function(err, res) { - if (err) { - return c(err); - } - previousBlockHash = res.result.hash; - c(); - } - ); - } - ], - function(err) { - if (err) { - // Unfortunately the subprovider code doesn't let us return an error - // through the callback cb(). So we'll just ignore it.... (famous last words). - } - - // If we got the previous block, use it. Otherwise do nothing. - // Then stop watching because we only want on getFilterChanges to react this way. - if (previousBlockHash) { - result[0] = previousBlockHash; - } - - delete self.watching[filterId]; - cb(); - } - ); - }); -}; diff --git a/lib/subproviders/gethdefaults.js b/lib/subproviders/gethdefaults.js deleted file mode 100644 index b2b65954b1..0000000000 --- a/lib/subproviders/gethdefaults.js +++ /dev/null @@ -1,39 +0,0 @@ -var Subprovider = require("web3-provider-engine/subproviders/subprovider.js"); -var inherits = require("util").inherits; - -inherits(GethDefaults, Subprovider); - -module.exports = GethDefaults; - -function GethDefaults() {} - -// Massage eth_estimateGas requests, setting default data (e.g., from) if -// not specified. This is here specifically to make the testrpc -// react like Geth. -GethDefaults.prototype.handleRequest = function(payload, next, end) { - if (payload.method !== "eth_estimateGas" && payload.method !== "eth_call") { - return next(); - } - - var params = payload.params[0]; - - if (params.from == null) { - this.emitPayload( - { - method: "eth_coinbase" - }, - function(err, result) { - if (err) { - return end(err); - } - - var coinbase = result.result; - - params.from = coinbase; - next(); - } - ); - } else { - next(); - } -}; diff --git a/lib/subproviders/reactiveblocktracker.js b/lib/subproviders/reactiveblocktracker.js deleted file mode 100644 index 4db8dd717e..0000000000 --- a/lib/subproviders/reactiveblocktracker.js +++ /dev/null @@ -1,52 +0,0 @@ -var Subprovider = require("web3-provider-engine/subproviders/subprovider.js"); -var inherits = require("util").inherits; - -inherits(ReactiveBlockTracker, Subprovider); - -module.exports = ReactiveBlockTracker; - -function ReactiveBlockTracker() { - this.methods = { - eth_call: "before", - eth_getStorageAt: "before", - eth_getLogs: "before" - }; -} - -// Fetch the block before certain requests to make sure we're completely updated -// before those methods are processed. Also, fetch the block after certain requests -// to speed things up. -ReactiveBlockTracker.prototype.handleRequest = function(payload, next, end) { - var self = this; - - var when = this.methods[payload.method]; - - if (when == null) { - return next(); - } - - function fetchBlock(cb) { - self.engine.fetchBlock("latest", function(err, block) { - if (err) { - return end(err); - } - if (!self.engine.currentBlock || self.engine.currentBlock.hash.compare(block.hash) !== 0) { - self.engine._setCurrentBlock(block); - } - cb(); - }); - } - - if (when === "before") { - fetchBlock(function() { - next(); - }); - } else { - next(function(error, result, cb) { - if (error) { - return cb(error); - } - fetchBlock(cb); - }); - } -}; diff --git a/lib/subproviders/requestfunnel.js b/lib/subproviders/requestfunnel.js deleted file mode 100644 index b6e3293648..0000000000 --- a/lib/subproviders/requestfunnel.js +++ /dev/null @@ -1,62 +0,0 @@ -var Subprovider = require("web3-provider-engine/subproviders/subprovider.js"); -var inherits = require("util").inherits; - -inherits(RequestFunnel, Subprovider); - -module.exports = RequestFunnel; - -// See if any payloads for the specified methods are marked as external. -// If they are external, and match the method list, process them one at -// a time. -function RequestFunnel() { - // We use an object here for O(1) lookups (speed). - this.methods = { - eth_call: true, - eth_getStorageAt: true, - eth_sendTransaction: true, - eth_sendRawTransaction: true, - - // Ensure block filter and filter changes are process one at a time - // as well so filter requests that come in after a transaction get - // processed once that transaction has finished processing. - eth_newBlockFilter: true, - eth_getFilterChanges: true, - eth_getFilterLogs: true - }; - this.queue = []; - this.isWorking = false; -} - -RequestFunnel.prototype.handleRequest = function(payload, next, end) { - if (payload.external !== true || this.methods[payload.method] !== true) { - return next(); - } - - this.queue.push([payload, next]); - - if (this.isWorking === false) { - this.processNext(); - } -}; - -RequestFunnel.prototype.processNext = function() { - var self = this; - - if (this.queue.length === 0) { - this.isWorking = false; - return; - } - - this.isWorking = true; - - var item = this.queue.shift(); - var next = item[1]; - - next(function(error, request, cb) { - if (error) { - return cb(error); - } - cb(); - self.processNext(); - }); -}; diff --git a/lib/utils/block_helper.js b/lib/utils/block_helper.js deleted file mode 100644 index 3c4d84d38a..0000000000 --- a/lib/utils/block_helper.js +++ /dev/null @@ -1,34 +0,0 @@ -var to = require("./to"); - -module.exports = { - toJSON: function(block, includeFullTransactions) { - return { - number: to.rpcQuantityHexString(block.header.number), - hash: to.hex(block.hash()), - parentHash: to.hex(block.header.parentHash), // common.hash - mixHash: to.hex(block.header.mixHash), - nonce: to.rpcDataHexString(to.hex(block.header.nonce), 16), - sha3Uncles: to.hex(block.header.uncleHash), - logsBloom: to.hex(block.header.bloom), - transactionsRoot: to.hex(block.header.transactionsTrie), - stateRoot: to.hex(block.header.stateRoot), - receiptsRoot: to.hex(block.header.receiptTrie), - miner: to.hex(block.header.coinbase), - difficulty: to.rpcQuantityHexString(block.header.difficulty), - totalDifficulty: to.rpcQuantityHexString(block.header.difficulty), // TODO: Figure out what to do here. - extraData: to.rpcDataHexString(block.header.extraData), - size: to.hex(1000), // TODO: Do something better here - gasLimit: to.rpcQuantityHexString(block.header.gasLimit), - gasUsed: to.rpcQuantityHexString(block.header.gasUsed), - timestamp: to.rpcQuantityHexString(block.header.timestamp), - transactions: block.transactions.map(function(tx) { - if (includeFullTransactions) { - return tx.toJsonRpc(block); - } else { - return to.hex(tx.hash()); - } - }), - uncles: [] // block.uncleHeaders.map(function(uncleHash) {return to.hex(uncleHash)}) - }; - } -}; diff --git a/lib/utils/errorhelper.js b/lib/utils/errorhelper.js deleted file mode 100644 index e31a6959d2..0000000000 --- a/lib/utils/errorhelper.js +++ /dev/null @@ -1,19 +0,0 @@ -class LevelUpOutOfRangeError extends Error { - constructor(type, index, len) { - const message = "LevelUpArrayAdapter named '" + type + "' index out of range: index " + index + "; length: " + len; - super(message); - this.name = `${this.constructor.name}:${type}`; - this.type = type; - } -} - -class BlockOutOfRangeError extends LevelUpOutOfRangeError { - constructor(index, len) { - super("blocks", index, len); - } -} - -module.exports = { - LevelUpOutOfRangeError, - BlockOutOfRangeError -}; diff --git a/lib/utils/gas/binSearch.js b/lib/utils/gas/binSearch.js deleted file mode 100644 index 4efb6827d3..0000000000 --- a/lib/utils/gas/binSearch.js +++ /dev/null @@ -1,38 +0,0 @@ -const { BN } = require("ethereumjs-util"); -const hexToBn = (val = 0) => new BN(parseInt("0x" + val.toString("hex"), 16)); -const MULTIPLE = 64 / 63; - -module.exports = async function binSearch(generateVM, runArgs, result, callback) { - const MAX = hexToBn(runArgs.block.header.gasLimit); - const gasRefund = result.execResult.gasRefund; - const startingGas = gasRefund ? result.gasEstimate.add(gasRefund) : result.gasEstimate; - const range = { lo: startingGas, hi: startingGas }; - const isEnoughGas = async(gas) => { - const vm = generateVM(); // Generate fresh VM - runArgs.tx.gasLimit = gas.toBuffer(); - const result = await vm.runTx(runArgs).catch((vmerr) => ({ vmerr })); - return !result.vmerr && !result.execResult.exceptionError; - }; - - if (!(await isEnoughGas(range.hi))) { - do { - range.hi = range.hi.muln(MULTIPLE); - } while (!(await isEnoughGas(range.hi))); - while (range.lo.addn(1).lt(range.hi)) { - const mid = range.lo.add(range.hi).divn(2); - if (await isEnoughGas(mid)) { - range.hi = mid; - } else { - range.lo = mid; - } - } - if (range.hi.gte(MAX)) { - if (!(await isEnoughGas(range.hi))) { - return callback(new Error("gas required exceeds allowance or always failing transaction")); - } - } - } - - result.gasEstimate = range.hi; - callback(null, result); -}; diff --git a/lib/utils/gas/estimateGas.js b/lib/utils/gas/estimateGas.js deleted file mode 100644 index bd72ed6d14..0000000000 --- a/lib/utils/gas/estimateGas.js +++ /dev/null @@ -1,14 +0,0 @@ -const estimateGas = require("./guestimation"); -const binSearch = require("./binSearch"); - -module.exports = async(generateVM, runArgs, callback) => { - const vm = generateVM(); - estimateGas(vm, runArgs, async(err, result) => { - if (err) { - callback(err); - return; - } - - await binSearch(generateVM, runArgs, result, callback); - }); -}; diff --git a/lib/utils/log.js b/lib/utils/log.js deleted file mode 100644 index ae56a2b2f3..0000000000 --- a/lib/utils/log.js +++ /dev/null @@ -1,39 +0,0 @@ -var to = require("./to.js"); - -// Expects: -// -// logIndex: ... -// transactionIndex: ... -// transactionHash: ... -// block: ... -// address: ... -// data: ... -// topics: ... -// type: ... - -function Log(data) { - var self = this; - Object.keys(data).forEach(function(key) { - self[key] = data[key]; - }); -} - -Log.prototype.toJSON = function() { - // RPC quantity values like this.transactionIndex can be set to "0x00", - // use the explicit rpcQuantityHexString to properly format the JSON, removing leading zeroes. - // See RPC log format spec: https://github.com/ethereum/wiki/wiki/JSON-RPC - return { - logIndex: to.rpcQuantityHexString(this.logIndex), - transactionIndex: to.rpcQuantityHexString(this.transactionIndex), - transactionHash: to.rpcDataHexString(this.transactionHash), - blockHash: to.rpcDataHexString(this.block.hash()), - blockNumber: to.rpcQuantityHexString(this.block.header.number), - address: to.rpcDataHexString(this.address), - data: to.rpcDataHexString(this.data), - topics: this.topics, - type: "mined", - removed: (this.removed || false) - }; -}; - -module.exports = Log; diff --git a/lib/utils/random.js b/lib/utils/random.js deleted file mode 100644 index a261bcf357..0000000000 --- a/lib/utils/random.js +++ /dev/null @@ -1,28 +0,0 @@ -module.exports = { - // Mimics crypto.random bytes, but takes in a random number generator - // as its second parameter. rng is expected to be a function that takes - // no parameters and returns a result like Math.random(). - // This is important because it allows for a seeded random number generator. - // Since this is a mock RPC library, the rng doesn't need to be cryptographically secure. - randomBytes: function(length, rng) { - var buf = []; - - for (var i = 0; i < length; i++) { - buf.push(rng() * 255); - } - - return Buffer.from(buf); - }, - - randomAlphaNumericString: function(length, rng) { - const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - - let text = ""; - - for (var i = 0; i < length; i++) { - text += alphabet.charAt(Math.floor((rng || Math.random)() * alphabet.length)); - } - - return text; - } -}; diff --git a/lib/utils/receipt.js b/lib/utils/receipt.js deleted file mode 100644 index 272dc07709..0000000000 --- a/lib/utils/receipt.js +++ /dev/null @@ -1,49 +0,0 @@ -var to = require("./to"); - -function Receipt(tx, block, logs, gasUsed, cumulativeGasUsed, contractAddress, status, logsBloom) { - this.tx = tx; - this.block = block; - this.logs = logs; - this.gasUsed = gasUsed; - this.cumulativeGasUsed = cumulativeGasUsed; - this.contractAddress = contractAddress; - this.status = status; - this.logsBloom = logsBloom; - - this.transactionIndex = 0; - - this.txHash = tx.hash(); - - for (var i = 0; i < block.transactions.length; i++) { - var current = block.transactions[i]; - if (current.hash().equals(this.txHash)) { - this.transactionIndex = i; - break; - } - } -} - -Receipt.prototype.toJSON = function() { - // Enforce Hex formatting as defined in the RPC spec. - return { - transactionHash: to.rpcDataHexString(this.txHash), - transactionIndex: to.rpcQuantityHexString(this.transactionIndex), - blockHash: to.rpcDataHexString(this.block.hash()), - blockNumber: to.rpcQuantityHexString(this.block.header.number), - from: to.rpcDataHexString(this.tx.from), - to: to.nullableRpcDataHexString(this.tx.to), - gasUsed: to.rpcQuantityHexString(this.gasUsed), - cumulativeGasUsed: to.rpcQuantityHexString(this.cumulativeGasUsed), - contractAddress: this.contractAddress != null ? to.rpcDataHexString(this.contractAddress) : null, - logs: this.logs.map(function(log) { - return log.toJSON(); - }), - status: to.rpcQuantityHexString(this.status), - logsBloom: to.rpcDataHexString(this.logsBloom), - v: to.rpcQuantityHexString(this.tx.v), - r: to.rpcQuantityHexString(this.tx.r), - s: to.rpcQuantityHexString(this.tx.s) - }; -}; - -module.exports = Receipt; diff --git a/lib/utils/runtimeerror.js b/lib/utils/runtimeerror.js deleted file mode 100644 index 2d2725a97d..0000000000 --- a/lib/utils/runtimeerror.js +++ /dev/null @@ -1,103 +0,0 @@ -var inherits = require("util").inherits; -var to = require("./to"); -var abi = require("ethereumjs-abi"); - -inherits(RuntimeError, Error); - -// Note: ethereumjs-vm will return an object that has a "results" and "receipts" keys. -// You should pass in the whole object. -function RuntimeError(transactions, vmOutput) { - // Why not just Error.apply(this, [message])? See - // https://gist.github.com/justmoon/15511f92e5216fa2624b#anti-patterns - Error.captureStackTrace(this, this.constructor); - this.name = this.constructor.name; - - this.results = {}; - this.hashes = []; - - // handles creating this.message - this.combine(transactions, vmOutput); -} - -RuntimeError.prototype.combine = function(transactions, vmOutput) { - // Can be combined with vmOutput or another RuntimeError. - if (transactions instanceof RuntimeError) { - var err = transactions; - var keys = Object.keys(err.results); - - for (var i = 0; i < keys.length; i++) { - var key = keys[i]; - this.results[key] = err.results[key]; - Array.prototype.push.apply(this.hashes, err.hashes); - } - } else { - var results = vmOutput.results; - - for (i = 0; i < transactions.length; i++) { - var tx = transactions[i]; - var result = results[i]; - - // 1 means no error, oddly. - if (result.execResult.exceptionError) { - var hash = to.hex(tx.hash()); - this.hashes.push(hash); - var reason; - var returnData = result.execResult.returnValue; - if (returnData && returnData.slice(0, 4).toString("hex") === "08c379a0") { - try { - reason = abi.rawDecode(["string"], returnData.slice(4))[0]; - } catch (_) { - // if decoding fails, we'll just leave the reason empty - } - } - - const runState = result.execResult.runState; - this.results[hash] = { - error: result.execResult.exceptionError.error || result.execResult.exceptionError, - program_counter: runState && runState.programCounter, - return: to.hex(returnData), - reason: reason - }; - } - } - } - - // Once combined, set the message - if (this.hashes.length === 1) { - var exceptionResult = this.results[this.hashes[0]]; - var message = "VM Exception while processing transaction: " + exceptionResult.error; - if (exceptionResult.reason) { - message += " " + exceptionResult.reason; - } - this.message = message; - } else { - message = "Multiple VM Exceptions while processing transactions: \n\n"; - - for (i = 0; i < this.hashes.length; i++) { - hash = this.hashes[i]; - exceptionResult = this.results[hash]; - message += hash + ": " + exceptionResult.error; - if (exceptionResult.reason) { - message += " " + exceptionResult.reason; - } - message += "\n"; - } - this.message = message; - } -}; - -RuntimeError.prototype.count = function() { - return Object.keys(this.results).length; -}; - -RuntimeError.fromResults = function(transactions, vmOutput) { - var err = new RuntimeError(transactions, vmOutput); - - if (err.count() === 0) { - return null; - } - - return err; -}; - -module.exports = RuntimeError; diff --git a/lib/utils/to.js b/lib/utils/to.js deleted file mode 100644 index fa64f20f34..0000000000 --- a/lib/utils/to.js +++ /dev/null @@ -1,145 +0,0 @@ -const utils = require("ethereumjs-util"); - -module.exports = { - buffer: function(val) { - let data; - if (typeof val === "string") { - // strings need to be treated as hex, so we have to prep them: - data = val.indexOf("0x") === 0 ? val.slice(2) : val; - data = data.length % 2 === 1 ? `0${data}` : data; - data = Buffer.from(data, "hex"); - } else if (Buffer.isBuffer(val)) { - // no need to copy the Buffer to a new Buffer, so we just use the Buffer - // exactly as it was given to us: - data = val; - } else { - // all other types get the Buffer treatment and built-in type checking: - data = Buffer.from(val); - } - return data; - }, - // Note: Do not use to.hex() when you really mean utils.addHexPrefix(). - hex: function(val) { - if (typeof val === "string") { - if (val.indexOf("0x") === 0) { - return val.trim(); - } else { - val = new utils.BN(val); - } - } - - if (typeof val === "boolean") { - val = val ? 1 : 0; - } - - if (typeof val === "number") { - val = utils.intToHex(val); - } else if (val == null) { - return "0x"; - } else if (typeof val === "object") { - // Support Buffer, BigInteger and BN library - // Hint: BN is used in ethereumjs - val = val.toString("hex"); - } - - return utils.addHexPrefix(val); - }, - - _rpcQuantityHexString: function(val) { - val = this.hex(val); - // remove all zeroes leading zeros, `0+`, from the hex-encoded value - // This doesn't remove the last 0 which would be captured by `(.+?)` - val = val.replace(/^(?:0x)(?:0+(.+?))?$/, "0x$1"); - return val; - }, - - rpcQuantityHexString: function(val) { - val = this._rpcQuantityHexString(val); - - // RPC Quantities must represent `0` as `0x0` - if (val === "0x") { - val = "0x0"; - } - - return val; - }, - - rpcQuantityBuffer: function(val) { - val = this._rpcQuantityHexString(val); - - if (val === "0x0") { - val = "0x"; - } - - return utils.rlp.encode(val); - }, - - rpcDataHexString: function(val, length) { - if (typeof length === "number") { - val = this.hex(val).replace("0x", ""); - - val = new Array(length - val.length).fill("0").join("") + val; - } else { - if (val.length === 0) { - return "0x"; - } - val = this.hex(val).replace("0x", ""); - - if (val.length % 2 !== 0) { - val = "0" + val; - } - } - return "0x" + val; - }, - - nullableRpcDataHexString: function(val, length) { - if (val === null) { - return null; - } else { - const rpcDataHex = this.rpcDataHexString(val, length); - return rpcDataHex === "0x" ? null : rpcDataHex; - } - }, - - nullableRpcQuantityHexString: function(val, length) { - if (val === null) { - return null; - } else { - const rpcQuantityHex = this._rpcQuantityHexString(val, length); - return rpcQuantityHex === "0x" ? null : rpcQuantityHex; - } - }, - - hexWithZeroPadding: function(val) { - val = this.hex(val); - const digits = val.replace("0x", ""); - if (digits.length & 0x1) { - return "0x0" + digits; - } - return val; - }, - - number: function(val) { - if (typeof val === "number") { - return val; - } - if (typeof val === "string") { - if (val.indexOf("0x") !== 0) { - return parseInt(val, 10); - } - } - var bufVal = utils.toBuffer(val); - return utils.bufferToInt(bufVal); - }, - - rpcError: function(id, code, msg) { - return JSON.stringify({ - jsonrpc: "2.0", - id: id, - error: { - code: code, - message: msg - } - }); - } -}; diff --git a/lib/utils/transaction.js b/lib/utils/transaction.js deleted file mode 100644 index 32ecb244ba..0000000000 --- a/lib/utils/transaction.js +++ /dev/null @@ -1,331 +0,0 @@ -const EthereumJsTransaction = require("ethereumjs-tx").Transaction; -const EthereumJsFakeTransaction = require("ethereumjs-tx").FakeTransaction; -const Common = require("ethereumjs-common").default; -const ethUtil = require("ethereumjs-util"); -const assert = require("assert"); -const rlp = ethUtil.rlp; -const to = require("./to"); - -const sign = EthereumJsTransaction.prototype.sign; -const fakeHash = function() { - // this isn't memoization of the hash. previous versions of ganache-core - // created hashes in a different/incorrect way and are recorded this way - // in snapshot dbs. We are preserving the chain's immutability by using the - // stored hash instead of calculating it. - if (this._hash != null) { - return this._hash; - } - return EthereumJsFakeTransaction.prototype.hash.apply(this, arguments); -}; -const BUFFER_ZERO = Buffer.from([0]); - -function configZeroableField(tx, fieldName, fieldLength = 32) { - const index = tx._fields.indexOf(fieldName); - const descriptor = Object.getOwnPropertyDescriptor(tx, fieldName); - // eslint-disable-next-line accessor-pairs - Object.defineProperty(tx, fieldName, { - set: (v) => { - descriptor.set.call(tx, v); - v = ethUtil.toBuffer(v); - assert(fieldLength >= v.length, `The field ${fieldName} must not have more ${fieldLength} bytes`); - tx._originals[index] = v; - }, - get: () => { - return tx._originals[index]; - } - }); -} - -/** - * etheruemjs-tx's Transactions don't behave quite like we need them to, so - * we're monkey-patching them to do what we want here. - * @param {Transaction} tx The Transaction to fix - * @param {Object} [data] The data object - */ -function fixProps(tx, data) { - // ethereumjs-tx doesn't allow for a `0` value in fields, but we want it to - // in order to differentiate between a value that isn't set and a value - // that is set to 0 in a fake transaction. - // Once https://github.com/ethereumjs/ethereumjs-tx/issues/112 is figured - // out we can probably remove this fix/hack. - // We keep track of the original value and return that value when - // referenced by its property name. This lets us properly encode a `0` as - // an empty buffer while still being able to differentiate between a `0` - // and `null`/`undefined`. - tx._originals = []; - const fieldNames = ["nonce", "gasPrice", "gasLimit", "value"]; - fieldNames.forEach((fieldName) => configZeroableField(tx, fieldName, 32)); - - if (tx.isFake()) { - /** - * @prop {Buffer} from (read/write) Set from address to bypass transaction - * signing on fake transactions. - */ - Object.defineProperty(tx, "from", { - enumerable: true, - configurable: true, - get: tx.getSenderAddress.bind(tx), - set: (val) => { - if (val) { - tx._from = ethUtil.toBuffer(val); - } else { - tx._from = null; - } - } - }); - - if (data && data.from) { - tx.from = data.from; - } - - tx.hash = fakeHash; - } -} - -/** - * Parses the given data object and adds its properties to the given tx. - * @param {Transaction} tx - * @param {Object} [data] - */ -function initData(tx, data) { - if (data) { - if (typeof data === "string") { - data = to.buffer(data); - } - if (Buffer.isBuffer(data)) { - data = rlp.decode(data); - } - const self = tx; - if (Array.isArray(data)) { - if (data.length > tx._fields.length) { - throw new Error("wrong number of fields in data"); - } - - // make sure all the items are buffers - data.forEach((d, i) => { - self[self._fields[i]] = ethUtil.toBuffer(d); - }); - } else if ((typeof data === "undefined" ? "undefined" : typeof data) === "object") { - const keys = Object.keys(data); - tx._fields.forEach(function(field) { - if (keys.indexOf(field) !== -1) { - let val = data[field]; - if (typeof val === "string" && !val.startsWith("0x")) { - val = "0x" + val; - } - self[field] = val; - } - if (field === "gasLimit") { - if (keys.indexOf("gas") !== -1) { - self.gas = data.gas; - } - } else if (field === "data") { - if (keys.indexOf("input") !== -1) { - self.input = data.input; - } - } - }); - - // Set chainId value from the data, if it's there and the data didn't - // contain a `v` value with chainId in it already. If we do have a - // data.chainId value let's set the interval v value to it. - if (!tx._chainId && data && data.chainId != null) { - tx.raw[self._fields.indexOf("v")] = tx._chainId = data.chainId || 0; - } - } else { - throw new Error("invalid data"); - } - } -} - -module.exports = class Transaction extends EthereumJsTransaction { - /** - * @param {Object} [data] The data for this Transaction. - * @param {Number} type The `Transaction.types` bit flag for this transaction - * @param {Object} [common] EthereumJS common.fromCustomChain() - * Can be a combination of `Transaction.types.none`, `Transaction.types.signed`, and `Transaction.types.fake`. - */ - constructor(data, type = Transaction.types.none, common) { - if (data.chainId && !common) { - common = Common.forCustomChain( - "mainnet", // TODO needs to match chain id - { - name: "ganache", - networkId: 1, - chainId: data.chainId, - comment: "Local test network", - bootstrapNodes: [] - }, - "muirGlacier" - ); - } - super(undefined, { common }); - - this.ganacheTxCommon = common; - this.type = type; - - fixProps(this, data); - initData(this, data); - } - - static get types() { - // values must be powers of 2 - return { - none: 0, - signed: 1, - fake: 2 - }; - } - - /** - * Prepares arbitrary JSON data for use in a Transaction. - * @param {Object} json JSON object representing the Transaction - * @param {Number} type The `Transaction.types` bit flag for this transaction - * @param {Object} [common] EthereumJS common.fromCustomChain() - * @param {Number} [networkId] - * @param {string} [hardfork] - * Can be a combination of `Transaction.types.none`, `Transaction.types.signed`, and `Transaction.types.fake`. - */ - static fromJSON(json, type, common, networkId, hardfork) { - let toAccount; - if (json.to) { - // Remove all padding and make it easily comparible. - const buf = to.buffer(json.to); - if (buf.equals(Buffer.from([0]))) { - // if the address is 0x0 make it 0x0{20} - toAccount = ethUtil.setLengthLeft(buf, 20); - } else { - toAccount = buf; - } - } - const data = json.data || json.input; - const options = { - nonce: ethUtil.toBuffer(to.hex(json.nonce)), - from: ethUtil.toBuffer(to.hex(json.from)), - value: ethUtil.toBuffer(to.hex(json.value)), - gasLimit: ethUtil.toBuffer(to.hex(json.gas || json.gasLimit)), - gasPrice: ethUtil.toBuffer(to.hex(json.gasPrice)), - data: data ? to.buffer(data) : null, - to: toAccount, - v: ethUtil.toBuffer(json.v), - r: ethUtil.toBuffer(json.r), - s: ethUtil.toBuffer(json.s) - }; - if (!common && options.v.length > 0) { - const chainId = Math.floor((json.v - 35) / 2); - common = Common.forCustomChain( - "mainnet", // TODO needs to match chain id - { - name: "ganache", - networkId: networkId, - chainId: chainId >= 0 ? chainId : 1, - comment: "Local test network", - bootstrapNodes: [] - }, - hardfork - ); - } - const tx = new Transaction(options, type, common); - tx._hash = json.hash ? to.buffer(json.hash) : null; - return tx; - } - - /** - * Encodes the Transaction in order to be used in a database. Can be decoded - * into an identical Transaction via `Transaction.decode(encodedTx)`. - */ - encode() { - const resultJSON = { - hash: to.nullableRpcDataHexString(this.hash()), - nonce: to.nullableRpcQuantityHexString(this.nonce) || "0x", - from: to.rpcDataHexString(this.from), - to: to.nullableRpcDataHexString(this.to), - value: to.nullableRpcQuantityHexString(this.value), - gas: to.nullableRpcQuantityHexString(this.gasLimit), - gasPrice: to.nullableRpcQuantityHexString(this.gasPrice), - data: this.data ? this.data.toString("hex") : null, - v: to.nullableRpcQuantityHexString(this.v), - r: to.nullableRpcQuantityHexString(this.r), - s: to.nullableRpcQuantityHexString(this.s), - _type: this.type, - _options: { - hardfork: this.ganacheTxCommon.hardfork(), - chainId: this.ganacheTxCommon.chainId(), - networkId: this.ganacheTxCommon.networkId() - } - }; - return resultJSON; - } - - isFake() { - return (this.type & Transaction.types.fake) === Transaction.types.fake; - } - - isSigned() { - return (this.type & Transaction.types.signed) === Transaction.types.signed; - } - - /** - * Compares the transaction's nonce value to the given expectedNonce taking in - * to account the type of transaction and comparison rules for each type. - * - * In a signed transaction a nonce of Buffer([]) is the same as Buffer([0]), - * but in a fake transaction Buffer([]) is null and Buffer([0]) is 0. - * - * @param {Buffer} expectedNonce The value of the from account's next nonce. - */ - validateNonce(expectedNonce) { - let nonce; - if (this.isSigned() && this.nonce.length === 0) { - nonce = BUFFER_ZERO; - } else { - nonce = this.nonce; - } - return nonce.equals(expectedNonce); - } - - /** - * Signs the transaction and sets the `type` bit for `signed` to 1, - * i.e., `isSigned() === true` - */ - sign() { - sign.apply(this, arguments); - this.type |= Transaction.types.signed; - } - - /** - * Returns a JSON-RPC spec compliant representation of this Transaction. - * - * @param {Object} block The block this Transaction appears in. - */ - toJsonRpc(block) { - const hash = this.hash(); - - let transactionIndex = null; - for (let i = 0, txns = block.transactions, l = txns.length; i < l; i++) { - if (txns[i].hash().equals(hash)) { - transactionIndex = i; - break; - } - } - - const resultJSON = { - hash: to.nullableRpcDataHexString(hash), - nonce: to.rpcQuantityHexString(this.nonce), - blockHash: to.nullableRpcDataHexString(block.hash()), - blockNumber: to.nullableRpcQuantityHexString(block.header.number), - transactionIndex: to.nullableRpcQuantityHexString(transactionIndex), - from: to.rpcDataHexString(this.from), - to: to.nullableRpcDataHexString(this.to), - value: to.rpcQuantityHexString(this.value), - gas: to.rpcQuantityHexString(this.gasLimit), - gasPrice: to.rpcQuantityHexString(this.gasPrice), - input: to.rpcDataHexString(this.data), - v: to.nullableRpcQuantityHexString(this.v), - r: to.nullableRpcQuantityHexString(this.r), - s: to.nullableRpcQuantityHexString(this.s) - }; - - return resultJSON; - } -}; diff --git a/lib/utils/txrejectederror.js b/lib/utils/txrejectederror.js deleted file mode 100644 index 588a29ec4b..0000000000 --- a/lib/utils/txrejectederror.js +++ /dev/null @@ -1,14 +0,0 @@ -var inherits = require("util").inherits; - -// raised when the transaction is rejected prior to running it in the EVM. -function TXRejectedError(message) { - // Why not just Error.apply(this, [message])? See - // https://gist.github.com/justmoon/15511f92e5216fa2624b#anti-patterns - Error.captureStackTrace(this, this.constructor); - this.name = this.constructor.name; - this.message = message; -} - -inherits(TXRejectedError, Error); - -module.exports = TXRejectedError; diff --git a/package.json b/package.json index da2a8d5064..01d6bb00ae 100644 --- a/package.json +++ b/package.json @@ -1,174 +1,58 @@ { - "name": "godmode-ganache-core", - "version": "2.13.2", - "description": "GodMode version", - "main": "./index.js", - "types": "./typings/index.d.ts", + "name": "root", + "author": "David Murdoch (https://davidmurdoch.com)", + "private": true, "engines": { - "node": ">=8.9.0" - }, - "directories": { - "lib": "./lib" + "node": ">=10.7.0 <=14.14.0", + "npm": ">=6.1.0" }, "scripts": { - "_mocha": "mocha --check-leaks --recursive", - "nyc_mocha": "nyc npm run _mocha", - "_lint": "eslint --ignore-path .gitignore .", - "build": "webpack-cli --config ./webpack/node/core.webpack.config.js", - "build-web": "webpack-cli --config ./webpack/web-experimental/core.webpack.config.js", - "format": "prettier --write \"{lib,perf,test}/**/*.js\" && eslint --fix --ignore-path .gitignore .", - "prepare": "patch-package --exclude 'nothing'", - "prepublishOnly": "npm run test && npm run build && npm run test-build && npm prune --production && mv npm-shrinkwrap.json npm-shrinkwrap.json.bak && npm shrinkwrap", - "postpublish": "rm npm-shrinkwrap.json && mv npm-shrinkwrap.json.bak npm-shrinkwrap.json && npm ci", - "test": "npm run _lint && npm run nyc_mocha ./test/local", - "test-smoke": "npm run _mocha ./test/smoke", - "test-build": "cross-env TEST_BUILD=node npm run _mocha", - "test-web-experimental": "cross-env TEST_BUILD=web-experimental npm run _mocha", - "coverage": "nyc report --reporter=text-lcov | coveralls" - }, - "bundleDependencies": [ - "keccak" - ], - "dependencies": { - "abstract-leveldown": "3.0.0", - "async": "2.6.2", - "bip39": "2.5.0", - "cachedown": "1.0.0", - "clone": "2.1.2", - "debug": "3.2.6", - "encoding-down": "5.0.4", - "eth-sig-util": "3.0.0", - "ethereumjs-abi": "0.6.8", - "ethereumjs-account": "3.0.0", - "ethereumjs-block": "2.2.2", - "ethereumjs-common": "1.5.0", - "ethereumjs-tx": "2.1.2", - "ethereumjs-util": "6.2.0", - "ethereumjs-vm": "4.1.3", - "heap": "0.2.6", - "keccak": "3.0.1", - "level-sublevel": "6.6.4", - "levelup": "3.1.1", - "lodash": "4.17.20", - "lru-cache": "5.1.1", - "merkle-patricia-tree": "3.0.0", - "patch-package": "6.2.2", - "seedrandom": "3.0.1", - "source-map-support": "0.5.12", - "tmp": "0.1.0", - "web3": "1.2.11", - "web3-provider-engine": "14.2.1", - "websocket": "1.0.32" + "build": "lerna run build", + "clean": "npx shx rm -rf node_modules && npx lerna clean -y && npm run tsc.clean", + "create": "ts-node ./scripts/create", + "docs.build": "lerna run docs.build", + "docs.preview": "lerna run docs.preview", + "postinstall": "lerna bootstrap && ts-node ./scripts/postinstall", + "reinstall": "npm run clean && npm install", + "test": "lerna exec -- npm run test", + "tsc": "lerna exec -- npm run tsc", + "tsc.clean": "npx lerna exec -- npx shx rm -rf lib dist" }, "devDependencies": { - "@types/web3": "^1.0.19", - "assert-match": "^1.1.1", - "browserfs": "1.4.3", - "coveralls": "^3.0.9", - "cross-env": "6.0.3", - "eslint": "6.8.0", - "eslint-config-standard": "14.1.0", - "eslint-plugin-import": "2.20.0", - "eslint-plugin-node": "11.0.0", - "eslint-plugin-promise": "4.2.1", - "eslint-plugin-standard": "4.0.1", - "ethereumjs-wallet": "0.6.5", - "ethers": "4.0.48", - "husky": "4.0.10", - "js-scrypt": "0.2.0", - "lint-staged": "10.0.0", - "memdown": "5.1.0", - "mocha": "7.2.0", - "mocha-lcov-reporter": "^1.3.0", - "number-to-bn": "1.7.0", - "nyc": "^15.1.0", - "pify": "4.0.1", - "portfinder": "^1.0.25", - "prettier": "^1.19.1", - "request": "^2.88.0", - "semver": "7.1.1", - "solc": "0.6.1", - "temp": "0.9.1", - "terser-webpack-plugin": "2.3.8", - "web3": "1.2.11", - "webpack": "4.41.5", - "webpack-bundle-size-analyzer": "3.1.0", - "webpack-cli": "3.3.10" - }, - "optionalDependencies": { - "ethereumjs-wallet": "0.6.5", - "web3": "1.2.11" - }, - "repository": { - "type": "git", - "url": "https://github.com/xGodMode/godmode-ganache-core" + "@istanbuljs/nyc-config-typescript": "1.0.1", + "@types/fs-extra": "9.0.2", + "@types/mocha": "8.0.3", + "@types/node": "14.14.6", + "@types/npm-package-arg": "6.1.0", + "@types/prettier": "2.1.5", + "@types/yargs": "15.0.9", + "@zerollup/ts-transform-paths": "1.7.18", + "camelcase": "6.1.0", + "chalk": "4.1.0", + "cli-highlight": "2.1.4", + "cross-env": "7.0.2", + "fs-extra": "9.0.1", + "git-user-name": "2.0.0", + "husky": "4.3.0", + "into-stream": "6.0.0", + "lerna": "3.22.1", + "mocha": "8.2.0", + "monaco-editor": "0.21.2", + "npm-package-arg": "8.1.0", + "nyc": "15.1.0", + "prettier": "2.1.2", + "pretty-quick": "3.1.0", + "shx": "0.3.3", + "ts-node": "9.0.0", + "ts-transformer-inline-file": "0.1.1", + "ttypescript": "1.5.12", + "typescript": "4.1.1-rc", + "yargs": "16.1.0" }, "license": "MIT", "husky": { "hooks": { - "pre-commit": "lint-staged" + "pre-commit": "pretty-quick --staged" } - }, - "lint-staged": { - "*.js": [ - "prettier --write", - "eslint --fix --ignore-path .gitignore", - "git add" - ] - }, - "eslintConfig": { - "root": true, - "env": { - "node": true, - "es6": true - }, - "parserOptions": { - "ecmaVersion": 8, - "sourceType": "module", - "ecmaFeatures": { - "impliedStrict": true - } - }, - "extends": "standard", - "rules": { - "brace-style": [ - "error", - "1tbs", - { - "allowSingleLine": false - } - ], - "curly": [ - "error", - "all" - ], - "max-len": [ - "error", - 120, - { - "ignoreRegExpLiterals": true - } - ], - "quotes": [ - "error", - "double" - ], - "semi": [ - "error", - "always" - ], - "space-before-function-paren": [ - "error", - "never" - ], - "wrap-iife": [ - "error", - "outside" - ] - } - }, - "prettier": { - "printWidth": 120, - "arrowParens": "always" } } diff --git a/patches/keccak+3.0.1.patch b/patches/keccak+3.0.1.patch deleted file mode 100644 index 56a6f56ba4..0000000000 --- a/patches/keccak+3.0.1.patch +++ /dev/null @@ -1,11 +0,0 @@ -diff --git a/node_modules/keccak/index.js b/node_modules/keccak/index.js -index 24fb71a..bb62b01 100644 ---- a/node_modules/keccak/index.js -+++ b/node_modules/keccak/index.js -@@ -1,5 +1 @@ --try { -- module.exports = require('./bindings') --} catch (err) { -- module.exports = require('./js') --} -+module.exports = require('./js') diff --git a/patches/web3-provider-engine+14.2.1.patch b/patches/web3-provider-engine+14.2.1.patch deleted file mode 100644 index 5d61c1fa7c..0000000000 --- a/patches/web3-provider-engine+14.2.1.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/node_modules/web3-provider-engine/package.json b/node_modules/web3-provider-engine/package.json -index 71a37ab..a8503a8 100644 ---- a/node_modules/web3-provider-engine/package.json -+++ b/node_modules/web3-provider-engine/package.json -@@ -22,7 +22,7 @@ - "cross-fetch": "^2.1.0", - "eth-block-tracker": "^3.0.0", - "eth-json-rpc-infura": "^3.1.0", -- "eth-sig-util": "^1.4.2", -+ "eth-sig-util": "3.0.0", - "ethereumjs-block": "^1.2.2", - "ethereumjs-tx": "^1.2.0", - "ethereumjs-util": "^5.1.5", diff --git a/perf/transactions.js b/perf/transactions.js deleted file mode 100755 index faa9376947..0000000000 --- a/perf/transactions.js +++ /dev/null @@ -1,94 +0,0 @@ -#!/usr/bin/env node -var TestRPC = require("../"); -var Web3 = require("web3"); -var async = require("async"); - -var server = TestRPC.server(); -var port = 12345; -var web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:" + port)); - -function runTest(times, fn, callback) { - var start = new Date(); - - async.timesSeries(times, fn, function(err) { - if (err) { - return callback(err); - } - - var end = new Date(); - var actualTime = end.getTime() - start.getTime(); - - callback(null, actualTime); - }); -} - -function runAverage(title, numberOfRuns, fnTimes, fn, callback) { - var results = new Array(numberOfRuns); - - async.timesSeries( - numberOfRuns, - function(n, next) { - process.stdout.write(title + " " + (n + 1) + "..."); - - runTest(fnTimes, fn, function(err, totalTime) { - if (err) { - return next(err); - } - results[n] = totalTime; - - console.log(totalTime / 1000 + " seconds"); - next(); - }); - }, - function(err) { - if (err) { - return callback(err); - } - - var sum = results.reduce(function(a, b) { - return a + b; - }, 0); - - var average = sum / numberOfRuns; - - console.log("Average " + average / 1000 + " seconds"); - - callback(null, average); - } - ); -} - -function bailIfError(_) { - if (_) { - process.exit(1); - } -} - -server.listen(port, function(err) { - bailIfError(err); - - web3.eth.getAccounts(function(err, accounts) { - bailIfError(err); - - runAverage( - "Running transactions test", - 4, - 1000, - function(n, cb) { - web3.eth.sendTransaction( - { - from: accounts[0], - to: accounts[1], - value: 500, // wei - gas: 90000 - }, - cb - ); - }, - function(err) { - bailIfError(err); - server.close(); - } - ); - }); -}); diff --git a/public-exports.js b/public-exports.js deleted file mode 100644 index 5b2c85e771..0000000000 --- a/public-exports.js +++ /dev/null @@ -1,16 +0,0 @@ -// make sourcemaps work! -require("source-map-support/register"); - -const Provider = require("./lib/provider"); -const Server = require("./lib/server"); - -// This interface exists so as not to cause breaking changes. -module.exports = { - server: function(options) { - return Server.create(options); - }, - provider: function(options) { - return new Provider(options); - }, - _webpacked: true -}; diff --git a/scripts/create.ts b/scripts/create.ts new file mode 100644 index 0000000000..aab83848c2 --- /dev/null +++ b/scripts/create.ts @@ -0,0 +1,279 @@ +// TODO: make this its own package? + +import chalk from "chalk"; +import yargs from "yargs"; +import prettier from "prettier"; +import camelCase from "camelcase"; +import npa from "npm-package-arg"; +import userName from "git-user-name"; +import { join, resolve } from "path"; +import { highlight } from "cli-highlight"; +import { mkdir, mkdirSync, writeFile } from "fs-extra"; +import { + lstatSync as lstat, + readdirSync as readDir, + readFileSync as readFile +} from "fs"; + +const isDir = (s: string) => lstat(s).isDirectory(); +const getDirectories = (s: string) => readDir(s).filter(n => isDir(join(s, n))); + +const COLORS = { + Bold: "\x1b[1m", + Reset: "\x1b[0m", + FgRed: "\x1b[31m" +}; + +const scopes = getDirectories(join(__dirname, "../src")); +const argv = yargs + .command( + `$0 --location`, + `Create a new package in the given location with the provided name.`, + yargs => { + return yargs + .usage( + chalk`{hex("#e4a663").bold Create a new package in the given location with the provided name.}\n\n` + + chalk`{bold Usage}\n {bold $} {dim <}name{dim >} {dim --}location {dim <}${scopes.join( + chalk.dim(" | ") + )}{dim >}` + ) + .positional("", { + describe: ` The name of the new package`, + type: "string", + demandOption: true + }) + .alias("name", "") + .option("location", { + alias: "l", + default: "packages", + describe: `The location for the new package.`, + choices: scopes, + type: "string", + demandOption: true + }); + } + ) + .demandCommand() + .version(false) + .help(false) + .updateStrings({ + "Positionals:": chalk.bold("Options"), + "Options:": ` `, + "Not enough non-option arguments: got %s, need at least %s": { + one: chalk`{red {bold ERROR! Not enough non-option arguments:}\n got %s, need at least %s}`, + other: chalk`{red {bold ERROR! Not enough non-option arguments:}\n got %s, need at least %s}` + } as any, + "Invalid values:": `${COLORS.FgRed}${COLORS.Bold}ERROR! Invalid values:${COLORS.Reset}${COLORS.FgRed}` + }) + .fail((msg, err, yargs) => { + // we use a custom `fail` fn so that NPM doesn't print its own giant error message. + if (err) throw err; + + console.error(yargs.help().toString().replace("\n\n\n", "\n")); + console.error(); + console.error(msg); + process.exit(0); + }).argv; +process.stdout.write(`${COLORS.Reset}`); + +(async function () { + let name = argv.name; + let location = argv.location; + + try { + const workspaceDir = join(__dirname, "../"); + const LICENSE = readFile(join(workspaceDir, "LICENSE"), "utf-8"); + + const prettierConfig = await prettier.resolveConfig(process.cwd()); + + name = npa(name).name; + + const packageName = `@ganache/${name}`; + let packageAuthor = userName(); + const version = "0.1.0"; + + const pkg = { + name: packageName, + version, + description: "", + author: packageAuthor || require("../package.json").author, + homepage: `https://github.com/trufflesuite/ganache-core/tree/develop/src/${location}/${name}#readme`, + license: "MIT", + main: "lib/index.js", + types: "src/index.ts", + source: "index.ts", + directories: { + lib: "lib", + test: "test" + }, + files: ["lib"], + repository: { + type: "git", + url: "https://github.com/trufflesuite/ganache-core.git", + directory: `src/${location}/${name}` + }, + scripts: { + tsc: "ttsc", + test: "nyc npm run mocha", + mocha: + "cross-env TS_NODE_COMPILER=ttypescript TS_NODE_FILES=true mocha --exit --check-leaks --throw-deprecation --trace-warnings --require ts-node/register 'tests/**/*.test.ts'" + }, + bugs: { + url: "https://github.com/trufflesuite/ganache-core/issues" + }, + keywords: [ + "ganache", + `ganache-${name}`, + "ethereum", + "evm", + "blockchain", + "smart contracts", + "dapps", + "solidity", + "vyper", + "fe", + "web3", + "tooling", + "truffle" + ] + }; + + const tsConfig = { + extends: "../../../tsconfig.json", + compilerOptions: { + outDir: "lib" + }, + include: ["src"] + }; + + const shrinkwrap = { + name: packageName, + version: version, + lockfileVersion: 1 + }; + + const testFile = `import assert from "assert"; +import ${camelCase(name)} from "../src/"; + +describe("${packageName}", () => { + it("needs tests"); +})`; + + const indexFile = `export default { + // TODO +} +`; + + const dir = join(workspaceDir, "src", location, name); + const tests = join(dir, "tests"); + const src = join(dir, "src"); + + function initSrc() { + return writeFile( + join(src, "index.ts"), + prettier.format(indexFile, { ...prettierConfig, parser: "typescript" }) + ); + } + + function initIndex() { + // When a bundler compiles our libs this headerdoc comment will cause that + // tool to retain our LICENSE information in their bundled output. + const headerdoc = `/*! + * ${packageName} + * + * @copyright Truffle Blockchain Group + * @author ${pkg.author} + * @license ${pkg.license} +*/ + +`; + return writeFile( + join(dir, "index.ts"), + prettier.format(headerdoc + indexFile, { + ...prettierConfig, + parser: "typescript" + }) + ); + } + + function initRootFiles() { + return Promise.all([ + writeFile( + join(dir, ".npmignore"), + `./index.ts +tests +.nyc_output +coverage +scripts +src +tsconfig.json +typedoc.json +` + ), + writeFile(join(dir, "LICENSE"), LICENSE) + ]); + } + + function initTests() { + return writeFile( + join(tests, "index.test.ts"), + prettier.format(testFile, { ...prettierConfig, parser: "typescript" }) + ); + } + + const pkgStr = JSON.stringify(pkg, null, 2) + "\n"; + const pkgPath = join(dir, "package.json"); + + console.log(`About to write to ${resolve(__dirname, pkgPath)}`); + console.log(""); + + mkdirSync(dir); + + await Promise.all([ + initRootFiles(), + initIndex(), + mkdir(tests).then(initTests), + mkdir(src).then(initSrc), + writeFile( + join(dir, "tsconfig.json"), + JSON.stringify(tsConfig, null, 2) + "\n" + ), + writeFile( + join(dir, "README.md"), + prettier.format(`# \`${packageName}\`\n> TODO: description`, { + ...prettierConfig, + parser: "markdown" + }) + ), + writeFile(pkgPath, pkgStr), + writeFile( + join(dir, "npm-shrinkwrap.json"), + JSON.stringify(shrinkwrap) + "\n" + ) + ]); + + console.log( + highlight(pkgStr, { + language: "json", + theme: { + attr: chalk.hex("#3FE0C5"), + string: chalk.hex("#e4a663") + } + }) + ); + + console.log( + chalk`{green success} {magenta create} New package {bgBlack ${name} } created at ./src/packages/${name}.` + ); + console.log(""); + console.log( + chalk` Update the package.json here: {bold ${dir}/package.json}` + ); + } catch (e) { + console.error(e); + console.log(""); + console.log( + chalk`{red fail} {magenta create} New package {bgBlack ${name} } not created. See error above.` + ); + } +})(); diff --git a/scripts/postinstall.ts b/scripts/postinstall.ts new file mode 100644 index 0000000000..bf6bb8e904 --- /dev/null +++ b/scripts/postinstall.ts @@ -0,0 +1,7 @@ +import chalk from "chalk"; + +console.log(""); +console.log( + chalk`{bold.cyan Tip:} {cyan run} {bold.yellow.dim source completions.sh} {cyan to supply bash completions for npm scripts}` +); +console.log(""); diff --git a/src/chains/ethereum/.npmignore b/src/chains/ethereum/.npmignore new file mode 100644 index 0000000000..115ab4f024 --- /dev/null +++ b/src/chains/ethereum/.npmignore @@ -0,0 +1,8 @@ +./index.ts +tests +.nyc_output +coverage +scripts +src +tsconfig.json +typedoc.json diff --git a/src/chains/ethereum/LICENSE b/src/chains/ethereum/LICENSE new file mode 100644 index 0000000000..39f3b14498 --- /dev/null +++ b/src/chains/ethereum/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2019-2020 Truffle Blockchain Group + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/chains/ethereum/README.md b/src/chains/ethereum/README.md new file mode 100644 index 0000000000..7a449f8f46 --- /dev/null +++ b/src/chains/ethereum/README.md @@ -0,0 +1,3 @@ +# `@ganache/ethereum` + +This is ganache's Ethereum client implementation. diff --git a/src/chains/ethereum/index.ts b/src/chains/ethereum/index.ts new file mode 100644 index 0000000000..e035a1fa9f --- /dev/null +++ b/src/chains/ethereum/index.ts @@ -0,0 +1,9 @@ +/*! + * @ganache/ethereum + * + * @copyright 2019-2020 Truffle Blockchain Group + * @author David Murdoch (https://davidmurdoch.com) + * @license MIT + */ + +export * from "./src/connector"; diff --git a/src/chains/ethereum/npm-shrinkwrap.json b/src/chains/ethereum/npm-shrinkwrap.json new file mode 100644 index 0000000000..9e70451782 --- /dev/null +++ b/src/chains/ethereum/npm-shrinkwrap.json @@ -0,0 +1,3919 @@ +{ + "name": "@ganache/ethereum", + "version": "0.1.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@koa/cors": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@koa/cors/-/cors-3.1.0.tgz", + "integrity": "sha512-7ulRC1da/rBa6kj6P4g2aJfnET3z8Uf3SWu60cjbtxTA5g8lxRdX/Bd2P92EagGwwAhANeNw8T8if99rJliR6Q==", + "dev": true, + "requires": { + "vary": "^1.1.2" + } + }, + "@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "dev": true + }, + "@trufflesuite/typedoc-default-themes": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@trufflesuite/typedoc-default-themes/-/typedoc-default-themes-0.6.1.tgz", + "integrity": "sha512-/wt3Jp+fD/DsxArEMixt94hDjHlB6R82Xa2ffjoCzlXrF8OSidzmzYkMvuxi53t1PaQvobNY5wMTXUqGnt/HXA==", + "dev": true, + "requires": { + "backbone": "^1.4.0", + "jquery": "^3.4.1", + "lunr": "^2.3.6", + "underscore": "^1.9.1" + } + }, + "@types/bn.js": { + "version": "4.11.6", + "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-4.11.6.tgz", + "integrity": "sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==", + "requires": { + "@types/node": "*" + } + }, + "@types/fs-extra": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.2.tgz", + "integrity": "sha512-jp0RI6xfZpi5JL8v7WQwpBEQTq63RqW2kxwTZt+m27LcJqQdPVU1yGnT1ZI4EtCDynQQJtIGyQahkiCGCS7e+A==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/keccak": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/keccak/-/keccak-3.0.1.tgz", + "integrity": "sha512-/MxAVmtyyeOvZ6dGf3ciLwFRuV5M8DRIyYNFGHYI6UyBW4/XqyO0LZw+JFMvaeY3cHItQAkELclBU1x5ank6mg==", + "requires": { + "@types/node": "*" + } + }, + "@types/lodash": { + "version": "4.14.164", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.164.tgz", + "integrity": "sha512-fXCEmONnrtbYUc5014avwBeMdhHHO8YJCkOBflUL9EoJBSKZ1dei+VO74fA7JkTHZ1GvZack2TyIw5U+1lT8jg==", + "dev": true + }, + "@types/lodash.clonedeep": { + "version": "4.5.6", + "resolved": "https://registry.npmjs.org/@types/lodash.clonedeep/-/lodash.clonedeep-4.5.6.tgz", + "integrity": "sha512-cE1jYr2dEg1wBImvXlNtp0xDoS79rfEdGozQVgliDZj1uERH4k+rmEMTudP9b4VQ8O6nRb5gPqft0QzEQGMQgA==", + "dev": true, + "requires": { + "@types/lodash": "*" + } + }, + "@types/node": { + "version": "14.0.23", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.23.tgz", + "integrity": "sha512-Z4U8yDAl5TFkmYsZdFPdjeMa57NOvnaf1tljHzhouaPEp7LCj2JKkejpI1ODviIAQuW4CcQmxkQ77rnLsOOoKw==" + }, + "@types/pbkdf2": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/pbkdf2/-/pbkdf2-3.1.0.tgz", + "integrity": "sha512-Cf63Rv7jCQ0LaL8tNXmEyqTHuIJxRdlS5vMh1mj5voN4+QFhVZnlZruezqpWYDiJ8UTzhP0VmeLXCmBk66YrMQ==", + "requires": { + "@types/node": "*" + } + }, + "@types/secp256k1": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/secp256k1/-/secp256k1-4.0.1.tgz", + "integrity": "sha512-+ZjSA8ELlOp8SlKi0YLB2tz9d5iPNEmOBd+8Rz21wTMdaXQIa9b6TEnD6l5qKOCypE7FSyPyck12qZJxSDNoog==", + "requires": { + "@types/node": "*" + } + }, + "@types/seedrandom": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@types/seedrandom/-/seedrandom-2.4.28.tgz", + "integrity": "sha512-SMA+fUwULwK7sd/ZJicUztiPs8F1yCPwF3O23Z9uQ32ME5Ha0NmDK9+QTsYE4O2tHXChzXomSWWeIhCnoN1LqA==", + "dev": true + }, + "@types/ws": { + "version": "7.2.7", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.2.7.tgz", + "integrity": "sha512-UUFC/xxqFLP17hTva8/lVT0SybLUrfSD9c+iapKb0fEiC8uoDbA+xuZ3pAN603eW+bY8ebSMLm9jXdIPnD0ZgA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "dev": true, + "requires": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + } + }, + "abstract-leveldown": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-2.6.3.tgz", + "integrity": "sha512-2++wDf/DYqkPR3o5tbfdhF96EfMApo1GpPfzOsR/ZYXdkSmELlvOOEAl9iKkRsktMPHdGjO4rtkBpf2I7TiTeA==", + "requires": { + "xtend": "~4.0.0" + }, + "dependencies": { + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + } + } + }, + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "dev": true, + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + } + }, + "agent-base": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.1.tgz", + "integrity": "sha512-01q25QQDwLSsyfhrKbn8yuur+JNw0H+0Y4JiGIKd3z9aYk/w/2kxD/Upc+t2ZBBSUNff50VjPsSW2YxM8QYKVg==", + "dev": true, + "requires": { + "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "ansi-escape-sequences": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/ansi-escape-sequences/-/ansi-escape-sequences-5.1.2.tgz", + "integrity": "sha512-JcpoVp1W1bl1Qn4cVuiXEhD6+dyXKSOgCn2zlzE8inYgCJCBy1aPnUhlz6I4DFum8D4ovb9Qi/iAjUcGvG2lqw==", + "dev": true, + "requires": { + "array-back": "^4.0.0" + } + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=", + "dev": true + }, + "array-back": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-4.0.1.tgz", + "integrity": "sha512-Z/JnaVEXv+A9xabHzN43FiiiWEE7gPCRXMrVmRm00tWbjZRul1iHm7ECzlyNq1p4a4ATXz+G9FJ3GqGOkOV3fg==", + "dev": true + }, + "async": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", + "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "requires": { + "lodash": "^4.17.14" + } + }, + "async-eventemitter": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/async-eventemitter/-/async-eventemitter-0.2.4.tgz", + "integrity": "sha512-pd20BwL7Yt1zwDFy+8MX8F1+WCT8aQeKj0kQnTrH9WaeRETlRamVhD0JtRPmrV4GfOJ2F9CvdQkZeZhnh2TuHw==", + "requires": { + "async": "^2.4.0" + } + }, + "at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true + }, + "backbone": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/backbone/-/backbone-1.4.0.tgz", + "integrity": "sha512-RLmDrRXkVdouTg38jcgHhyQ/2zjg7a8E6sz2zxfz21Hh17xDJYUHBZimVIt5fUyS8vbfpeSmTL3gUjTEvUV3qQ==", + "dev": true, + "requires": { + "underscore": ">=1.8.3" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "base-x": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.8.tgz", + "integrity": "sha512-Rl/1AWP4J/zRrk54hhlxH4drNxPJXYUaKffODVI53/dAsV4t9fBxyxYKAVPU1XBHxYwOWP9h9H0hM2MVw4YfJA==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" + }, + "basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dev": true, + "requires": { + "safe-buffer": "5.1.2" + }, + "dependencies": { + "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==", + "dev": true + } + } + }, + "batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", + "dev": true + }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "requires": { + "file-uri-to-path": "1.0.0" + } + }, + "bip39": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/bip39/-/bip39-3.0.2.tgz", + "integrity": "sha512-J4E1r2N0tUylTKt07ibXvhpT2c5pyAFgvuA5q1H9uDy6dEGpjV8jmymh3MTYJDLCNbIVClSB9FbND49I6N24MQ==", + "requires": { + "@types/node": "11.11.6", + "create-hash": "^1.1.0", + "pbkdf2": "^3.0.9", + "randombytes": "^2.0.1" + }, + "dependencies": { + "@types/node": { + "version": "11.11.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-11.11.6.tgz", + "integrity": "sha512-Exw4yUWMBXM3X+8oqzJNRqZSwUAaS4+7NdvHqQuFi/d+synz++xmX3QIf+BFqneW8N31R8Ky+sikfZUXq07ggQ==" + } + } + }, + "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" + } + }, + "blakejs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.1.0.tgz", + "integrity": "sha1-ad+S75U6qIylGjLfarHFShVfx6U=" + }, + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==" + }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", + "dev": true + }, + "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=" + }, + "browserify-aes": { + "version": "1.2.0", + "resolved": "https://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" + } + }, + "bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha1-vhYedsNU9veIrkBx9j806MTwpCo=", + "requires": { + "base-x": "^3.0.2" + } + }, + "bs58check": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz", + "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", + "requires": { + "bs58": "^4.0.0", + "create-hash": "^1.1.0", + "safe-buffer": "^5.1.2" + } + }, + "buffer": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz", + "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==", + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" + } + }, + "buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=" + }, + "byte-size": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/byte-size/-/byte-size-6.2.0.tgz", + "integrity": "sha512-6EspYUCAPMc7E2rltBgKwhG+Cmk0pDm9zDtF1Awe2dczNUL3YpZ8mTs/dueOTS1hqGWBOatqef4jYMGjln7WmA==", + "dev": true + }, + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "dev": true + }, + "cache-content-type": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-content-type/-/cache-content-type-1.0.1.tgz", + "integrity": "sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==", + "dev": true, + "requires": { + "mime-types": "^2.1.18", + "ylru": "^1.2.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "checkpoint-store": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/checkpoint-store/-/checkpoint-store-1.1.0.tgz", + "integrity": "sha1-BOTLUWuRQziTWB5tRgGnjpVS6gY=", + "requires": { + "functional-red-black-tree": "^1.0.1" + } + }, + "cheerio": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.3.tgz", + "integrity": "sha512-0td5ijfUPuubwLUu0OBoe98gZj8C/AA+RW3v67GPlGOrvxWjZmBXiBCRU+I8VEiNyJzjth40POfHiz2RB3gImA==", + "dev": true, + "requires": { + "css-select": "~1.2.0", + "dom-serializer": "~0.1.1", + "entities": "~1.1.1", + "htmlparser2": "^3.9.1", + "lodash": "^4.15.0", + "parse5": "^3.0.1" + } + }, + "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" + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true + }, + "co-body": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/co-body/-/co-body-6.0.0.tgz", + "integrity": "sha512-9ZIcixguuuKIptnY8yemEOuhb71L/lLf+Rl5JfJEUiDNJk0e02MBt7BPxR2GEh5mw8dPthQYR4jPI/BnS1MQgw==", + "dev": true, + "requires": { + "inflation": "^2.0.0", + "qs": "^6.5.2", + "raw-body": "^2.3.3", + "type-is": "^1.6.16" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "command-exists": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", + "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==", + "dev": true + }, + "command-line-args": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.1.1.tgz", + "integrity": "sha512-hL/eG8lrll1Qy1ezvkant+trihbGnaKaeEjj6Scyr3DN+RC7iQ5Rz84IeLERfAWDGo0HBSNAakczwgCilDXnWg==", + "dev": true, + "requires": { + "array-back": "^3.0.1", + "find-replace": "^3.0.0", + "lodash.camelcase": "^4.3.0", + "typical": "^4.0.0" + }, + "dependencies": { + "array-back": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", + "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==", + "dev": true + }, + "typical": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz", + "integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==", + "dev": true + } + } + }, + "command-line-usage": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/command-line-usage/-/command-line-usage-6.1.0.tgz", + "integrity": "sha512-Ew1clU4pkUeo6AFVDFxCbnN7GIZfXl48HIOQeFQnkO3oOqvpI7wdqtLRwv9iOCZ/7A+z4csVZeiDdEcj8g6Wiw==", + "dev": true, + "requires": { + "array-back": "^4.0.0", + "chalk": "^2.4.2", + "table-layout": "^1.0.0", + "typical": "^5.2.0" + }, + "dependencies": { + "typical": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", + "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", + "dev": true + } + } + }, + "commander": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-3.0.2.tgz", + "integrity": "sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==", + "dev": true + }, + "common-log-format": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/common-log-format/-/common-log-format-1.0.0.tgz", + "integrity": "sha512-fFn/WPNbsTCGTTwdCpZfVZSa5mgqMEkA0gMTRApFSlEsYN+9B2FPfiqch5FT+jsv5IV1RHV3GeZvCa7Qg+jssw==", + "dev": true + }, + "compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "requires": { + "mime-db": ">= 1.43.0 < 2" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "dev": true, + "requires": { + "safe-buffer": "5.1.2" + }, + "dependencies": { + "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==", + "dev": true + } + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true + }, + "cookies": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.8.0.tgz", + "integrity": "sha512-8aPsApQfebXnuI+537McwYsDtjVxGm8gTIzQI3FDW6t5t/DAhERxtnbEPN/8RX+uZthoz4eCOgloXaE5cYyNow==", + "dev": true, + "requires": { + "depd": "~2.0.0", + "keygrip": "~1.1.0" + }, + "dependencies": { + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true + } + } + }, + "copy-to": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/copy-to/-/copy-to-2.0.1.tgz", + "integrity": "sha1-JoD7uAaKSNCGVrYJgJK9r8kG9KU=", + "dev": true + }, + "core-js-pure": { + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.6.5.tgz", + "integrity": "sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA==" + }, + "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=" + }, + "create-hash": { + "version": "1.2.0", + "resolved": "https://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": "https://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" + } + }, + "create-mixin": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/create-mixin/-/create-mixin-3.0.0.tgz", + "integrity": "sha512-LkdMqnWT9LaqBN4huqpUnMz56Yr1mVSoCduAd2xXefgH/YZP2sXCMAyztXjk4q8hTF/TlcDa+zQW2aTgGdjjKQ==", + "dev": true + }, + "css-select": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", + "dev": true, + "requires": { + "boolbase": "~1.0.0", + "css-what": "2.1", + "domutils": "1.5.1", + "nth-check": "~1.0.1" + } + }, + "css-what": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", + "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==", + "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" + } + }, + "deep-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", + "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", + "dev": true + }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true + }, + "deferred-leveldown": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/deferred-leveldown/-/deferred-leveldown-1.2.2.tgz", + "integrity": "sha512-uukrWD2bguRtXilKt6cAWKyoXrTSMo5m7crUdLfWQmu8kIm88w3QZoUL+6nhpfKVmhHANER6Re3sKoNoZ3IKMA==", + "requires": { + "abstract-leveldown": "~2.6.0" + } + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "requires": { + "object-keys": "^1.0.12" + }, + "dependencies": { + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + } + } + }, + "defined": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/defined/-/defined-0.0.0.tgz", + "integrity": "sha1-817qfXBekzuvE7LwOz+D2SFAOz4=" + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "dev": true + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", + "dev": true + }, + "dom-serializer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", + "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", + "dev": true, + "requires": { + "domelementtype": "^1.3.0", + "entities": "^1.1.1" + } + }, + "domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", + "dev": true + }, + "domhandler": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", + "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "dev": true, + "requires": { + "domelementtype": "1" + } + }, + "domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "dev": true, + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "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" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "dev": true + }, + "elliptic": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz", + "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==", + "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" + } + }, + "emittery": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.7.2.tgz", + "integrity": "sha512-A8OG5SR/ij3SsJdWDJdkkSYUjQdCUx6APQXem0SaEePBSRg4eymGYwBkKo1Y6DU+af/Jn2dBQqDBvjnr9Vi8nQ==" + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "dev": true + }, + "encoding-down": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/encoding-down/-/encoding-down-5.0.4.tgz", + "integrity": "sha512-8CIZLDcSKxgzT+zX8ZVfgNbu8Md2wq/iqa1Y7zyVR18QBEAc0Nmzuvj/N5ykSKpfGzjM8qxbaFntLPwnVoUhZw==", + "requires": { + "abstract-leveldown": "^5.0.0", + "inherits": "^2.0.3", + "level-codec": "^9.0.0", + "level-errors": "^2.0.0", + "xtend": "^4.0.1" + }, + "dependencies": { + "abstract-leveldown": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-5.0.0.tgz", + "integrity": "sha512-5mU5P1gXtsMIXg65/rsYGsi93+MlogXZ9FA8JnwKurHQg64bfXwGYVdVdijNTVNOlAsuIiOwHdvFFD5JqCJQ7A==", + "requires": { + "xtend": "~4.0.0" + } + }, + "level-codec": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/level-codec/-/level-codec-9.0.2.tgz", + "integrity": "sha512-UyIwNb1lJBChJnGfjmO0OR+ezh2iVu1Kas3nvBS/BzGnx79dv6g7unpKIDNPMhfdTEGoc7mC8uAu51XEtX+FHQ==", + "requires": { + "buffer": "^5.6.0" + } + }, + "level-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/level-errors/-/level-errors-2.0.1.tgz", + "integrity": "sha512-UVprBJXite4gPS+3VznfgDSU8PTRuVX0NXwoWW50KLxd2yw4Y1t2JUR5In1itQnudZqRMT9DlAM3Q//9NCjCFw==", + "requires": { + "errno": "~0.1.1" + } + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + } + } + }, + "entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", + "dev": true + }, + "errno": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", + "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", + "requires": { + "prr": "~1.0.1" + } + }, + "es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", + "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + }, + "dependencies": { + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + } + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", + "dev": true + }, + "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 + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "dev": true + }, + "eth-sig-util": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/eth-sig-util/-/eth-sig-util-2.5.3.tgz", + "integrity": "sha512-KpXbCKmmBUNUTGh9MRKmNkIPietfhzBqqYqysDavLseIiMUGl95k6UcPEkALAZlj41e9E6yioYXc1PC333RKqw==", + "requires": { + "buffer": "^5.2.1", + "elliptic": "^6.4.0", + "ethereumjs-abi": "0.6.5", + "ethereumjs-util": "^5.1.1", + "tweetnacl": "^1.0.0", + "tweetnacl-util": "^0.15.0" + }, + "dependencies": { + "ethereumjs-abi": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/ethereumjs-abi/-/ethereumjs-abi-0.6.5.tgz", + "integrity": "sha1-WmN+8Wq0NHP6cqKa2QhxQFs/UkE=", + "requires": { + "bn.js": "^4.10.0", + "ethereumjs-util": "^4.3.0" + }, + "dependencies": { + "ethereumjs-util": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-4.5.1.tgz", + "integrity": "sha512-WrckOZ7uBnei4+AKimpuF1B3Fv25OmoRgmYCpGsP7u8PFxXAmAgiJSYT2kRWnt6fVIlKaQlZvuwXp7PIrmn3/w==", + "requires": { + "bn.js": "^4.8.0", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "rlp": "^2.0.0" + } + } + } + }, + "ethereumjs-util": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.1.tgz", + "integrity": "sha512-v3kT+7zdyCm1HIqWlLNrHGqHGLpGYIhjeHxQjnDXjLT2FyGJDsd3LWMYUo7pAFRrk86CR3nUJfhC81CCoJNNGQ==", + "requires": { + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "^0.1.3", + "rlp": "^2.0.0", + "safe-buffer": "^5.1.1" + } + } + } + }, + "ethashjs": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ethashjs/-/ethashjs-0.0.8.tgz", + "integrity": "sha512-/MSbf/r2/Ld8o0l15AymjOTlPqpN8Cr4ByUEA9GtR4x0yAh3TdtDzEg29zMjXCNPI7u6E5fOQdj/Cf9Tc7oVNw==", + "requires": { + "async": "^2.1.2", + "buffer-xor": "^2.0.1", + "ethereumjs-util": "^7.0.2", + "miller-rabin": "^4.0.0" + }, + "dependencies": { + "bn.js": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.2.tgz", + "integrity": "sha512-40rZaf3bUNKTVYu9sIeeEGOg7g14Yvnj9kH7b50EiwX0Q7A6umbvfI5tvHaOERH0XigqKkfLkFQxzb4e6CIXnA==" + }, + "buffer-xor": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-2.0.2.tgz", + "integrity": "sha512-eHslX0bin3GB+Lx2p7lEYRShRewuNZL3fUl4qlVJGGiwoPGftmt8JQgk2Y9Ji5/01TnVDo33E5b5O3vUB1HdqQ==", + "requires": { + "safe-buffer": "^5.1.1" + } + }, + "ethereumjs-util": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.0.3.tgz", + "integrity": "sha512-uLQsGPOwsRxe50WV1Dybh5N8zXDz4ev7wP49LKX9kr28I5TmcDILPgpKK/BFe5zYSfRGEeo+hPT7W3tjghYLuA==", + "requires": { + "@types/bn.js": "^4.11.3", + "bn.js": "^5.1.2", + "create-hash": "^1.1.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "0.1.6", + "rlp": "^2.2.4" + } + } + } + }, + "ethereum-cryptography": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", + "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", + "requires": { + "@types/pbkdf2": "^3.0.0", + "@types/secp256k1": "^4.0.1", + "blakejs": "^1.1.0", + "browserify-aes": "^1.2.0", + "bs58check": "^2.1.2", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "hash.js": "^1.1.7", + "keccak": "^3.0.0", + "pbkdf2": "^3.0.17", + "randombytes": "^2.1.0", + "safe-buffer": "^5.1.2", + "scrypt-js": "^3.0.0", + "secp256k1": "^4.0.1", + "setimmediate": "^1.0.5" + } + }, + "ethereumjs-abi": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/ethereumjs-abi/-/ethereumjs-abi-0.6.8.tgz", + "integrity": "sha512-Tx0r/iXI6r+lRsdvkFDlut0N08jWMnKRZ6Gkq+Nmw75lZe4e6o3EkSnkaBP5NF6+m5PTGAr9JP43N3LyeoglsA==", + "requires": { + "bn.js": "^4.11.8", + "ethereumjs-util": "^6.0.0" + }, + "dependencies": { + "ethereumjs-util": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-6.2.1.tgz", + "integrity": "sha512-W2Ktez4L01Vexijrm5EB6w7dg4n/TgpoYU4avuT5T3Vmnw/eCRtiBrJfQYS/DCSvDIOLn2k57GcHdeBcgVxAqw==", + "requires": { + "@types/bn.js": "^4.11.3", + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "0.1.6", + "rlp": "^2.2.3" + } + } + } + }, + "ethereumjs-account": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ethereumjs-account/-/ethereumjs-account-3.0.0.tgz", + "integrity": "sha512-WP6BdscjiiPkQfF9PVfMcwx/rDvfZTjFKY0Uwc09zSQr9JfIVH87dYIJu0gNhBhpmovV4yq295fdllS925fnBA==", + "requires": { + "ethereumjs-util": "^6.0.0", + "rlp": "^2.2.1", + "safe-buffer": "^5.1.1" + }, + "dependencies": { + "ethereumjs-util": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-6.2.1.tgz", + "integrity": "sha512-W2Ktez4L01Vexijrm5EB6w7dg4n/TgpoYU4avuT5T3Vmnw/eCRtiBrJfQYS/DCSvDIOLn2k57GcHdeBcgVxAqw==", + "requires": { + "@types/bn.js": "^4.11.3", + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "0.1.6", + "rlp": "^2.2.3" + } + } + } + }, + "ethereumjs-block": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/ethereumjs-block/-/ethereumjs-block-2.2.2.tgz", + "integrity": "sha512-2p49ifhek3h2zeg/+da6XpdFR3GlqY3BIEiqxGF8j9aSRIgkb7M1Ky+yULBKJOu8PAZxfhsYA+HxUk2aCQp3vg==", + "requires": { + "async": "^2.0.1", + "ethereumjs-common": "^1.5.0", + "ethereumjs-tx": "^2.1.1", + "ethereumjs-util": "^5.0.0", + "merkle-patricia-tree": "^2.1.2" + }, + "dependencies": { + "ethereumjs-util": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.1.tgz", + "integrity": "sha512-v3kT+7zdyCm1HIqWlLNrHGqHGLpGYIhjeHxQjnDXjLT2FyGJDsd3LWMYUo7pAFRrk86CR3nUJfhC81CCoJNNGQ==", + "requires": { + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "^0.1.3", + "rlp": "^2.0.0", + "safe-buffer": "^5.1.1" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "levelup": { + "version": "1.3.9", + "resolved": "https://registry.npmjs.org/levelup/-/levelup-1.3.9.tgz", + "integrity": "sha512-VVGHfKIlmw8w1XqpGOAGwq6sZm2WwWLmlDcULkKWQXEA5EopA8OBNJ2Ck2v6bdk8HeEZSbCSEgzXadyQFm76sQ==", + "requires": { + "deferred-leveldown": "~1.2.1", + "level-codec": "~7.0.0", + "level-errors": "~1.0.3", + "level-iterator-stream": "~1.3.0", + "prr": "~1.0.1", + "semver": "~5.4.1", + "xtend": "~4.0.0" + } + }, + "merkle-patricia-tree": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/merkle-patricia-tree/-/merkle-patricia-tree-2.3.2.tgz", + "integrity": "sha512-81PW5m8oz/pz3GvsAwbauj7Y00rqm81Tzad77tHBwU7pIAtN+TJnMSOJhxBKflSVYhptMMb9RskhqHqrSm1V+g==", + "requires": { + "async": "^1.4.2", + "ethereumjs-util": "^5.0.0", + "level-ws": "0.0.0", + "levelup": "^1.2.1", + "memdown": "^1.0.0", + "readable-stream": "^2.0.0", + "rlp": "^2.0.0", + "semaphore": ">=1.0.1" + }, + "dependencies": { + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" + } + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "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" + } + }, + "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==" + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + } + } + }, + "ethereumjs-blockchain": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/ethereumjs-blockchain/-/ethereumjs-blockchain-4.0.3.tgz", + "integrity": "sha512-0nJWbyA+Gu0ZKZr/cywMtB/77aS/4lOVsIKbgUN2sFQYscXO5rPbUfrEe7G2Zhjp86/a0VqLllemDSTHvx3vZA==", + "requires": { + "async": "^2.6.1", + "ethashjs": "~0.0.7", + "ethereumjs-block": "~2.2.2", + "ethereumjs-common": "^1.5.0", + "ethereumjs-util": "~6.1.0", + "flow-stoplight": "^1.0.0", + "level-mem": "^3.0.1", + "lru-cache": "^5.1.1", + "rlp": "^2.2.2", + "semaphore": "^1.1.0" + }, + "dependencies": { + "ethereumjs-util": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-6.1.0.tgz", + "integrity": "sha512-URESKMFbDeJxnAxPppnk2fN6Y3BIatn9fwn76Lm8bQlt+s52TpG8dN9M66MLPuRAiAOIqL3dfwqWJf0sd0fL0Q==", + "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" + } + }, + "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" + } + }, + "secp256k1": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-3.8.0.tgz", + "integrity": "sha512-k5ke5avRZbtl9Tqx/SA7CbY3NF6Ro+Sj9cZxezFzuBlLDmyqPiL8hJJ+EmzD8Ig4LUDByHJ3/iPOVoRixs/hmw==", + "requires": { + "bindings": "^1.5.0", + "bip66": "^1.1.5", + "bn.js": "^4.11.8", + "create-hash": "^1.2.0", + "drbg.js": "^1.0.1", + "elliptic": "^6.5.2", + "nan": "^2.14.0", + "safe-buffer": "^5.1.2" + } + } + } + }, + "ethereumjs-common": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/ethereumjs-common/-/ethereumjs-common-1.5.2.tgz", + "integrity": "sha512-hTfZjwGX52GS2jcVO6E2sx4YuFnf0Fhp5ylo4pEPhEffNln7vS59Hr5sLnp3/QCazFLluuBZ+FZ6J5HTp0EqCA==" + }, + "ethereumjs-tx": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ethereumjs-tx/-/ethereumjs-tx-2.1.2.tgz", + "integrity": "sha512-zZEK1onCeiORb0wyCXUvg94Ve5It/K6GD1K+26KfFKodiBiS6d9lfCXlUKGBBdQ+bv7Day+JK0tj1K+BeNFRAw==", + "requires": { + "ethereumjs-common": "^1.5.0", + "ethereumjs-util": "^6.0.0" + }, + "dependencies": { + "ethereumjs-util": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-6.2.1.tgz", + "integrity": "sha512-W2Ktez4L01Vexijrm5EB6w7dg4n/TgpoYU4avuT5T3Vmnw/eCRtiBrJfQYS/DCSvDIOLn2k57GcHdeBcgVxAqw==", + "requires": { + "@types/bn.js": "^4.11.3", + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "0.1.6", + "rlp": "^2.2.3" + } + } + } + }, + "ethereumjs-util": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.0.7.tgz", + "integrity": "sha512-vU5rtZBlZsgkTw3o6PDKyB8li2EgLavnAbsKcfsH2YhHH1Le+PP8vEiMnAnvgc1B6uMoaM5GDCrVztBw0Q5K9g==", + "requires": { + "@types/bn.js": "^4.11.3", + "bn.js": "^5.1.2", + "create-hash": "^1.1.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "0.1.6", + "rlp": "^2.2.4" + }, + "dependencies": { + "bn.js": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.3.tgz", + "integrity": "sha512-GkTiFpjFtUzU9CbMeJ5iazkCzGL3jrhzerzZIuqLABjbwRaFt33I9tUdSNryIptM+RxDet6OKm2WnLXzW51KsQ==" + } + } + }, + "ethereumjs-vm": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/ethereumjs-vm/-/ethereumjs-vm-4.2.0.tgz", + "integrity": "sha512-X6qqZbsY33p5FTuZqCnQ4+lo957iUJMM6Mpa6bL4UW0dxM6WmDSHuI4j/zOp1E2TDKImBGCJA9QPfc08PaNubA==", + "requires": { + "async": "^2.1.2", + "async-eventemitter": "^0.2.2", + "core-js-pure": "^3.0.1", + "ethereumjs-account": "^3.0.0", + "ethereumjs-block": "^2.2.2", + "ethereumjs-blockchain": "^4.0.3", + "ethereumjs-common": "^1.5.0", + "ethereumjs-tx": "^2.1.2", + "ethereumjs-util": "^6.2.0", + "fake-merkle-patricia-tree": "^1.0.1", + "functional-red-black-tree": "^1.0.1", + "merkle-patricia-tree": "^2.3.2", + "rustbn.js": "~0.2.0", + "safe-buffer": "^5.1.1", + "util.promisify": "^1.0.0" + }, + "dependencies": { + "ethereumjs-util": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-6.2.1.tgz", + "integrity": "sha512-W2Ktez4L01Vexijrm5EB6w7dg4n/TgpoYU4avuT5T3Vmnw/eCRtiBrJfQYS/DCSvDIOLn2k57GcHdeBcgVxAqw==", + "requires": { + "@types/bn.js": "^4.11.3", + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "0.1.6", + "rlp": "^2.2.3" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "levelup": { + "version": "1.3.9", + "resolved": "https://registry.npmjs.org/levelup/-/levelup-1.3.9.tgz", + "integrity": "sha512-VVGHfKIlmw8w1XqpGOAGwq6sZm2WwWLmlDcULkKWQXEA5EopA8OBNJ2Ck2v6bdk8HeEZSbCSEgzXadyQFm76sQ==", + "requires": { + "deferred-leveldown": "~1.2.1", + "level-codec": "~7.0.0", + "level-errors": "~1.0.3", + "level-iterator-stream": "~1.3.0", + "prr": "~1.0.1", + "semver": "~5.4.1", + "xtend": "~4.0.0" + } + }, + "merkle-patricia-tree": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/merkle-patricia-tree/-/merkle-patricia-tree-2.3.2.tgz", + "integrity": "sha512-81PW5m8oz/pz3GvsAwbauj7Y00rqm81Tzad77tHBwU7pIAtN+TJnMSOJhxBKflSVYhptMMb9RskhqHqrSm1V+g==", + "requires": { + "async": "^1.4.2", + "ethereumjs-util": "^5.0.0", + "level-ws": "0.0.0", + "levelup": "^1.2.1", + "memdown": "^1.0.0", + "readable-stream": "^2.0.0", + "rlp": "^2.0.0", + "semaphore": ">=1.0.1" + }, + "dependencies": { + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" + }, + "ethereumjs-util": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.1.tgz", + "integrity": "sha512-v3kT+7zdyCm1HIqWlLNrHGqHGLpGYIhjeHxQjnDXjLT2FyGJDsd3LWMYUo7pAFRrk86CR3nUJfhC81CCoJNNGQ==", + "requires": { + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "^0.1.3", + "rlp": "^2.0.0", + "safe-buffer": "^5.1.1" + } + } + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "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": { + "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==" + } + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + }, + "dependencies": { + "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==" + } + } + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + } + } + }, + "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" + } + }, + "fake-merkle-patricia-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fake-merkle-patricia-tree/-/fake-merkle-patricia-tree-1.0.1.tgz", + "integrity": "sha1-S4w6z7Ugr635hgsfFM2M40As3dM=", + "requires": { + "checkpoint-store": "^1.1.0" + } + }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" + }, + "find-replace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz", + "integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==", + "dev": true, + "requires": { + "array-back": "^3.0.1" + }, + "dependencies": { + "array-back": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", + "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==", + "dev": true + } + } + }, + "flow-stoplight": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/flow-stoplight/-/flow-stoplight-1.0.0.tgz", + "integrity": "sha1-SiksW8/4s5+mzAyxqFPYbyfu/3s=" + }, + "follow-redirects": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz", + "integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==", + "dev": true + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "dev": true + }, + "fs-extra": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz", + "integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==", + "dev": true, + "requires": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^1.0.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=" + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "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" + } + }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + }, + "handlebars": { + "version": "4.7.6", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.6.tgz", + "integrity": "sha512-1f2BACcBfiwAfStCKZNrUCgqNZkGsAT7UM3kkYtXuLo0KnaVfjKOyf7PRzB6++aK9STyT1Pd2ZCPe3EGOXleXA==", + "dev": true, + "requires": { + "minimist": "^1.2.5", + "neo-async": "^2.6.0", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4", + "wordwrap": "^1.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" + }, + "hash-base": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", + "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", + "requires": { + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + } + }, + "hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "hdkey": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hdkey/-/hdkey-2.0.1.tgz", + "integrity": "sha512-c+tl9PHG9/XkGgG0tD7CJpRVaE0jfZizDNmnErUAKQ4EjQSOcOUcV3EN9ZEZS8pZ4usaeiiK0H7stzuzna8feA==", + "requires": { + "bs58check": "^2.1.2", + "safe-buffer": "^5.1.1", + "secp256k1": "^4.0.0" + } + }, + "highlight.js": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.1.2.tgz", + "integrity": "sha512-Q39v/Mn5mfBlMff9r+zzA+gWxRsCRKwEMvYTiisLr/XUiFI/4puWt0Ojdko3R3JCNWGdOWaA5g/Yxqa23kC5AA==", + "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" + } + }, + "htmlparser2": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", + "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", + "dev": true, + "requires": { + "domelementtype": "^1.3.1", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^3.1.1" + } + }, + "http-assert": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.4.1.tgz", + "integrity": "sha512-rdw7q6GTlibqVVbXr0CKelfV5iY8G2HqEUkhSk297BMbSpSL8crXC+9rjKoMcZZEsksX30le6f/4ul4E28gegw==", + "dev": true, + "requires": { + "deep-equal": "~1.0.1", + "http-errors": "~1.7.2" + }, + "dependencies": { + "http-errors": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", + "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + } + } + }, + "http-errors": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.0.tgz", + "integrity": "sha512-4I8r0C5JDhT5VkvI47QktDW75rNlGVsUf/8hzjCC/wkWI/jdTRmBb9aI7erSG82r1bjKY3F6k28WnsVxB1C73A==", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + }, + "dependencies": { + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + } + } + }, + "http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "dev": true, + "requires": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "dev": true, + "requires": { + "agent-base": "6", + "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + }, + "immediate": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.3.0.tgz", + "integrity": "sha512-HR7EVodfFUdQCTIeySw+WDRFJlPcLOJbXfwwZ7Oom6tjsvZ3bOkCDJHehQC3nxJrv7+f9XecwazynjU8e4Vw3Q==" + }, + "inflation": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/inflation/-/inflation-2.0.0.tgz", + "integrity": "sha1-i0F+R8KPklpFEz2RTKH9OJEH8w8=", + "dev": true + }, + "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.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "dev": true + }, + "is-callable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", + "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==" + }, + "is-date-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==" + }, + "is-docker": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.0.0.tgz", + "integrity": "sha512-pJEdRugimx4fBMra5z2/5iRdZ63OhYV0vr0Dwm5+xtW4D1FvRkB8hamMIhnWfyJeDdyr/aa7BDyNbtG38VxgoQ==", + "dev": true + }, + "is-generator-function": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.7.tgz", + "integrity": "sha512-YZc5EwyO4f2kWCax7oegfuSr9mFz1ZvieNYBEjmukLxgXfBUbxAWGVF7GZf0zidYtoBl3WvC07YK0wT76a+Rtw==", + "dev": true + }, + "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-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.0.tgz", + "integrity": "sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw==", + "requires": { + "has-symbols": "^1.0.1" + } + }, + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "requires": { + "has-symbols": "^1.0.1" + } + }, + "is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "requires": { + "is-docker": "^2.0.0" + } + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "jquery": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.5.1.tgz", + "integrity": "sha512-XwIBPqcMn57FxfT+Go5pzySnm4KWkT1Tv7gjrpT1srtf8Weynl6R273VJ5GjkRb51IzMp5nbaPjJXMWeju2MKg==", + "dev": true + }, + "js-sha3": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", + "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==", + "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 + }, + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + }, + "dependencies": { + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true + } + } + }, + "jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", + "dev": true + }, + "keccak": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.0.tgz", + "integrity": "sha512-/4h4FIfFEpTEuySXi/nVFM5rqSKPnnhI7cL4K3MFSwoI3VyM7AhPSq3SsysARtnEBEeIKMBUWD8cTh9nHE8AkA==", + "requires": { + "node-addon-api": "^2.0.0", + "node-gyp-build": "^4.2.0" + } + }, + "keygrip": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz", + "integrity": "sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==", + "dev": true, + "requires": { + "tsscmp": "1.0.6" + } + }, + "klaw": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", + "integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.9" + } + }, + "koa": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/koa/-/koa-2.13.0.tgz", + "integrity": "sha512-i/XJVOfPw7npbMv67+bOeXr3gPqOAw6uh5wFyNs3QvJ47tUx3M3V9rIE0//WytY42MKz4l/MXKyGkQ2LQTfLUQ==", + "dev": true, + "requires": { + "accepts": "^1.3.5", + "cache-content-type": "^1.0.0", + "content-disposition": "~0.5.2", + "content-type": "^1.0.4", + "cookies": "~0.8.0", + "debug": "~3.1.0", + "delegates": "^1.0.0", + "depd": "^1.1.2", + "destroy": "^1.0.4", + "encodeurl": "^1.0.2", + "escape-html": "^1.0.3", + "fresh": "~0.5.2", + "http-assert": "^1.3.0", + "http-errors": "^1.6.3", + "is-generator-function": "^1.0.7", + "koa-compose": "^4.1.0", + "koa-convert": "^1.2.0", + "on-finished": "^2.3.0", + "only": "~0.0.2", + "parseurl": "^1.3.2", + "statuses": "^1.5.0", + "type-is": "^1.6.16", + "vary": "^1.1.2" + } + }, + "koa-bodyparser": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/koa-bodyparser/-/koa-bodyparser-4.3.0.tgz", + "integrity": "sha512-uyV8G29KAGwZc4q/0WUAjH+Tsmuv9ImfBUF2oZVyZtaeo0husInagyn/JH85xMSxM0hEk/mbCII5ubLDuqW/Rw==", + "dev": true, + "requires": { + "co-body": "^6.0.0", + "copy-to": "^2.0.1" + } + }, + "koa-compose": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-4.1.0.tgz", + "integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==", + "dev": true + }, + "koa-compress": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/koa-compress/-/koa-compress-3.1.0.tgz", + "integrity": "sha512-0m24/yS/GbhWI+g9FqtvStY+yJwTObwoxOvPok6itVjRen7PBWkjsJ8pre76m+99YybXLKhOJ62mJ268qyBFMQ==", + "dev": true, + "requires": { + "bytes": "^3.0.0", + "compressible": "^2.0.0", + "koa-is-json": "^1.0.0", + "statuses": "^1.0.0" + } + }, + "koa-conditional-get": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/koa-conditional-get/-/koa-conditional-get-2.0.0.tgz", + "integrity": "sha1-pD83I8HQFLcwo07Oit8wuTyCM/I=", + "dev": true + }, + "koa-convert": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/koa-convert/-/koa-convert-1.2.0.tgz", + "integrity": "sha1-2kCHXfSd4FOQmNFwC1CCDOvNIdA=", + "dev": true, + "requires": { + "co": "^4.6.0", + "koa-compose": "^3.0.0" + }, + "dependencies": { + "koa-compose": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-3.2.1.tgz", + "integrity": "sha1-qFzLQLfZhtjlo0Wzoazo6rz1Tec=", + "dev": true, + "requires": { + "any-promise": "^1.1.0" + } + } + } + }, + "koa-etag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/koa-etag/-/koa-etag-3.0.0.tgz", + "integrity": "sha1-nvc4Ld1agqsN6xU0FckVg293HT8=", + "dev": true, + "requires": { + "etag": "^1.3.0", + "mz": "^2.1.0" + } + }, + "koa-is-json": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/koa-is-json/-/koa-is-json-1.0.0.tgz", + "integrity": "sha1-JzwH7c3Ljfaiwat9We52SRRR7BQ=", + "dev": true + }, + "koa-json": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/koa-json/-/koa-json-2.0.2.tgz", + "integrity": "sha1-Nq8U5uofXWRtfESihXAcb4Wk/eQ=", + "dev": true, + "requires": { + "koa-is-json": "1", + "streaming-json-stringify": "3" + } + }, + "koa-morgan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/koa-morgan/-/koa-morgan-1.0.1.tgz", + "integrity": "sha1-CAUuDODYOdPEMXi5CluzQkvvH5k=", + "dev": true, + "requires": { + "morgan": "^1.6.1" + } + }, + "koa-range": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/koa-range/-/koa-range-0.3.0.tgz", + "integrity": "sha1-NYjjSWRzqDmhvSZNKkKx2FvX/qw=", + "dev": true, + "requires": { + "stream-slice": "^0.1.2" + } + }, + "koa-route": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/koa-route/-/koa-route-3.2.0.tgz", + "integrity": "sha1-dimLmaa8+p44yrb+XHmocz51i84=", + "dev": true, + "requires": { + "debug": "*", + "methods": "~1.1.0", + "path-to-regexp": "^1.2.0" + }, + "dependencies": { + "path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dev": true, + "requires": { + "isarray": "0.0.1" + } + } + } + }, + "koa-send": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/koa-send/-/koa-send-5.0.1.tgz", + "integrity": "sha512-tmcyQ/wXXuxpDxyNXv5yNNkdAMdFRqwtegBXUaowiQzUKqJehttS0x2j0eOZDQAyloAth5w6wwBImnFzkUz3pQ==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "http-errors": "^1.7.3", + "resolve-path": "^1.4.0" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "koa-static": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/koa-static/-/koa-static-5.0.0.tgz", + "integrity": "sha512-UqyYyH5YEXaJrf9S8E23GoJFQZXkBVJ9zYYMPGz919MSX1KuvAcycIuS0ci150HCoPf4XQVhQ84Qf8xRPWxFaQ==", + "dev": true, + "requires": { + "debug": "^3.1.0", + "koa-send": "^5.0.0" + } + }, + "level-codec": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/level-codec/-/level-codec-7.0.1.tgz", + "integrity": "sha512-Ua/R9B9r3RasXdRmOtd+t9TCOEIIlts+TN/7XTT2unhDaL6sJn83S3rUyljbr6lVtw49N3/yA0HHjpV6Kzb2aQ==" + }, + "level-concat-iterator": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/level-concat-iterator/-/level-concat-iterator-2.0.1.tgz", + "integrity": "sha512-OTKKOqeav2QWcERMJR7IS9CUo1sHnke2C0gkSmcR7QuEtFNLLzHQAvnMw8ykvEcv0Qtkg0p7FOwP1v9e5Smdcw==" + }, + "level-errors": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/level-errors/-/level-errors-1.0.5.tgz", + "integrity": "sha512-/cLUpQduF6bNrWuAC4pwtUKA5t669pCsCi2XbmojG2tFeOr9j6ShtdDCtFFQO1DRt+EVZhx9gPzP9G2bUaG4ig==", + "requires": { + "errno": "~0.1.1" + } + }, + "level-iterator-stream": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/level-iterator-stream/-/level-iterator-stream-1.3.1.tgz", + "integrity": "sha1-5Dt4sagUPm+pek9IXrjqUwNS8u0=", + "requires": { + "inherits": "^2.0.1", + "level-errors": "^1.0.3", + "readable-stream": "^1.0.33", + "xtend": "^4.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + } + } + }, + "level-mem": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/level-mem/-/level-mem-3.0.1.tgz", + "integrity": "sha512-LbtfK9+3Ug1UmvvhR2DqLqXiPW1OJ5jEh0a3m9ZgAipiwpSxGj/qaVVy54RG5vAQN1nCuXqjvprCuKSCxcJHBg==", + "requires": { + "level-packager": "~4.0.0", + "memdown": "~3.0.0" + }, + "dependencies": { + "abstract-leveldown": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-5.0.0.tgz", + "integrity": "sha512-5mU5P1gXtsMIXg65/rsYGsi93+MlogXZ9FA8JnwKurHQg64bfXwGYVdVdijNTVNOlAsuIiOwHdvFFD5JqCJQ7A==", + "requires": { + "xtend": "~4.0.0" + } + }, + "immediate": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.2.3.tgz", + "integrity": "sha1-0UD6j2FGWb1lQSMwl92qwlzdmRw=" + }, + "memdown": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/memdown/-/memdown-3.0.0.tgz", + "integrity": "sha512-tbV02LfZMWLcHcq4tw++NuqMO+FZX8tNJEiD2aNRm48ZZusVg5N8NART+dmBkepJVye986oixErf7jfXboMGMA==", + "requires": { + "abstract-leveldown": "~5.0.0", + "functional-red-black-tree": "~1.0.1", + "immediate": "~3.2.3", + "inherits": "~2.0.1", + "ltgt": "~2.2.0", + "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==" + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + } + } + }, + "level-option-wrap": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/level-option-wrap/-/level-option-wrap-1.1.0.tgz", + "integrity": "sha1-rSDmjZ88IsiJdTHMaqevWWse0Sk=", + "requires": { + "defined": "~0.0.0" + } + }, + "level-packager": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/level-packager/-/level-packager-4.0.1.tgz", + "integrity": "sha512-svCRKfYLn9/4CoFfi+d8krOtrp6RoX8+xm0Na5cgXMqSyRru0AnDYdLl+YI8u1FyS6gGZ94ILLZDE5dh2but3Q==", + "requires": { + "encoding-down": "~5.0.0", + "levelup": "^3.0.0" + }, + "dependencies": { + "abstract-leveldown": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-5.0.0.tgz", + "integrity": "sha512-5mU5P1gXtsMIXg65/rsYGsi93+MlogXZ9FA8JnwKurHQg64bfXwGYVdVdijNTVNOlAsuIiOwHdvFFD5JqCJQ7A==", + "requires": { + "xtend": "~4.0.0" + } + }, + "deferred-leveldown": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/deferred-leveldown/-/deferred-leveldown-4.0.2.tgz", + "integrity": "sha512-5fMC8ek8alH16QiV0lTCis610D1Zt1+LA4MS4d63JgS32lrCjTFDUFz2ao09/j2I4Bqb5jL4FZYwu7Jz0XO1ww==", + "requires": { + "abstract-leveldown": "~5.0.0", + "inherits": "^2.0.3" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "level-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/level-errors/-/level-errors-2.0.1.tgz", + "integrity": "sha512-UVprBJXite4gPS+3VznfgDSU8PTRuVX0NXwoWW50KLxd2yw4Y1t2JUR5In1itQnudZqRMT9DlAM3Q//9NCjCFw==", + "requires": { + "errno": "~0.1.1" + } + }, + "level-iterator-stream": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/level-iterator-stream/-/level-iterator-stream-3.0.1.tgz", + "integrity": "sha512-nEIQvxEED9yRThxvOrq8Aqziy4EGzrxSZK+QzEFAVuJvQ8glfyZ96GB6BoI4sBbLfjMXm2w4vu3Tkcm9obcY0g==", + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.3.6", + "xtend": "^4.0.0" + } + }, + "levelup": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/levelup/-/levelup-3.1.1.tgz", + "integrity": "sha512-9N10xRkUU4dShSRRFTBdNaBxofz+PGaIZO962ckboJZiNmLuhVT6FZ6ZKAsICKfUBO76ySaYU6fJWX/jnj3Lcg==", + "requires": { + "deferred-leveldown": "~4.0.0", + "level-errors": "~2.0.0", + "level-iterator-stream": "~3.0.0", + "xtend": "~4.0.0" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "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" + } + }, + "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==" + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + } + } + }, + "level-supports": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/level-supports/-/level-supports-1.0.1.tgz", + "integrity": "sha512-rXM7GYnW8gsl1vedTJIbzOrRv85c/2uCMpiiCzO2fndd06U/kUXEEU9evYn4zFggBOg36IsBW8LzqIpETwwQzg==", + "requires": { + "xtend": "^4.0.2" + }, + "dependencies": { + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + } + } + }, + "level-ws": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/level-ws/-/level-ws-0.0.0.tgz", + "integrity": "sha1-Ny5RIXeSSgBCSwtDrvK7QkltIos=", + "requires": { + "readable-stream": "~1.0.15", + "xtend": "~2.1.1" + }, + "dependencies": { + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + } + } + }, + "leveldown": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/leveldown/-/leveldown-5.6.0.tgz", + "integrity": "sha512-iB8O/7Db9lPaITU1aA2txU/cBEXAt4vWwKQRrrWuS6XDgbP4QZGj9BL2aNbwb002atoQ/lIotJkfyzz+ygQnUQ==", + "requires": { + "abstract-leveldown": "~6.2.1", + "napi-macros": "~2.0.0", + "node-gyp-build": "~4.1.0" + }, + "dependencies": { + "abstract-leveldown": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-6.2.3.tgz", + "integrity": "sha512-BsLm5vFMRUrrLeCcRc+G0t2qOaTzpoJQLOubq2XM72eNpjF5UdU5o/5NvlNhx95XHcAvcl8OMXr4mlg/fRgUXQ==", + "requires": { + "buffer": "^5.5.0", + "immediate": "^3.2.3", + "level-concat-iterator": "~2.0.0", + "level-supports": "~1.0.0", + "xtend": "~4.0.0" + } + }, + "node-gyp-build": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.1.1.tgz", + "integrity": "sha512-dSq1xmcPDKPZ2EED2S6zw/b9NKsqzXRE6dVr8TVQnI3FJOTteUMuqF3Qqs6LZg+mLGYJWqQzMbIjMtJqTv87nQ==" + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + } + } + }, + "levelup": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/levelup/-/levelup-4.4.0.tgz", + "integrity": "sha512-94++VFO3qN95cM/d6eBXvd894oJE0w3cInq9USsyQzzoJxmiYzPAocNcuGCPGGjoXqDVJcr3C1jzt1TSjyaiLQ==", + "requires": { + "deferred-leveldown": "~5.3.0", + "level-errors": "~2.0.0", + "level-iterator-stream": "~4.0.0", + "level-supports": "~1.0.0", + "xtend": "~4.0.0" + }, + "dependencies": { + "abstract-leveldown": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-6.2.3.tgz", + "integrity": "sha512-BsLm5vFMRUrrLeCcRc+G0t2qOaTzpoJQLOubq2XM72eNpjF5UdU5o/5NvlNhx95XHcAvcl8OMXr4mlg/fRgUXQ==", + "requires": { + "buffer": "^5.5.0", + "immediate": "^3.2.3", + "level-concat-iterator": "~2.0.0", + "level-supports": "~1.0.0", + "xtend": "~4.0.0" + } + }, + "deferred-leveldown": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/deferred-leveldown/-/deferred-leveldown-5.3.0.tgz", + "integrity": "sha512-a59VOT+oDy7vtAbLRCZwWgxu2BaCfd5Hk7wxJd48ei7I+nsg8Orlb9CLG0PMZienk9BSUKgeAqkO2+Lw+1+Ukw==", + "requires": { + "abstract-leveldown": "~6.2.1", + "inherits": "^2.0.3" + } + }, + "level-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/level-errors/-/level-errors-2.0.1.tgz", + "integrity": "sha512-UVprBJXite4gPS+3VznfgDSU8PTRuVX0NXwoWW50KLxd2yw4Y1t2JUR5In1itQnudZqRMT9DlAM3Q//9NCjCFw==", + "requires": { + "errno": "~0.1.1" + } + }, + "level-iterator-stream": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/level-iterator-stream/-/level-iterator-stream-4.0.2.tgz", + "integrity": "sha512-ZSthfEqzGSOMWoUGhTXdX9jv26d32XJuHz/5YnuHZzH6wldfWMOVwI9TBtKcya4BKTyTt3XVA0A3cF3q5CY30Q==", + "requires": { + "inherits": "^2.0.4", + "readable-stream": "^3.4.0", + "xtend": "^4.0.2" + } + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + } + } + }, + "load-module": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/load-module/-/load-module-3.0.0.tgz", + "integrity": "sha512-ZqprfrTx4vfH5+1mgpspPh5JYsNyA193NkMUdb3GwpmVqMczOh8cUDJgZBmEZVlSR42JBGYTUxlBAX9LHIBtIA==", + "dev": true, + "requires": { + "array-back": "^4.0.1" + } + }, + "local-web-server": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/local-web-server/-/local-web-server-4.2.1.tgz", + "integrity": "sha512-v71LZool2w7uYA+tDP5HhfjzUxz5SFfcrPPB/zC98yFFawt7A6fcmAr2MR4Q9AHk/A8oyd/wrhEJBJLndwHxNQ==", + "dev": true, + "requires": { + "lws": "^3.1.0", + "lws-basic-auth": "^2.0.0", + "lws-blacklist": "^3.0.0", + "lws-body-parser": "^2.0.0", + "lws-compress": "^2.0.0", + "lws-conditional-get": "^2.0.0", + "lws-cors": "^3.0.0", + "lws-index": "^2.0.0", + "lws-json": "^2.0.0", + "lws-log": "^2.0.0", + "lws-mime": "^2.0.0", + "lws-range": "^3.0.0", + "lws-request-monitor": "^2.0.0", + "lws-rewrite": "^3.1.1", + "lws-spa": "^3.0.0", + "lws-static": "^2.0.0", + "node-version-matches": "^2.0.1" + } + }, + "lodash": { + "version": "4.17.19", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", + "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==" + }, + "lodash.assignwith": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.assignwith/-/lodash.assignwith-4.2.0.tgz", + "integrity": "sha1-EnqX8CrcQXUalU0ksN4X4QDgOOs=", + "dev": true + }, + "lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=", + "dev": true + }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=" + }, + "lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=", + "dev": true + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "requires": { + "yallist": "^3.0.2" + } + }, + "ltgt": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ltgt/-/ltgt-2.2.1.tgz", + "integrity": "sha1-81ypHEk/e3PaDgdJUwTxezH4fuU=" + }, + "lunr": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.8.tgz", + "integrity": "sha512-oxMeX/Y35PNFuZoHp+jUj5OSEmLCaIH4KTFJh7a93cHBoFmpw2IoPs22VIz7vyO2YUnx2Tn9dzIwO2P/4quIRg==", + "dev": true + }, + "lws": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lws/-/lws-3.1.0.tgz", + "integrity": "sha512-I8rTgZxz8OJL0hjdlDxs6WpcVG7WSyalVHPQXXK+WPNVjm3KhkT5gV0Qmsgm2FRLbRUp15tso80xmDxMsyt7zA==", + "dev": true, + "requires": { + "ansi-escape-sequences": "^5.1.2", + "array-back": "^4.0.1", + "byte-size": "^6.2.0", + "command-line-args": "^5.1.1", + "command-line-usage": "^6.1.0", + "create-mixin": "^3.0.0", + "koa": "^2.11.0", + "load-module": "^3.0.0", + "lodash.assignwith": "^4.2.0", + "node-version-matches": "^2.0.1", + "open": "^7.0.4", + "qrcode-terminal": "^0.12.0", + "reduce-flatten": "^3.0.0", + "typical": "^6.0.0", + "walk-back": "^4.0.0" + } + }, + "lws-basic-auth": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lws-basic-auth/-/lws-basic-auth-2.0.0.tgz", + "integrity": "sha512-zzyoGFLQPuKaQJvHMLmmSyfT6lIvocwcDXllTVW5brD0t0YgHYopILkzja+x+MIlJX/YhNKniaTSasujniYVjw==", + "dev": true, + "requires": { + "basic-auth": "^2.0.1" + } + }, + "lws-blacklist": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lws-blacklist/-/lws-blacklist-3.0.0.tgz", + "integrity": "sha512-KNXGDBmbj+UGfWMBAefe2vrfuWpEQms/9Fd7kfMScTqAKF6nrVoEs4pkxfefArG3bX0bu7jWLyB4tJGma5WC6Q==", + "dev": true, + "requires": { + "array-back": "^4.0.1", + "path-to-regexp": "^6.1.0" + } + }, + "lws-body-parser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lws-body-parser/-/lws-body-parser-2.0.0.tgz", + "integrity": "sha512-QFDzln3sSdKWL9fVNWy2+ZmrKy/XaYRO0/FFB0MBrDCsNnzepeCD4I7rOOfyuphLn42yR8XUpWdcJ3Ii5aauRA==", + "dev": true, + "requires": { + "koa-bodyparser": "^4.2.1" + } + }, + "lws-compress": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lws-compress/-/lws-compress-2.0.0.tgz", + "integrity": "sha512-5qDXI9pukVYWm07WjAOfpItLXKtL8lCHvjmW4RiXULhTRJj1qqBjNcmqReyk8L7NLUKhc+8eqoDDJFKURQEp0w==", + "dev": true, + "requires": { + "koa-compress": "^3.0.0" + } + }, + "lws-conditional-get": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lws-conditional-get/-/lws-conditional-get-2.0.0.tgz", + "integrity": "sha512-U05yDlFJKIYa7gJZYfnc1HIEuXbKpDJztgkvNYyxCqJC28j/k9ORoNnFNOIHpBh/jlPJgV8x7uH34mIxFAryWA==", + "dev": true, + "requires": { + "koa-conditional-get": "^2.0.0", + "koa-etag": "^3.0.0" + } + }, + "lws-cors": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lws-cors/-/lws-cors-3.0.0.tgz", + "integrity": "sha512-diUkoyVZyzLB8LamdtUYYAfJdPAyu/+IjE3ZUcdnNQz9koECe4O2x3SWD7LSV43pd3CKgyiwwSxWJ4hTBZFIvQ==", + "dev": true, + "requires": { + "@koa/cors": "^3.0.0" + } + }, + "lws-index": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lws-index/-/lws-index-2.0.0.tgz", + "integrity": "sha512-qfkeQmKYnd13LmQubzI5LtFV2N8PJQG4QvgSoefoiB3dWre9k2T4C7ajjOTKO8mgSzYpUEREduNcQcLyt62n0g==", + "dev": true, + "requires": { + "serve-index-75lb": "^2.0.1" + } + }, + "lws-json": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lws-json/-/lws-json-2.0.0.tgz", + "integrity": "sha512-vqUFrAQ5BGpkMS2Mm/ZhgvUMi6Tgia7YtESG7pKjNoiSsD+TxncG0nqp8YjUh2xrEzi/SYFc/ed+9ZOl/t0A0g==", + "dev": true, + "requires": { + "koa-json": "^2.0.2" + } + }, + "lws-log": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lws-log/-/lws-log-2.0.0.tgz", + "integrity": "sha512-YveoazSZ0Qb1Tljdm8G8yn9c+mAMXgvLMACZzh5aZIk7p8YJwiXf9r1S+xY7wbXEcKG629KfVO0B5G5gRFcyDQ==", + "dev": true, + "requires": { + "koa-morgan": "^1.0.1", + "stream-log-stats": "^3.0.2" + } + }, + "lws-mime": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lws-mime/-/lws-mime-2.0.0.tgz", + "integrity": "sha512-mfrAgRQ5+hkQ7LJ6EAgwnUeymNeYxwLXZY3UQ6C2hSTr7BqMSzm9k5O0C8wWP2dzdhChzITYKwzWbUnAYVBwtA==", + "dev": true + }, + "lws-range": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lws-range/-/lws-range-3.0.0.tgz", + "integrity": "sha512-7ZhA/LqQnKjolKBo/2BFj9DyDDXcJGY3v05TwYRD0qDGrxW4vuatEjluC3SV7ZO/k4PxDLdxuk+RCgL5t3ThtQ==", + "dev": true, + "requires": { + "koa-range": "^0.3.0" + } + }, + "lws-request-monitor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lws-request-monitor/-/lws-request-monitor-2.0.0.tgz", + "integrity": "sha512-ZTo0/pS42qiejcYlL+wlpurSbDSS0J7pDDohqBx7jjUQkgni2Qd8cPzn/kW8QI82gXgDmdZH+ps0vheLHlgdgg==", + "dev": true, + "requires": { + "byte-size": "^6.2.0" + } + }, + "lws-rewrite": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lws-rewrite/-/lws-rewrite-3.1.1.tgz", + "integrity": "sha512-cOeaPXIlLUVLxS6BZ52QzZVzI8JjCzlWD4RWizB5Hd+0YGO0SPa3Vgk7CIghtAOsSdjtXg/wSOap2H1h+tw8BQ==", + "dev": true, + "requires": { + "array-back": "^4.0.1", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "koa-route": "^3.2.0", + "path-to-regexp": "^6.1.0" + } + }, + "lws-spa": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lws-spa/-/lws-spa-3.0.0.tgz", + "integrity": "sha512-Tz10LfuOTUsRG6z+OCJ/vBN+4LQWoAGIJ1R02CFPrDk0pY3rHezM7/cCpq6Z6dXD+ipdNE8alkVn4zL2M+eVGg==", + "dev": true, + "requires": { + "koa-send": "^5.0.0" + } + }, + "lws-static": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lws-static/-/lws-static-2.0.0.tgz", + "integrity": "sha512-P25A0+IXdkB6Y6gZAG7X0mnaa+FJ8aTiWLUgM5kazaWmruRO7lyhSjitsA3y5TLI3DpPCZn0mWE4SRREujUZLg==", + "dev": true, + "requires": { + "koa-static": "^5.0.0" + } + }, + "marked": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-1.0.0.tgz", + "integrity": "sha512-Wo+L1pWTVibfrSr+TTtMuiMfNzmZWiOPeO7rZsQUY5bgsxpHesBEcIWJloWVTFnrMXnf/TL30eTFSGJddmQAng==", + "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" + } + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "dev": true + }, + "memdown": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/memdown/-/memdown-1.4.1.tgz", + "integrity": "sha1-tOThkhdGZP+65BNhqlAPMRnv4hU=", + "requires": { + "abstract-leveldown": "~2.7.1", + "functional-red-black-tree": "^1.0.1", + "immediate": "^3.2.3", + "inherits": "~2.0.1", + "ltgt": "~2.2.0", + "safe-buffer": "~5.1.1" + }, + "dependencies": { + "abstract-leveldown": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-2.7.2.tgz", + "integrity": "sha512-+OVvxH2rHVEhWLdbudP6p0+dNMXu8JA1CbhP19T8paTYAcX7oJ4OVjT+ZUVpv7mITxXHqDMej+GdqXBmXkw09w==", + "requires": { + "xtend": "~4.0.0" + } + }, + "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==" + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + } + } + }, + "memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI=", + "dev": true + }, + "merkle-patricia-tree": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/merkle-patricia-tree/-/merkle-patricia-tree-3.0.0.tgz", + "integrity": "sha512-soRaMuNf/ILmw3KWbybaCjhx86EYeBbD8ph0edQCTed0JN/rxDt1EBN52Ajre3VyGo+91f8+/rfPIRQnnGMqmQ==", + "requires": { + "async": "^2.6.1", + "ethereumjs-util": "^5.2.0", + "level-mem": "^3.0.1", + "level-ws": "^1.0.0", + "readable-stream": "^3.0.6", + "rlp": "^2.0.0", + "semaphore": ">=1.0.1" + }, + "dependencies": { + "ethereumjs-util": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.1.tgz", + "integrity": "sha512-v3kT+7zdyCm1HIqWlLNrHGqHGLpGYIhjeHxQjnDXjLT2FyGJDsd3LWMYUo7pAFRrk86CR3nUJfhC81CCoJNNGQ==", + "requires": { + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "^0.1.3", + "rlp": "^2.0.0", + "safe-buffer": "^5.1.1" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "level-ws": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/level-ws/-/level-ws-1.0.0.tgz", + "integrity": "sha512-RXEfCmkd6WWFlArh3X8ONvQPm8jNpfA0s/36M4QzLqrLEIt1iJE9WBHLZ5vZJK6haMjJPJGJCQWfjMNnRcq/9Q==", + "requires": { + "inherits": "^2.0.3", + "readable-stream": "^2.2.8", + "xtend": "^4.0.1" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "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" + } + } + } + }, + "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==" + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + } + } + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "dev": true + }, + "miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "requires": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + } + }, + "mime-db": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", + "dev": true + }, + "mime-types": { + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", + "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "dev": true, + "requires": { + "mime-db": "1.44.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.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "morgan": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", + "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", + "dev": true, + "requires": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.0.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true + } + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "requires": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "nan": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz", + "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==" + }, + "napi-macros": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-macros/-/napi-macros-2.0.0.tgz", + "integrity": "sha512-A0xLykHtARfueITVDernsAWdtIMbOJgKgcluwENp3AlsKN/PloyO10HtmoqnFAQAcxPkgZN7wdfPfEd0zNGxbg==" + }, + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", + "dev": true + }, + "neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "node-addon-api": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", + "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==" + }, + "node-gyp-build": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.2.3.tgz", + "integrity": "sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg==" + }, + "node-version-matches": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/node-version-matches/-/node-version-matches-2.0.1.tgz", + "integrity": "sha512-oqk6+05FC0dNVY5NuXuhPEMq+m1b9ZjS9SIhVE9EjwCHZspnmjSO8npbKAEieinR8GeEgbecoQcYIvI/Kwcf6Q==", + "dev": true, + "requires": { + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "dev": true, + "requires": { + "boolbase": "~1.0.0" + } + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "object-inspect": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", + "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==" + }, + "object-keys": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", + "integrity": "sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=" + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + }, + "dependencies": { + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + } + } + }, + "object.getownpropertydescriptors": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", + "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + } + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "only": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/only/-/only-0.0.2.tgz", + "integrity": "sha1-Kv3oTQPlC5qO3EROMGEKcCle37Q=", + "dev": true + }, + "open": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/open/-/open-7.1.0.tgz", + "integrity": "sha512-lLPI5KgOwEYCDKXf4np7y1PBEkj7HYIyP2DY8mVDRnx0VIIu6bNrRB0R66TuO7Mack6EnTNLm4uvcl1UoklTpA==", + "dev": true, + "requires": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "parse5": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz", + "integrity": "sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true + }, + "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-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "path-to-regexp": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.1.0.tgz", + "integrity": "sha512-h9DqehX3zZZDCEm+xbfU0ZmwCGFCAAraPJWMXJ4+v32NjZJilVg3k1TcKsRgIb8IQ/izZSaydDc1OhJCZvs2Dw==", + "dev": true + }, + "pbkdf2": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz", + "integrity": "sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg==", + "requires": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, + "prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=" + }, + "qrcode-terminal": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz", + "integrity": "sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ==", + "dev": true + }, + "qs": { + "version": "6.9.4", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.4.tgz", + "integrity": "sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ==", + "dev": true + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "raw-body": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.1.tgz", + "integrity": "sha512-9WmIKF6mkvA0SLmA2Knm9+qj89e+j1zqgyn8aXGd7+nAduPoqgI9lO57SAZNn/Byzo5P7JhXTyg9PzaJbH73bA==", + "dev": true, + "requires": { + "bytes": "3.1.0", + "http-errors": "1.7.3", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "dependencies": { + "http-errors": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", + "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + } + } + }, + "reachdown": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reachdown/-/reachdown-1.1.0.tgz", + "integrity": "sha512-6LsdRe4cZyOjw4NnvbhUd/rGG7WQ9HMopPr+kyL018Uci4kijtxcGR5kVb5Ln13k4PEE+fEFQbjfOvNw7cnXmA==" + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "dev": true, + "requires": { + "resolve": "^1.1.6" + } + }, + "reduce-flatten": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-3.0.0.tgz", + "integrity": "sha512-eczl8wAYBxJ6Egl6I1ECIF+8z6sHu+KE7BzaEDZTpPXKXfy9SUDQlVYwkRcNTjJLC3Iakxbhss50KuT/R6SYfg==", + "dev": true + }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true + }, + "resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-path": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/resolve-path/-/resolve-path-1.4.0.tgz", + "integrity": "sha1-xL2p9e+y/OZSR4c6s2u02DT+Fvc=", + "dev": true, + "requires": { + "http-errors": "~1.6.2", + "path-is-absolute": "1.0.1" + }, + "dependencies": { + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + } + } + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "requires": { + "glob": "^7.1.3" + } + }, + "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.2.6", + "resolved": "https://registry.npmjs.org/rlp/-/rlp-2.2.6.tgz", + "integrity": "sha512-HAfAmL6SDYNWPUOJNrM500x4Thn4PZsEy5pijPh40U9WfNk0z15hUYzO9xVIMAdIHdFtD8CBDHd75Td1g36Mjg==", + "requires": { + "bn.js": "^4.11.1" + } + }, + "rustbn.js": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/rustbn.js/-/rustbn.js-0.2.0.tgz", + "integrity": "sha512-4VlvkRUuCJvr2J6Y0ImW7NvTCriMi7ErOAqWk1y69vAdoNIzCF3yPmgeNzx+RQTLEDFq5sHfscn1MwHxP9hNfA==" + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "scrypt-js": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", + "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==" + }, + "secp256k1": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.2.tgz", + "integrity": "sha512-UDar4sKvWAksIlfX3xIaQReADn+WFnHvbVujpcbr+9Sf/69odMwy2MUsz5CKLQgX9nsIyrjuxL2imVyoNHa3fg==", + "requires": { + "elliptic": "^6.5.2", + "node-addon-api": "^2.0.0", + "node-gyp-build": "^4.2.0" + } + }, + "seedrandom": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz", + "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==" + }, + "semaphore": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/semaphore/-/semaphore-1.1.0.tgz", + "integrity": "sha512-O4OZEaNtkMd/K0i6js9SL+gqy0ZCBMgUvlSqHKi4IBdjhe7wB8pwztUk1BbZ1fmrvpwFrPbHzqd2w5pTcJH6LA==" + }, + "semver": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", + "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==" + }, + "serve-index-75lb": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/serve-index-75lb/-/serve-index-75lb-2.0.1.tgz", + "integrity": "sha512-/d9r8bqJlFQcwy0a0nb1KnWAA+Mno+V+VaoKocdkbW5aXKRQd/+4bfnRhQRQr6uEoYwTRJ4xgztOyCJvWcpBpQ==", + "dev": true, + "requires": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.18", + "parseurl": "~1.3.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + } + } + }, + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" + }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", + "dev": true + }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://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" + } + }, + "shelljs": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.4.tgz", + "integrity": "sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ==", + "dev": true, + "requires": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + } + }, + "solc": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/solc/-/solc-0.7.4.tgz", + "integrity": "sha512-IVLqAfUkJqgTS0JIgFPeC50ehUeBXu2eE+iU+rqb6UeOyf6w/BB/EsNcTSTpjtUti8BTG/sCd2qVhrWVYy7p0g==", + "dev": true, + "requires": { + "command-exists": "^1.2.8", + "commander": "3.0.2", + "follow-redirects": "^1.12.1", + "fs-extra": "^0.30.0", + "js-sha3": "0.8.0", + "memorystream": "^0.3.1", + "require-from-string": "^2.0.0", + "semver": "^5.5.0", + "tmp": "0.0.33" + }, + "dependencies": { + "fs-extra": { + "version": "0.30.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz", + "integrity": "sha1-8jP/zAjU2n1DLapEl3aYnbHfk/A=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^2.1.0", + "klaw": "^1.0.0", + "path-is-absolute": "^1.0.0", + "rimraf": "^2.2.8" + } + }, + "jsonfile": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", + "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + } + } + }, + "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 + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true + }, + "stream-log-stats": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/stream-log-stats/-/stream-log-stats-3.0.2.tgz", + "integrity": "sha512-393j7aeF9iRdHvyANqEQU82UQmpw2CTxgsT83caefh+lOxavVLbVrw8Mr4zjXeZLh2+xeHZMKfVx4T0rJ/EchA==", + "dev": true, + "requires": { + "JSONStream": "^1.3.5", + "ansi-escape-sequences": "^5.1.2", + "byte-size": "^6.2.0", + "common-log-format": "^1.0.0", + "lodash.throttle": "^4.1.1", + "stream-via": "^1.0.4", + "table-layout": "~1.0.0" + } + }, + "stream-slice": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/stream-slice/-/stream-slice-0.1.2.tgz", + "integrity": "sha1-LcT04bk2+xPz6zmi3vGTJ5jQeks=", + "dev": true + }, + "stream-via": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/stream-via/-/stream-via-1.0.4.tgz", + "integrity": "sha512-DBp0lSvX5G9KGRDTkR/R+a29H+Wk2xItOF+MpZLLNDWbEV9tGPnqLPxHEYjmiz8xGtJHRIqmI+hCjmNzqoA4nQ==", + "dev": true + }, + "streaming-json-stringify": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/streaming-json-stringify/-/streaming-json-stringify-3.1.0.tgz", + "integrity": "sha1-gCAEN6mTzDnE/gAmO3s7kDrIevU=", + "dev": true, + "requires": { + "json-stringify-safe": "5", + "readable-stream": "2" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": 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" + } + }, + "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==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "string.prototype.trimend": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", + "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "string.prototype.trimstart": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", + "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "requires": { + "safe-buffer": "~5.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" + } + }, + "subleveldown": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/subleveldown/-/subleveldown-5.0.1.tgz", + "integrity": "sha512-cVqd/URpp7si1HWu5YqQ3vqQkjuolAwHypY1B4itPlS71/lsf6TQPZ2Y0ijT22EYVkvH5ove9JFJf4u7VGPuZw==", + "requires": { + "abstract-leveldown": "^6.3.0", + "encoding-down": "^6.2.0", + "inherits": "^2.0.3", + "level-option-wrap": "^1.1.0", + "levelup": "^4.4.0", + "reachdown": "^1.1.0" + }, + "dependencies": { + "abstract-leveldown": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-6.3.0.tgz", + "integrity": "sha512-TU5nlYgta8YrBMNpc9FwQzRbiXsj49gsALsXadbGHt9CROPzX5fB0rWDR5mtdpOOKa5XqRFpbj1QroPAoPzVjQ==", + "requires": { + "buffer": "^5.5.0", + "immediate": "^3.2.3", + "level-concat-iterator": "~2.0.0", + "level-supports": "~1.0.0", + "xtend": "~4.0.0" + } + }, + "encoding-down": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/encoding-down/-/encoding-down-6.3.0.tgz", + "integrity": "sha512-QKrV0iKR6MZVJV08QY0wp1e7vF6QbhnbQhb07bwpEyuz4uZiZgPlEGdkCROuFkUwdxlFaiPIhjyarH1ee/3vhw==", + "requires": { + "abstract-leveldown": "^6.2.1", + "inherits": "^2.0.3", + "level-codec": "^9.0.0", + "level-errors": "^2.0.0" + } + }, + "level-codec": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/level-codec/-/level-codec-9.0.2.tgz", + "integrity": "sha512-UyIwNb1lJBChJnGfjmO0OR+ezh2iVu1Kas3nvBS/BzGnx79dv6g7unpKIDNPMhfdTEGoc7mC8uAu51XEtX+FHQ==", + "requires": { + "buffer": "^5.6.0" + } + }, + "level-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/level-errors/-/level-errors-2.0.1.tgz", + "integrity": "sha512-UVprBJXite4gPS+3VznfgDSU8PTRuVX0NXwoWW50KLxd2yw4Y1t2JUR5In1itQnudZqRMT9DlAM3Q//9NCjCFw==", + "requires": { + "errno": "~0.1.1" + } + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + } + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "table-layout": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-1.0.1.tgz", + "integrity": "sha512-dEquqYNJiGwY7iPfZ3wbXDI944iqanTSchrACLL2nOB+1r+h1Nzu2eH+DuPPvWvm5Ry7iAPeFlgEtP5bIp5U7Q==", + "dev": true, + "requires": { + "array-back": "^4.0.1", + "deep-extend": "~0.6.0", + "typical": "^5.2.0", + "wordwrapjs": "^4.0.0" + }, + "dependencies": { + "typical": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", + "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", + "dev": true + } + } + }, + "thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "requires": { + "any-promise": "^1.0.0" + } + }, + "thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=", + "dev": true, + "requires": { + "thenify": ">= 3.1.0 < 4" + } + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "requires": { + "rimraf": "^3.0.0" + } + }, + "tmp-promise": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-3.0.2.tgz", + "integrity": "sha512-OyCLAKU1HzBjL6Ev3gxUeraJNlbNingmi8IrHHEsYH8LTmEuhvYfqvhn2F/je+mjf4N58UmZ96OMEy1JanSCpA==", + "requires": { + "tmp": "^0.2.0" + } + }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "dev": true + }, + "ts-transformer-inline-file": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ts-transformer-inline-file/-/ts-transformer-inline-file-0.1.1.tgz", + "integrity": "sha512-2bPkAFjATsRG4ld8TFTUqn4TvEdXLQf/wwGsepFeRKSXLPqFRhdUHusAGPB1/Zif3CVjppD+bfne58gynd8RfQ==", + "dev": true + }, + "tsscmp": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz", + "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==", + "dev": true + }, + "tweetnacl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" + }, + "tweetnacl-util": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/tweetnacl-util/-/tweetnacl-util-0.15.1.tgz", + "integrity": "sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw==" + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "typedoc": { + "version": "0.17.8", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.17.8.tgz", + "integrity": "sha512-/OyrHCJ8jtzu+QZ+771YaxQ9s4g5Z3XsQE3Ma7q+BL392xxBn4UMvvCdVnqKC2T/dz03/VXSLVKOP3lHmDdc/w==", + "dev": true, + "requires": { + "fs-extra": "^8.1.0", + "handlebars": "^4.7.6", + "highlight.js": "^10.0.0", + "lodash": "^4.17.15", + "lunr": "^2.3.8", + "marked": "1.0.0", + "minimatch": "^3.0.0", + "progress": "^2.0.3", + "shelljs": "^0.8.4", + "typedoc-default-themes": "^0.10.2" + }, + "dependencies": { + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true + } + } + }, + "typedoc-default-themes": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/typedoc-default-themes/-/typedoc-default-themes-0.10.2.tgz", + "integrity": "sha512-zo09yRj+xwLFE3hyhJeVHWRSPuKEIAsFK5r2u47KL/HBKqpwdUSanoaz5L34IKiSATFrjG5ywmIu98hPVMfxZg==", + "dev": true, + "requires": { + "lunr": "^2.3.8" + } + }, + "typical": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/typical/-/typical-6.0.1.tgz", + "integrity": "sha512-+g3NEp7fJLe9DPa1TArHm9QAA7YciZmWnfAqEaFrBihQ7epOv9i99rjtgb6Iz0wh3WuQDjsCTDfgRoGnmHN81A==", + "dev": true + }, + "uWebSockets.js": { + "version": "github:uNetworking/uWebSockets.js#3dbec7b56d627193e20705844b6bd10e49848b8c", + "from": "github:uNetworking/uWebSockets.js#v18.4.0", + "dev": true + }, + "uglify-js": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.10.0.tgz", + "integrity": "sha512-Esj5HG5WAyrLIdYU74Z3JdG2PxdIusvj6IWHMtlyESxc7kcDz7zYlYjpnSokn1UbpV0d/QX9fan7gkCNd/9BQA==", + "dev": true, + "optional": true + }, + "underscore": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.10.2.tgz", + "integrity": "sha512-N4P+Q/BuyuEKFJ43B9gYuOj4TQUHXX+j2FqguVOpjkssLUUrnJofCcBccJSCoeturDoZU6GorDTHSvUDlSQbTg==", + "dev": true + }, + "universalify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz", + "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==", + "dev": true + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "util.promisify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.1.tgz", + "integrity": "sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.2", + "has-symbols": "^1.0.1", + "object.getownpropertydescriptors": "^2.1.0" + } + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "dev": true + }, + "walk-back": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/walk-back/-/walk-back-4.0.0.tgz", + "integrity": "sha512-kudCA8PXVQfrqv2mFTG72vDBRi8BKWxGgFLwPpzHcpZnSwZk93WMwUDVcLHWNsnm+Y0AC4Vb6MUNRgaHfyV2DQ==", + "dev": true + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + }, + "wordwrapjs": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-4.0.0.tgz", + "integrity": "sha512-Svqw723a3R34KvsMgpjFBYCgNOSdcW3mQFK4wIfhGQhtaFVOJmdYoXgi63ne3dTlWgatVcUc7t4HtQ/+bUVIzQ==", + "dev": true, + "requires": { + "reduce-flatten": "^2.0.0", + "typical": "^5.0.0" + }, + "dependencies": { + "reduce-flatten": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-2.0.0.tgz", + "integrity": "sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w==", + "dev": true + }, + "typical": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", + "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", + "dev": true + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "xtend": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz", + "integrity": "sha1-bv7MKk2tjmlixJAbM3znuoe10os=", + "requires": { + "object-keys": "~0.4.0" + } + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + }, + "ylru": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ylru/-/ylru-1.2.1.tgz", + "integrity": "sha512-faQrqNMzcPCHGVC2aaOINk13K+aaBDUPjGWl0teOXywElLjyVAB6Oe2jj62jHYtwsU49jXhScYbvPENK+6zAvQ==", + "dev": true + } + } +} diff --git a/src/chains/ethereum/package.json b/src/chains/ethereum/package.json new file mode 100644 index 0000000000..eab86a37a9 --- /dev/null +++ b/src/chains/ethereum/package.json @@ -0,0 +1,92 @@ +{ + "name": "@ganache/ethereum", + "version": "0.1.0", + "description": "Ganache's Ethereum client implementation", + "author": "David Murdoch (https://davidmurdoch.com)", + "homepage": "https://github.com/trufflesuite/ganache-core/tree/develop/src/ethereum#readme", + "license": "MIT", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "source": "index.ts", + "directories": { + "lib": "lib", + "test": "tests" + }, + "files": [ + "lib" + ], + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "https://github.com/trufflesuite/ganache-core.git", + "directory": "src/chains/ethereum" + }, + "scripts": { + "docs.build": "rm -rf ./lib/docs ./lib/api.json && typedoc --options ./typedoc.json --readme ./README.md --out ./lib/docs --json ./lib/api.json src/api.ts && npm run docs.post-process", + "docs.post-process": "node ./scripts/post-process-docs.js", + "tsc": "ttsc", + "test": "nyc --reporter lcov npm run mocha", + "mocha": "cross-env TS_NODE_COMPILER=ttypescript TS_NODE_FILES=true mocha --exit --check-leaks --throw-deprecation --trace-warnings --require ts-node/register 'tests/**/*.test.ts'" + }, + "bugs": { + "url": "https://github.com/trufflesuite/ganache-core/issues" + }, + "keywords": [ + "ganache", + "ganache-ethereum", + "ethereum", + "evm", + "blockchain", + "smart contracts", + "dapps", + "solidity", + "vyper", + "fe", + "web3", + "tooling" + ], + "dependencies": { + "@ganache/options": "^0.1.0", + "@ganache/promise-queue": "^0.1.0", + "@ganache/utils": "^0.1.0", + "@types/keccak": "3.0.1", + "bip39": "3.0.2", + "emittery": "0.7.2", + "eth-sig-util": "2.5.3", + "ethereumjs-abi": "0.6.8", + "ethereumjs-account": "3.0.0", + "ethereumjs-block": "2.2.2", + "ethereumjs-common": "1.5.2", + "ethereumjs-tx": "2.1.2", + "ethereumjs-util": "7.0.7", + "ethereumjs-vm": "4.2.0", + "hdkey": "2.0.1", + "keccak": "3.0.0", + "leveldown": "5.6.0", + "levelup": "4.4.0", + "lodash.clonedeep": "4.5.0", + "merkle-patricia-tree": "3.0.0", + "scrypt-js": "3.0.1", + "secp256k1": "4.0.2", + "seedrandom": "3.0.5", + "subleveldown": "5.0.1", + "tmp-promise": "3.0.2" + }, + "devDependencies": { + "@trufflesuite/typedoc-default-themes": "0.6.1", + "@types/fs-extra": "9.0.2", + "@types/lodash.clonedeep": "4.5.6", + "@types/secp256k1": "4.0.1", + "@types/seedrandom": "2.4.28", + "@types/ws": "7.2.7", + "cheerio": "1.0.0-rc.3", + "fs-extra": "9.0.1", + "local-web-server": "4.2.1", + "solc": "0.7.4", + "ts-transformer-inline-file": "0.1.1", + "typedoc": "0.17.8", + "uWebSockets.js": "github:uNetworking/uWebSockets.js#v18.4.0" + } +} diff --git a/src/chains/ethereum/scripts/post-process-docs.js b/src/chains/ethereum/scripts/post-process-docs.js new file mode 100644 index 0000000000..5db1da3f63 --- /dev/null +++ b/src/chains/ethereum/scripts/post-process-docs.js @@ -0,0 +1,23 @@ +const cheerio = require("cheerio"); +const { readFileSync, writeFileSync } = require("fs"); +const { randomBytes } = require("crypto"); +const $ = cheerio.load( + readFileSync("./lib/docs/classes/_api_.ethereumapi.html") +); + +$(`.tsd-page-title`).after(``); + +$(".runkit-example").each(function () { + const sanitizedCode = $(this) + .text() + .replace(/;(\s+)/gi, ";\n") + .trim(); + $(this).text(""); + const id = randomBytes(4).toString("hex"); + $(this).attr("id", id).html(); + $(this).prepend( + `` + ); +}); + +writeFileSync("./lib/docs/classes/_api_.ethereumapi.html", $.html()); diff --git a/src/chains/ethereum/src/@types/ethereumjs-block/header.d.ts b/src/chains/ethereum/src/@types/ethereumjs-block/header.d.ts new file mode 100644 index 0000000000..6f69e9149e --- /dev/null +++ b/src/chains/ethereum/src/@types/ethereumjs-block/header.d.ts @@ -0,0 +1,62 @@ +declare module "ethereumjs-block/header" { + import BN from "bn.js"; + import { Block } from "ethereumjs-block"; + import { Blockchain } from "ethereumjs-blockchain"; + + type LargeNumber = string | Buffer | BN; + type Callback = (err: Error | null, result: T) => void; + + export interface IBlockHeader { + parentHash: Buffer; + uncleHash: Buffer; + coinbase: Buffer; + stateRoot: Buffer; + transactionTrie: Buffer; + receiptTrie: Buffer; + bloom: Buffer; + difficulty: Buffer; + number: Buffer; + gasLimit: Buffer; + gasUsed: Buffer; + timestamp: Buffer; + extraData: Buffer; + } + + export class BlockHeader { + public raw!: Buffer[]; + public parentHash!: Buffer; + public uncleHash!: Buffer; + public coinbase!: Buffer; + public stateRoot!: Buffer; + public transactionsTrie!: Buffer; + public receiptTrie!: Buffer; + public bloom!: Buffer; + public difficulty!: Buffer; + public number!: Buffer; + public gasLimit!: Buffer; + public gasUsed!: Buffer; + public timestamp!: Buffer; + public extraData!: Buffer; + public mixHash!: Buffer; + public nonce!: Buffer; + + constructor( + data: Buffer | PrefixedHexString | BufferLike[] | BlockHeaderData = {}, + opts: ChainOptions = {} + ); + serialize(): Buffer; + canonicalDifficulty(block: Block): BN; + validateDifficulty(block: Block): boolean; + validateGasLimit(block: Block): boolean; + validate( + blockChain: Blockchain, + height: BN | Callback, + cb?: Callback + ): void; + hash(): Buffer; + isGenesis(): boolean; + toJSON(labeled: boolean): object; + } + + export default BlockHeader; +} diff --git a/src/chains/ethereum/src/@types/ethereumjs-block/index.d.ts b/src/chains/ethereum/src/@types/ethereumjs-block/index.d.ts new file mode 100644 index 0000000000..4c5d858c8e --- /dev/null +++ b/src/chains/ethereum/src/@types/ethereumjs-block/index.d.ts @@ -0,0 +1,43 @@ +declare module "ethereumjs-block" { + import BN from "bn.js"; + import { Blockchain } from "ethereumjs-blockchain"; + import Transaction from "ethereumjs-tx"; + import BlockHeader from "ethereumjs-block/header"; + import Trie from "merkle-patricia-tree/baseTrie"; + + type LargeNumber = string | Buffer | BN; + type Callback = (err: Error | null, result: T) => void; + + export interface IBlock { + transactions: Transaction[]; + uncleHeaders: BlockHeader[]; + txTrie: Trie; + header: BlockHeader; + } + + export class Block { + transactions: Transaction[]; + uncleHeaders: BlockHeader[]; + txTrie: Trie; + header: BlockHeader; + raw: Buffer[]; + + constructor( + data: Buffer | [Buffer[], Buffer[], Buffer[]] | BlockData = {}, + opts: ChainOptions = {} + ); + hash(): Buffer; + isGenesis(): boolean; + setGenesisParams(): void; + serialize(rlpEncode: boolean): Buffer; + genTxTrie(cb: Callback): void; + validateTransactionTrie(): boolean; + validateTransactions(sringError: boolean): boolean | string; + validate(blockChain: Blockchain, cb: Callback): void; + validateUnclesHash(): boolean; + validateUncles(blockChain: Blockchain, cb: Callback): void; + toJSON(labeled?: boolean): object; + } + + export default Block; +} diff --git a/src/chains/ethereum/src/@types/ethereumjs-util/index.d.ts b/src/chains/ethereum/src/@types/ethereumjs-util/index.d.ts new file mode 100644 index 0000000000..24678e1695 --- /dev/null +++ b/src/chains/ethereum/src/@types/ethereumjs-util/index.d.ts @@ -0,0 +1,8 @@ +import "ethereumjs-util"; + +declare module "ethereumjs-util" { + export declare const publicToAddress: ( + pubKey: Buffer, + sanitize?: boolean + ) => Buffer; +} diff --git a/src/chains/ethereum/src/@types/hdkey/index.d.ts b/src/chains/ethereum/src/@types/hdkey/index.d.ts new file mode 100644 index 0000000000..486d9d12a7 --- /dev/null +++ b/src/chains/ethereum/src/@types/hdkey/index.d.ts @@ -0,0 +1,37 @@ +declare module "hdkey" { + type HDKeyJSON = { + xpriv: string; + xpub: string; + }; + type HDKeyVersions = { private: number; public: number }; + export default class HDKey { + public versions: HDKeyVersions; + public depth: number; + public index: number; + public readonly parentFingerprint: number; + public readonly fingerprint: number; + public readonly identifier?: Uint8Array; + public readonly pubKeyHash?: Uint8Array; + public readonly privateKey?: Buffer; + public publicKey?: Buffer; + public readonly privateExtendedKey?: string; + public readonly publicExtendedKey: string; + public readonly chainCode?: Buffer; + public derive: (path: string) => HDKey; + public deriveChild: (index: number) => HDKey; + public sign: (hash: Buffer) => Buffer; + public verify: (hash: Buffer, signature: Buffer) => boolean; + public wipePrivateData: () => HDKey; + public toJSON: (path: string) => HDKeyJSON; + public static fromMasterSeed: ( + seedBuffer: Buffer, + version?: HDKeyVersions + ) => HDKey; + public static fromExtendedKey: ( + base58key: string, + version?: HDKeyVersions + ) => HDKey; + public static fromJSON: (obj: HDKeyJSON) => HDKey; + public static HARDENED_OFFSET: 0x80000000; + } +} diff --git a/src/chains/ethereum/src/@types/levelup/index.d.ts b/src/chains/ethereum/src/@types/levelup/index.d.ts new file mode 100644 index 0000000000..8274b83162 --- /dev/null +++ b/src/chains/ethereum/src/@types/levelup/index.d.ts @@ -0,0 +1,128 @@ +/// + +declare module "levelup" { + import { AbstractLevelDOWN } from "abstract-leveldown"; + + export = levelup; + + var levelup: levelup.LevelUpConstructor; + + namespace levelup { + interface CustomEncoding { + encode(val: any): Buffer | string; + decode(val: Buffer | string): any; + buffer: boolean; + type: string; + } + + type Encoding = string | CustomEncoding; + + interface Batch { + type: string; + key: any; + value?: any; + keyEncoding?: Encoding; + valueEncoding?: Encoding; + } + + interface LevelUpBase { + open(callback?: (error: any) => any): void; + + close(callback?: (error: any) => any): void; + + put(key: any, value: any): Promise; + put(key: any, value: any, callback: (error: any) => any): void; + put(key: any, value: any, options: { sync?: boolean }): Promise; + put( + key: any, + value: any, + options: { sync?: boolean }, + callback: (error: any) => any + ): void; + + get(key: any): Promise; + get(key: any, callback: (error: any, value: any) => any): void; + get( + key: any, + options: { keyEncoding?: Encoding; fillCache?: boolean } + ): Promise; + get( + key: any, + options: { keyEncoding?: Encoding; fillCache?: boolean }, + callback: (error: any, value: any) => any + ): void; + + del(key: any): Promise; + del(key: any, callback: (error: any) => any): void; + del( + key: any, + options: { keyEncoding?: Encoding; sync?: boolean } + ): Promise; + del( + key: any, + options: { keyEncoding?: Encoding; sync?: boolean }, + callback: (error: any) => any + ): void; + + batch(): LevelUpChain; + batch(array: BatchType[]): Promise; + batch(array: BatchType[], callback: (error?: any) => any): void; + batch( + array: BatchType[], + options: { + keyEncoding?: Encoding; + valueEncoding?: Encoding; + sync?: boolean; + } + ): Promise; + batch( + array: BatchType[], + options: { + keyEncoding?: Encoding; + valueEncoding?: Encoding; + sync?: boolean; + }, + callback: (error?: any) => any + ): void; + + isOpen(): boolean; + isClosed(): boolean; + createReadStream(options?: any): any; + createKeyStream(options?: any): any; + createValueStream(options?: any): any; + } + + type LevelUp = LevelUpBase; + + interface LevelUpChain { + put(key: any, value: any): LevelUpChain; + put(key: any, value: any, options?: { sync?: boolean }): LevelUpChain; + del(key: any): LevelUpChain; + del( + key: any, + options?: { keyEncoding?: Encoding; sync?: boolean } + ): LevelUpChain; + clear(): LevelUpChain; + write(): Promise; + write(callback?: (error?: any) => any): void; + } + + interface levelupOptions< + K = any, + V = any, + O = any, + PO = any, + GO = any, + DO = any, + IO = any, + BO = any + > { + db?: (location: string) => AbstractLevelDOWN; + } + + interface LevelUpConstructor { + (location: string, options?: levelupOptions): LevelUp; + (options: levelupOptions): LevelUp; + } + } +} diff --git a/src/chains/ethereum/src/@types/merkle-patricia-tree/baseTrie.ts b/src/chains/ethereum/src/@types/merkle-patricia-tree/baseTrie.ts new file mode 100644 index 0000000000..3ab25a0f0c --- /dev/null +++ b/src/chains/ethereum/src/@types/merkle-patricia-tree/baseTrie.ts @@ -0,0 +1,45 @@ +declare module "merkle-patricia-tree/baseTrie" { + import BN from "bn.js"; + + import TrieNode from "merkle-patricia-tree/trieNode"; + import ReadStream from "merkle-patricia-tree/readStream"; + + type Callback = (err: Error | null, result: T) => void; + type FindPathCallback = ( + err: Error, + node: TrieNode, + keyRemainder: Buffer, + stack: TrieNode[] + ) => void; + type LargeNumber = string | Buffer | BN; + + // Rather than using LevelUp here, specify the minimal interface we need + // so that other structurally identical types can be used in its place + export interface Database { + get(key: Buffer, opt: any, cb: Callback): void; + put(key: Buffer, val: Buffer, options: any, cb: Callback): void; + del(key: Buffer, opt: any, cb: Callback): void; + } + + export interface BatchOperation { + type: "del" | "put"; + key: LargeNumber; + value?: LargeNumber; + } + + export class Trie { + root: Buffer; + constructor(db: Database, root: Buffer); + get(key: LargeNumber, cb: Callback): void; + put(key: LargeNumber, value: LargeNumber, cb: Callback): void; + del(key: LargeNumber, cb: Callback): void; + getRaw(key: LargeNumber, cb: Callback): void; + findPath(key: LargeNumber, cb: FindPathCallback): void; + createReadStream(): ReadStream; + copy(): Trie; + batch(ops: BatchOperation[], cb: (err: Error[]) => void): void; + checkRoot(root: LargeNumber, cb: Callback): void; + } + + export default Trie; +} diff --git a/src/chains/ethereum/src/@types/merkle-patricia-tree/index.d.ts b/src/chains/ethereum/src/@types/merkle-patricia-tree/index.d.ts new file mode 100644 index 0000000000..098dcd79ab --- /dev/null +++ b/src/chains/ethereum/src/@types/merkle-patricia-tree/index.d.ts @@ -0,0 +1,38 @@ +declare module "merkle-patricia-tree" { + import BN from "bn.js"; + import { Readable } from "stream"; + + import { Trie, Database } from "merkle-patricia-tree/baseTrie"; + import TrieNode from "merkle-patricia-tree/trieNode"; + + type MerkleProof = TrieNode[]; + type Callback = (err: Error | null, result: T) => void; + type LargeNumber = string | Buffer | BN; + + export class ScratchReadStream extends Readable { + trie: Trie; + } + + export class CheckpointTrie extends Trie { + readonly isCheckpoint: boolean; + + root: Buffer; + constructor(db: Database, root: Buffer); + get(key: LargeNumber, cb: Callback): void; + put(key: LargeNumber, value: LargeNumber, cb: Callback): void; + copy(): Trie; + + checkpoint(): void; + commit(cb: Callback): void; + revert(cb: Callback): void; + createScratchReadStream(scratch: Database): ScratchReadStream; + static prove(trie: Trie, key: LargeNumber, cb: Callback): void; + static verifyProof( + rootHash: LargeNumber, + key: LargeNumber, + proof: MerkleProof, + cb: Callback + ): void; + } + export default CheckpointTrie; +} diff --git a/src/chains/ethereum/src/@types/merkle-patricia-tree/readStream.ts b/src/chains/ethereum/src/@types/merkle-patricia-tree/readStream.ts new file mode 100644 index 0000000000..3972b94c62 --- /dev/null +++ b/src/chains/ethereum/src/@types/merkle-patricia-tree/readStream.ts @@ -0,0 +1,10 @@ +declare module "merkle-patricia-tree/readStream" { + import Trie from "merkle-patricia-tree/baseTrie"; + import { Readable } from "stream"; + + export class TrieReadStream extends Readable { + constructor(trie: Trie); + } + + export default TrieReadStream; +} diff --git a/src/chains/ethereum/src/@types/merkle-patricia-tree/trieNode.ts b/src/chains/ethereum/src/@types/merkle-patricia-tree/trieNode.ts new file mode 100644 index 0000000000..25fe0e6d67 --- /dev/null +++ b/src/chains/ethereum/src/@types/merkle-patricia-tree/trieNode.ts @@ -0,0 +1,45 @@ +declare module "merkle-patricia-tree/trieNode" { + import BN from "bn.js"; + + type LargeNumber = string | Buffer | BN; + type Callback = (err: Error | null, result: T) => void; + type NodeType = "branch" | "leaf" | "extension"; + type NibbleArray = number[]; + + export class TrieNode { + value: Buffer; + key: Buffer; + type: NodeType; + raw: number[]; + + constructor( + type: NodeType | NibbleArray, + key?: NibbleArray, + value?: NibbleArray + ); + parseNode(node: NibbleArray): void; + setValue(key: NibbleArray, value: NibbleArray): void; + getValue(key: NibbleArray): NibbleArray; + setKey(key: NibbleArray): void; + getKey(): NibbleArray; + serialize(): Buffer; + hash(): Buffer; + toString: string; + getChildren(): TrieNode[]; + static addHexPrefix(key: NibbleArray, terminator: boolean): NibbleArray; + static removeHexPrefix(val: NibbleArray): NibbleArray; + static isTerminator(key: NibbleArray): boolean; + static stringToNibbles(key: LargeNumber): NibbleArray; + static nibblesToBuffer(arr: NibbleArray): Buffer; + static getNodeType(node: TrieNode): NodeType; + } + + function addHexPrefix(key: NibbleArray, terminator: boolean): NibbleArray; + function removeHexPrefix(val: NibbleArray): NibbleArray; + function isTerminator(key: NibbleArray): boolean; + function stringToNibbles(key: LargeNumber): NibbleArray; + function nibblesToBuffer(arr: NibbleArray): Buffer; + function getNodeType(node: TrieNode): NodeType; + + export default TrieNode; +} diff --git a/src/chains/ethereum/src/api.ts b/src/chains/ethereum/src/api.ts new file mode 100644 index 0000000000..0bc3f12cf6 --- /dev/null +++ b/src/chains/ethereum/src/api.ts @@ -0,0 +1,2047 @@ +//#region Imports +import { RuntimeBlock, Block } from "./things/runtime-block"; +import { + toRpcSig, + KECCAK256_NULL, + ecsign, + hashPersonalMessage +} from "ethereumjs-util"; +import { TypedData as NotTypedData, signTypedData_v4 } from "eth-sig-util"; +import { EthereumInternalOptions } from "./options"; +import { types, Data, Quantity } from "@ganache/utils"; +import Blockchain from "./blockchain"; +import Tag from "./things/tags"; +import { VM_EXCEPTION, VM_EXCEPTIONS } from "./errors/errors"; +import Address from "./things/address"; +import Transaction from "./things/transaction"; +import Wallet from "./wallet"; +import { decode as rlpDecode } from "rlp"; +import { $INLINE_JSON } from "ts-transformer-inline-file"; +import keccak from "keccak"; + +import { PromiEvent, utils } from "@ganache/utils"; +import Emittery from "emittery"; +import Common from "ethereumjs-common"; +import BlockLogs from "./things/blocklogs"; +import EthereumAccount from "ethereumjs-account"; +import estimateGas from "./helpers/gas-estimator"; +import CodedError, { ErrorCodes } from "./errors/coded-error"; +import { WhisperPostObject } from "./types/shh"; +import { + BaseFilterArgs, + Filter, + FilterArgs, + FilterTypes, + RangeFilterArgs +} from "./types/filters"; +import { assertArgLength } from "./helpers/assert-arg-length"; +import Account from "./things/account"; +import { SubscriptionId, SubscriptionName } from "./types/subscriptions"; +import { + parseFilter, + parseFilterDetails, + parseFilterRange +} from "./helpers/filter-parsing"; +import { Hardfork } from "./options/chain-options"; + +// Read in the current ganache version from core's package.json +const { version } = $INLINE_JSON("../../../packages/ganache/package.json"); +//#endregion + +//#region Constants +const RPCQUANTITY_ZERO = utils.RPCQUANTITY_ZERO; +const CLIENT_VERSION = `Ganache/v${version}/EthereumJS TestRPC/v${version}/ethereum-js`; +const PROTOCOL_VERSION = Data.from("0x3f"); +const RPC_MODULES = { + eth: "1.0", + net: "1.0", + rpc: "1.0", + web3: "1.0", + evm: "1.0", + personal: "1.0" +} as const; +const KNOWN_CHAINIDS = new Set([1, 3, 4, 5, 42]); +//#endregion + +//#region misc types +type TypedData = Exclude< + Parameters[1]["data"], + NotTypedData +>; +//#endregion + +//#region helpers +function assertExceptionalTransactions(transactions: Transaction[]) { + let baseError: string = null; + let errors: string[]; + const data = {}; + + transactions.forEach(transaction => { + if (transaction.execException) { + if (baseError) { + baseError = VM_EXCEPTIONS; + errors.push( + `${Data.from(transaction.hash(), 32).toString()}: ${ + transaction.execException + }\n` + ); + data[transaction.execException.data.hash] = + transaction.execException.data; + } else { + baseError = VM_EXCEPTION; + errors = [transaction.execException.message]; + data[transaction.execException.data.hash] = + transaction.execException.data; + } + } + }); + + if (baseError) { + const err = new Error(baseError + errors.join("\n")); + (err as any).data = data; + throw err; + } +} + +function parseCoinbaseAddress( + coinbase: string | number | Address, + initialAccounts: Account[] +) { + switch (typeof coinbase) { + case "object": + return coinbase; + case "number": + const account = initialAccounts[coinbase]; + if (account) { + return account.address; + } else { + throw new Error(`invalid coinbase address index: ${coinbase}`); + } + case "string": + return Address.from(coinbase); + default: { + throw new Error( + `coinbase address must be string or number, received: ${coinbase}` + ); + } + } +} + +function createCommon(chainId: number, networkId: number, hardfork: Hardfork) { + return Common.forCustomChain( + // if we were given a chain id that matches a real chain, use it + // NOTE: I don't think Common serves a purpose ther than instructing the + // VM what hardfork is in use. But just incase things change in the future + // its configured "more correctly" here. + KNOWN_CHAINIDS.has(chainId) ? chainId : 1, + { + name: "ganache", + networkId: networkId, + chainId: chainId, + comment: "Local test network" + }, + hardfork + ); +} +//#endregion helpers + +export default class EthereumApi implements types.Api { + readonly [index: string]: (...args: any) => Promise; + + readonly #getId = (id => () => Quantity.from(++id))(0); + readonly #common: Common; + readonly #filters = new Map(); + readonly #subscriptions = new Map(); + readonly #blockchain: Blockchain; + readonly #options: EthereumInternalOptions; + readonly #wallet: Wallet; + + /** + * This is the Ethereum API that the provider interacts with. + * The only methods permitted on the prototype are the supported json-rpc + * methods. + * @param options + * @param ready Callback for when the API is fully initialized + */ + constructor( + options: EthereumInternalOptions, + wallet: Wallet, + emitter: Emittery.Typed<{ message: any }, "connect" | "disconnect"> + ) { + this.#options = options; + + const { chain } = options; + const { initialAccounts } = (this.#wallet = wallet); + const coinbaseAddress = parseCoinbaseAddress( + options.miner.coinbase, + initialAccounts + ); + const common = (this.#common = createCommon( + chain.chainId, + chain.networkId, + chain.hardfork + )); + + const blockchain = (this.#blockchain = new Blockchain( + options, + common, + initialAccounts, + coinbaseAddress + )); + blockchain.on("start", () => emitter.emit("connect")); + emitter.on("disconnect", blockchain.stop.bind(blockchain)); + } + + //#region db + /** + * Stores a string in the local database. + * + * @param {String} dbName - Database name. + * @param {String} key - Key name. + * @param {String} value - String to store. + * @returns returns true if the value was stored, otherwise false. + */ + @assertArgLength(3) + async db_putString(dbName: string, key: string, value: string) { + return false; + } + + /** + * Returns string from the local database + * + * @param {String} dbName - Database name. + * @param {String} key - Key name. + * @returns The previously stored string. + */ + @assertArgLength(2) + async db_getString(dbName: string, key: string) { + return ""; + } + + /** + * Stores binary data in the local database. + * + * @param {String} dbName - Database name. + * @param {String} key - Key name. + * @param {DATA} data - Data to store. + * @returns true if the value was stored, otherwise false. + */ + @assertArgLength(3) + async db_putHex(dbName: string, key: string, data: string) { + return false; + } + + /** + * Returns binary data from the local database + * + * @param {String} dbName - Database name. + * @param {String} key - Key name. + * @returns The previously stored data. + */ + @assertArgLength(2) + async db_getHex(dbName: string, key: string) { + return "0x00"; + } + //#endregion + + //#region bzz + @assertArgLength(0) + async bzz_hive() { + return []; + } + + @assertArgLength(0) + async bzz_info() { + return []; + } + //#endregion + + //#region evm + /** + * Force a single block to be mined. + * + * Mines a block independent of whether or not mining is started or stopped. + * Will mine an empty block if there are no available transactions to mine. + * + * @param timestamp the timestamp a block should setup as the mining time. + */ + @assertArgLength(0, 1) + async evm_mine(timestamp?: number) { + const transactions = await this.#blockchain.mine(-1, timestamp, true); + if (this.#options.chain.vmErrorsOnRPCResponse) { + assertExceptionalTransactions(transactions); + } + + return "0x0"; + } + + @assertArgLength(3, 4) + async evm_setStorageAt( + address: string, + position: bigint | number, + storage: string, + blockNumber: string | Buffer | Tag = Tag.LATEST + ) { + const blockProm = this.#blockchain.blocks.getRaw(blockNumber); + + const trie = this.#blockchain.trie.copy(); + const getFromTrie = (address: Buffer): Promise => + new Promise((resolve, reject) => { + trie.get(address, (err, data) => { + if (err) return void reject(err); + resolve(data); + }); + }); + const block = await blockProm; + if (!block) throw new Error("header not found"); + + const blockData = (rlpDecode(block) as unknown) as [ + [Buffer, Buffer, Buffer, Buffer /* stateRoot */] /* header */, + Buffer[], + Buffer[] + ]; + const headerData = blockData[0]; + const blockStateRoot = headerData[3]; + trie.root = blockStateRoot; + + const addressDataPromise = getFromTrie(Address.from(address).toBuffer()); + + const posBuff = Quantity.from(position).toBuffer(); + const length = posBuff.length; + let paddedPosBuff: Buffer; + if (length < 32) { + // storage locations are 32 bytes wide, so we need to expand any value + // given to 32 bytes. + paddedPosBuff = Buffer.allocUnsafe(32).fill(0); + posBuff.copy(paddedPosBuff, 32 - length); + } else if (length === 32) { + paddedPosBuff = posBuff; + } else { + // if the position value we're passed is > 32 bytes, truncate it. This is + // what geth does. + paddedPosBuff = posBuff.slice(-32); + } + + const addressData = await addressDataPromise; + // An address's stateRoot is stored in the 3rd rlp entry + this.#blockchain.trie.root = ((rlpDecode(addressData) as any) as [ + Buffer /*nonce*/, + Buffer /*amount*/, + Buffer /*stateRoot*/, + Buffer /*codeHash*/ + ])[2]; + + return new Promise((resolve, reject) => { + this.#blockchain.trie.put(paddedPosBuff, storage, err => { + if (err) return reject(err); + resolve(void 0); + }); + }); + } + + /** + * Sets the given account's nonce to the specified value. Mines a new block + * before returning. + * + * Warning: this will result in an invalid state tree. + * + * @param address + * @param nonce + * @returns true if it worked + */ + @assertArgLength(2) + async evm_setAccountNonce(address: string, nonce: string) { + return new Promise((resolve, reject) => { + const buffer = Address.from(address).toBuffer(); + const blockchain = this.#blockchain; + const stateManager = blockchain.vm.stateManager; + stateManager.getAccount( + buffer, + (err: Error, account: EthereumAccount) => { + if (err) { + reject(err); + return; + } + account.nonce = Quantity.from(nonce).toBuffer(); + stateManager.putAccount(buffer, account, (err: Error) => { + if (err) { + reject(err); + return; + } + + blockchain.mine(0).then(() => resolve(true), reject); + }); + } + ); + }); + } + + /** + * Jump forward in time by the given amount of time, in seconds. + * @param seconds Must be greater than or equal to `0` + * @returns Returns the total time adjustment, in seconds. + */ + @assertArgLength(1) + async evm_increaseTime(seconds: number | string) { + const milliseconds = + (typeof seconds === "number" + ? seconds + : Quantity.from(seconds).toNumber()) * 1000; + return Math.floor(this.#blockchain.increaseTime(milliseconds) / 1000); + } + + /** + * Sets the internal clock time to the given timestamp. + * + * Warning: This will allow you to move *backwards* in time, which may cause + * new blocks to appear to be mined before old blocks. This is will result in + * an invalid state. + * + * @param timestamp JavaScript timestamp (millisecond precision) + * @returns The amount of *seconds* between the given timestamp and now. + */ + @assertArgLength(0, 1) + async evm_setTime(time: string | Date | number) { + let t: number; + switch (typeof time) { + case "object": + t = time.getTime(); + break; + case "number": + t = time; + break; + default: + t = Quantity.from(time).toNumber(); + break; + } + return Math.floor(this.#blockchain.setTime(t) / 1000); + } + + /** + * Revert the state of the blockchain to a previous snapshot. Takes a single + * parameter, which is the snapshot id to revert to. This deletes the given + * snapshot, as well as any snapshots taken after (Ex: reverting to id 0x1 + * will delete snapshots with ids 0x1, 0x2, etc... If no snapshot id is + * passed it will revert to the latest snapshot. + * + * @param snapshotId the snapshot id to revert + * @returns `true` if a snapshot was reverted, otherwise `false` + * + * @example + * ```javascript + * const snapshotId = await provider.send("evm_snapshot"); + * const isReverted = await provider.send("evm_revert", [snapshotId]); + * ``` + * + * @example + * ```javascript + * const provider = ganache.provider(); + * const [from, to] = await provider.send("eth_accounts"); + * const startingBalance = BigInt(await provider.send("eth_getBalance", [from])); + * + * // take a snapshot + * const snapshotId = await provider.send("evm_snapshot"); + * + * // send value to another account (over-simplified example) + * await provider.send("eth_subscribe", ["newHeads"]); + * await provider.send("eth_sendTransaction", [{from, to, value: "0xffff"}]); + * await provider.once("message"); // Note: `await provider.once` is non-standard + * + * // ensure balance has updated + * const newBalance = await provider.send("eth_getBalance", [from]); + * assert(BigInt(newBalance) < startingBalance); + * + * // revert the snapshot + * const isReverted = await provider.send("evm_revert", [snapshotId]); + * assert(isReverted); + * + * const endingBalance = await provider.send("eth_getBalance", [from]); + * assert.strictEqual(BigInt(endingBalance), startingBalance); + * ``` + */ + @assertArgLength(1) + async evm_revert(snapshotId: string | number) { + return this.#blockchain.revert(Quantity.from(snapshotId)); + } + + /** + * Snapshot the state of the blockchain at the current block. Takes no + * parameters. Returns the id of the snapshot that was created. A snapshot can + * only be reverted once. After a successful `evm_revert`, the same snapshot + * id cannot be used again. Consider creating a new snapshot after each + * `evm_revert` if you need to revert to the same point multiple times. + * + * @returns The hex-encoded identifier for this snapshot + * + * @example + * ```javascript + * const snapshotId = await provider.send("evm_snapshot"); + * ``` + * + * @example + * ```javascript + * const provider = ganache.provider(); + * const [from, to] = await provider.send("eth_accounts"); + * const startingBalance = BigInt(await provider.send("eth_getBalance", [from])); + * + * // take a snapshot + * const snapshotId = await provider.send("evm_snapshot"); + * + * // send value to another account (over-simplified example) + * await provider.send("eth_subscribe", ["newHeads"]); + * await provider.send("eth_sendTransaction", [{from, to, value: "0xffff"}]); + * await provider.once("message"); // Note: `await provider.once` is non-standard + * + * // ensure balance has updated + * const newBalance = await provider.send("eth_getBalance", [from]); + * assert(BigInt(newBalance) < startingBalance); + * + * // revert the snapshot + * const isReverted = await provider.send("evm_revert", [snapshotId]); + * assert(isReverted); + * + * const endingBalance = await provider.send("eth_getBalance", [from]); + * assert.strictEqual(BigInt(endingBalance), startingBalance); + * ``` + */ + async evm_snapshot() { + return Quantity.from(this.#blockchain.snapshot()); + } + + /** + * Unlocks any unknown account. + * @param address address the address of the account to unlock + * @param duration (default: disabled) Duration in seconds how long the account + * should remain unlocked for. Set to 0 to disable automatic locking. + * @returns `true` if the account was unlocked successfully, `false` if the + * account was already unlocked. Throws an error if the account could not be + * unlocked. + */ + async evm_unlockUnknownAccount(address: string, duration: number = 0) { + return this.#wallet.unlockUnknownAccount(address.toLowerCase(), duration); + } + + /** + * Locks any unknown account. + * + * Note: accounts known to the `personal` namespace and accounts returned by + * `eth_accounts` cannot be locked using this method. + * + * @param address address the address of the account to lock + * @returns `true` if the account was locked successfully, `false` if the + * account was already locked. Throws an error if the account could not be + * locked. + */ + async evm_lockUnknownAccount(address: string) { + const lowerAddress = address.toLowerCase(); + // if this is a known account, don'we can't unlock it this way + if (this.#wallet.knownAccounts.has(lowerAddress)) { + throw new Error("cannot lock known/personal account"); + } + return this.#wallet.lockAccount(lowerAddress); + } + + //#endregion evm + + //#region miner + /** + * Resume the CPU mining process with the given number of threads. + * + * Note: `threads` is ignored. + * @param threads + * @returns true + */ + @assertArgLength(0, 1) + async miner_start(threads: number = 1) { + if (this.#options.miner.legacyInstamine === true) { + const transactions = await this.#blockchain.resume(threads); + if (transactions != null && this.#options.chain.vmErrorsOnRPCResponse) { + assertExceptionalTransactions(transactions); + } + } else { + this.#blockchain.resume(threads); + } + return true; + } + + /** + * Stop the CPU mining operation. + */ + @assertArgLength(0) + async miner_stop() { + this.#blockchain.pause(); + return true; + } + + /** + * + * @param number Sets the minimal accepted gas price when mining transactions. + * Any transactions that are below this limit are excluded from the mining + * process. + */ + @assertArgLength(1) + async miner_setGasPrice(number: string) { + this.#options.miner.gasPrice = Quantity.from(number); + return true; + } + + /** + * Sets the etherbase, where mining rewards will go. + * @param address + */ + @assertArgLength(1) + async miner_setEtherbase(address: string) { + this.#blockchain.coinbase = Address.from(address); + return true; + } + + /** + * Set the extraData block header field a miner can include + * @param extra + */ + @assertArgLength(1) + async miner_setExtra(extra: string) { + const bytes = Data.from(extra); + const length = bytes.toBuffer().length; + if (length > 32) { + throw new Error(`extra exceeds max length. ${length} > 32`); + } + this.#options.miner.extraData = bytes; + return true; + } + //#endregion + + //#region web3 + /** + * Returns the current client version. + * @returns The current client version. + */ + @assertArgLength(0) + async web3_clientVersion() { + return CLIENT_VERSION; + } + + /** + * Returns Keccak-256 (not the standardized SHA3-256) of the given data. + * @param {data} the data to convert into a SHA3 hash. + * @returns The SHA3 result of the given string. + */ + @assertArgLength(1) + async web3_sha3(data: string) { + return Data.from(keccak("keccak256").update(data).digest()); + } + //#endregion + + //#region net + /** + * Returns the current network id. + * @returns The current network id. This value should NOT be JSON-RPC + * Quantity/Data encoded. + */ + @assertArgLength(0) + async net_version() { + return this.#options.chain.networkId.toString(); + } + + /** + * Returns `true` if client is actively listening for network connections. + * @returns `true` when listening, otherwise `false`. + */ + @assertArgLength(0) + async net_listening() { + return true; + } + + /** + * Returns number of peers currently connected to the client. + * @returns integer of the number of connected peers. + */ + @assertArgLength(0) + async net_peerCount() { + return RPCQUANTITY_ZERO; + } + //#endregion + + //#region eth + + /** + * Generates and returns an estimate of how much gas is necessary to allow the + * transaction to complete. The transaction will not be added to the + * blockchain. Note that the estimate may be significantly more than the + * amount of gas actually used by the transaction, for a variety of reasons + * including EVM mechanics and node performance. + * + * @returns the amount of gas used. + */ + @assertArgLength(1, 2) + async eth_estimateGas( + transaction: any, + blockNumber: Buffer | Tag | string = Tag.LATEST + ) { + const blockchain = this.#blockchain; + const blocks = blockchain.blocks; + const parentBlock = await blocks.get(blockNumber); + const parentHeader = parentBlock.header; + const options = this.#options; + + const generateVM = () => { + return blockchain.vm.copy(); + }; + return new Promise((resolve, reject) => { + const { coinbase } = blockchain; + const tx = Transaction.fromJSON( + transaction, + this.#common, + Transaction.types.fake + ); + if (tx._from == null) { + tx._from = coinbase.toBuffer(); + } + if (tx.gasLimit.length !== 0) { + tx.gas = tx.gasLimit; + } else { + if (tx.gas.length !== 0) { + tx.gasLimit = tx.gas; + } else { + // eth_estimateGas isn't subject to regular transaction gas limits + tx.gas = tx.gasLimit = options.miner.callGasLimit.toBuffer(); + } + } + const newBlock = new RuntimeBlock( + parentHeader.number, + parentHeader.parentHash, + parentHeader.miner, + tx.gas, + parentHeader.timestamp + ); + const runArgs = { + tx: tx, + block: newBlock, + skipBalance: true, + skipNonce: true + }; + estimateGas(generateVM, runArgs, (err: Error, result: any) => { + if (err) return reject(err); + resolve(Quantity.from(result.gasEstimate.toBuffer())); + }); + }); + // return GasEstimator.binSearch(blockchain, tx, blockNumber); + } + + /** + * Returns the current ethereum protocol version. + * @returns The current ethereum protocol version. + */ + @assertArgLength(0) + async eth_protocolVersion() { + return PROTOCOL_VERSION; + } + + /** + * Returns an object with data about the sync status or false. + * @returns An object with sync status data or false, when not syncing: + * startingBlock: {bigint} - The block at which the import started (will + * only be reset, after the sync reached his head) + * currentBlock: {bigint} - The current block, same as eth_blockNumber + * highestBlock: {bigint} - The estimated highest block + */ + @assertArgLength(0) + async eth_syncing() { + return false; + } + + /** + * Returns the client coinbase address. + * @returns 20 bytes - the current coinbase address. + */ + @assertArgLength(0) + async eth_coinbase() { + return this.#blockchain.coinbase; + } + + /** + * Returns information about a block by block number. + * @param number QUANTITY|TAG - integer of a block number, or the string "earliest", "latest" or "pending", as in the + * default block parameter. + * @param transactions Boolean - If true it returns the full transaction objects, if false only the hashes of the + * transactions. + * @returns the block, `null` if the block doesn't exist. + */ + @assertArgLength(1, 2) + async eth_getBlockByNumber(number: string | Buffer, transactions = false) { + const block = await this.#blockchain.blocks.get(number).catch(_ => null); + return block ? block.toJSON(transactions) : null; + } + + /** + * Returns information about a block by block hash. + * @param number QUANTITY|TAG - integer of a block number, or the string "earliest", "latest" or "pending", as in the + * default block parameter. + * @param transactions Boolean - If true it returns the full transaction objects, if false only the hashes of the + * transactions. + * @returns Block + */ + @assertArgLength(1, 2) + async eth_getBlockByHash(hash: string | Buffer, transactions = false) { + const block = await this.#blockchain.blocks + .getByHash(hash) + .catch(_ => null); + return block ? block.toJSON(transactions) : null; + } + + /** + * Returns the number of transactions in a block from a block matching the given block number. + * @param number QUANTITY|TAG - integer of a block number, or the string "earliest", "latest" or "pending", as in the + * default block parameter. + */ + @assertArgLength(1) + async eth_getBlockTransactionCountByNumber(number: string | Buffer) { + const rawBlock = await this.#blockchain.blocks.getRaw(number); + if (rawBlock) { + const data = rlpDecode(rawBlock); + return Quantity.from((data[1] as any).length); + } else { + return null; + } + } + + /** + * Returns the number of transactions in a block from a block matching the given block hash. + * @param hash DATA, 32 Bytes - hash of a block. + */ + @assertArgLength(1) + async eth_getBlockTransactionCountByHash(hash: string | Buffer) { + const number = await this.#blockchain.blocks.getNumberFromHash(hash); + if (number) { + return this.eth_getBlockTransactionCountByNumber(number); + } else { + return null; + } + } + + @assertArgLength(0) + async eth_getCompilers() { + return [] as string[]; + } + + /** + * Returns information about a transaction by block hash and transaction index position. + * @param hash DATA, 32 Bytes - hash of a block. + * @param index QUANTITY - integer of the transaction index position. + */ + @assertArgLength(2) + async eth_getTransactionByBlockHashAndIndex( + hash: string | Buffer, + index: string + ) { + const block = await this.eth_getBlockByHash(hash, true); + if (block) { + const tx = block.transactions[parseInt(index, 10)]; + if (tx) return tx; + } + return null; + } + + /** + * Returns information about a transaction by block number and transaction index position. + * @param number QUANTITY|TAG - a block number, or the string "earliest", "latest" or "pending", as in the default + * block parameter. + * @param index QUANTITY - integer of the transaction index position. + */ + @assertArgLength(2) + async eth_getTransactionByBlockNumberAndIndex( + number: string | Buffer, + index: string + ) { + const block = await this.eth_getBlockByNumber(number, true); + return block.transactions[parseInt(index, 10)]; + } + + /** + * Returns the number of uncles in a block from a block matching the given block hash. + * @param hash DATA, 32 Bytes - hash of a block. + */ + @assertArgLength(1) + async eth_getUncleCountByBlockHash(hash: string | Buffer) { + return RPCQUANTITY_ZERO; + } + + /** + * Returns the number of uncles in a block from a block matching the given block hash. + * @param hash DATA, 32 Bytes - hash of a block. + */ + @assertArgLength(1) + async eth_getUncleCountByBlockNumber(number: string | Buffer) { + return RPCQUANTITY_ZERO; + } + + /** + * Returns information about a uncle of a block by hash and uncle index position. + * + * @param hash - hash of a block + * @param index - the uncle's index position. + */ + @assertArgLength(2) + async eth_getUncleByBlockHashAndIndex(hash: Data, index: Quantity) { + return null as ReturnType; + } + + /** + * Returns information about a uncle of a block by hash and uncle index position. + * + * @param blockNumber - a block number, or the string "earliest", "latest" or "pending", as in the default block + * parameter. + * @param uncleIndex - the uncle's index position. + */ + @assertArgLength(2) + async eth_getUncleByBlockNumberAndIndex( + blockNumber: Buffer | Tag, + uncleIndex: Quantity + ) { + return null as ReturnType; + } + + /** + * Returns: An Array with the following elements + * 1: DATA, 32 Bytes - current block header pow-hash + * 2: DATA, 32 Bytes - the seed hash used for the DAG. + * 3: DATA, 32 Bytes - the boundary condition ("target"), 2^256 / difficulty. + * + * @param {QUANTITY} filterId - A filter id + * @returns the hash of the current block, the seedHash, and the boundary condition to be met ("target"). + */ + @assertArgLength(1) + async eth_getWork(filterId: Quantity) { + return [] as [string, string, string] | []; + } + + /** + * Used for submitting a proof-of-work solution + * + * @param {DATA, 8 Bytes} nonce - The nonce found (64 bits) + * @param {DATA, 32 Bytes} powHash - The header's pow-hash (256 bits) + * @param {DATA, 32 Bytes} digest - The mix digest (256 bits) + * @returns `true` if the provided solution is valid, otherwise `false`. + */ + @assertArgLength(3) + async eth_submitWork(nonce: Data, powHash: Data, digest: Data) { + return false; + } + + /** + * Used for submitting mining hashrate. + * + * @param {String} hashRate - a hexadecimal string representation (32 bytes) of the hash rate + * @param {String} clientID - A random hexadecimal(32 bytes) ID identifying the client + * @returns `true` if submitting went through succesfully and `false` otherwise. + */ + @assertArgLength(2) + async eth_submitHashrate(hashRate: string, clientID: string) { + return false; + } + + /** + * Returns `true` if client is actively mining new blocks. + * @returns returns `true` if the client is mining, otherwise `false`. + */ + @assertArgLength(0) + async eth_mining() { + // we return the blockchain's started state + return this.#blockchain.isStarted(); + } + + /** + * Returns the number of hashes per second that the node is mining with. + * @returns number of hashes per second. + */ + @assertArgLength(0) + async eth_hashrate() { + return RPCQUANTITY_ZERO; + } + + /** + * Returns the current price per gas in wei. + * @returns integer of the current gas price in wei. + */ + @assertArgLength(0) + async eth_gasPrice() { + return this.#options.miner.gasPrice; + } + + /** + * Returns a list of addresses owned by client. + * @returns Array of 20 Bytes - addresses owned by the client. + */ + @assertArgLength(0) + async eth_accounts() { + return this.#wallet.addresses; + } + + /** + * Returns the number of most recent block. + * @returns integer of the current block number the client is on. + */ + @assertArgLength(0) + async eth_blockNumber() { + return this.#blockchain.blocks.latest.header.number; + } + + /** + * Returns the currently configured chain id, a value used in + * replay-protected transaction signing as introduced by EIP-155. + * @returns The chain id as a string. + * @EIP [155 – Simple replay attack protection](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md) + * + * @example + * ```javascript + * await provider.send("eth_chainId"); + * ``` + */ + @assertArgLength(0) + async eth_chainId() { + return Quantity.from(this.#options.chain.chainId); + } + + /** + * Returns the balance of the account of given address. + * @param address 20 Bytes - address to check for balance. + * @param blockNumber integer block number, or the string "latest", "earliest" + * or "pending", see the default block parameter + */ + @assertArgLength(1, 2) + async eth_getBalance( + address: string, + blockNumber: Buffer | Tag = Tag.LATEST + ) { + return this.#blockchain.accounts.getBalance( + Address.from(address), + blockNumber + ); + } + + /** + * Returns code at a given address. + * + * @param address 20 Bytes - address + * @param blockNumber integer block number, or the string "latest", "earliest" or "pending", see the default block + * parameter + * @returns the code from the given address. + */ + @assertArgLength(1, 2) + async eth_getCode(address: Buffer, blockNumber: Buffer | Tag = Tag.LATEST) { + const blockchain = this.#blockchain; + const blockProm = blockchain.blocks.getRaw(blockNumber); + + const trie = blockchain.trie.copy(); + const getFromTrie = (address: Buffer): Promise => + new Promise((resolve, reject) => { + trie.get(address, (err, data) => { + if (err) return void reject(err); + resolve(data); + }); + }); + const block = await blockProm; + if (!block) throw new Error("header not found"); + + const blockData = (rlpDecode(block) as unknown) as [ + [Buffer, Buffer, Buffer, Buffer /* stateRoot */] /* header */, + Buffer[], + Buffer[] + ]; + const headerData = blockData[0]; + const blockStateRoot = headerData[3]; + trie.root = blockStateRoot; + + const addressDataPromise = getFromTrie(Address.from(address).toBuffer()); + + const addressData = await addressDataPromise; + // An address's codeHash is stored in the 4th rlp entry + const codeHash = ((rlpDecode(addressData) as any) as [ + Buffer /*nonce*/, + Buffer /*amount*/, + Buffer /*stateRoot*/, + Buffer /*codeHash*/ + ])[3]; + // if this address isn't a contract, return 0x + if (!codeHash || KECCAK256_NULL.equals(codeHash)) { + return Data.from("0x"); + } + return new Promise((resolve, reject) => { + trie.getRaw(codeHash, (err, data) => { + if (err) return void reject(err); + resolve(Data.from(data)); + }); + }); + } + + /** + * Returns the value from a storage position at a given address. + * @param data 20 Bytes - address of the storage. + * @param quantity integer of the position in the storage. + * @param blockNumber integer block number, or the string "latest", "earliest" + * or "pending", see the default block parameter + */ + @assertArgLength(2, 3) + async eth_getStorageAt( + address: string, + position: bigint | number, + blockNumber: string | Buffer | Tag = Tag.LATEST + ) { + const blockProm = this.#blockchain.blocks.getRaw(blockNumber); + + const trie = this.#blockchain.trie.copy(); + const getFromTrie = (address: Buffer): Promise => + new Promise((resolve, reject) => { + trie.get(address, (err, data) => { + if (err) return void reject(err); + resolve(data); + }); + }); + const block = await blockProm; + if (!block) throw new Error("header not found"); + + const blockData = (rlpDecode(block) as unknown) as [ + [Buffer, Buffer, Buffer, Buffer /* stateRoot */] /* header */, + Buffer[], + Buffer[] + ]; + const headerData = blockData[0]; + const blockStateRoot = headerData[3]; + trie.root = blockStateRoot; + + const addressDataPromise = getFromTrie(Address.from(address).toBuffer()); + + const posBuff = Quantity.from(position).toBuffer(); + const length = posBuff.length; + let paddedPosBuff: Buffer; + if (length < 32) { + // storage locations are 32 bytes wide, so we need to expand any value + // given to 32 bytes. + paddedPosBuff = Buffer.allocUnsafe(32).fill(0); + posBuff.copy(paddedPosBuff, 32 - length); + } else if (length === 32) { + paddedPosBuff = posBuff; + } else { + // if the position value we're passed is > 32 bytes, truncate it. This is + // what geth does. + paddedPosBuff = posBuff.slice(-32); + } + + const addressData = await addressDataPromise; + // An address's stateRoot is stored in the 3rd rlp entry + trie.root = ((rlpDecode(addressData) as any) as [ + Buffer /*nonce*/, + Buffer /*amount*/, + Buffer /*stateRoot*/, + Buffer /*codeHash*/ + ])[2]; + const value = await getFromTrie(paddedPosBuff); + return Data.from(value); + } + + /** + * Returns the information about a transaction requested by transaction hash. + * + * @param transactionHash 32 Bytes - hash of a transaction + */ + @assertArgLength(1) + async eth_getTransactionByHash(transactionHash: string) { + const { transactions } = this.#blockchain; + const hashBuffer = Data.from(transactionHash).toBuffer(); + + // we must check the database before checking the pending cache, because the + // cache is updated _after_ the transaction is already in the database, and + // the database contains block info whereas the pending cache doesn't. + const transaction = await transactions.get(hashBuffer); + + if (transaction === null) { + // if we can't find it in the list of pending transactions, check the db! + const tx = transactions.transactionPool.find(hashBuffer); + return tx ? tx.toJSON(null) : null; + } else { + return transaction.toJSON(); + } + } + + /** + * Returns the receipt of a transaction by transaction hash. + * + * Note That the receipt is not available for pending transactions. + * + * @param transactionHash 32 Bytes - hash of a transaction + * @returns Returns the receipt of a transaction by transaction hash. + */ + @assertArgLength(1) + async eth_getTransactionReceipt(transactionHash: string) { + const { transactions, transactionReceipts, blocks } = this.#blockchain; + const txHash = Data.from(transactionHash).toBuffer(); + + const transactionPromise = transactions.get(txHash); + const receiptPromise = transactionReceipts.get(txHash); + const blockPromise = transactionPromise.then(t => + t ? blocks.get(t._blockNum) : null + ); + const [transaction, receipt, block] = await Promise.all([ + transactionPromise, + receiptPromise, + blockPromise + ]); + if (transaction) { + return receipt.toJSON(block, transaction); + } else { + return null; + } + } + + /** + * Creates new message call transaction or a contract creation, if the data field contains code. + * @param transaction + * @returns The transaction hash + */ + @assertArgLength(1) + async eth_sendTransaction(transaction: any) { + let fromString = transaction.from; + let from: Address; + if (fromString) { + from = Address.from(transaction.from); + fromString = from.toString().toLowerCase(); + } + + if (fromString == null) { + throw new Error("from not found; is required"); + } + + // Error checks. It's possible to JSON.stringify a Buffer to JSON. + // we actually now handle this "properly" (not sure about spec), but for + // legacy reasons we don't allow it. + if (transaction.to && typeof transaction.to !== "string") { + throw new Error("invalid to address"); + } + + const wallet = this.#wallet; + const isKnownAccount = wallet.knownAccounts.has(fromString); + const isUnlockedAccount = wallet.unlockedAccounts.has(fromString); + + if (!isUnlockedAccount) { + const msg = isKnownAccount + ? "authentication needed: password or unlock" + : "sender account not recognized"; + throw new Error(msg); + } + + const type = isKnownAccount + ? Transaction.types.none + : Transaction.types.fake; + + const tx = Transaction.fromJSON(transaction, this.#common, type); + if (tx.gasLimit.length === 0) { + tx.gasLimit = this.#options.miner.defaultTransactionGasLimit.toBuffer(); + } + + if (tx.gasPrice.length === 0) { + tx.gasPrice = this.#options.miner.gasPrice.toBuffer(); + } + + if (tx.value.length === 0) { + tx.value = Buffer.from([0]); + } + + if (tx.to.equals(utils.BUFFER_ZERO)) { + tx.to = utils.BUFFER_EMPTY; + } + + if (isUnlockedAccount) { + const secretKey = wallet.unlockedAccounts.get(fromString); + return this.#blockchain.queueTransaction(tx, secretKey); + } else { + return this.#blockchain.queueTransaction(tx); + } + } + + /** + * Creates new message call transaction or a contract creation for signed transactions. + * @param transaction + * @returns The transaction hash + */ + @assertArgLength(1) + async eth_sendRawTransaction(transaction: string) { + const tx = new Transaction( + transaction, + this.#common, + Transaction.types.signed + ); + return this.#blockchain.queueTransaction(tx); + } + + /** + * The sign method calculates an Ethereum specific signature with: + * `sign(keccak256("\x19Ethereum Signed Message:\n" + message.length + message)))`. + * + * By adding a prefix to the message makes the calculated signature + * recognizable as an Ethereum specific signature. This prevents misuse where a malicious DApp can sign arbitrary data + * (e.g. transaction) and use the signature to impersonate the victim. + * + * Note the address to sign with must be unlocked. + * + * @param account address + * @param data message to sign + * @returns Signature + */ + @assertArgLength(2) + async eth_sign(address: string | Buffer, message: string | Buffer) { + const account = Address.from(address).toString().toLowerCase(); + + const privateKey = this.#wallet.unlockedAccounts.get(account); + if (privateKey == null) { + throw new Error("cannot sign data; no private key"); + } + + const chainId = this.#options.chain.chainId; + const messageHash = hashPersonalMessage(Data.from(message).toBuffer()); + const { v, r, s } = ecsign(messageHash, privateKey.toBuffer(), chainId); + return toRpcSig(v, r, s, chainId); + } + + /** + * + * @param address Address of the account that will sign the messages. + * @param typedData Typed structured data to be signed. + * @returns Signature. As in `eth_sign`, it is a hex encoded 129 byte array + * starting with `0x`. It encodes the `r`, `s`, and `v` parameters from + * appendix F of the [yellow paper](https://ethereum.github.io/yellowpaper/paper.pdf) + * in big-endian format. Bytes 0...64 contain the `r` parameter, bytes + * 64...128 the `s` parameter, and the last byte the `v` parameter. Note + * that the `v` parameter includes the chain id as specified in [EIP-155](https://eips.ethereum.org/EIPS/eip-155). + * @EIP [712](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md) + */ + @assertArgLength(2) + async eth_signTypedData(address: string | Buffer, typedData: TypedData) { + const account = Address.from(address).toString().toLowerCase(); + + const privateKey = this.#wallet.unlockedAccounts.get(account); + if (privateKey == null) { + throw new Error("cannot sign data; no private key"); + } + + if (!typedData.types) { + throw new Error("cannot sign data; types missing"); + } + + if (!typedData.types.EIP712Domain) { + throw new Error("cannot sign data; EIP712Domain definition missing"); + } + + if (!typedData.domain) { + throw new Error("cannot sign data; domain missing"); + } + + if (!typedData.primaryType) { + throw new Error("cannot sign data; primaryType missing"); + } + + if (!typedData.message) { + throw new Error("cannot sign data; message missing"); + } + + return signTypedData_v4(privateKey.toBuffer(), { data: typedData }); + } + + /** + * Starts a subscription to a particular event. For every event that matches + * the subscription a JSON-RPC notification with event details and + * subscription ID will be sent to a client. + * + * @param subscriptionName + * @returns A subscription id. + */ + eth_subscribe(subscriptionName: SubscriptionName): PromiEvent; + /** + * Starts a subscription to a particular event. For every event that matches + * the subscription a JSON-RPC notification with event details and + * subscription ID will be sent to a client. + * + * @param subscriptionName + * @param options Filter options: + * * `address`: either an address or an array of addresses. Only logs that + * are created from these addresses are returned + * * `topics`, only logs which match the specified topics + * @returns A subscription id. + */ + eth_subscribe( + subscriptionName: "logs", + options: BaseFilterArgs + ): PromiEvent; + @assertArgLength(1, 2) + eth_subscribe(subscriptionName: SubscriptionName, options?: BaseFilterArgs) { + const subscriptions = this.#subscriptions; + switch (subscriptionName) { + case "newHeads": { + const subscription = this.#getId(); + const promiEvent = PromiEvent.resolve(subscription); + + const unsubscribe = this.#blockchain.on("block", (block: Block) => { + const value = block; + const header = value.header; + const result = { + logsBloom: header.logsBloom, + miner: header.miner, + difficulty: header.difficulty, + extraData: header.extraData, + gasLimit: header.gasLimit, + gasUsed: header.gasUsed, + hash: block.hash(), + mixHash: block.header.mixHash, + nonce: header.nonce, + number: header.number, + parentHash: header.parentHash, + receiptsRoot: header.receiptsRoot, + stateRoot: header.stateRoot, + timestamp: header.timestamp, + transactionsRoot: header.transactionsRoot, + sha3Uncles: header.sha3Uncles + }; + + // TODO: move the JSON stringification closer to where the message + // is actually sent to the listener + promiEvent.emit("message", { + type: "eth_subscription", + data: { + result: JSON.parse(JSON.stringify(result)), + subscription: subscription.toString() + } + }); + }); + subscriptions.set(subscription.toString(), unsubscribe); + return promiEvent; + } + case "logs": { + const subscription = this.#getId(); + const promiEvent = PromiEvent.resolve(subscription); + + const { addresses, topics } = options + ? parseFilterDetails(options) + : { addresses: [], topics: [] }; + const unsubscribe = this.#blockchain.on( + "blockLogs", + (blockLogs: BlockLogs) => { + // TODO: move the JSON stringification closer to where the message + // is actually sent to the listener + const result = JSON.parse( + JSON.stringify([...blockLogs.filter(addresses, topics)]) + ); + promiEvent.emit("message", { + type: "eth_subscription", + data: { + result, + subscription: subscription.toString() + } + }); + } + ); + subscriptions.set(subscription.toString(), unsubscribe); + return promiEvent; + } + case "newPendingTransactions": { + const subscription = this.#getId(); + const promiEvent = PromiEvent.resolve(subscription); + + const unsubscribe = this.#blockchain.on( + "pendingTransaction", + (transaction: Transaction) => { + const result = Data.from(transaction.hash(), 32).toString(); + promiEvent.emit("message", { + type: "eth_subscription", + data: { + result, + subscription: subscription.toString() + } + }); + } + ); + subscriptions.set(subscription.toString(), unsubscribe); + return promiEvent; + } + case "syncing": { + // ganache doesn't sync, so doing nothing is perfectly valid. + const subscription = this.#getId(); + const promiEvent = PromiEvent.resolve(subscription); + + this.#subscriptions.set(subscription.toString(), () => {}); + return promiEvent; + } + default: + throw new CodedError( + `no \"${subscriptionName}\" subscription in eth namespace`, + ErrorCodes.METHOD_NOT_FOUND + ); + } + } + + @assertArgLength(1) + async eth_unsubscribe(subscriptionId: SubscriptionId) { + const subscriptions = this.#subscriptions; + const unsubscribe = subscriptions.get(subscriptionId); + if (unsubscribe) { + subscriptions.delete(subscriptionId); + unsubscribe(); + return true; + } else { + return false; + } + } + + /** + * Creates a filter in the node, to notify when a new block arrives. To check + * if the state has changed, call `eth_getFilterChanges`. + * + * @returns A filter id. + */ + @assertArgLength(0) + async eth_newBlockFilter() { + const unsubscribe = this.#blockchain.on("block", (block: Block) => { + value.updates.push(block.hash()); + }); + const value = { + updates: [], + unsubscribe, + filter: null, + type: FilterTypes.block + }; + const filterId = this.#getId(); + this.#filters.set(filterId.toString(), value); + return filterId; + } + + /** + * Creates a filter in the node, to notify when new pending transactions + * arrive. To check if the state has changed, call `eth_getFilterChanges`. + * + * @returns A filter id. + */ + @assertArgLength(0) + async eth_newPendingTransactionFilter() { + const unsubscribe = this.#blockchain.on( + "pendingTransaction", + (transaction: Transaction) => { + value.updates.push(Data.from(transaction.hash(), 32)); + } + ); + const value = { + updates: [], + unsubscribe, + filter: null, + type: FilterTypes.pendingTransaction + }; + const filterId = this.#getId(); + this.#filters.set(filterId.toString(), value); + return filterId; + } + + /** + * Creates a filter object, based on filter options, to notify when the state + * changes (logs). To check if the state has changed, call + * `eth_getFilterChanges`. + * + * If the from `fromBlock` or `toBlock` option are equal to "latest" the + * filter continually append logs for whatever block is seen as latest at the + * time the block was mined, not just for the block that was "latest" when the + * filter was created. + * + * ### A note on specifying topic filters: + * Topics are order-dependent. A transaction with a log with topics [A, B] + * will be matched by the following topic filters: + * * `[]` “anything” + * * `[A]` “A in first position (and anything after)” + * * `[null, B]` “anything in first position AND B in second position (and + * anything after)” + * * `[A, B]` “A in first position AND B in second position (and anything + * after)” + * * `[[A, B], [A, B]]` “(A OR B) in first position AND (A OR B) in second + * position (and anything after)” + * + * @param filter The filter options + */ + @assertArgLength(0, 1) + async eth_newFilter(filter: RangeFilterArgs = {}) { + const blockchain = this.#blockchain; + const { addresses, topics } = parseFilterDetails(filter); + const unsubscribe = blockchain.on("blockLogs", (blockLogs: BlockLogs) => { + const blockNumber = blockLogs.blockNumber; + // everytime we get a blockLogs message we re-check what the filter's + // range is. We do this because "latest" isn't the latest block at the + // time the filter was set up, rather it is the actual latest *mined* + // block (that is: not pending) + const { fromBlock, toBlock } = parseFilterRange(filter, blockchain); + if (fromBlock <= blockNumber && toBlock >= blockNumber) { + value.updates.push(...blockLogs.filter(addresses, topics)); + } + }); + const value = { updates: [], unsubscribe, filter, type: FilterTypes.log }; + const filterId = this.#getId(); + this.#filters.set(filterId.toString(), value); + return filterId; + } + + /** + * Polling method for a filter, which returns an array of logs, block hashes, + * or transaction hashes, depending on the filter type, which occurred since + * last poll. + * + * @param filterId the filter id. + * @returns an array of logs, block hashes, or transaction hashes, depending + * on the filter type, which occurred since last poll. + */ + @assertArgLength(1) + async eth_getFilterChanges(filterId: string) { + const filter = this.#filters.get(filterId); + if (filter) { + const updates = filter.updates; + filter.updates = []; + return updates; + } else { + throw new Error("filter not found"); + } + } + + /** + * Uninstalls a filter with given id. Should always be called when watch is + * no longer needed. + * + * @param filterId the filter id. + * @returns `true` if the filter was successfully uninstalled, otherwise + * `false`. + */ + @assertArgLength(1) + async eth_uninstallFilter(filterId: string) { + const filter = this.#filters.get(filterId); + if (!filter) return false; + filter.unsubscribe(); + return this.#filters.delete(filterId); + } + + /** + * Returns an array of all logs matching filter with given id. + * + * @returns Array of log objects, or an empty array. + */ + @assertArgLength(1) + async eth_getFilterLogs(filterId: string) { + const filter = this.#filters.get(filterId); + if (filter && filter.type === FilterTypes.log) { + return this.eth_getLogs(filter.filter); + } else { + throw new Error("filter not found"); + } + } + + /** + * Returns an array of all logs matching a given filter object. + * + * @param filter The filter options + * @returns Array of log objects, or an empty array. + */ + @assertArgLength(1) + async eth_getLogs(filter: FilterArgs) { + const blockchain = this.#blockchain; + if ("blockHash" in filter) { + const { addresses, topics } = parseFilterDetails(filter); + const blockNumber = await blockchain.blocks.getNumberFromHash( + filter.blockHash + ); + if (!blockNumber) return []; + const blockLogs = blockchain.blockLogs; + const logs = await blockLogs.get(blockNumber); + return logs ? [...logs.filter(addresses, topics)] : []; + } else { + const { addresses, topics, fromBlock, toBlockNumber } = parseFilter( + filter, + blockchain + ); + + const blockLogs = blockchain.blockLogs; + const pendingLogsPromises: Promise[] = [ + blockLogs.get(fromBlock.toBuffer()) + ]; + + const fromBlockNumber = fromBlock.toNumber(); + // if we have a range of blocks to search, do that here: + if (fromBlockNumber !== toBlockNumber) { + // fetch all the blockLogs in-between `fromBlock` and `toBlock` (excluding + // from, because we already started fetching that one) + for (let i = fromBlockNumber + 1, l = toBlockNumber + 1; i < l; i++) { + pendingLogsPromises.push(blockLogs.get(Quantity.from(i).toBuffer())); + } + } + + // now filter and compute all the blocks' blockLogs (in block order) + return Promise.all(pendingLogsPromises).then(blockLogsRange => { + const filteredBlockLogs: ReturnType< + typeof BlockLogs["logToJSON"] + >[] = []; + blockLogsRange.forEach(blockLogs => { + // TODO(perf): this loops over all addresses for every block. + // Maybe make it loop only once? + if (blockLogs) + filteredBlockLogs.push(...blockLogs.filter(addresses, topics)); + }); + return filteredBlockLogs; + }); + } + } + + /** + * Returns the number of transactions sent from an address. + * + * @param address + * @param blockNumber integer block number, or the string "latest", "earliest" + * or "pending", see the default block parameter + * @returns integer of the number of transactions sent from this address. + */ + @assertArgLength(1, 2) + async eth_getTransactionCount( + address: string, + blockNumber: Buffer | Tag = Tag.LATEST + ) { + return this.#blockchain.accounts.getNonce( + Address.from(address), + blockNumber + ); + } + + /** + * Executes a new message call immediately without creating a transaction on the block chain. + * + * @param transaction + * @param blockNumber + * + * @returns the return value of executed contract. + */ + @assertArgLength(1, 2) + async eth_call( + transaction: any, + blockNumber: string | Buffer | Tag = Tag.LATEST + ) { + const blockchain = this.#blockchain; + const blocks = blockchain.blocks; + const parentBlock = await blocks.get(blockNumber); + const parentHeader = parentBlock.header; + const options = this.#options; + + let gas: Quantity; + if (typeof transaction.gasLimit === "undefined") { + if (typeof transaction.gas !== "undefined") { + gas = Quantity.from(transaction.gas); + } else { + // eth_call isn't subject to regular transaction gas limits by default + gas = options.miner.callGasLimit; + } + } else { + gas = Quantity.from(transaction.gasLimit); + } + + let data: Data; + if (typeof transaction.data === "undefined") { + if (typeof transaction.input === "undefined") { + data = Data.from(transaction.input); + } + } else { + data = Data.from(transaction.data); + } + + const block = new RuntimeBlock( + parentHeader.number, + parentHeader.parentHash, + blockchain.coinbase, + gas.toBuffer(), + parentHeader.timestamp + ); + + const simulatedTransaction = { + gas, + // if we don't have a from address, our caller sut be the configured coinbase address + from: + transaction.from == null + ? blockchain.coinbase + : Address.from(transaction.from), + to: transaction.to == null ? null : Address.from(transaction.to), + gasPrice: Quantity.from( + transaction.gasPrice == null ? 0 : transaction.gasPrice + ), + value: + transaction.value == null ? null : Quantity.from(transaction.value), + data, + block + }; + + return blockchain.simulateTransaction(simulatedTransaction, parentBlock); + } + //#endregion + + //#region personal + /** + * Returns all the Ethereum account addresses of all keys that have been + * added. + * @returns the Ethereum account addresses of all keys that have been added. + */ + @assertArgLength(0) + async personal_listAccounts() { + return this.#wallet.addresses; + } + + /** + * Generates a new account with private key. Returns the address of the new + * account. + * @param passphrase + * @returns The new account's address + */ + @assertArgLength(1) + async personal_newAccount(passphrase: string) { + if (typeof passphrase !== "string") { + throw new Error("missing value for required argument `passphrase`"); + } + + const wallet = this.#wallet; + const newAccount = wallet.createRandomAccount( + this.#options.wallet.mnemonic + ); + const address = newAccount.address; + const strAddress = address.toString(); + const encryptedKeyFile = await wallet.encrypt( + newAccount.privateKey, + passphrase + ); + wallet.encryptedKeyFiles.set(strAddress, encryptedKeyFile); + wallet.addresses.push(strAddress); + wallet.knownAccounts.add(strAddress); + return newAccount.address; + } + + /** + * Imports the given unencrypted private key (hex string) into the key store, encrypting it with the passphrase. + * + * @param rawKey + * @param passphrase + * @returnsReturns the address of the new account. + */ + @assertArgLength(2) + async personal_importRawKey(rawKey: string, passphrase: string) { + if (typeof passphrase !== "string") { + throw new Error("missing value for required argument `passphrase`"); + } + + const wallet = this.#wallet; + const newAccount = Wallet.createAccountFromPrivateKey(Data.from(rawKey)); + const address = newAccount.address; + const strAddress = address.toString(); + const encryptedKeyFile = await wallet.encrypt( + newAccount.privateKey, + passphrase + ); + wallet.encryptedKeyFiles.set(strAddress, encryptedKeyFile); + wallet.addresses.push(strAddress); + wallet.knownAccounts.add(strAddress); + return newAccount.address; + } + + /** + * Locks the account. The account can no longer be used to send transactions. + * @param address + */ + @assertArgLength(1) + async personal_lockAccount(address: string) { + return this.#wallet.lockAccount(address.toLowerCase()); + } + + /** + * Unlocks the account for use. + * + * The unencrypted key will be held in memory until the unlock duration + * expires. The unlock duration defaults to 300 seconds. An explicit duration + * of zero seconds unlocks the key until geth exits. + * + * The account can be used with eth_sign and eth_sendTransaction while it is + * unlocked. + * @param address 20 Bytes - The address of the account to unlock. + * @param passphrase Passphrase to unlock the account. + * @param duration (default: 300) Duration in seconds how long the account + * should remain unlocked for. Set to 0 to disable automatic locking. + * @returns true if it worked. Throws an error if it did not. + */ + @assertArgLength(2, 3) + async personal_unlockAccount( + address: string, + passphrase: string, + duration: number = 300 + ) { + return this.#wallet.unlockAccount( + address.toLowerCase(), + passphrase, + duration + ); + } + + /** + * Validate the given passphrase and submit transaction. + * + * The transaction is the same argument as for eth_sendTransaction and + * contains the from address. If the passphrase can be used to decrypt the + * private key belogging to tx.from the transaction is verified, signed and + * send onto the network. The account is not unlocked globally in the node + * and cannot be used in other RPC calls. + * + * @param txData + * @param passphrase + */ + @assertArgLength(2) + async personal_sendTransaction(transaction: any, passphrase: string) { + let fromString = transaction.from; + let from: Address; + if (fromString) { + from = Address.from(transaction.from); + fromString = from.toString().toLowerCase(); + } + + if (fromString == null) { + throw new Error("from not found; is required"); + } + + const wallet = this.#wallet; + const encryptedKeyFile = wallet.encryptedKeyFiles.get(fromString); + if (encryptedKeyFile === undefined) { + throw new Error("no key for given address or file"); + } + let tx: Transaction; + if (encryptedKeyFile !== null) { + const secretKey = await wallet.decrypt(encryptedKeyFile, passphrase); + + tx = new Transaction(transaction, this.#common); + tx.sign(secretKey); + } else { + tx = new Transaction(transaction, this.#common, Transaction.types.fake); + } + + return this.#blockchain.queueTransaction(tx); + } + //#endregion + + //#region rpc + @assertArgLength(0) + async rpc_modules() { + return RPC_MODULES; + } + //endregion + + //#region shh + + /** + * Creates new whisper identity in the client. + * + * @returns {DATA, 60 Bytes} result - the address of the new identiy. + */ + @assertArgLength(0) + async shh_newIdentity() { + return "0x00"; + } + + /** + * Checks if the client hold the private keys for a given identity. + * + * @param {DATA, 60 Bytes} address - The identity address to check. + * @returns returns true if the client holds the privatekey for that identity, otherwise false. + */ + @assertArgLength(1) + async shh_hasIdentity(address: string) { + return false; + } + + /** + * Creates a new group. + * + * @returns the address of the new group. + */ + @assertArgLength(0) + async shh_newGroup() { + return "0x00"; + } + + /** + * Adds a whisper identity to the group + * + * @param {DATA, 60 Bytes} - The identity address to add to a group. + * @returns true if the identity was successfully added to the group, otherwise false. + */ + @assertArgLength(1) + async shh_addToGroup(address: string) { + return false; + } + + /** + * Creates filter to notify, when client receives whisper message matching the filter options. + * + * @param {DATA, 60 Bytes} to - + * ^(optional) Identity of the receiver. When present it will try to decrypt any incoming message + * if the client holds the private key to this identity. + * @param {Array of DATA} topics - Array of DATA topics which the incoming message's topics should match. + * @returns returns true if the identity was successfully added to the group, otherwise false. + */ + @assertArgLength(2) + async shh_newFilter(to: string, topics: any[]) { + return false; + } + + /** + * Uninstalls a filter with given id. Should always be called when watch is no longer needed. + * Additonally Filters timeout when they aren't requested with shh_getFilterChanges for a period of time. + * + * @param {QUANTITY} id - The filter id. Ex: "0x7" + * @returns true if the filter was successfully uninstalled, otherwise false. + */ + @assertArgLength(1) + async shh_uninstallFilter(id: string) { + return false; + } + + /** + * Polling method for whisper filters. Returns new messages since the last call of this method. + * + * @param {QUANTITY} id - The filter id. Ex: "0x7" + * @returns More Info: https://github.com/ethereum/wiki/wiki/JSON-RPC#shh_getfilterchanges + */ + @assertArgLength(1) + async shh_getFilterChanges(id: string) { + return []; + } + + /** + * Get all messages matching a filter. Unlike shh_getFilterChanges this returns all messages. + * + * @param {QUANTITY} id - The filter id. Ex: "0x7" + * @returns See: shh_getFilterChanges + */ + @assertArgLength(1) + async shh_getMessages(id: string) { + return false; + } + + /** + * Creates a whisper message and injects it into the network for distribution. + * + * @param postData + * @returns returns true if the message was sent, otherwise false. + */ + @assertArgLength(1) + async shh_post(postData: WhisperPostObject) { + return false; + } + + /** + * Returns the current whisper protocol version. + * + * @returns The current whisper protocol version + */ + @assertArgLength(0) + async shh_version() { + return "2"; + } + //#endregion +} diff --git a/src/chains/ethereum/src/blockchain.ts b/src/chains/ethereum/src/blockchain.ts new file mode 100644 index 0000000000..57aded2c45 --- /dev/null +++ b/src/chains/ethereum/src/blockchain.ts @@ -0,0 +1,821 @@ +import { EOL } from "os"; +import RuntimeError, { RETURN_TYPES } from "./errors/runtime-error"; +import Miner from "./miner/miner"; +import Database from "./database"; +import Emittery from "emittery"; +import BlockManager from "./data-managers/block-manager"; +import BlockLogs from "./things/blocklogs"; +import TransactionManager from "./data-managers/transaction-manager"; +import CheckpointTrie from "merkle-patricia-tree"; +import { BN, KECCAK256_RLP } from "ethereumjs-util"; +import Account from "./things/account"; +import { promisify } from "util"; +import { Quantity, Data } from "@ganache/utils"; +import EthereumJsAccount from "ethereumjs-account"; +import AccountManager from "./data-managers/account-manager"; +import { utils } from "@ganache/utils"; +import Transaction from "./things/transaction"; +import Manager from "./data-managers/manager"; +import TransactionReceipt from "./things/transaction-receipt"; +import { encode as rlpEncode } from "rlp"; +import Common from "ethereumjs-common"; +import VM from "ethereumjs-vm"; +import Address from "./things/address"; +import BlockLogManager from "./data-managers/blocklog-manager"; +import { EVMResult } from "ethereumjs-vm/dist/evm/evm"; +import { VmError, ERROR } from "ethereumjs-vm/dist/exceptions"; +import { EthereumInternalOptions } from "./options"; +import { Snapshots } from "./types/snapshots"; +import { RuntimeBlock, Block } from "./things/runtime-block"; +const { + BUFFER_EMPTY, + RPCQUANTITY_EMPTY, + BUFFER_32_ZERO, + BUFFER_256_ZERO, + RPCQUANTITY_ZERO +} = utils; + +type SimulationTransaction = { + /** + * The address the transaction is sent from. + */ + from: Address; + /** + * The address the transaction is directed to. + */ + to?: Address; + /** + * Integer of the gas provided for the transaction execution. eth_call consumes zero gas, but this parameter may be needed by some executions. + */ + gas: Quantity; + /** + * Integer of the gasPrice used for each paid gas + */ + gasPrice: Quantity; + /** + * Integer of the value sent with this transaction + */ + value?: Quantity; + /** + * Hash of the method signature and encoded parameters. For details see Ethereum Contract ABI in the Solidity documentation + */ + data?: Data; + block: RuntimeBlock; +}; + +const unref = utils.unref; + +export enum Status { + // Flags + started = 1, // 0000 0001 + starting = 2, // 0000 0010 + stopped = 4, // 0000 0100 + stopping = 8, // 0000 1000 + paused = 16 // 0001 0000 +} + +type BlockchainTypedEvents = { + block: Block; + blockLogs: BlockLogs; + pendingTransaction: Transaction; +}; +type BlockchainEvents = "start" | "stop"; + +/** + * Sets the provided VM state manager's state root *without* first + * checking for checkpoints or flushing the existing cache. + * + * Useful if you know the state manager is not in a checkpoint and its internal + * cache is safe to discard. + * + * @param stateManager + * @param stateRoot + */ +function setStateRootSync(stateManager: VM["stateManager"], stateRoot: Buffer) { + stateManager._trie.root = stateRoot; + stateManager._cache.clear(); + stateManager._storageTries = {}; +} + +export default class Blockchain extends Emittery.Typed< + BlockchainTypedEvents, + BlockchainEvents +> { + #state: Status = Status.starting; + #miner: Miner; + #blockBeingSavedPromise: Promise<{ block: Block; blockLogs: BlockLogs }>; + public blocks: BlockManager; + public blockLogs: BlockLogManager; + public transactions: TransactionManager; + public transactionReceipts: Manager; + public accounts: AccountManager; + public vm: VM; + public trie: CheckpointTrie; + + readonly #database: Database; + readonly #common: Common; + readonly #options: EthereumInternalOptions; + readonly #instamine: boolean; + + /** + * Initializes the underlying Database and handles synchronization between + * the API and the database. + * + * Emits a `ready` event once the database and all dependencies are fully + * initialized. + * @param options + */ + constructor( + options: EthereumInternalOptions, + common: Common, + initialAccounts: Account[], + coinbaseAddress: Address + ) { + super(); + this.#options = options; + this.#common = common; + + const instamine = (this.#instamine = + !options.miner.blockTime || options.miner.blockTime <= 0); + const legacyInstamine = options.miner.legacyInstamine; + + { + // warnings and errors + if (legacyInstamine) { + console.info( + "Legacy instamining, where transactions are fully mined before the hash is returned, is deprecated and will be removed in the future." + ); + } + + if (!instamine) { + if (legacyInstamine) { + console.info( + "Setting `legacyInstamine` to `true` has no effect when blockTime is non-zero" + ); + } + + if (options.chain.vmErrorsOnRPCResponse) { + console.info( + "Setting `vmErrorsOnRPCResponse` to `true` has no effect on transactions when blockTime is non-zero" + ); + } + } + } + + const database = (this.#database = new Database(options.database, this)); + database.once("ready").then(async () => { + const blocks = (this.blocks = await BlockManager.initialize( + common, + database.blockIndexes, + database.blocks + )); + + // if we have a latest block, use it to set up the trie. + const latest = blocks.latest; + if (latest) { + this.#blockBeingSavedPromise = Promise.resolve({ + block: latest, + blockLogs: null + }); + this.trie = new CheckpointTrie( + database.trie, + latest.header.stateRoot.toBuffer() + ); + } else { + this.trie = new CheckpointTrie(database.trie, null); + } + + this.blockLogs = new BlockLogManager(database.blockLogs); + this.transactions = new TransactionManager( + options.miner, + common, + this, + database.transactions + ); + this.transactionReceipts = new Manager( + database.transactionReceipts, + TransactionReceipt + ); + this.accounts = new AccountManager(this, database.trie); + + this.coinbase = coinbaseAddress; + + // create VM and listen to step events + this.vm = this.createVmFromStateTrie( + this.trie, + options.chain.allowUnlimitedContractSize + ); + + await this.#commitAccounts(initialAccounts); + + { + // create first block + let firstBlockTime: number; + if (options.chain.time != null) { + // If we were given a timestamp, use it instead of the `_currentTime` + const t = +options.chain.time; + firstBlockTime = Math.floor(t / 1000); + this.setTime(t); + } else { + firstBlockTime = this.#currentTime(); + } + + // if we don't already have a latest block, create a genesis block! + if (!latest) { + this.#blockBeingSavedPromise = this.#initializeGenesisBlock( + firstBlockTime, + options.miner.blockGasLimit + ); + blocks.earliest = blocks.latest = await this.#blockBeingSavedPromise.then( + ({ block }) => block + ); + } + } + + { + // configure and start miner + const txPool = this.transactions.transactionPool; + const minerOpts = options.miner; + const miner = (this.#miner = new Miner( + minerOpts, + txPool.executables, + instamine, + this.vm, + this.#readyNextBlock + )); + + //#region automatic mining + const nullResolved = Promise.resolve(null); + const mineAll = (maxTransactions: number) => + this.#isPaused() ? nullResolved : this.mine(maxTransactions); + if (instamine) { + // insta mining + // whenever the transaction pool is drained mine the txs into blocks + txPool.on("drain", mineAll.bind(null, 1)); + } else { + // interval mining + const wait = () => unref(setTimeout(next, minerOpts.blockTime * 1e3)); + const next = () => mineAll(-1).then(wait); + wait(); + } + //#endregion + + miner.on("block", this.#handleNewBlockData); + + this.once("stop").then(() => miner.clearListeners()); + } + + this.#state = Status.started; + this.emit("start"); + }); + } + + #saveNewBlock = ({ + block, + serialized + }: { + block: Block; + serialized: Buffer; + }) => { + const { blocks } = this; + blocks.latest = block; + return this.#database.batch(() => { + const blockHash = block.hash().toBuffer(); + const blockHeader = block.header; + const blockNumberQ = blockHeader.number; + const blockNumber = blockHeader.number.toBuffer(); + const blockLogs = BlockLogs.create(blockHash); + const timestamp = new Date( + blockHeader.timestamp.toNumber() * 1000 + ).toString(); + const logOutput: string[] = []; + block.getTransactions().forEach((tx: Transaction, i: number) => { + const hash = tx.hash(); + const index = Quantity.from(i).toBuffer(); + const txAndExtraData = [ + ...tx.raw, + blockHash, + blockNumber, + index, + Buffer.from([tx.type]), + tx.from + ]; + const encodedTx = rlpEncode(txAndExtraData); + this.transactions.set(hash, encodedTx); + + const receipt = tx.getReceipt(); + const encodedReceipt = receipt.serialize(true); + this.transactionReceipts.set(hash, encodedReceipt); + + tx.getLogs().forEach(blockLogs.append.bind(blockLogs, index, hash)); + + logOutput.push( + this.#getTransactionLogOutput( + hash, + receipt, + blockNumberQ, + timestamp, + tx.execException + ) + ); + }); + + blockLogs.blockNumber = blockNumberQ; + this.blockLogs.set(blockNumber, blockLogs.serialize()); + blocks.putBlock(blockNumber, blockHash, serialized); + this.#options.logging.logger.log(logOutput.join(EOL)); + return { block, blockLogs }; + }); + }; + + #emitNewBlock = async (blockInfo: { block: Block; blockLogs: BlockLogs }) => { + const options = this.#options; + const { block, blockLogs } = blockInfo; + + // emit the block once everything has been fully saved to the database + block.getTransactions().forEach(transaction => { + transaction.finalize("confirmed", transaction.execException); + }); + + if (this.#instamine && options.miner.legacyInstamine) { + // in legacy instamine mode we must delay the broadcast of new blocks + await new Promise(resolve => { + process.nextTick(async () => { + // emit block logs first so filters can pick them up before + // block listeners are notified + await Promise.all([ + this.emit("blockLogs", blockLogs), + this.emit("block", block) + ]); + resolve(void 0); + }); + }); + } else { + // emit block logs first so filters can pick them up before + // block listeners are notified + await Promise.all([ + this.emit("blockLogs", blockLogs), + this.emit("block", block) + ]); + } + + return blockInfo; + }; + + #getTransactionLogOutput = ( + hash: Buffer, + receipt: TransactionReceipt, + blockNumber: Quantity, + timestamp: string, + error: RuntimeError | undefined + ) => { + let str = `${EOL} Transaction: ${Data.from(hash)}${EOL}`; + + const contractAddress = receipt.contractAddress; + if (contractAddress != null) { + str += ` Contract created: ${Address.from(contractAddress)}${EOL}`; + } + + str += ` Gas usage: ${Quantity.from(receipt.raw[1]).toNumber()}${EOL} + Block number: ${blockNumber.toNumber()}${EOL} + Block time: ${timestamp}${EOL}`; + + if (error) { + str += ` Runtime error: ${error.data.message}${EOL}`; + if (error.data.reason) { + str += ` Revert reason: ${error.data.reason}${EOL}`; + } + } + + return str; + }; + + #handleNewBlockData = async (blockData: { + block: Block; + serialized: Buffer; + }) => { + this.#blockBeingSavedPromise = this.#blockBeingSavedPromise + .then(() => this.#saveNewBlock(blockData)) + .then(this.#emitNewBlock); + + return this.#blockBeingSavedPromise; + }; + + coinbase: Address; + + #readyNextBlock = (previousBlock: Block, timestamp?: number) => { + const previousHeader = previousBlock.header; + const previousNumber = previousHeader.number.toBigInt() || 0n; + return new RuntimeBlock( + Quantity.from(previousNumber + 1n), + previousBlock.hash(), + this.coinbase, + this.#options.miner.blockGasLimit.toBuffer(), + Quantity.from(timestamp == null ? this.#currentTime() : timestamp) + ); + }; + + isStarted = () => { + return this.#state === Status.started; + }; + + mine = async ( + maxTransactions: number, + timestamp?: number, + onlyOneBlock: boolean = false + ) => { + await this.#blockBeingSavedPromise; + const nextBlock = this.#readyNextBlock(this.blocks.latest, timestamp); + return this.#miner.mine(nextBlock, maxTransactions, onlyOneBlock); + }; + + #isPaused = () => { + return (this.#state & Status.paused) !== 0; + }; + + pause() { + this.#state |= Status.paused; + } + + resume(_threads: number = 1) { + if (!this.#isPaused()) { + console.log("Warning: startMining called when miner was already started"); + return; + } + + // toggles the `paused` bit + this.#state ^= Status.paused; + + // if we are instamining mine a block right away + if (this.#instamine) { + return this.mine(-1); + } + } + + createVmFromStateTrie = ( + stateTrie: CheckpointTrie, + allowUnlimitedContractSize: boolean + ) => { + const blocks = this.blocks; + // ethereumjs vm doesn't use the callback style anymore + const getBlock = class T { + static async [promisify.custom](number: BN) { + const block = await blocks.get(number.toBuffer()).catch(_ => null); + return block ? { hash: () => block.hash().toBuffer() } : null; + } + }; + + return new VM({ + state: stateTrie, + activatePrecompiles: true, + common: this.#common, + allowUnlimitedContractSize, + blockchain: { + getBlock + } as any + }); + }; + + #commitAccounts = async (accounts: Account[]): Promise => { + const stateManager = this.vm.stateManager; + const putAccount = promisify(stateManager.putAccount.bind(stateManager)); + const checkpoint = promisify(stateManager.checkpoint.bind(stateManager)); + const commit = promisify(stateManager.commit.bind(stateManager)); + await checkpoint(); + const l = accounts.length; + const pendingAccounts = Array(l); + for (let i = 0; i < l; i++) { + const account = accounts[i]; + const ethereumJsAccount = new EthereumJsAccount(); + (ethereumJsAccount.nonce = account.nonce.toBuffer()), + (ethereumJsAccount.balance = account.balance.toBuffer()); + pendingAccounts[i] = putAccount( + account.address.toBuffer(), + ethereumJsAccount + ); + } + await Promise.all(pendingAccounts); + await commit(); + }; + + #initializeGenesisBlock = async ( + timestamp: number, + blockGasLimit: Quantity + ) => { + // README: block `0` is weird in that a `0` _should_ be hashed as `[]`, + // but actually return a real `0` with read back later. So we pull some + // shenangians here so we don't require checks in functions that consume + // this block's `0` value later + const rawBlockNumber = RPCQUANTITY_EMPTY; + + // create the genesis block + const genesis = new RuntimeBlock( + rawBlockNumber, + Quantity.from(BUFFER_32_ZERO), + this.#options.miner.coinbase as Address, + blockGasLimit.toBuffer(), + Quantity.from(timestamp) + ); + + // store the genesis block in the database + const { block, serialized } = genesis.finalize( + KECCAK256_RLP, + KECCAK256_RLP, + BUFFER_256_ZERO, + this.trie.root, + BUFFER_EMPTY, + this.#options.miner.extraData, + [] + ); + // README: set the block number to an actual 0 now. + block.header.number = RPCQUANTITY_ZERO; + const hash = block.hash().toBuffer(); + return this.blocks + .putBlock(block.header.number.toBuffer(), hash, serialized) + .then(_ => ({ + block, + blockLogs: BlockLogs.create(hash) + })); + }; + + #timeAdjustment: number = 0; + + /** + * Returns the timestamp, adjusted by the timeAdjustent offset, in seconds. + */ + #currentTime = () => { + return Math.floor((Date.now() + this.#timeAdjustment) / 1000); + }; + + /** + * @param seconds + * @returns the total time offset *in milliseconds* + */ + public increaseTime(seconds: number) { + if (seconds < 0) { + seconds = 0; + } + return (this.#timeAdjustment += seconds); + } + + /** + * @param seconds + * @returns the total time offset *in milliseconds* + */ + public setTime(timestamp: number) { + return (this.#timeAdjustment = timestamp - Date.now()); + } + + #deleteBlockData = (blocksToDelete: Block[]) => { + return this.#database.batch(() => { + const { blocks, transactions, transactionReceipts, blockLogs } = this; + blocksToDelete.forEach(value => { + value.getTransactions().forEach(tx => { + const txHash = tx.hash(); + transactions.del(txHash); + transactionReceipts.del(txHash); + }); + const blockNum = value.header.number.toBuffer(); + blocks.del(blockNum); + blocks.del(value.hash().toBuffer()); + blockLogs.del(blockNum); + }); + }); + }; + + // TODO(stability): this.#snapshots is a potential unbound memory suck. Caller + // could call `evm_snapshot` over and over to grow the snapshot stack + // indefinitely. `this.#snapshots.blocks` is even worse. To solve this we + // might need to store in the db. An unlikely real problem, but possible. + #snapshots: Snapshots = { + snaps: [], + blocks: null, + unsubscribeFromBlocks: null + }; + + public snapshot() { + const snapshots = this.#snapshots; + const snaps = snapshots.snaps; + + // Subscription ids are based on the number of active snapshots. Weird? Yes. + // But it's the way it's been since the beginning so it just hasn't been + // changed. Feel free to change it so ids are unique if it bothers you + // enough. + const id = snaps.push({ + block: this.blocks.latest, + timeAdjustment: this.#timeAdjustment + }); + + // start listening to new blocks if this is the first snapshot + if (id === 1) { + snapshots.unsubscribeFromBlocks = this.on("block", block => { + snapshots.blocks = { + current: block.hash().toBuffer(), + next: snapshots.blocks + }; + }); + } + + this.#options.logging.logger.log("Saved snapshot #" + id); + + return id; + } + + public async revert(snapshotId: Quantity) { + const rawValue = snapshotId.valueOf(); + if (rawValue === null || rawValue === undefined) { + throw new Error("invalid snapshotId"); + } + + this.#options.logging.logger.log("Reverting to snapshot #" + snapshotId); + + // snapshot ids can't be < 1, so we do a quick sanity check here + if (rawValue < 1n) { + return false; + } + + const snapshots = this.#snapshots; + const snaps = snapshots.snaps; + const snapshotIndex = Number(rawValue - 1n); + const snapshot = snaps[snapshotIndex]; + + if (!snapshot) { + return false; + } + + // pause processing new transactions... + await this.transactions.pause(); + + // then pause the miner, too. + await this.#miner.pause(); + + // wait for anything in the process of being saved to finish up + await this.#blockBeingSavedPromise; + + // Pending transactions are always removed when you revert, even if they + // were present before the snapshot was created. Ideally, we'd remove only + // the new transactions.. but we'll leave that for another day. + this.transactions.clear(); + + const blocks = this.blocks; + const currentHash = blocks.latest.hash().toBuffer(); + const snapshotBlock = snapshot.block; + const snapshotHeader = snapshotBlock.header; + const snapshotHash = snapshotBlock.hash().toBuffer(); + + // remove this and all stored snapshots after this snapshot + snaps.splice(snapshotIndex); + + // if there are no more listeners, stop listening to new blocks + if (snaps.length === 0) { + snapshots.unsubscribeFromBlocks(); + } + + // if the snapshot's hash is different than the latest block's hash we've + // got new blocks to clean up. + if (!currentHash.equals(snapshotHash)) { + // if we've added blocks since we snapshotted we need to delete them and put + // some things back the way they were. + const blockPromises = []; + let blockList = snapshots.blocks; + while (blockList !== null) { + if (blockList.current.equals(snapshotHash)) break; + blockPromises.push(blocks.getByHash(blockList.current)); + blockList = blockList.next; + } + snapshots.blocks = blockList; + + await Promise.all(blockPromises).then(this.#deleteBlockData); + + setStateRootSync( + this.vm.stateManager, + snapshotHeader.stateRoot.toBuffer() + ); + blocks.latest = snapshotBlock; + } + + // put our time adjustment back + this.#timeAdjustment = snapshot.timeAdjustment; + + // resume mining + this.#miner.resume(); + + // resume processing transactions + this.transactions.resume(); + + return true; + } + + public async queueTransaction(transaction: Transaction, secretKey?: Data) { + // NOTE: this.transactions.add *must* be awaited before returning the + // `transaction.hash()`, as the transactionPool may change the transaction + // (and thus its hash!) + // It may also throw Errors that must be returned to the caller. + const isExecutable = + (await this.transactions.add(transaction, secretKey)) === true; + if (isExecutable) { + process.nextTick(this.emit.bind(this), "pendingTransaction", transaction); + } + + const hash = Data.from(transaction.hash(), 32); + if (this.#isPaused() || !this.#instamine) { + return hash; + } else { + if (this.#instamine && this.#options.miner.legacyInstamine) { + // in legacyInstamine mode we must wait for the transaction to be saved + // before we can return the hash + const { status, error } = await transaction.once("finalized"); + // in legacyInstamine mode we must throw on all rejected transaction + // errors. We must also throw on `confirmed` tranactions when + // vmErrorsOnRPCResposnse is enabled. + if ( + error && + (status === "rejected" || this.#options.chain.vmErrorsOnRPCResponse) + ) + throw error; + } + return hash; + } + } + + public async simulateTransaction( + transaction: SimulationTransaction, + parentBlock: Block + ) { + let result: EVMResult; + const options = this.#options; + + const data = transaction.data; + let gasLeft = transaction.gas.toBigInt(); + // subtract out the transaction's base fee from the gas limit before + // simulating the tx, because `runCall` doesn't account for raw gas costs. + gasLeft -= Transaction.calculateIntrinsicGas( + data ? data.toBuffer() : null, + options.chain.hardfork + ); + + if (gasLeft >= 0) { + const stateTrie = new CheckpointTrie( + this.#database.trie, + parentBlock.header.stateRoot.toBuffer() + ); + const vm = this.createVmFromStateTrie( + stateTrie, + this.vm.allowUnlimitedContractSize + ); + + result = await vm.runCall({ + caller: transaction.from.toBuffer(), + data: transaction.data && transaction.data.toBuffer(), + gasPrice: transaction.gasPrice.toBuffer(), + gasLimit: Quantity.from(gasLeft).toBuffer(), + to: transaction.to && transaction.to.toBuffer(), + value: transaction.value && transaction.value.toBuffer(), + block: transaction.block + }); + } else { + result = { + execResult: { + runState: { programCounter: 0 }, + exceptionError: new VmError(ERROR.OUT_OF_GAS), + returnValue: BUFFER_EMPTY + } + } as any; + } + if (result.execResult.exceptionError) { + if (this.#options.chain.vmErrorsOnRPCResponse) { + // eth_call transactions don't really have a transaction hash + const hash = BUFFER_EMPTY; + throw new RuntimeError(hash, result, RETURN_TYPES.RETURN_VALUE); + } else { + return Data.from(result.execResult.returnValue || "0x"); + } + } else { + return Data.from(result.execResult.returnValue || "0x"); + } + } + + /** + * Gracefully shuts down the blockchain service and all of its dependencies. + */ + public async stop() { + // If the blockchain is still initalizing we don't want to shut down + // yet because there may still be database calls in flight. Leveldb may + // cause a segfault due to a race condition between a db write and the close + // call. + if (this.#state === Status.starting) { + await this.once("start"); + } + + // clean up listeners + this.vm.removeAllListeners(); + this.transactions.transactionPool.clearListeners(); + await this.emit("stop"); + + if (this.#state === Status.started) { + this.#state = Status.stopping; + await this.#database.close(); + this.#state = Status.stopped; + } + } +} diff --git a/src/chains/ethereum/src/connector.ts b/src/chains/ethereum/src/connector.ts new file mode 100644 index 0000000000..0c8a3f14fc --- /dev/null +++ b/src/chains/ethereum/src/connector.ts @@ -0,0 +1,140 @@ +import Emittery from "emittery"; +import EthereumApi from "./api"; +import { JsonRpcTypes, types, utils } from "@ganache/utils"; +import EthereumProvider from "./provider"; +import { RecognizedString, WebSocket, HttpRequest } from "uWebSockets.js"; +import CodedError, { ErrorCodes } from "./errors/coded-error"; +import { EthereumProviderOptions, EthereumLegacyOptions } from "./options"; + +function isHttp( + connection: HttpRequest | WebSocket +): connection is HttpRequest { + return connection.constructor.name === "uWS.HttpRequest"; +} + +export type ProviderOptions = EthereumProviderOptions | EthereumLegacyOptions; +export type Provider = EthereumProvider; +export const Provider = EthereumProvider; +export const FlavorName = "ethereum" as const; + +export class Connector + extends Emittery.Typed + implements + types.Connector< + EthereumApi, + JsonRpcTypes.Request | JsonRpcTypes.Request[], + JsonRpcTypes.Response + > { + #provider: EthereumProvider; + + get provider() { + return this.#provider; + } + + constructor( + providerOptions: ProviderOptions = null, + executor: utils.Executor + ) { + super(); + + const provider = (this.#provider = new EthereumProvider( + providerOptions, + executor + )); + provider.on("connect", () => { + // tell the consumer (like a `ganache-core` server/connector) everything is ready + this.emit("ready"); + }); + } + + parse(message: Buffer) { + try { + return JSON.parse(message) as JsonRpcTypes.Request; + } catch (e) { + throw new CodedError(e.message, ErrorCodes.PARSE_ERROR); + } + } + + handle( + payload: + | JsonRpcTypes.Request + | JsonRpcTypes.Request[], + connection: HttpRequest | WebSocket + ) { + if (Array.isArray(payload)) { + // handle batch transactions + const promises = payload.map(payload => + this.#handle(payload, connection) + .then(({ value }) => value) + .catch(e => e) + ); + return Promise.resolve({ value: Promise.all(promises) }); + } else { + return this.#handle(payload, connection); + } + } + #handle = ( + payload: JsonRpcTypes.Request, + connection: HttpRequest | WebSocket + ) => { + const method = payload.method; + if (method === "eth_subscribe") { + if (isHttp(connection)) { + return Promise.reject( + new CodedError( + "notifications not supported", + ErrorCodes.METHOD_NOT_SUPPORTED + ) + ); + } + } + const params = payload.params as Parameters; + return this.#provider._requestRaw({ method, params }); + }; + + format( + result: any, + payload: JsonRpcTypes.Request + ): RecognizedString; + format( + results: any[], + payloads: JsonRpcTypes.Request[] + ): RecognizedString; + format( + results: any | any[], + payload: + | JsonRpcTypes.Request + | JsonRpcTypes.Request[] + ): RecognizedString { + if (Array.isArray(payload)) { + return JSON.stringify( + payload.map((payload, i) => { + const result = results[i]; + if (result instanceof Error) { + return JsonRpcTypes.Error(payload.id, result as any); + } else { + return JsonRpcTypes.Response(payload.id, result); + } + }) + ); + } else { + const json = JsonRpcTypes.Response(payload.id, results); + return JSON.stringify(json); + } + } + + formatError( + error: Error & { code: number }, + payload: JsonRpcTypes.Request + ): RecognizedString { + const json = JsonRpcTypes.Error( + payload && payload.id ? payload.id : null, + error + ); + return JSON.stringify(json); + } + + close() { + return this.#provider.disconnect(); + } +} diff --git a/src/chains/ethereum/src/data-managers/account-manager.ts b/src/chains/ethereum/src/data-managers/account-manager.ts new file mode 100644 index 0000000000..f0be2b9c37 --- /dev/null +++ b/src/chains/ethereum/src/data-managers/account-manager.ts @@ -0,0 +1,71 @@ +import Account from "../things/account"; +import Address from "../things/address"; +import Trie from "merkle-patricia-tree/baseTrie"; +import Blockchain from "../blockchain"; +import Tag from "../things/tags"; +import { LevelUp } from "levelup"; +import { rlp } from "ethereumjs-util"; +import { utils, Quantity } from "@ganache/utils"; + +const RPCQUANTITY_ZERO = utils.RPCQUANTITY_ZERO; + +export default class AccountManager { + #blockchain: Blockchain; + #trie: LevelUp; + constructor(blockchain: Blockchain, trie: LevelUp) { + this.#blockchain = blockchain; + this.#trie = trie; + } + + public async getRaw( + address: Address, + blockNumber: Buffer | Tag = Tag.LATEST + ): Promise { + const blockchain = this.#blockchain; + const block = await blockchain.blocks.get(blockNumber); + const trieCopy = new Trie(this.#trie, block.header.stateRoot.toBuffer()); + return new Promise((resolve, reject) => { + trieCopy.get(address.toBuffer(), (err, data) => { + if (err) return reject(err); + resolve(data); + }); + }); + } + + public async getNonce( + address: Address, + blockNumber: Buffer | Tag = Tag.LATEST + ): Promise { + return this.getRaw(address, blockNumber).then(data => { + if (data) { + const [nonce] = (rlp.decode(data) as any) as Buffer[]; + return nonce.length === 0 ? RPCQUANTITY_ZERO : Quantity.from(nonce); + } else { + return RPCQUANTITY_ZERO; + } + }); + } + + public async getBalance( + address: Address, + blockNumber: Buffer | Tag = Tag.LATEST + ): Promise { + return this.getRaw(address, blockNumber).then(data => { + if (data) { + const [, balance] = (rlp.decode(data) as any) as Buffer[]; + return balance.length === 0 ? RPCQUANTITY_ZERO : Quantity.from(balance); + } else { + return RPCQUANTITY_ZERO; + } + }); + } + + public async get( + address: Address, + blockNumber: Buffer | Tag = Tag.LATEST + ): Promise { + return this.getRaw(address, blockNumber).then(data => { + return Account.fromBuffer(data); + }); + } +} diff --git a/src/chains/ethereum/src/data-managers/block-manager.ts b/src/chains/ethereum/src/data-managers/block-manager.ts new file mode 100644 index 0000000000..c05bf9505b --- /dev/null +++ b/src/chains/ethereum/src/data-managers/block-manager.ts @@ -0,0 +1,153 @@ +import Manager from "./manager"; +import Tag from "../things/tags"; +import { LevelUp } from "levelup"; +import { Quantity, Data } from "@ganache/utils"; +import Common from "ethereumjs-common"; +import { Block } from "../things/runtime-block"; + +const NOTFOUND = 404; + +const EMPTY_BUFFER = Buffer.from([]); + +export default class BlockManager extends Manager { + /** + * The earliest block + */ + public earliest: Block; + + /** + * The latest block + */ + public latest: Block; + + /** + * The next block + */ + public pending: Block; + + #common: Common; + #blockIndexes: LevelUp; + + static async initialize( + common: Common, + blockIndexes: LevelUp, + base: LevelUp + ) { + const bm = new BlockManager(common, blockIndexes, base); + await bm.updateTaggedBlocks(); + return bm; + } + + constructor(common: Common, blockIndexes: LevelUp, base: LevelUp) { + super(base, Block, common); + + this.#common = common; + this.#blockIndexes = blockIndexes; + } + + getBlockByTag(tag: Tag) { + switch (Tag.normalize(tag as Tag)) { + case Tag.LATEST: + return this.latest; + case void 0: + case null: + // the key is probably a hex string, let nature takes its course. + break; + case Tag.PENDING: + // TODO: build a real pending block! + return this.latest; // this.createBlock(this.latest.header); + case Tag.EARLIEST: + return this.earliest; + default: + // this probably can't happen. but if someone passed something like + // `toString` in as a block tag and it got this far... maybe we'd + // get here... + throw new Error(`Invalid block Tag: ${tag}`); + } + } + + getEffectiveNumber(tagOrBlockNumber: string | Buffer | Tag = Tag.LATEST) { + if (typeof tagOrBlockNumber === "string") { + const block = this.getBlockByTag(tagOrBlockNumber as Tag); + if (block) { + return block.header.number; + } + } + return Quantity.from(tagOrBlockNumber); + } + + async getNumberFromHash(hash: string | Buffer | Tag) { + return this.#blockIndexes.get(Data.from(hash).toBuffer()).catch(e => { + if (e.status === NOTFOUND) return null; + throw e; + }) as Promise; + } + + async getByHash(hash: string | Buffer | Tag) { + const number = await this.getNumberFromHash(hash); + return number ? super.get(number) : null; + } + + async getRaw(tagOrBlockNumber: string | Buffer | Tag) { + // TODO(perf): make the block's raw fields accessible on latest/earliest/pending so + // we don't have to fetch them from the db each time a block tag is used. + return super.getRaw(this.getEffectiveNumber(tagOrBlockNumber).toBuffer()); + } + + async get(tagOrBlockNumber: string | Buffer | Tag) { + if (typeof tagOrBlockNumber === "string") { + const block = this.getBlockByTag(tagOrBlockNumber as Tag); + if (block) return block; + } + + const block = await super.get(tagOrBlockNumber); + if (block) return block; + + throw new Error("header not found"); + } + + /** + * Writes the block object to the underlying database. + * @param block + */ + async putBlock(number: Buffer, hash: Buffer, serialized: Buffer) { + let key = number; + // ensure we can store Block #0 as key "00", not "" + if (EMPTY_BUFFER.equals(key)) { + key = Buffer.from([0]); + } + const secondaryKey = hash; + await Promise.all([ + this.#blockIndexes.put(secondaryKey, key), + super.set(key, serialized) + ]); + } + + updateTaggedBlocks() { + return new Promise((resolve, reject) => { + this.base + .createValueStream({ limit: 1 }) + .on("data", (data: Buffer) => { + this.earliest = new Block(data, this.#common); + }) + .on("error", (err: Error) => { + reject(err); + }) + .on("end", () => { + resolve(void 0); + }); + + this.base + .createValueStream({ reverse: true, limit: 1 }) + .on("data", (data: Buffer) => { + this.latest = new Block(data, this.#common); + }) + .on("error", (err: Error) => { + reject(err); + }) + .on("end", () => { + resolve(void 0); + }); + }); + } +} diff --git a/src/chains/ethereum/src/data-managers/blocklog-manager.ts b/src/chains/ethereum/src/data-managers/blocklog-manager.ts new file mode 100644 index 0000000000..069151ba03 --- /dev/null +++ b/src/chains/ethereum/src/data-managers/blocklog-manager.ts @@ -0,0 +1,18 @@ +import BlockLog from "../things/blocklogs"; +import { LevelUp } from "levelup"; +import Manager from "./manager"; +import { Quantity } from "@ganache/utils"; + +export default class BlockLogManager extends Manager { + constructor(base: LevelUp) { + super(base, BlockLog); + } + + async get(key: string | Buffer) { + const log = await super.get(key); + if (log) { + log.blockNumber = key instanceof Quantity ? key : Quantity.from(key); + } + return log; + } +} diff --git a/src/chains/ethereum/src/data-managers/manager.ts b/src/chains/ethereum/src/data-managers/manager.ts new file mode 100644 index 0000000000..49c5930334 --- /dev/null +++ b/src/chains/ethereum/src/data-managers/manager.ts @@ -0,0 +1,46 @@ +import { LevelUp } from "levelup"; +import { Data } from "@ganache/utils"; +import Tag from "../things/tags"; +const NOTFOUND = 404; + +export type Instantiable = { new (...args: any[]): T }; + +export default class Manager { + #Type: Instantiable; + #options: {}; + protected base: LevelUp; + constructor( + base: LevelUp, + type: Instantiable, + options?: ConstructorParameters>[1] + ) { + this.#Type = type; + this.#options = options; + this.base = base; + } + getRaw(key: string | Buffer | Tag): Promise { + if (typeof key === "string") { + key = Data.from(key).toBuffer(); + } + + if (key.length === 0) { + return null; + } + + return this.base.get(key).catch(e => { + if (e.status === NOTFOUND) return null; + throw e; + }) as Promise; + } + async get(key: string | Buffer) { + const raw = await this.getRaw(key); + if (!raw) return null; + return new this.#Type(raw, this.#options); + } + set(key: Buffer, value: Buffer): Promise { + return this.base.put(key, value); + } + del(key: Buffer) { + return this.base.del(key); + } +} diff --git a/src/chains/ethereum/src/data-managers/transaction-manager.ts b/src/chains/ethereum/src/data-managers/transaction-manager.ts new file mode 100644 index 0000000000..8cd74d109d --- /dev/null +++ b/src/chains/ethereum/src/data-managers/transaction-manager.ts @@ -0,0 +1,104 @@ +import Transaction from "../things/transaction"; +import Manager from "./manager"; +import TransactionPool from "../transaction-pool"; +import { EthereumInternalOptions } from "../options"; +import { LevelUp } from "levelup"; +import Blockchain from "../blockchain"; +import PromiseQueue from "@ganache/promise-queue"; +import Common from "ethereumjs-common"; +import { Data } from "@ganache/utils"; + +export default class TransactionManager extends Manager { + public readonly transactionPool: TransactionPool; + + readonly #queue = new PromiseQueue(); + #paused = false; + #resumer: Promise; + #resolver: (value: void) => void; + + constructor( + options: EthereumInternalOptions["miner"], + common: Common, + blockchain: Blockchain, + base: LevelUp + ) { + super(base, Transaction, common); + + this.transactionPool = new TransactionPool(options, blockchain); + } + + /** + * Adds the transaction to the transaction pool. + * + * Returns a promise that is only resolved in the order it was added. + * + * @param transaction + * @param secretKey + * @returns `true` if the `transaction` is immediately executable, `false` if + * it may be valid in the future. Throws if the transaction is invalid. + */ + public async add(transaction: Transaction, secretKey?: Data) { + if (this.#paused) { + await this.#resumer; + } + // Because ganache requires determinism, we can't allow varying IO times to + // potentially affect the order in which transactions are inserted into the + // pool, so we use a FIFO queue to _return_ transaction insertions in the + // order the were received. + const insertion = this.transactionPool.prepareTransaction( + transaction, + secretKey + ); + const result = await this.#queue.add(insertion); + + if (result) { + this.transactionPool.drain(); + } + return result; + } + + /** + * Immediately ignores all transactions that were in the process of being + * added to the pool. These transactions' `push` promises will be resolved + * immediately with the value `false` and will _not_ be added to the pool. + * + * Also clears all transactions that were already added to the pool. + * + * Transactions that are currently in the process of being mined may still be + * mined. + */ + public clear() { + this.#queue.clear(false); + this.transactionPool.clear(); + } + + /** + * Stop processing _new_ transactions; puts new requests in a queue. Has no + * affect if already paused. + */ + public async pause() { + if (!this.#paused) { + // stop processing new transactions immediately + this.#paused = true; + this.#resumer = new Promise(resolve => { + this.#resolver = resolve; + }); + } + + // then wait until all async things we were already processing are done + // before returning + if (this.#queue.isBusy()) { + await this.#queue.emit("idle"); + } + } + + /** + * Resume processing transactions. Has no effect if not paused. + */ + public resume = () => { + if (!this.#paused) return; + + this.#paused = false; + this.#resolver(); + }; +} diff --git a/src/chains/ethereum/src/database.ts b/src/chains/ethereum/src/database.ts new file mode 100644 index 0000000000..b20b39026b --- /dev/null +++ b/src/chains/ethereum/src/database.ts @@ -0,0 +1,171 @@ +import { AbstractLevelDOWN } from "abstract-leveldown"; +import Emittery from "emittery"; +import { dir, setGracefulCleanup } from "tmp-promise"; +import levelup, { LevelUp } from "levelup"; +import Blockchain from "./blockchain"; +import { EthereumInternalOptions } from "./options"; +const leveldown = require("leveldown"); +const sub = require("subleveldown"); +const encode = require("encoding-down"); + +setGracefulCleanup(); +const tmpOptions = { prefix: "ganache-core_", unsafeCleanup: true }; +const noop = () => Promise.resolve(); + +export default class Database extends Emittery { + public readonly blockchain: Blockchain; + readonly #options: EthereumInternalOptions["database"]; + #cleanupDirectory = noop; + #closed = false; + public directory: string = null; + public db: LevelUp = null; + public blocks: LevelUp; + public blockIndexes: LevelUp; + public blockLogs: LevelUp; + public transactions: LevelUp; + public transactionReceipts: LevelUp; + public trie: LevelUp; + public readonly initialized: boolean; + #rootStore: AbstractLevelDOWN; + + /** + * The Database handles the creation of the database, and all access to it. + * Once the database has been fully initialized it will emit a `ready` + * event. + * @param options Supports one of two options: `db` (a leveldown compliant + * store instance) or `dbPath` (the path to store/read the db instance) + * @param blockchain + */ + constructor( + options: EthereumInternalOptions["database"], + blockchain: Blockchain + ) { + super(); + + this.#options = options; + this.blockchain = blockchain; + this.#initialize(); + } + + #initialize = async () => { + const levelupOptions: any = { valueEncoding: "binary" }; + const store = this.#options.db; + let db: levelup.LevelUp; + if (store) { + this.#rootStore = encode(store, levelupOptions); + db = levelup(this.#rootStore, {}); + } else { + let directory = this.#options.dbPath; + if (!directory) { + const dirInfo = await dir(tmpOptions); + directory = dirInfo.path; + this.#cleanupDirectory = dirInfo.cleanup; + + // don't continue if we closed while we were waiting for the dir + if (this.#closed) return this.#cleanup(); + } + this.directory = directory; + + // specify an empty `prefix` for browser-based leveldown (level-js) + const leveldownOpts = { prefix: "" }; + const store = encode(leveldown(directory, leveldownOpts), levelupOptions); + this.#rootStore = store; + db = levelup(store, {}); + } + + // don't continue if we closed while we were waiting for the db + if (this.#closed) return this.#cleanup(); + + const open = db.open(); + this.trie = sub(db, "T", levelupOptions); + + this.db = db; + await open; + + // don't continue if we closed while we were waiting for it to open + if (this.#closed) return this.#cleanup(); + + this.blocks = sub(db, "b", levelupOptions); + this.blockIndexes = sub(db, "i", levelupOptions); + this.blockLogs = sub(db, "l", levelupOptions); + this.transactions = sub(db, "t", levelupOptions); + this.transactionReceipts = sub(db, "r", levelupOptions); + + return this.emit("ready"); + }; + + /** + * Call `batch` to batch `put` and `del` operations within the same + * event loop tick of the provided function. All db operations within the + * batch _must_ be executed synchronously. + * @param fn Within this function's event loop tick, all `put` and + * `del` database operations are applied in a single atomic operation. This + * provides a single write call and if any individual put/del's fail the + * entire operation fails and no modifications are made. + * @returns a Promise that resolves to the return value + * of the provided function. + */ + public batch(fn: () => T) { + const rootDb = this.#rootStore.db; + const batch = this.db.batch(); + + const originalPut = rootDb.put; + const originalDel = rootDb.del; + + rootDb.put = batch.put.bind(batch); + rootDb.del = batch.del.bind(batch); + let prom: Promise; + try { + const ret = fn(); + // PSA: don't let vscode (or yourself) rewrite this to `await` the + // `batch.write` call. The `finally` block needs to run _before_ the + // write promise has resolved. + prom = batch.write().then(() => ret); + } finally { + rootDb.put = originalPut; + rootDb.del = originalDel; + } + return prom; + } + + /** + * Gracefully closes the database and cleans up the file system and waits for + * it to fully shut down. Emits a `close` event once complete. + * Note: only emits `close` once. + */ + public async close() { + const wasClosed = this.#closed; + this.#closed = true; + await this.#cleanup(); + + // only emit `close` once + if (!wasClosed) { + this.emit("close"); + return; + } + } + + /** + * Cleans up the database connections and our tmp directory. + */ + #cleanup = async () => { + const db = this.db; + if (db) { + await new Promise((resolve, reject) => + db.close(err => { + if (err) return void reject(err); + resolve(void 0); + }) + ); + await Promise.all([ + this.blocks.close(), + this.blockIndexes.close(), + this.blockIndexes.close(), + this.transactionReceipts.close(), + this.transactions.close(), + this.trie.close() + ]); + } + return this.#cleanupDirectory(); + }; +} diff --git a/src/chains/ethereum/src/errors/coded-error.ts b/src/chains/ethereum/src/errors/coded-error.ts new file mode 100644 index 0000000000..569f924155 --- /dev/null +++ b/src/chains/ethereum/src/errors/coded-error.ts @@ -0,0 +1,63 @@ +export const ErrorCodes = { + /** + * Invalid JSON was received by the server. + * An error occurred on the server while parsing the JSON text. + */ + PARSE_ERROR: -32700, + + /** + * The JSON sent is not a valid Request object. + */ + INVALID_REQUEST: -32600, + + /** + * The method does not exist / is not available. + */ + METHOD_NOT_FOUND: -32601, + + /** + * Invalid method parameter(s). + */ + INVALID_PARAMS: -32602, + + /** + * Internal JSON-RPC error. + */ + INTERNAL_ERROR: -32603, + + /** + * Missing or invalid parameters + */ + INVALID_INPUT: -32000, + + /** + * Transaction creation failed + */ + TRANSACTION_REJECTED: -32003, + + /** + * Method is not implemented + */ + METHOD_NOT_SUPPORTED: -32004, + + /** + * Version of JSON-RPC protocol is not supported + */ + JSON_RPC_VERSION_NOT_SUPPORTED: -32006 +} as const; + +export default class CodedError extends Error { + code: number; + constructor(message: string, code: number) { + super(message); + Error.captureStackTrace(this, this.constructor); + + this.code = code; + } + + static from(error: Error, code: number) { + const codedError = new CodedError(error.message, code); + codedError.stack = error.stack; + return codedError; + } +} diff --git a/src/chains/ethereum/src/errors/errors.ts b/src/chains/ethereum/src/errors/errors.ts new file mode 100644 index 0000000000..8dad72db9b --- /dev/null +++ b/src/chains/ethereum/src/errors/errors.ts @@ -0,0 +1,35 @@ +/** + * Returned if the transaction contains an invalid signature. + */ +export const INVALID_SENDER = "invalid sender"; + +/** + * Returned if the nonce of a transaction is lower than the one present in the local chain. + */ +export const NONCE_TOO_LOW = "nonce too low"; + +/** + * Returned if a transaction's gas price is below the minimum configured for the transaction pool. + */ +export const UNDERPRICED = "transaction underpriced"; + +/** + * Returned if the transaction is specified to use less gas than required to start the invocation. + */ +export const INTRINSIC_GAS_TOO_LOW = "intrinsic gas too low"; + +/** + * Returned if a transaction's requested gas limit exceeds the maximum allowance of the current block. + */ +export const GAS_LIMIT = "exceeds block gas limit"; + +/** + * Prefix for a single VM Exception occuring when running a transaction or block + */ +export const VM_EXCEPTION = "VM Exception while processing transaction: "; + +/** + * Prefix for multiple VM Exceptions occuring when running transactions or a block + */ +export const VM_EXCEPTIONS = + "Multiple VM Exceptions while processing transactions: : \n\n"; diff --git a/src/chains/ethereum/src/errors/runtime-error.ts b/src/chains/ethereum/src/errors/runtime-error.ts new file mode 100644 index 0000000000..47d16569cd --- /dev/null +++ b/src/chains/ethereum/src/errors/runtime-error.ts @@ -0,0 +1,69 @@ +import { EVMResult } from "ethereumjs-vm/dist/evm/evm"; +import { VM_EXCEPTION } from "./errors"; +import { Data } from "@ganache/utils"; +import { rawDecode } from "ethereumjs-abi"; +import CodedError, { ErrorCodes } from "./coded-error"; + +const REVERT_REASON = Buffer.from("08c379a0", "hex"); // keccak("Error(string)").slice(0, 4) + +export enum RETURN_TYPES { + TRANSACTION_HASH, + RETURN_VALUE +} + +export default class RuntimeError extends CodedError { + public code: typeof ErrorCodes.INVALID_INPUT; + public data: { + hash: string; + programCounter: number; + result: string; + reason?: string; + message: string; + }; + constructor( + transactionHash: Buffer, + result: EVMResult, + returnType: RETURN_TYPES + ) { + const execResult = result.execResult; + const error = execResult.exceptionError.error; + let message = VM_EXCEPTION + error; + + super(message, ErrorCodes.INVALID_INPUT); + + Error.captureStackTrace(this, this.constructor); + this.name = this.constructor.name; + + const returnValue = execResult.returnValue; + const hash = `0x${transactionHash.toString("hex")}`; + let reason: string | null; + if ( + returnValue.length > 4 && + REVERT_REASON.compare(returnValue, 0, 4) === 0 + ) { + try { + // it is possible for the `returnValue` to be gibberish that can't be + // decoded. See: https://github.com/trufflesuite/ganache-core/pull/452 + reason = rawDecode(["bytes"], returnValue.slice(4))[0].toString(); + message += " " + reason; + } catch { + // ignore error since reason string recover is impossible + reason = null; + } + } else { + reason = null; + } + + this.message = message; + this.data = { + hash: hash, + programCounter: execResult.runState.programCounter, + result: + returnType === RETURN_TYPES.TRANSACTION_HASH + ? hash + : Data.from(returnValue || "0x").toString(), + reason: reason, + message: error + }; + } +} diff --git a/src/chains/ethereum/src/helpers/assert-arg-length.ts b/src/chains/ethereum/src/helpers/assert-arg-length.ts new file mode 100644 index 0000000000..29f00476e1 --- /dev/null +++ b/src/chains/ethereum/src/helpers/assert-arg-length.ts @@ -0,0 +1,27 @@ +type UnknownFn = (this: unknown, ...args: any[]) => unknown; +type FunctionPropertyDescriptor = TypedPropertyDescriptor< + T +>; +export function assertArgLength(min: number, max: number = min) { + return function ( + target: O, + propertyKey: keyof O, + descriptor: FunctionPropertyDescriptor + ) { + const original = descriptor.value; + descriptor.value = function (this: unknown) { + const length = arguments.length; + if (length < min || length > max) { + throw new Error( + `Incorrect number of arguments. '${propertyKey}' requires ${ + min === max + ? `exactly ${min} ${min === 1 ? "argument" : "arguments"}.` + : `between ${min} and ${max} arguments.` + }` + ); + } + return Reflect.apply(original, this, arguments); + } as T; + return descriptor as FunctionPropertyDescriptor; + }; +} diff --git a/src/chains/ethereum/src/helpers/filter-parsing.ts b/src/chains/ethereum/src/helpers/filter-parsing.ts new file mode 100644 index 0000000000..e9f2f82d15 --- /dev/null +++ b/src/chains/ethereum/src/helpers/filter-parsing.ts @@ -0,0 +1,62 @@ +import { Quantity } from "@ganache/utils"; +import Blockchain from "../blockchain"; +import Address from "../things/address"; +import { FilterArgs, RangeFilterArgs } from "../types/filters"; + +export function parseFilterDetails( + filter: Pick +) { + // `filter.address` may be a single address or an array + const addresses = filter.address + ? (Array.isArray(filter.address) + ? filter.address + : [filter.address] + ).map(a => Address.from(a.toLowerCase()).toBuffer()) + : []; + const topics = filter.topics ? filter.topics : []; + return { addresses, topics }; +} + +export function parseFilterRange( + filter: Omit, + blockchain: Blockchain +) { + const latestBlock = blockchain.blocks.latest.header.number; + const fromBlock = blockchain.blocks.getEffectiveNumber( + filter.fromBlock || "latest" + ); + const latestBlockNumber = latestBlock.toNumber(); + const toBlock = blockchain.blocks.getEffectiveNumber( + filter.toBlock || "latest" + ); + let toBlockNumber: number; + // don't search after the "latest" block, unless it's "pending", of course. + if (toBlock > latestBlock) { + toBlockNumber = latestBlockNumber; + } else { + toBlockNumber = toBlock.toNumber(); + } + return { + fromBlock, + toBlock, + toBlockNumber + }; +} +export function parseFilter( + filter: RangeFilterArgs = { address: [], topics: [] }, + blockchain: Blockchain +) { + const { addresses, topics } = parseFilterDetails(filter); + const { fromBlock, toBlock, toBlockNumber } = parseFilterRange( + filter, + blockchain + ); + + return { + addresses, + fromBlock, + toBlock, + toBlockNumber, + topics + }; +} diff --git a/lib/utils/gas/guestimation.js b/src/chains/ethereum/src/helpers/gas-estimator.ts similarity index 57% rename from lib/utils/gas/guestimation.js rename to src/chains/ethereum/src/helpers/gas-estimator.ts index a8adc751e4..9372cc2bb9 100644 --- a/lib/utils/gas/guestimation.js +++ b/src/chains/ethereum/src/helpers/gas-estimator.ts @@ -1,20 +1,123 @@ -const RuntimeError = require("../runtimeerror"); +import { Quantity } from "@ganache/utils"; +import { BN } from "ethereumjs-util"; +import RuntimeError, { RETURN_TYPES } from "../errors/runtime-error"; -const { BN } = require("ethereumjs-util"); -const bn = (val = 0) => new BN(val); +const bn = (val = 0) => new (BN as any)(val); const STIPEND = bn(2300); +const MULTIPLE = 64 / 63; -const check = (set) => (opname) => set.has(opname); -const isCall = check(new Set(["CALL", "DELEGATECALL", "STATICCALL", "CALLCODE"])); +const check = set => opname => set.has(opname); +const isCall = check( + new Set(["CALL", "DELEGATECALL", "STATICCALL", "CALLCODE"]) +); const isCallOrCallcode = check(new Set(["CALL", "CALLCODE"])); const isCreate = check(new Set(["CREATE", "CREATE2"])); -const isTerminator = check(new Set(["STOP", "RETURN", "REVERT", "INVALID", "SELFDESTRUCT"])); +const isTerminator = check( + new Set(["STOP", "RETURN", "REVERT", "INVALID", "SELFDESTRUCT"]) +); -module.exports = async(vm, runArgs, callback) => { +const stepTracker = () => { + const sysOps = []; + const allOps = []; + const preCompile = new Set(); + let preCompileCheck = false; + let precompileCallDepth = 0; + return { + collect: info => { + if (preCompileCheck) { + if (info.depth === precompileCallDepth) { + // If the current depth is unchanged. + // we record its position. + preCompile.add(allOps.length - 1); + } + // Reset the flag immediately here + preCompileCheck = false; + } + if (isCall(info.opcode.name)) { + info.stack = info.stack.map(val => val.clone()); + preCompileCheck = true; + precompileCallDepth = info.depth; + sysOps.push({ + index: allOps.length, + depth: info.depth, + name: info.opcode.name + }); + } else if (isCreate(info.opcode.name) || isTerminator(info.opcode.name)) { + sysOps.push({ + index: allOps.length, + depth: info.depth, + name: info.opcode.name + }); + } + // This goes last so we can use the length for the index ^ + allOps.push(info); + }, + isPrecompile: index => preCompile.has(index), + done: () => + !allOps.length || + sysOps.length < 2 || + !isTerminator(allOps[allOps.length - 1].opcode.name), + ops: allOps, + systemOps: sysOps + }; +}; + +const estimateGas = (generateVM, runArgs, callback) => { + exactimate(generateVM(), runArgs, (err, result) => { + if (err) return callback(err); + binSearch(generateVM, runArgs, result, (err, result) => { + if (err) return callback(err); + callback(null, result); + }); + }); +}; + +const binSearch = async (generateVM, runArgs, result, callback) => { + const MAX = Quantity.from(runArgs.block.header.gasLimit).toBigInt(); + const gasRefund = result.execResult.gasRefund; + const startingGas = gasRefund + ? result.gasEstimate.add(gasRefund) + : result.gasEstimate; + const range = { lo: startingGas, hi: startingGas }; + const isEnoughGas = async gas => { + const vm = generateVM(); // Generate fresh VM + runArgs.tx.gasLimit = gas.toBuffer(); + const result = await vm.runTx(runArgs).catch(vmerr => ({ vmerr })); + return !result.vmerr && !result.execResult.exceptionError; + }; + + if (!(await isEnoughGas(range.hi))) { + do { + range.hi = range.hi.muln(MULTIPLE); + } while (!(await isEnoughGas(range.hi))); + while (range.lo.addn(1).lt(range.hi)) { + const mid = range.lo.add(range.hi).divn(2); + if (await isEnoughGas(mid)) { + range.hi = mid; + } else { + range.lo = mid; + } + } + if (range.hi.gte(MAX)) { + if (!(await isEnoughGas(range.hi))) { + return callback( + new Error( + "gas required exceeds allowance or always failing transaction" + ) + ); + } + } + } + + result.gasEstimate = range.hi; + callback(null, result); +}; + +const exactimate = async (vm, runArgs, callback) => { const steps = stepTracker(); vm.on("step", steps.collect); - const Context = (index, fee) => { + const Context = (index: number, fee?: BN) => { const base = index === 0; let start = index; let stop = 0; @@ -41,25 +144,29 @@ module.exports = async(vm, runArgs, callback) => { return { start: () => start, stop: () => stop, - setStart: (val) => { + setStart: val => { start = val; compositeContext = true; }, - setStop: (val) => { + setStop: val => { stop = val; }, getCost: () => ({ cost, sixtyFloorths }), - transfer: (ctx) => { + transfer: ctx => { const values = ctx.getCost(); addGas(values.cost); sixtyFloorths.iadd(values.sixtyFloorths); }, - addSixtyFloorth: (sixtyFloorth) => { + addSixtyFloorth: sixtyFloorth => { sixtyFloorths.iadd(sixtyFloorth); }, addRange: (fee = bn()) => { // only occurs on stack increasing ops - addGas(steps.ops[base || compositeContext ? start : start + 1].gasLeft.sub(steps.ops[stop].gasLeft).add(fee)); + addGas( + steps.ops[base || compositeContext ? start : start + 1].gasLeft + .sub(steps.ops[stop].gasLeft) + .add(fee) + ); }, finalizeRange: () => { let range; @@ -74,7 +181,10 @@ module.exports = async(vm, runArgs, callback) => { } range.isub(callingFee); addGas(range); - if (isCallOrCallcode(op.opcode.name) && !op.stack[op.stack.length - 3].isZero()) { + if ( + isCallOrCallcode(op.opcode.name) && + !op.stack[op.stack.length - 3].isZero() + ) { cost.iadd(sixtyFloorths); const innerCost = next.gasLeft.sub(steps.ops[stop - 1].gasLeft); if (innerCost.gt(STIPEND)) { @@ -93,7 +203,7 @@ module.exports = async(vm, runArgs, callback) => { const getTotal = () => { const sysops = steps.systemOps; const ops = steps.ops; - const opIndex = (cursor) => sysops[cursor].index; + const opIndex = cursor => sysops[cursor].index; const stack = []; let cursor = 0; let context = Context(0); @@ -115,7 +225,9 @@ module.exports = async(vm, runArgs, callback) => { } } else if (isTerminator(name)) { // only on the last operation - context.setStop(currentIndex + 1 < steps.ops.length ? currentIndex + 1 : currentIndex); + context.setStop( + currentIndex + 1 < steps.ops.length ? currentIndex + 1 : currentIndex + ); context.finalizeRange(); const ctx = stack.pop(); if (ctx) { @@ -134,55 +246,28 @@ module.exports = async(vm, runArgs, callback) => { return gas.cost.add(gas.sixtyFloorths); }; - const result = await vm.runTx(runArgs).catch((vmerr) => ({ vmerr })); + const result = await vm.runTx(runArgs).catch(vmerr => ({ vmerr })); const vmerr = result.vmerr; if (vmerr) { return callback(vmerr); } else if (result.execResult.exceptionError) { - const vmerr = RuntimeError.fromResults([runArgs.tx], { results: [result] }); - return callback(vmerr, result); + const error = new RuntimeError( + runArgs.tx.hash(), + result, + RETURN_TYPES.RETURN_VALUE + ); + return callback(error, result); } else if (steps.done()) { const estimate = result.gasUsed; result.gasEstimate = estimate; } else { - const actualUsed = steps.ops[0].gasLeft.sub(steps.ops[steps.ops.length - 1].gasLeft); + const actualUsed = steps.ops[0].gasLeft.sub( + steps.ops[steps.ops.length - 1].gasLeft + ); const sixtyFloorths = getTotal().sub(actualUsed); result.gasEstimate = result.gasUsed.add(sixtyFloorths); } callback(vmerr, result); }; -const stepTracker = () => { - const sysOps = []; - const allOps = []; - const preCompile = new Set(); - let preCompileCheck = false; - let precompileCallDepth = 0; - return { - collect: (info) => { - if (preCompileCheck) { - if (info.depth === precompileCallDepth) { - // If the current depth is unchanged. - // we record its position. - preCompile.add(allOps.length - 1); - } - // Reset the flag immediately here - preCompileCheck = false; - } - if (isCall(info.opcode.name)) { - info.stack = info.stack.map((val) => val.clone()); - preCompileCheck = true; - precompileCallDepth = info.depth; - sysOps.push({ index: allOps.length, depth: info.depth, name: info.opcode.name }); - } else if (isCreate(info.opcode.name) || isTerminator(info.opcode.name)) { - sysOps.push({ index: allOps.length, depth: info.depth, name: info.opcode.name }); - } - // This goes last so we can use the length for the index ^ - allOps.push(info); - }, - isPrecompile: (index) => preCompile.has(index), - done: () => !allOps.length || sysOps.length < 2 || !isTerminator(allOps[allOps.length - 1].opcode.name), - ops: allOps, - systemOps: sysOps - }; -}; +export default estimateGas; diff --git a/src/chains/ethereum/src/miner/miner.ts b/src/chains/ethereum/src/miner/miner.ts new file mode 100644 index 0000000000..26f8526f70 --- /dev/null +++ b/src/chains/ethereum/src/miner/miner.ts @@ -0,0 +1,476 @@ +import params from "../things/params"; +import Transaction from "../things/transaction"; +import { utils, Quantity, Data } from "@ganache/utils"; +import { promisify } from "util"; +import Trie from "merkle-patricia-tree"; +import Emittery from "emittery"; +import VM from "ethereumjs-vm"; +import { encode as rlpEncode } from "rlp"; +import { EthereumInternalOptions } from "../options"; +import RuntimeError, { RETURN_TYPES } from "../errors/runtime-error"; +import { Executables } from "../types/executables"; +import replaceFromHeap from "./replace-from-heap"; +import { Block, RuntimeBlock } from "../things/runtime-block"; +const { BUFFER_EMPTY, BUFFER_256_ZERO } = utils; + +export type BlockData = { + blockTransactions: Transaction[]; + transactionsTrie: Trie; + receiptTrie: Trie; + gasUsed: bigint; + timestamp: Buffer; + extraData: string; +}; + +const putInTrie = (trie: Trie, key: Buffer, val: Buffer) => + promisify(trie.put.bind(trie))(key, val); + +const sortByPrice = (values: Transaction[], a: number, b: number) => + Quantity.from(values[a].gasPrice) > Quantity.from(values[b].gasPrice); + +export default class Miner extends Emittery.Typed< + { block: { block: Block; serialized: Buffer } }, + "idle" +> { + #currentlyExecutingPrice = 0n; + #origins = new Set(); + #pending: boolean; + #isBusy: boolean = false; + #paused: boolean = false; + #resumer: Promise; + #resolver: (value: void) => void; + readonly #executables: Executables; + readonly #options: EthereumInternalOptions["miner"]; + readonly #instamine: boolean; + readonly #vm: VM; + readonly #checkpoint: () => Promise; + readonly #commit: () => Promise; + readonly #revert: () => Promise; + readonly #createBlock: (previousBlock: Block) => RuntimeBlock; + + public async pause() { + if (!this.#paused) { + this.#paused = true; + this.#resumer = new Promise(resolve => { + this.#resolver = resolve; + }); + } + + if (this.#isBusy) { + await this.once("idle"); + } + } + + public resume() { + if (!this.#paused) return; + + this.#paused = false; + this.#resolver(); + } + + // create a Heap that sorts by gasPrice + readonly #priced = new utils.Heap(sortByPrice); + /* + * @param executables A live Map of pending transactions from the transaction + * pool. The miner will update this Map by removing the best transactions + * and putting them in new blocks. + */ + constructor( + options: EthereumInternalOptions["miner"], + executables: Executables, + instamine: boolean, + vm: VM, + createBlock: (previousBlock: Block) => RuntimeBlock + ) { + super(); + const stateManager = vm.stateManager; + + this.#vm = vm; + this.#options = options; + this.#executables = executables; + this.#instamine = instamine; + this.#checkpoint = promisify(stateManager.checkpoint.bind(stateManager)); + this.#commit = promisify(stateManager.commit.bind(stateManager)); + this.#revert = promisify(stateManager.revert.bind(stateManager)); + this.#createBlock = createBlock; + + // initialize the heap with an empty array + this.#priced.init([]); + } + + /** + * @param maxTransactions: maximum number of transactions per block. If `-1`, + * unlimited. + * @param onlyOneBlock: set to `true` if only 1 block should be mined. + * + * @returns the transactions mined in the _first_ block + */ + public async mine( + block: RuntimeBlock, + maxTransactions: number = -1, + onlyOneBlock = false + ) { + if (this.#paused) { + await this.#resumer; + } + + // only allow mining a single block at a time (per miner) + if (this.#isBusy) { + // if we are currently mining a block, set the `pending` property + // so the miner knows it can immediately start mining another block once + // it is done with its current work. + this.#pending = true; + this.#updatePricedHeap(); + return; + } else { + this.#setPricedHeap(); + const result = await this.#mine(block, maxTransactions, onlyOneBlock); + this.emit("idle"); + return result; + } + } + + #mine = async ( + block: RuntimeBlock, + maxTransactions: number = -1, + onlyOneBlock = false + ) => { + const { block: lastBlock, transactions } = await this.#mineTxs( + block, + maxTransactions, + onlyOneBlock + ); + + // if there are more txs to mine, start mining them without awaiting their + // result. + if (this.#pending) { + this.#setPricedHeap(); + this.#pending = false; + if (!onlyOneBlock && this.#priced.length > 0) { + const nextBlock = this.#createBlock(lastBlock); + await this.#mine(nextBlock, this.#instamine ? 1 : -1); + } + } + return transactions; + }; + + #mineTxs = async ( + runtimeBlock: RuntimeBlock, + maxTransactions: number, + onlyOneBlock: boolean + ) => { + let block: Block; + + const { pending, inProgress } = this.#executables; + const options = this.#options; + + let keepMining = true; + const priced = this.#priced; + const legacyInstamine = this.#options.legacyInstamine; + let blockTransactions: Transaction[]; + do { + keepMining = false; + this.#isBusy = true; + + blockTransactions = []; + const transactionsTrie = new Trie(null, null); + const receiptTrie = new Trie(null, null); + + // don't mine anything at all if maxTransactions is `0` + if (maxTransactions === 0) { + await this.#checkpoint(); + await this.#commit(); + const finalizedBlockData = runtimeBlock.finalize( + transactionsTrie.root, + receiptTrie.root, + BUFFER_256_ZERO, + this.#vm.stateManager._trie.root, + BUFFER_EMPTY, // gas used + options.extraData, + [] + ); + this.emit("block", finalizedBlockData); + this.#reset(); + return { block: finalizedBlockData.block, transactions: [] }; + } + + let numTransactions = 0; + let blockGasLeft = options.blockGasLimit.toBigInt(); + let blockGasUsed = 0n; + + const blockBloom = Buffer.allocUnsafe(256).fill(0); + const promises: Promise[] = []; + + // Set a block-level checkpoint so our unsaved trie doesn't update the + // vm's "live" trie. + await this.#checkpoint(); + + // Run until we run out of items, or until the inner loop stops us. + // we don't call `shift()` here because we will may need to `replace` + // this `best` transaction with the next best transaction from the same + // origin later. + let best: Transaction; + while ((best = priced.peek())) { + const origin = Data.from(best.from).toString(); + + if (best.calculateIntrinsicGas() > blockGasLeft) { + // if the current best transaction can't possibly fit in this block + // go ahead and run the next best transaction, ignoring all other + // pending transactions from this account for this block. + // * We don't replace this "best" transaction with another from the + // same account. + // * We do "unlock" this transaction in the transaction pool's `pending` + // queue so it can be replaced, if needed. + best.locked = false; + this.#removeBestAndOrigin(origin); + continue; + } + + this.#currentlyExecutingPrice = Quantity.from(best.gasPrice).toBigInt(); + + // Set a transaction-level checkpoint so we can undo state changes in + // the case where the transaction is rejected by the VM. + await this.#checkpoint(); + + const result = await this.#runTx(best, runtimeBlock, origin, pending); + if (result !== null) { + const gasUsed = Quantity.from( + result.gasUsed.toArrayLike(Buffer) + ).toBigInt(); + if (blockGasLeft >= gasUsed) { + // if the transaction will fit in the block, commit it! + await this.#commit(); + blockTransactions[numTransactions] = best; + + blockGasLeft -= gasUsed; + blockGasUsed += gasUsed; + + // calculate receipt and tx tries + const txKey = rlpEncode(numTransactions); + promises.push(putInTrie(transactionsTrie, txKey, best.serialize())); + const receipt = best.fillFromResult(result, blockGasUsed); + promises.push(putInTrie(receiptTrie, txKey, receipt)); + + // update the block's bloom + const bloom = result.bloom.bitvector; + for (let i = 0; i < 256; i++) { + blockBloom[i] |= bloom[i]; + } + + numTransactions++; + + const pendingOrigin = pending.get(origin); + // since this transaction was successful, remove it from the "pending" + // transaction pool. + keepMining = pendingOrigin.removeBest(); + inProgress.add(best); + best.once("finalized").then(() => { + // it is in the database (or thrown out) so delete it from the + // `inProgress` Set + inProgress.delete(best); + }); + + // if we: + // * don't have enough gas left for even the smallest of transactions + // * Or if we've mined enough transactions + // we're done with this block! + // notice: when `maxTransactions` is `-1` (AKA infinite), `numTransactions === maxTransactions` + // will always return false, so this comparison works out fine. + if ( + blockGasLeft <= params.TRANSACTION_GAS || + numTransactions === maxTransactions + ) { + if (keepMining) { + // remove the newest (`best`) tx from this account's pending queue + // as we know we can fit another transaction in the block. Stick + // this tx into our `priced` heap. + keepMining = replaceFromHeap(priced, pendingOrigin); + } else { + keepMining = this.#removeBestAndOrigin(origin); + } + break; + } + + if (keepMining) { + // remove the newest (`best`) tx from this account's pending queue + // as we know we can fit another transaction in the block. Stick + // this tx into our `priced` heap. + keepMining = replaceFromHeap(priced, pendingOrigin); + } else { + // since we don't have any more txs from this account, just get the + // next bext transaction sorted in our `priced` heap. + keepMining = this.#removeBestAndOrigin(origin); + } + } else { + // didn't fit in the current block + await this.#revert(); + + // unlock the transaction so the transaction pool can reconsider this + // transaction + best.locked = false; + + // didn't fit. remove it from the priced transactions without replacing + // it with another from the account. This transaction will have to be + // run again in another block. + keepMining = priced.removeBest(); + } + } else { + // no result means the transaction is an "always failing tx", so we + // revert its changes here. + // Note: we don't clean up (`removeBest`, etc) because `runTx`'s + // error handler does the clean up itself. + await this.#revert(); + } + } + + await Promise.all(promises); + await this.#commit(); + + const finalizedBlockData = runtimeBlock.finalize( + transactionsTrie.root, + receiptTrie.root, + blockBloom, + this.#vm.stateManager._trie.root, + blockGasUsed === 0n + ? BUFFER_EMPTY + : Quantity.from(blockGasUsed).toBuffer(), + options.extraData, + blockTransactions + ); + block = finalizedBlockData.block; + const emitBlockProm = this.emit("block", finalizedBlockData); + if (legacyInstamine === true) { + // we need to wait for each block to be done mining when in legacy + // mode because things like `mine` and `miner_start` must wait for the + // first mine operation to be fully complete. + await emitBlockProm; + } + + if (onlyOneBlock) { + this.#currentlyExecutingPrice = 0n; + this.#reset(); + break; + } else { + this.#currentlyExecutingPrice = 0n; + this.#updatePricedHeap(); + + if (priced.length !== 0) { + maxTransactions = this.#instamine ? 1 : -1; + runtimeBlock = this.#createBlock(block); + } else { + // reset the miner + this.#reset(); + } + } + } while (keepMining); + + return { block, transactions: blockTransactions }; + }; + + #runTx = async ( + tx: Transaction, + block: RuntimeBlock, + origin: string, + pending: Map> + ) => { + try { + return await this.#vm.runTx({ tx, block } as any); + } catch (err) { + const errorMessage = err.message; + // We do NOT want to re-run this transaction. + // Update the `priced` heap with the next best transaction from this + // account + const pendingOrigin = pending.get(origin); + if (pendingOrigin.removeBest()) { + replaceFromHeap(this.#priced, pendingOrigin); + } else { + // if there are no more transactions from this origin remove this tx + // from the priced heap and clear out it's origin so it can accept new + // transactions from this origin. + this.#removeBestAndOrigin(origin); + } + + const e = { + execResult: { + runState: { programCounter: 0 }, + exceptionError: { error: errorMessage }, + returnValue: BUFFER_EMPTY + } + }; + tx.finalize( + "rejected", + new RuntimeError(tx.hash(), e as any, RETURN_TYPES.TRANSACTION_HASH) + ); + return null; + } + }; + + #removeBestAndOrigin = (origin: string) => { + this.#origins.delete(origin); + return this.#priced.removeBest(); + }; + + #reset = () => { + this.#origins.clear(); + this.#priced.clear(); + this.#isBusy = false; + }; + + /** + * Adds one transaction from each origin into the "priced" heap, which + * sorts each tx by gasPrice (high to low) + */ + #setPricedHeap = () => { + const { pending } = this.#executables; + const origins = this.#origins; + const priced = this.#priced; + + for (let mapping of pending) { + const heap = mapping[1]; + const next = heap.peek(); + if (next && !next.locked) { + const origin = Data.from(next.from).toString(); + origins.add(origin); + priced.push(next); + next.locked = true; + } + } + }; + + /** + * Updates the "priced" heap with transactions from origins it doesn't yet + * contain. + */ + #updatePricedHeap = () => { + const { pending } = this.#executables; + const origins = this.#origins; + const priced = this.#priced; + // Note: the `pending` Map passed here is "live", meaning it is constantly + // being updated by the `transactionPool`. This allows us to begin + // processing a block with the _current_ pending transactions, and while + // that is processing, to receive new transactions, updating our `priced` + // heap with these new pending transactions. + for (let mapping of pending) { + const heap = mapping[1]; + const next = heap.peek(); + if (next && !next.locked) { + const price = Quantity.from(next.gasPrice).toBigInt(); + + if (this.#currentlyExecutingPrice > price) { + // don't insert a transaction into the miner's `priced` heap + // if it will be better than its last + continue; + } + const origin = Data.from(next.from).toString(); + if (origins.has(origin)) { + // don't insert a transaction into the miner's `priced` heap if it + // has already queued up transactions for that origin + continue; + } + origins.add(origin); + priced.push(next); + next.locked = true; + } + } + }; +} diff --git a/src/chains/ethereum/src/miner/replace-from-heap.ts b/src/chains/ethereum/src/miner/replace-from-heap.ts new file mode 100644 index 0000000000..fe93e2f0c5 --- /dev/null +++ b/src/chains/ethereum/src/miner/replace-from-heap.ts @@ -0,0 +1,20 @@ +import { utils } from "@ganache/utils"; +import Transaction from "../things/transaction"; + +export default function replaceFromHeap( + priced: utils.Heap, + source: utils.Heap +) { + // get the next best for this account, removing from the source Heap: + const next = source.peek(); + if (next) { + // remove the current best priced transaction from this account and replace + // it with the account's next lowest nonce transaction: + priced.replaceBest(next); + next.locked = true; + return true; + } else { + // since we don't have a next, just remove this item from priced + return priced.removeBest(); + } +} diff --git a/src/chains/ethereum/src/options/chain-options.ts b/src/chains/ethereum/src/options/chain-options.ts new file mode 100644 index 0000000000..3fa46bde30 --- /dev/null +++ b/src/chains/ethereum/src/options/chain-options.ts @@ -0,0 +1,169 @@ +import { normalize } from "./helpers"; +import { Definitions } from "@ganache/options"; + +export type Hardfork = + | "constantinople" + | "byzantium" + | "petersburg" + | "istanbul" + | "muirGlacier"; + +export type ChainConfig = { + options: { + /** + * Allows unlimited contract sizes while debugging. By setting this to + * `true`, the check within the EVM for a contract size limit of 24KB (see + * [EIP-170](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-170.md)) + * is bypassed. Setting this to `true` will cause ganache to behave + * differently than production environments. You should only set this to + * `true` during local debugging. + * + * @default false + */ + readonly allowUnlimitedContractSize: { + type: boolean; + hasDefault: true; + legacy: { + /** + * @deprecated Use chain.allowUnlimitedContractSize instead + */ + allowUnlimitedContractSize: boolean; + }; + }; + + /** + * When set to `false` only one request will be processed at a time. + * + * @default true + */ + readonly asyncRequestProcessing: { + type: boolean; + hasDefault: true; + legacy: { + /** + * @deprecated Use chain.asyncRequestProcessing instead + */ + asyncRequestProcessing: boolean; + }; + }; + + /** + * The currently configured chain id, a value used in replay-protected + * transaction signing as introduced by + * [EIP-155](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md). + * + * @default 1337 + */ + readonly chainId: { + type: number; + hasDefault: true; + legacy: { + /** + * @deprecated Use chain.chainId instead + */ + chainId: number; + }; + }; + + /** + * The id of the network returned by the RPC method `net_version`. + * + * Defaults to the current timestamp, via JavaScript's `Date.now()` (the + * number of millisconds since the UNIX epoch). + * + * @default Date.now() + */ + readonly networkId: { + type: number; + hasDefault: true; + legacy: { + /** + * @deprecated Use chain.networkId instead + */ + networkId: number; + }; + }; + + /** + * Date that the first block should start. Use this feature, along with the + * `evm_increaseTime` RPC, to test time-dependent code. + */ + readonly time: { + type: number | Date; + legacy: { + /** + * @deprecated Use chain.time instead + */ + time: number | Date; + }; + }; + + /** + * Set the hardfork rules for the EVM. + * @default "muirGlacier" + */ + readonly hardfork: { + type: Hardfork; + hasDefault: true; + legacy: { + /** + * @deprecated Use chain.hardfork instead + */ + hardfork: Hardfork; + }; + }; + + /** + * Whether to report runtime errors from EVM code as RPC errors. + * + * @default false + */ + readonly vmErrorsOnRPCResponse: { + type: boolean; + hasDefault: true; + legacy: { + /** + * @deprecated Use chain.vmErrorsOnRPCResponse instead + */ + vmErrorsOnRPCResponse: boolean; + }; + }; + }; +}; + +export const ChainOptions: Definitions = { + allowUnlimitedContractSize: { + normalize, + default: () => false, + legacyName: "allowUnlimitedContractSize" + }, + asyncRequestProcessing: { + normalize, + default: () => true, + legacyName: "asyncRequestProcessing" + }, + chainId: { + normalize, + default: () => 1337, + legacyName: "chainId" + }, + networkId: { + normalize, + default: () => Date.now(), + legacyName: "networkId" + }, + time: { + normalize, + legacyName: "time" + }, + hardfork: { + normalize, + default: () => "muirGlacier", + legacyName: "hardfork" + }, + vmErrorsOnRPCResponse: { + normalize, + default: () => false, + legacyName: "vmErrorsOnRPCResponse" + } +}; diff --git a/src/chains/ethereum/src/options/database-options.ts b/src/chains/ethereum/src/options/database-options.ts new file mode 100644 index 0000000000..97dc431f9b --- /dev/null +++ b/src/chains/ethereum/src/options/database-options.ts @@ -0,0 +1,45 @@ +import { normalize } from "./helpers"; +import { Definitions } from "@ganache/options"; + +export type DatabaseConfig = { + options: { + /** + * Specify an alternative database instance, like MemDOWN + */ + db: { + type: string | object; + legacy: { + /** + * @deprecated Use database.db instead + */ + db: string | object; + }; + }; + /** + * Specify a path to a directory to save the chain database. If a database + * already exists, that chain will be initialized instead of creating a new + * one. + */ + dbPath: { + type: string; + legacy: { + /** + * @deprecated Use database.dbPath instead + */ + db_path: string; + }; + }; + }; + exclusiveGroups: [["db", "dbPath"]]; +}; + +export const DatabaseOptions: Definitions = { + db: { + normalize, + legacyName: "db" + }, + dbPath: { + normalize, + legacyName: "db_path" + } +}; diff --git a/src/chains/ethereum/src/options/helpers.ts b/src/chains/ethereum/src/options/helpers.ts new file mode 100644 index 0000000000..b2961afe57 --- /dev/null +++ b/src/chains/ethereum/src/options/helpers.ts @@ -0,0 +1 @@ +export const normalize = (rawInput: T) => rawInput; diff --git a/src/chains/ethereum/src/options/index.ts b/src/chains/ethereum/src/options/index.ts new file mode 100644 index 0000000000..388aeacc15 --- /dev/null +++ b/src/chains/ethereum/src/options/index.ts @@ -0,0 +1,80 @@ +import { ChainConfig, ChainOptions } from "./chain-options"; +import { DatabaseConfig, DatabaseOptions } from "./database-options"; +import { LoggingConfig, LoggingOptions } from "./logging-options"; +import { MinerConfig, MinerOptions } from "./miner-options"; +import { WalletConfig, WalletOptions } from "./wallet-options"; +import { + Base, + Defaults, + Definitions, + ExternalConfig, + InternalConfig, + Legacy, + LegacyOptions, + OptionName, + OptionRawType, + Options, + OptionsConfig +} from "@ganache/options"; + +export type EthereumOptions = { + chain: ChainConfig; + database: DatabaseConfig; + logging: LoggingConfig; + miner: MinerConfig; + wallet: WalletConfig; +}; + +type MakeLegacyOptions = UnionToIntersection< + { + [K in OptionName]: K extends LegacyOptions + ? Legacy + : Record>; + }[keyof Options] +>; + +type UnionToIntersection = (U extends any ? (k: U) => void : never) extends ( + k: infer I +) => void + ? I + : never; + +type g = Partial< + { + [K in keyof UnionToIntersection< + MakeLegacyOptions + >]: UnionToIntersection>[K]; + } +>; + +export type EthereumLegacyOptions = Partial< + MakeLegacyOptions & + MakeLegacyOptions & + MakeLegacyOptions & + MakeLegacyOptions & + MakeLegacyOptions +>; + +export type EthereumProviderOptions = Partial< + { + [K in keyof EthereumOptions]: ExternalConfig; + } +>; + +export type EthereumInternalOptions = { + [K in keyof EthereumOptions]: InternalConfig; +}; + +export type EthereumDefaults = { + [K in keyof EthereumOptions]: Definitions; +}; + +export const ethereumDefaults: Defaults = { + chain: ChainOptions, + database: DatabaseOptions, + logging: LoggingOptions, + miner: MinerOptions, + wallet: WalletOptions +}; + +export const EthereumOptionsConfig = new OptionsConfig(ethereumDefaults); diff --git a/src/chains/ethereum/src/options/legacy-options.ts b/src/chains/ethereum/src/options/legacy-options.ts new file mode 100644 index 0000000000..113a08ba51 --- /dev/null +++ b/src/chains/ethereum/src/options/legacy-options.ts @@ -0,0 +1,25 @@ +// import { Base } from "@ganache/options"; +// import { OptionRawType, OptionName } from "@ganache/options"; +// import { ChainConfig } from "./chain-options"; +// import { DatabaseConfig } from "./database-options"; +// import { LoggingConfig } from "./logging-options"; +// import { MinerConfig } from "./miner-options"; +// import { WalletConfig } from "./wallet-options"; + +// type MakeLegacyOptions = { +// [ +// K in OptionName as void extends C["options"][K]["legacyName"] ? K : C["options"][K]["legacyName"] +// ]: +// OptionRawType +// }; + +// /** +// * @deprecated +// */ +// export type LegacyOptions = Partial< +// MakeLegacyOptions & +// MakeLegacyOptions & +// MakeLegacyOptions & +// MakeLegacyOptions & +// MakeLegacyOptions +// >; diff --git a/src/chains/ethereum/src/options/logging-options.ts b/src/chains/ethereum/src/options/logging-options.ts new file mode 100644 index 0000000000..72efb4af0b --- /dev/null +++ b/src/chains/ethereum/src/options/logging-options.ts @@ -0,0 +1,87 @@ +import { normalize } from "./helpers"; +import { Definitions } from "@ganache/options"; + +export type Logger = { + log(message?: any, ...optionalParams: any[]): void; +}; + +export type LoggingConfig = { + options: { + /** + * Set to `true` to log EVM opcodes. + * + * @default false + */ + readonly debug: { + type: boolean; + hasDefault: true; + legacy: { + /** + * @deprecated Use logging.debug instead + */ + debug: boolean; + }; + }; + + /** + * An object, like `console`, that implements a `log` function. + * + * Defaults to `console` (logs to stdout). + * + * @example + * ```typescript + * { + * log: (message: any) => { + * // handle `message` + * } + * } + * ``` + */ + readonly logger: { + type: Logger; + hasDefault: true; + legacy: { + /** + * @deprecated Use logging.logger instead + */ + logger: Logger; + }; + }; + + /** + * Set to `true` to log all RPC requests and responses. + * + * @default false + */ + readonly verbose: { + type: boolean; + hasDefault: true; + legacy: { + /** + * @deprecated Use logging.verbose instead + */ + verbose: boolean; + }; + }; + }; +}; + +const logger: Logger = { log: () => {} }; + +export const LoggingOptions: Definitions = { + debug: { + normalize, + default: () => false, + legacyName: "debug" + }, + logger: { + normalize, + default: () => logger, + legacyName: "logger" + }, + verbose: { + normalize, + default: () => false, + legacyName: "verbose" + } +}; diff --git a/src/chains/ethereum/src/options/miner-options.ts b/src/chains/ethereum/src/options/miner-options.ts new file mode 100644 index 0000000000..932aadd31a --- /dev/null +++ b/src/chains/ethereum/src/options/miner-options.ts @@ -0,0 +1,183 @@ +import { normalize } from "./helpers"; +import { Data, Quantity, utils } from "@ganache/utils"; +import { Definitions } from "@ganache/options"; +import Address from "../things/address"; + +export type MinerConfig = { + options: { + /** + * Sets the `blockTime` in seconds for automatic mining. A blockTime of `0` + * (default) enables "instamine mode", where new executable transactions + * will be mined instantly. + * + * Using the `blockTime` option is discouraged unless you have tests which + * require a specific mining interval. + * + * @default 0 // "instamine mode" + */ + blockTime: { + type: number; + hasDefault: true; + legacy: { + /** + * @deprecated Use miner.blockTime instead + */ + blockTime: number; + }; + }; + + /** + * Sets the default gas price in WEI for transactions if not otherwise specified. + * + * @default 2_000_000 + */ + gasPrice: { + type: Quantity; + rawType: string | number | bigint; + hasDefault: true; + legacy: { + /** + * @deprecated Use miner.gasPrice instead + */ + gasPrice: string | number | bigint; + }; + }; + + /** + * Sets the block gas limit in WEI. + * + * @default 12_000_000 + */ + blockGasLimit: { + type: Quantity; + rawType: string | number | bigint; + hasDefault: true; + legacy: { + /** + * @deprecated Use miner.blockGasLimit instead + */ + gasLimit: string | number | bigint; + }; + }; + + /** + * Sets the _default_ transaction gas limit in WEI. + * + * @default 9_000 + */ + defaultTransactionGasLimit: { + type: Quantity; + rawType: string | number | bigint; + hasDefault: true; + }; + + /** + * Sets the transaction gas limit in WEI for `eth_call` and + * eth_estimateGas` calls. + * + * @default 9_007_199_254_740_991 // 2**53 - 1 + */ + callGasLimit: { + type: Quantity; + rawType: string | number | bigint; + hasDefault: true; + legacy: { + /** + * @deprecated Use miner.callGasLimit instead + */ + callGasLimit: string | number | bigint; + }; + }; + + /** + * Enables legacy instamine mode, where transactions are fully mined before + * the transaction's hash is returned to the caller. If `legacyInstamine` is + * `true`, `blockTime` must be `0` (default). + * + * @default false + * @deprecated Will be removed in v4 + */ + legacyInstamine: { + type: boolean; + hasDefault: true; + // legacyInstamine is _not_ a legacy option, but it is used as one so users + // can use it just as they would other legacy options (without a namespace) + legacy: { + /** + * @deprecated Use miner.legacyInstamine instead. Will be removed in v4. + */ + legacyInstamine: boolean; + }; + }; + + /** + * Sets the address where mining rewards will go. + * + * * `{string}` hex-encoded address + * * `{number}` index of the account returned by `eth_getAccounts` + * + * @default "0x0000000000000000000000000000000000000000" + */ + coinbase: { + rawType: string | number; + type: Address | number; + hasDefault: true; + }; + + extraData: { + rawType: string; + type: Data; + hasDefault: true; + }; + }; +}; + +export const MinerOptions: Definitions = { + blockTime: { + normalize, + default: () => 0, + legacyName: "blockTime" + }, + gasPrice: { + normalize: Quantity.from, + default: () => Quantity.from(2_000_000_000), + legacyName: "gasPrice" + }, + blockGasLimit: { + normalize: Quantity.from, + default: () => Quantity.from(12_000_000), + legacyName: "gasLimit" + }, + defaultTransactionGasLimit: { + normalize: Quantity.from, + default: () => Quantity.from(90_000) + }, + callGasLimit: { + normalize: Quantity.from, + default: () => Quantity.from(Number.MAX_SAFE_INTEGER), + legacyName: "callGasLimit" + }, + coinbase: { + normalize: rawType => { + return typeof rawType === "number" ? rawType : Address.from(rawType); + }, + default: () => Address.from(utils.ACCOUNT_ZERO) + }, + legacyInstamine: { + normalize, + default: () => false, + legacyName: "legacyInstamine" + }, + extraData: { + normalize: (extra: string) => { + const bytes = Data.from(extra); + if (bytes.toBuffer().length > 32) { + throw new Error( + `extra exceeds max length. ${bytes.toBuffer().length} > 32` + ); + } + return bytes; + }, + default: () => Data.from(utils.BUFFER_EMPTY) + } +}; diff --git a/src/chains/ethereum/src/options/wallet-options.ts b/src/chains/ethereum/src/options/wallet-options.ts new file mode 100644 index 0000000000..2628835403 --- /dev/null +++ b/src/chains/ethereum/src/options/wallet-options.ts @@ -0,0 +1,225 @@ +import { normalize } from "./helpers"; +import seedrandom from "seedrandom"; +import { entropyToMnemonic } from "bip39"; + +import { Definitions } from "@ganache/options"; + +const { alea } = seedrandom; + +function randomBytes(length: number, rng: () => number) { + const buf = Buffer.allocUnsafe(length); + for (let i = 0; i < length; i++) { + buf[i] = (rng() * 255) | 0; + } + return buf; +} + +const randomAlphaNumericString = (() => { + const alphabet = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + const alphabetLength = alphabet.length; + return (length: number, rng: () => number) => { + let text = ""; + for (let i = 0; i < length; i++) { + text += alphabet[(rng() * alphabetLength) | 0]; + } + return text; + }; +})(); + +export type OptionsAccount = { + balance: string | number | bigint | Buffer; + secretKey?: string; +}; + +export type WalletConfig = { + options: { + /** + * Number of accounts to generate at startup. + * + * @default 10 + */ + totalAccounts: { + type: number; + hasDefault: true; + legacy: { + /** + * @deprecated Use wallet.totalAccounts instead + */ + total_accounts: number; + }; + }; + + /** + * Array of Accounts. Each object should have a balance key with a hexadecimal + * value. The key secretKey can also be specified, which represents the + * account's private key. If no secretKey, the address is auto-generated with + * the given balance. If specified, the key is used to determine the account's + * address. + */ + accounts: { + type: OptionsAccount[]; + legacy: { + /** + * @deprecated Use wallet.accounts instead + */ + accounts: number; + }; + }; + + /** + * Seed to use to generate a mnemonic + */ + seed: { + type: string; + hasDefault: true; + legacy: { + /** + * @deprecated Use wallet.seed instead + */ + seed: number; + }; + }; + + /** + * Use a specific HD wallet mnemonic to generate initial addresses. + */ + mnemonic: { + type: string; + hasDefault: true; + legacy: { + /** + * @deprecated Use wallet.mnemonic instead + */ + mnemonic: number; + }; + }; + + /** + * Array of addresses or address indexes specifying which accounts should be unlocked. + */ + unlockedAccounts: { + type: Array; + legacy: { + /** + * @deprecated Use wallet.unlockedAccounts instead + */ + unlocked_accounts: Array; + }; + }; + + /** + * Lock available accounts by default (good for third party transaction signing). Defaults to `false`. + * + * @default false + */ + secure: { + type: boolean; + hasDefault: true; + legacy: { + /** + * @deprecated Use wallet.secure instead + */ + secure: number; + }; + }; + + /** + * Specifies a file to save accounts and private keys to, for testing. + * + * Can be a filename or file descriptor. + * + * If specifying a filename, the directory path must already exist. + * + * See: https://nodejs.org/api/fs.html#fs_fs_writefilesync_file_data_options + */ + accountKeysPath: { + type: string | number; + legacy: { + /** + * @deprecated Use wallet.accountKeysPath instead + */ + account_keys_path: string | number; + }; + }; + + /** + * The default account balance, specified in ether. Defaults to `100` ether + * + * @default 100 // ether + */ + defaultBalance: { + type: number; + hasDefault: true; + legacy: { + /** + * @deprecated Use wallet.defaultBalance instead + */ + default_balance_ether: number; + }; + }; + + /** + * The hierarchical deterministic path to use when generating accounts. + * + * @default "m/44'/60'/0'/0/" + */ + hdPath: { + type: string; + hasDefault: true; + legacy: { + /** + * @deprecated Use wallet.totalAcchdPathounts instead + */ + hd_path: string; + }; + }; + }; + exclusiveGroups: [["totalAccounts", "accounts"], ["mnemonic", "seed"]]; +}; + +export const WalletOptions: Definitions = { + totalAccounts: { + normalize, + default: () => 10, + legacyName: "total_accounts" + }, + accounts: { + normalize, + legacyName: "accounts" + }, + seed: { + normalize, + default: () => randomAlphaNumericString(10, alea()), + legacyName: "seed" + }, + mnemonic: { + normalize, + default: config => + entropyToMnemonic(randomBytes(16, seedrandom(config.seed))), + legacyName: "mnemonic" + }, + unlockedAccounts: { + normalize, + legacyName: "unlocked_accounts" + }, + secure: { + normalize, + default: () => false, + legacyName: "secure" + }, + accountKeysPath: { + normalize, + legacyName: "account_keys_path" + }, + defaultBalance: { + normalize, + default: () => 100, + legacyName: "default_balance_ether" + }, + hdPath: { + normalize, + default: () => "m/44'/60'/0'/0/", + legacyName: "hd_path" + } +}; diff --git a/src/chains/ethereum/src/provider.ts b/src/chains/ethereum/src/provider.ts new file mode 100644 index 0000000000..0f4dcf841e --- /dev/null +++ b/src/chains/ethereum/src/provider.ts @@ -0,0 +1,302 @@ +import Emittery from "emittery"; +import EthereumApi from "./api"; +import { JsonRpcTypes } from "@ganache/utils"; +import { + EthereumProviderOptions, + EthereumInternalOptions, + EthereumOptionsConfig, + EthereumLegacyOptions +} from "./options"; +import cloneDeep from "lodash.clonedeep"; +import { PromiEvent, types, utils } from "@ganache/utils"; +import Wallet from "./wallet"; +declare type RequestMethods = types.KnownKeys; + +type mergePromiseGenerics = Promise< + Type extends Promise ? X : never +>; + +interface Callback { + (err?: Error, response?: JsonRpcTypes.Response): void; +} + +type RequestParams = { + readonly method: Method; + readonly params: Parameters | undefined; +}; + +const hasOwn = utils.hasOwn; + +export default class EthereumProvider + extends Emittery.Typed<{ message: any }, "connect" | "disconnect"> + implements types.Provider { + #options: EthereumInternalOptions; + #api: EthereumApi; + #executor: utils.Executor; + #wallet: Wallet; + + constructor( + options: EthereumProviderOptions | EthereumLegacyOptions = {}, + executor: utils.Executor + ) { + super(); + const providerOptions = (this.#options = EthereumOptionsConfig.normalize( + options as EthereumProviderOptions + )); + + this.#executor = executor; + const wallet = (this.#wallet = new Wallet(providerOptions.wallet)); + + this.#api = new EthereumApi(providerOptions, wallet, this); + } + + /** + * Returns the options, including defaults and generated, used to start Ganache. + */ + public getOptions() { + return cloneDeep(this.#options); + } + + /** + * Returns the unlocked accounts + */ + public getInitialAccounts() { + const accounts: Record< + string, + { unlocked: boolean; secretKey: string; balance: bigint } + > = {}; + const wallet = this.#wallet; + const unlockedAccounts = this.#wallet.unlockedAccounts; + wallet.initialAccounts.forEach(account => { + const address = account.address.toString(); + accounts[address] = { + secretKey: account.privateKey.toString(), + balance: account.balance.toBigInt(), + unlocked: unlockedAccounts.has(address) + }; + }); + return accounts; + } + + /** + * Remove an event subscription + */ + public removeListener = this.off; + + /** + * @param payload + * @param callback + * @deprecated Use the `request` method + */ + public send( + payload: JsonRpcTypes.Request, + callback?: Callback + ): undefined; + /** + * Legacy callback style API + * @param payload JSON-RPC payload + * @param callback callback + * @deprecated Batch transactions have been deprecated. Send payloads + * individually via the `request` method. + */ + public send( + payloads: JsonRpcTypes.Request[], + callback?: Callback + ): undefined; + /** + * @param method + * @param params + * @ignore Non standard! Do not use. + */ + public send( + method: RequestMethods, + params?: Parameters + ): any; + public send( + arg1: + | RequestMethods + | JsonRpcTypes.Request + | JsonRpcTypes.Request[], + arg2?: Callback | any[] + ) { + let method: RequestMethods; + let params: any; + let response: Promise<{}> | undefined; + if (typeof arg1 === "string") { + // this signature is (not) non-standard and is only a ganache thing!!! + // we should probably remove it, but I really like it so I haven't yet. + method = arg1; + params = arg2 as Parameters; + response = this.request({ method, params }); + } else if (typeof arg2 === "function") { + // handle backward compatibility with callback-style ganache-core + const callback = arg2 as Callback; + if (Array.isArray(arg1)) { + this.#legacySendPayloads(arg1).then(({ error, result }) => { + process.nextTick(callback, error, result); + }); + } else { + this.#legacySendPayload(arg1).then(({ error, result }) => { + process.nextTick(callback, error, result); + }); + } + } else { + throw new Error( + "No callback provided to provider's send function. As of web3 1.0, provider.send " + + "is no longer synchronous and must be passed a callback as its final argument." + ); + } + + return response; + } + + /** + * Legacy callback style API + * @param payload JSON-RPC payload + * @param callback callback + * @deprecated Use the `request` method. + */ + public sendAsync( + payload: JsonRpcTypes.Request, + callback?: Callback + ): void { + return this.send(payload, callback); + } + + /** + * EIP-1193 style request method + * @param args + * @returns A Promise that resolves with the method's result or rejects with a CodedError + * @EIP [1193](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1193.md) + */ + public async request( + args: Parameters["length"] extends 0 + ? Pick, "method"> + : never + ): mergePromiseGenerics>; + /** + * EIP-1193 style request method + * @param args + * @returns A Promise that resolves with the method's result or rejects with a CodedError + * @EIP [1193](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1193.md) + */ + public async request( + args: RequestParams + ): mergePromiseGenerics>; + public async request( + args: RequestParams + ) { + const rawResult = await this._requestRaw(args); + const value = await rawResult.value; + return JSON.parse(JSON.stringify(value)); + } + + /** + * INTERNAL. Used when the caller wants to access the orignal `PromiEvent`, which would + * otherwise be flattened into a regular Promise through the Promise chain. + * @param request + */ + public async _requestRaw({ + method, + params + }: RequestParams) { + this.#logRequest(method, params); + + const result = await this.#executor.execute(this.#api, method, params); + const promise = result.value as mergePromiseGenerics; + if (promise instanceof PromiEvent) { + promise.on("message", data => { + // EIP-1193 + this.emit("message" as never, data as never); + // legacy + this.emit( + "data" as never, + { + jsonrpc: "2.0", + method: "eth_subscription", + params: (data as any).data + } as never + ); + }); + } + const value = promise.catch((error: Error) => { + if (this.#options.chain.vmErrorsOnRPCResponse) { + if (hasOwn(error, "result")) { + // stringify the result here + // TODO: not sure why the stringification is even needed. + (error as any).result = JSON.parse( + JSON.stringify((error as any).result) + ); + } + } + // then rethrow + throw error; + }); + return { value: value }; + } + + #logRequest = ( + method: string, + params: Parameters + ) => { + const options = this.#options; + if (options.logging.verbose) { + options.logging.logger.log( + ` > ${method}: ${ + params == null + ? params + : JSON.stringify(params, null, 2).split("\n").join("\n > ") + }` + ); + } + }; + + public disconnect = async () => { + await this.emit("disconnect"); + return; + }; + + //#region legacy + #legacySendPayloads = (payloads: JsonRpcTypes.Request[]) => { + return Promise.all(payloads.map(this.#legacySendPayload)).then(results => { + let mainError: Error = null; + const responses: (JsonRpcTypes.Response | JsonRpcTypes.Error)[] = []; + results.forEach(({ error, result }, i) => { + responses.push(result); + if (error) { + if (mainError == null) { + mainError = new Error("Batch error:") as Error & { errors: [] }; + } + (mainError as any).errors[i] = error; + } + }); + return { error: mainError, result: responses }; + }); + }; + + #legacySendPayload = async (payload: JsonRpcTypes.Request) => { + const method = payload.method as RequestMethods; + const params = payload.params as Parameters; + try { + const result = await this.request({ method, params }); + return { + error: null as JsonRpcTypes.Error, + result: JsonRpcTypes.Response( + payload.id, + JSON.parse(JSON.stringify(result)) + ) + }; + } catch (error) { + let result: any; + // In order to provide `vmErrorsOnRPCResponse`, the `error` might have + // a `result` property that we need to move to the result field. Yes, + // it's super weird behavior! + if (hasOwn(error, "result")) { + result = error.result; + delete error.result; + } + return { error, result: JsonRpcTypes.Error(payload.id, error, result) }; + } + }; + //#endregion +} diff --git a/src/chains/ethereum/src/things/account.ts b/src/chains/ethereum/src/things/account.ts new file mode 100644 index 0000000000..88639235c8 --- /dev/null +++ b/src/chains/ethereum/src/things/account.ts @@ -0,0 +1,42 @@ +import { Data, Quantity } from "@ganache/utils"; +import Address from "./address"; +import { rlp, KECCAK256_RLP, KECCAK256_NULL } from "ethereumjs-util"; +import { utils } from "@ganache/utils"; + +const RPCQUANTITY_ZERO = utils.RPCQUANTITY_ZERO; + +export default class Account { + public address: Address; + public balance: Quantity; + public privateKey: Data; + public nonce: Quantity; + public stateRoot: Buffer = KECCAK256_RLP; + public codeHash: Buffer = KECCAK256_NULL; + + constructor(address: Address) { + this.address = address; + this.balance = RPCQUANTITY_ZERO; + this.nonce = RPCQUANTITY_ZERO; + } + + public static fromBuffer(buffer: Buffer) { + const account = Object.create(Account); + const arr = (rlp.decode(buffer) as any) as [Buffer, Buffer, Buffer, Buffer]; + account.nonce = Quantity.from(arr[0]); + account.balance = Quantity.from(arr[1]); + account.stateRoot = arr[2]; + account.codeHash = arr[3]; + return account; + } + + public serialize() { + return rlp.encode( + Buffer.concat([ + this.nonce.toBuffer(), + this.balance.toBuffer(), + this.stateRoot, + this.codeHash + ]) + ); + } +} diff --git a/src/chains/ethereum/src/things/address.ts b/src/chains/ethereum/src/things/address.ts new file mode 100644 index 0000000000..57576409b1 --- /dev/null +++ b/src/chains/ethereum/src/things/address.ts @@ -0,0 +1,15 @@ +import { Data } from "@ganache/utils"; + +class Address extends Data { + /** + * + * @param value + * @param byteLength the exact length the value represents when encoded as + * Ethereum JSON-RPC DATA. + */ + constructor(value: string | Buffer, byteLength: number = 20) { + super(value, byteLength); + } +} + +export default Address; diff --git a/src/chains/ethereum/src/things/blocklogs.ts b/src/chains/ethereum/src/things/blocklogs.ts new file mode 100644 index 0000000000..f71b435b47 --- /dev/null +++ b/src/chains/ethereum/src/things/blocklogs.ts @@ -0,0 +1,220 @@ +import { encode as rlpEncode, decode as rlpDecode } from "rlp"; +import { Data, Quantity } from "@ganache/utils"; +import Address from "./address"; +import { utils } from "@ganache/utils"; + +export type TransactionLog = [ + address: Buffer, + topics: Buffer[], + data: Buffer | Buffer[] +]; +export type BlockLog = [ + removed: Buffer, + transactionIndex: Buffer, + transactionHash: Buffer, + address: TransactionLog[0], + topics: TransactionLog[1], + data: TransactionLog[2] +]; + +const _raw = Symbol("raw"); +const _logs = Symbol("logs"); + +const filterByTopic = ( + expectedTopics: (string | string[])[], + logTopics: Buffer[] +) => { + // Exclude log if its number of topics is less than the number expected + if (expectedTopics.length > logTopics.length) return false; + + // for every expectedTopic, we must much the log topic in the same position + return expectedTopics.every((expectedTopic, logPosition) => { + // a `null` topic means "anything" + if (expectedTopic === null) return true; + + let expectedTopicSet: string[]; + if (!Array.isArray(expectedTopic)) { + return logTopics[logPosition].equals(Data.from(expectedTopic).toBuffer()); + } + // an empty rule set means "anything" + if (expectedTopic.length === 0) return true; + expectedTopicSet = expectedTopic; + + const logTopic = logTopics[logPosition]; + // "OR" logic, e.g., [[A, B]] means log topic in the first position matching either "A" OR "B": + return expectedTopicSet.some(expectedTopic => + logTopic.equals(Data.from(expectedTopic).toBuffer()) + ); + }); +}; + +export default class BlockLogs { + [_raw]: [blockHash: Buffer, blockLog: BlockLog[]]; + + constructor(data: Buffer) { + if (data) { + const decoded = (rlpDecode(data) as unknown) as [Buffer, BlockLog[]]; + this[_raw] = decoded; + } + } + + /** + * + * @param blockHash Creates an BlogLogs entity with an empty internal logs + * array. + */ + static create(blockHash: Buffer) { + const blockLog = Object.create(BlockLogs.prototype) as BlockLogs; + blockLog[_raw] = [blockHash, []]; + return blockLog; + } + + /** + * rlpEncode's the blockHash and logs array for db storage + */ + public serialize() { + return rlpEncode(this[_raw]); + } + + /** + * Appends the data to the internal logs array + * @param transactionIndex + * @param transactionHash + * @param log + */ + public append( + /*removed: boolean, */ transactionIndex: Buffer, + transactionHash: Buffer, + log: TransactionLog + ) { + this[_raw][1].push([ + utils.BUFFER_ZERO, // `removed`, TODO: this is used for reorgs, but we don't support them yet + transactionIndex, // transactionIndex + transactionHash, // transactionHash + log[0], // `address` + log[1], // `topics` + log[2] // `data` + ]); + } + + /** + * Returns the number of logs in the internal logs array. + */ + get length() { + return this[_raw][1].length; + } + + public blockNumber: Quantity; + + toJSON() { + return this[_logs]().toJSON(); + } + + [_logs]() { + const blockNumber = this.blockNumber; + const raw = this[_raw]; + const logs = raw[1]; + const l = this.length; + const blockHash = Data.from(raw[0]); + return { + toJSON() { + return { + *[Symbol.iterator]() { + for (let i = 0; i < l; i++) { + yield BlockLogs.logToJSON( + logs[i], + Quantity.from(i), + blockHash, + blockNumber + ); + } + } + }; + }, + *[Symbol.iterator]() { + for (let i = 0; i < l; i++) { + const log = logs[i]; + const address = log[3]; + const topics = log[4]; + yield { + address, + topics, + toJSON: () => + BlockLogs.logToJSON(log, Quantity.from(i), blockHash, blockNumber) + }; + } + } + }; + } + + /** + * + * @param log + * @param logIndex The index this log appears in the block + * @param blockHash The hash of the block + * @param blockNumber The block number + */ + protected static logToJSON( + log: BlockLog, + logIndex: Quantity, + blockHash: Data, + blockNumber: Quantity + ) { + const topics = log[4]; + const data = log[5]; + + return { + address: Address.from(log[3]), + blockHash, + blockNumber, + data: Array.isArray(data) + ? data.map(d => Data.from(d, d.length)) + : Data.from(data, data.length), + logIndex, // this is the index in the *block* + removed: log[0].equals(utils.BUFFER_ZERO) ? false : true, + topics: Array.isArray(topics) + ? topics.map(t => Data.from(t, 32)) + : Data.from(topics, 32), + transactionHash: Data.from(log[2], 32), + transactionIndex: Quantity.from(log[1]) + }; + } + + /** + * Note: you must set `this.blockNumber: Quantity` first! + * + * Topics are order-dependent. A transaction with a log with topics [A, B] will be matched by the following topic + * filters: + * ▸ [] "anything" + * ▸ [A] "A in first position (and anything after)" + * ▸ [null, B] "anything in first position AND B in second position (and anything after)" + * ▸ [A, B] "A" in first position AND B in second position (and anything after)" + * ▸ [[A, B], [A, B]] "(A OR B) in first position AND (A OR B) in second position (and anything after)" + * @param expectedAddresses + * @param expectedTopics + * @returns JSON representation of the filtered logs + */ + *filter(expectedAddresses: Buffer[], expectedTopics: (string | string[])[]) { + const logs = this[_logs](); + if (expectedAddresses.length !== 0) { + if (expectedTopics.length === 0) { + for (const log of logs) { + if (expectedAddresses.some(address => address.equals(log.address))) + yield log.toJSON(); + } + } else { + for (const log of logs) { + if (!expectedAddresses.some(address => address.equals(log.address))) + continue; + if (filterByTopic(expectedTopics, log.topics)) yield log.toJSON(); + } + } + } else if (expectedTopics.length !== 0) { + for (const log of logs) { + if (filterByTopic(expectedTopics, log.topics)) yield log.toJSON(); + } + } else { + yield* logs.toJSON(); + } + } +} diff --git a/src/chains/ethereum/src/things/params.ts b/src/chains/ethereum/src/things/params.ts new file mode 100644 index 0000000000..728ba674ea --- /dev/null +++ b/src/chains/ethereum/src/things/params.ts @@ -0,0 +1,28 @@ +// NOTE these params may need to be changed at each hardfork +// they can be tracked here: https://github.com/ethereumjs/ethereumjs-vm/blob/master/packages/common/src/hardforks/ + +export default { + /** + * Per transaction not creating a contract. NOTE: Not payable on data of calls between transactions. + */ + TRANSACTION_GAS: 21000n, + + /** + * Per byte of data attached to a transaction that is not equal to zero. NOTE: Not payable on data of calls between transactions. + */ + TRANSACTION_DATA_NON_ZERO_GAS: new Map< + "constantinople" | "byzantium" | "petersburg" | "istanbul" | "muirGlacier", + bigint + >([ + ["constantinople", 68n], + ["byzantium", 68n], + ["petersburg", 68n], + ["istanbul", 68n], + ["muirGlacier", 16n] + ]), + + /** + * Per byte of data attached to a transaction that equals zero. NOTE: Not payable on data of calls between transactions. + */ + TRANSACTION_DATA_ZERO_GAS: 4n +}; diff --git a/src/chains/ethereum/src/things/runtime-block.ts b/src/chains/ethereum/src/things/runtime-block.ts new file mode 100644 index 0000000000..95aa14a3d9 --- /dev/null +++ b/src/chains/ethereum/src/things/runtime-block.ts @@ -0,0 +1,230 @@ +import { Data, Quantity } from "@ganache/utils"; +import { utils } from "@ganache/utils"; +import Common from "ethereumjs-common"; +import keccak from "keccak"; +import { encode as rlpEncode, decode as rlpDecode } from "rlp"; +import Transaction from "../things/transaction"; +import Address from "./address"; +import { KECCAK256_RLP_ARRAY } from "ethereumjs-util"; + +const { BUFFER_EMPTY, RPCQUANTITY_ZERO } = utils; + +type BlockHeader = { + parentHash: Data; + sha3Uncles: Data; + miner: Data; + stateRoot: Data; + transactionsRoot: Data; + receiptsRoot: Data; + logsBloom: Data; + difficulty: Quantity; + number: Quantity; + gasLimit: Quantity; + gasUsed: Quantity; + timestamp: Quantity; + extraData: Data; + mixHash: Data; + nonce: Data; +}; + +function makeHeader(raw: Buffer[]) { + const number = raw[8]; + + return { + parentHash: Data.from(raw[0], 32), + sha3Uncles: Data.from(raw[1], 32), + miner: Data.from(raw[2], 20), + stateRoot: Data.from(raw[3], 32), + transactionsRoot: Data.from(raw[4], 32), + receiptsRoot: Data.from(raw[5], 32), + logsBloom: Data.from(raw[6], 256), + difficulty: Quantity.from(raw[7], false), + // HACK: because `number` here is used as a key for the db we need to ensure + // that the value here holds an actual `0` when the raw === Buffer([]) + // the other empty buffer values aren't ever used as keys, so leaving them + // empty will probably be okay. + number: Quantity.from(raw[8], false), + gasLimit: Quantity.from(raw[9], false), + gasUsed: Quantity.from(raw[10], false), + timestamp: Quantity.from(raw[11], false), + extraData: Data.from(raw[12]), + mixHash: Data.from(raw[13], 32), + nonce: Data.from(raw[14], 8) + }; +} + +export class Block { + private readonly _size: number; + private readonly _raw: Buffer[]; + private readonly _common: Common; + private _transactions: Transaction[] = null; + private readonly _rawTransactions: Buffer[][] = null; + + public readonly header: BlockHeader; + + constructor(serialized: Buffer, common: Common) { + if (serialized) { + this._common = common; + this._size = serialized.length; + const deserialized = (rlpDecode(serialized) as any) as [ + Buffer[], + Buffer[][] + ]; + const raw = (this._raw = deserialized[0]); + this._rawTransactions = deserialized[1]; + this.header = makeHeader(raw); + } + } + + private _hash: Data; + hash() { + return ( + this._hash || + (this._hash = Data.from( + keccak("keccak256").update(rlpEncode(this._raw)).digest(), + 32 + )) + ); + } + + getTransactions() { + if (this._transactions) { + return this._transactions; + } + const common = this._common; + return (this._transactions = this._rawTransactions.map( + raw => new Transaction(raw, common) + )); + } + + toJSON(includeFullTransactions = false) { + const txFn = this.getTxFn(includeFullTransactions); + let jsonTxs: Data[] | {}[]; + + let transactions = this._transactions; + if (transactions) { + jsonTxs = transactions.map(txFn); + } else { + const common = this._common; + transactions = this._transactions = []; + jsonTxs = this._rawTransactions.map(raw => { + const tx = new Transaction(raw, common); + transactions.push(tx); + return txFn(tx); + }); + } + + return { + hash: this.hash(), + ...this.header, + + // TODO(forking): since ganache's difficulty is always 0, `totalDifficulty` for new blocks + // should just be the forked block's `difficulty`. See https://ethereum.stackexchange.com/a/7102/44640 + totalDifficulty: RPCQUANTITY_ZERO, + size: Quantity.from(this._size), + transactions: jsonTxs, + uncles: [] as string[] // this.value.uncleHeaders.map(function(uncleHash) {return to.hex(uncleHash)}) + }; + } + + getTxFn( + include = false + ): (tx: Transaction) => { [key: string]: string | Data | Quantity } | Data { + if (include) { + return (tx: Transaction) => tx.toJSON(this as any); + } else { + return (tx: Transaction) => Data.from(tx.hash()); + } + } +} + +/** + * A minimal block that can be used by the EVM to run transactions. + */ +export class RuntimeBlock { + public readonly header: { + parentHash: Buffer; + coinbase: Buffer; + number: Buffer; + gasLimit: Buffer; + timestamp: Buffer; + }; + + constructor( + number: Quantity, + parentHash: Data, + coinbase: Address, + gasLimit: Buffer, + timestamp: Quantity + ) { + const ts = timestamp.toBuffer(); + this.header = { + parentHash: parentHash.toBuffer(), + coinbase: coinbase.toBuffer(), + number: number.toBuffer(), + gasLimit: gasLimit.length === 0 ? BUFFER_EMPTY : gasLimit, + timestamp: ts.length === 0 ? BUFFER_EMPTY : ts + }; + } + + /** + * Returns the serialization of all block data and returns the hash of the + * block header. + * + * @param transactionsTrie + * @param receiptTrie + * @param bloom + * @param stateRoot + * @param gasUsed + * @param extraData + * @param transactions + */ + finalize( + transactionsTrie: Buffer, + receiptTrie: Buffer, + bloom: Buffer, + stateRoot: Buffer, + gasUsed: Buffer, + extraData: Data, + transactions: Transaction[] + ) { + const { header } = this; + const rawHeader = [ + header.parentHash, + KECCAK256_RLP_ARRAY, // uncleHash + header.coinbase, + stateRoot, + transactionsTrie, + receiptTrie, + bloom, + BUFFER_EMPTY, // difficulty + header.number, + header.gasLimit, + gasUsed, + header.timestamp, + extraData.toBuffer(), + Buffer.allocUnsafe(32).fill(0), // mixHash + Buffer.allocUnsafe(8).fill(0) // nonce + ]; + const rawTransactions = transactions.map(tx => tx.raw); + const raw = [rawHeader, rawTransactions]; + + const serialized = rlpEncode(raw); + + // make a new block, but pass `null` so it doesn't do the extra + // deserialization work since we already have everything in a deserialized + // state here. We'll just set it ourselves by reaching into the "_private" + // fields. + const block = new Block(null, null); + (block as any)._size = serialized.length; + (block as any)._raw = rawHeader; + (block as any)._rawTransactions = rawTransactions; + (block as any)._transactions = transactions; + (block as any).header = makeHeader(rawHeader); + + return { + block, + serialized + }; + } +} diff --git a/src/chains/ethereum/src/things/tags.ts b/src/chains/ethereum/src/things/tags.ts new file mode 100644 index 0000000000..2a7963a2e2 --- /dev/null +++ b/src/chains/ethereum/src/things/tags.ts @@ -0,0 +1,29 @@ +enum Tag { + EARLIEST = "earliest", + LATEST = "latest", + PENDING = "pending" +} +enum _Tag { + earliest, + latest, + pending +} + +namespace Tag { + export function normalize(tag: keyof typeof _Tag | Tag): Tag { + if (typeof tag === "string") { + return (Tag)[tag.toUpperCase()]; + } else { + switch (tag) { + case _Tag.earliest: + return Tag.EARLIEST; + case _Tag.latest: + return Tag.LATEST; + case _Tag.pending: + return Tag.PENDING; + } + } + } +} + +export default Tag; diff --git a/src/chains/ethereum/src/things/transaction-receipt.ts b/src/chains/ethereum/src/things/transaction-receipt.ts new file mode 100644 index 0000000000..aa02c54b62 --- /dev/null +++ b/src/chains/ethereum/src/things/transaction-receipt.ts @@ -0,0 +1,120 @@ +import Transaction from "./transaction"; +import { encode as rlpEncode, decode as rlpDecode } from "rlp"; +import { Data, Quantity } from "@ganache/utils"; +import BlockLogs, { TransactionLog } from "./blocklogs"; +import { utils } from "@ganache/utils"; +import { Block } from "./runtime-block"; + +const STATUSES = [utils.RPCQUANTITY_ZERO, utils.RPCQUANTITY_ONE]; + +type OmitLastType]> = T extends [ + ...infer A, + infer _L +] + ? A + : never; +type FullRawReceipt = [ + status: Buffer, + cumulativeGasUsed: Buffer, + logsBloom: Buffer, + logs: Buffer[], + gasUsed: Buffer, + contractAddress: Buffer | null +]; +type RawReceipt = OmitLastType>; + +export default class TransactionReceipt { + public contractAddress: Buffer; + #gasUsed: Buffer; + raw: RawReceipt; + + constructor(data?: Buffer) { + if (data) { + const decoded = (rlpDecode(data) as unknown) as FullRawReceipt; + this.#init( + decoded[0], + decoded[1], + decoded[2], + decoded[3], + decoded[4], + decoded[5] + ); + } + } + #init = ( + status: Buffer, + cumulativeGasUsed: Buffer, + logsBloom: Buffer, + logs: Buffer[], + gasUsed: Buffer, + contractAddress: Buffer = null + ) => { + this.raw = [status, cumulativeGasUsed, logsBloom, logs]; + this.contractAddress = contractAddress; + this.#gasUsed = gasUsed; + }; + + static fromValues( + status: Buffer, + cumulativeGasUsed: Buffer, + logsBloom: Buffer, + logs: Buffer[], + gasUsed: Buffer, + contractAddress: Buffer + ) { + const receipt = new TransactionReceipt(); + receipt.#init( + status, + cumulativeGasUsed, + logsBloom, + logs, + contractAddress, + gasUsed + ); + return receipt; + } + + public serialize(all: boolean) { + if (all) { + // the database format includes the contractAddress: + return rlpEncode([ + ...this.raw, + this.#gasUsed, + this.contractAddress + ] as FullRawReceipt); + } else { + // receipt trie format: + return rlpEncode(this.raw); + } + } + + public toJSON(block: Block, transaction: Transaction) { + const raw = this.raw; + const contractAddress = + this.contractAddress.length === 0 + ? null + : Data.from(this.contractAddress); + const blockHash = block.hash(); + const blockNumber = block.header.number; + const blockLog = BlockLogs.create(blockHash.toBuffer()); + blockLog.blockNumber = blockNumber; + ((raw[3] as any) as TransactionLog[]).forEach(log => { + blockLog.append(transaction._index, transaction.hash(), log); + }); + const logs = [...blockLog.toJSON()]; + return { + transactionHash: Data.from(transaction.hash()), + transactionIndex: Quantity.from((transaction as any)._index), + blockNumber, + blockHash, + from: Data.from(transaction.from), + to: contractAddress ? null : Data.from(transaction.to), + cumulativeGasUsed: Quantity.from(raw[1]), + gasUsed: Quantity.from(this.#gasUsed), + contractAddress, + logs, + logsBloom: Data.from(raw[2], 256), + status: STATUSES[raw[0][0]] + }; + } +} diff --git a/src/chains/ethereum/src/things/transaction.ts b/src/chains/ethereum/src/things/transaction.ts new file mode 100644 index 0000000000..3d6f584a56 --- /dev/null +++ b/src/chains/ethereum/src/things/transaction.ts @@ -0,0 +1,532 @@ +import { INTRINSIC_GAS_TOO_LOW } from "../errors/errors"; +import RuntimeError, { RETURN_TYPES } from "../errors/runtime-error"; +import { utils, Data, Quantity } from "@ganache/utils"; +import params from "./params"; +import { + Transaction as EthereumJsTransaction, + FakeTransaction as EthereumJsFakeTransaction +} from "ethereumjs-tx"; +import * as ethUtil from "ethereumjs-util"; +import assert from "assert"; +import { decode as rlpDecode } from "rlp"; +import { RunTxResult } from "ethereumjs-vm/dist/runTx"; +import TransactionReceipt from "./transaction-receipt"; +import Common from "ethereumjs-common"; +import { TransactionLog } from "./blocklogs"; +import Address from "./address"; +import { ExtractValuesFromType } from "../types/extract-values-from-types"; +import { Block } from "./runtime-block"; + +const MAX_UINT64 = (1n << 64n) - 1n; +const ZERO_BUFFER = Buffer.from([0]); +const ONE_BUFFER = Buffer.from([1]); + +//#region helpers +const sign = EthereumJsTransaction.prototype.sign; +const fakeHash = function (this: Transaction) { + // this isn't memoization of the hash. previous versions of ganache-core + // created hashes in a different/incorrect way and are recorded this way + // in snapshot dbs. We are preserving the chain's immutability by using the + // stored hash instead of calculating it. + if (this._hash != null) { + return this._hash; + } + return EthereumJsFakeTransaction.prototype.hash.apply( + this, + (arguments as unknown) as [(boolean | undefined)?] + ); +}; + +function configZeroableField(tx: any, fieldName: string, fieldLength = 32) { + const index = tx._fields.indexOf(fieldName); + const descriptor = Object.getOwnPropertyDescriptor(tx, fieldName); + // eslint-disable-next-line accessor-pairs + Object.defineProperty(tx, fieldName, { + set: v => { + descriptor.set.call(tx, v); + v = ethUtil.toBuffer(v); + assert( + fieldLength >= v.length, + `The field ${fieldName} must not have more ${fieldLength} bytes` + ); + tx._originals[index] = v; + }, + get: () => { + return tx._originals[index]; + } + }); +} + +/** + * etheruemjs-tx's Transactions don't behave quite like we need them to, so + * we're monkey-patching them to do what we want here. + * @param {Transaction} tx The Transaction to fix + * @param {Object} [data] The data object + */ +function fixProps(tx: any, data: any) { + // ethereumjs-tx doesn't allow for a `0` value in fields, but we want it to + // in order to differentiate between a value that isn't set and a value + // that is set to 0 in a fake transaction. + // Once https://github.com/ethereumjs/ethereumjs-tx/issues/112 is figured + // out we can probably remove this fix/hack. + // We keep track of the original value and return that value when + // referenced by its property name. This lets us properly encode a `0` as + // an empty buffer while still being able to differentiate between a `0` + // and `null`/`undefined`. + tx._originals = []; + const fieldNames = ["nonce", "gasPrice", "gasLimit", "value"] as const; + fieldNames.forEach(fieldName => configZeroableField(tx, fieldName, 32)); + + // Ethereumjs-tx doesn't set the _chainId value whenever the v value is set, + // which causes transaction signing to fail on transactions that include a + // chain id in the v value (like ethers.js does). + // Whenever the v value changes we need to make sure the chainId is also set. + const vDescriptors = Object.getOwnPropertyDescriptor(tx, "v"); + // eslint-disable-next-line accessor-pairs + Object.defineProperty(tx, "v", { + set: v => { + vDescriptors.set.call(tx, v); + // calculate chainId from signature + const sigV = ethUtil.bufferToInt(tx.v); + let chainId = Math.floor((sigV - 35) / 2); + if (chainId < 0) { + chainId = 0; + } + tx._chainId = chainId || 0; + } + }); +} + +function makeFake(tx: any, data: any) { + if (tx.isFake()) { + /** + * @prop {Buffer} from (read/write) Set from address to bypass transaction + * signing on fake transactions. + */ + Object.defineProperty(tx, "from", { + enumerable: true, + configurable: true, + get: tx.getSenderAddress.bind(tx), + set: val => { + if (val) { + tx._from = ethUtil.toBuffer(val); + } else { + tx._from = null; + } + } + }); + + if (data && data.from) { + tx.from = data.from; + } + + tx.hash = fakeHash; + } +} + +/** + * Parses the given data object and adds its properties to the given tx. + * @param {Transaction} tx + * @param {Object} [data] + */ +function initData(tx: Transaction, data: any) { + if (data) { + let parts: Buffer[]; + if (typeof data === "string") { + //hex + parts = (rlpDecode(Data.from(data).toBuffer()) as any) as Buffer[]; + } else if (Buffer.isBuffer(data)) { + // Buffer + parts = (rlpDecode(data) as any) as Buffer[]; + } else if (data.type === "Buffer") { + // wire Buffer + // handle case where a Buffer is sent as `{data: "Buffer", data: number[]}` + // like if someone does `web3.eth.sendRawTransaction(tx.serialize())` + const obj = data.data; + const length = obj.length; + const buf = Buffer.allocUnsafe(length); + for (let i = 0; i < length; i++) { + buf[i] = obj[i]; + } + parts = (rlpDecode(buf) as any) as Buffer[]; + } else if (Array.isArray(data)) { + // rlpdecoded data + parts = data; + } else if (typeof data === "object") { + // JSON + const keys = Object.keys(data); + tx._fields.forEach((field: any) => { + if (keys.indexOf(field) !== -1) { + tx[field] = data[field]; + } + if (field === "gasLimit") { + if (keys.indexOf("gas") !== -1) { + tx["gas"] = data["gas"]; + } + } else if (field === "data") { + if (keys.indexOf("input") !== -1) { + tx["input"] = data["input"]; + } + } + }); + + // Set chainId value from the data, if it's there and the data didn't + // contain a `v` value with chainId in it already. If we do have a + // data.chainId value let's set the interval v value to it. + if (!tx._chainId && data && data.chainId != null) { + tx.raw[tx._fields.indexOf("v")] = tx._chainId = data.chainId || 0; + } + return; + } else { + throw new Error("invalid data"); + } + + // add in our hacked-in properties + // which is the index in the block the transaciton + // was mined in + if (parts.length === tx._fields.length + 5) { + tx._from = parts.pop(); + tx.type = parts.pop()[0]; + tx._index = parts.pop(); + tx._blockNum = parts.pop(); + tx._blockHash = parts.pop(); + } + if (parts.length > tx._fields.length) { + throw new Error("wrong number of fields in data"); + } + + // make sure all the items are buffers + parts.forEach((d, i) => { + tx[tx._fields[i]] = ethUtil.toBuffer(d); + }); + } +} + +//#endregion + +type TransactionFinalization = + | { status: "confirmed"; error?: Error } + | { status: "rejected"; error: Error }; + +interface Transaction extends Omit {} +// TODO fix the EthereumJsTransaction as any via some "fake" multi-inheritance: +class Transaction extends (EthereumJsTransaction as any) { + public locked: boolean = false; + type: number; + v: Buffer; + r: Buffer; + s: Buffer; + raw: any; + _chainId: any; + _hash: Buffer; + readonly from: Buffer; + #receipt: TransactionReceipt; + #logs: TransactionLog[]; + #finalizer: (eventData: TransactionFinalization) => void; + #finalized: Promise; + /** + * @param {Object} [data] The data for this Transaction. + * @param {Number} type The `Transaction.types` bit flag for this transaction + * Can be a combination of `Transaction.types.none`, `Transaction.types.signed`, and `Transaction.types.fake`. + */ + constructor( + data: any, + common: Common, + type: number = Transaction.types.none + ) { + super(void 0, { common }); + + // EthereumJS-TX Transaction overwrites our `toJSON`, so we overwrite it back here: + this.toJSON = Transaction.prototype.toJSON.bind(this); + + this.type = type; + + fixProps(this, data); + initData(this, data); + + if (this.isFake()) { + makeFake(this, data); + } + + let finalizer: (value: TransactionFinalization) => void; + this.#finalized = new Promise(resolve => { + finalizer = (...args: any[]) => process.nextTick(resolve, ...args); + }); + this.#finalizer = finalizer; + } + + static get types() { + // values must be powers of 2 + return { + none: 0 as const, + signed: 1 as const, + fake: 2 as const + }; + } + + cost(): bigint { + return ( + Quantity.from(this.gasPrice).toBigInt() * + Quantity.from(this.gasLimit).toBigInt() + + Quantity.from(this.value).toBigInt() + ); + } + + /** + * Returns a Promise that is resolve with the confirmation status and, if + * appropriate, an error property. + * + * Note: it is possible to be confirmed AND + * + * @param event "finalized" + */ + once(event: "finalized") { + return this.#finalized; + } + + /** + * Mark this transaction as finalized, notifying all past and future + * "finalized" event subscribers. + * + * Note: + * + * @param status + * @param error + */ + finalize(status: "confirmed" | "rejected", error: Error = null) { + // resolves the `#finalized` promise + this.#finalizer({ status, error }); + } + + /** + * Compute the 'intrinsic gas' for a message with the given data. + * @param data The transaction's data + * @param hardfork The hardfork use to determine gas costs + */ + public static calculateIntrinsicGas( + data: Buffer | null, + hardfork: + | "constantinople" + | "byzantium" + | "petersburg" + | "istanbul" + | "muirGlacier" + ) { + // Set the starting gas for the raw transaction + let gas = params.TRANSACTION_GAS; + if (data) { + // Bump the required gas by the amount of transactional data + const dataLength = data.byteLength; + if (dataLength > 0) { + const TRANSACTION_DATA_NON_ZERO_GAS = params.TRANSACTION_DATA_NON_ZERO_GAS.get( + hardfork + ); + const TRANSACTION_DATA_ZERO_GAS = params.TRANSACTION_DATA_ZERO_GAS; + + // Zero and non-zero bytes are priced differently + let nonZeroBytes: bigint = 0n; + for (const b of data) { + if (b !== 0) { + nonZeroBytes++; + } + } + // Make sure we don't exceed uint64 for all data combinations. + if ((MAX_UINT64 - gas) / TRANSACTION_DATA_NON_ZERO_GAS < nonZeroBytes) { + throw new Error(INTRINSIC_GAS_TOO_LOW); + } + gas += nonZeroBytes * TRANSACTION_DATA_NON_ZERO_GAS; + + const z = BigInt(dataLength) - nonZeroBytes; + if ((MAX_UINT64 - gas) / TRANSACTION_DATA_ZERO_GAS < z) { + throw new Error(INTRINSIC_GAS_TOO_LOW); + } + gas += z * TRANSACTION_DATA_ZERO_GAS; + } + } + return gas; + } + /** + * Compute the 'intrinsic gas' for a message with the given data. + */ + public calculateIntrinsicGas(): bigint { + return Transaction.calculateIntrinsicGas(this.data, this._common._hardfork); + } + + /** + * Prepares arbitrary JSON data for use in a Transaction. + * @param {Object} json JSON object representing the Transaction + * @param {Number} type The `Transaction.types` bit flag for this transaction + * Can be a combination of `Transaction.types.none`, `Transaction.types.signed`, and `Transaction.types.fake`. + */ + static fromJSON( + json: any, + common: Common, + type: ExtractValuesFromType + ) { + let toAccount: Buffer; + if (json.to) { + // Remove all padding and make it easily comparible. + const buf = Data.from(json.to).toBuffer(); + + if (buf.equals(utils.BUFFER_ZERO)) { + // if the address is 0x0 make it 0x0{20} + toAccount = utils.ACCOUNT_ZERO; + } else { + toAccount = buf; + } + } + const data = json.data || json.input; + const options = { + nonce: Data.from(json.nonce).toBuffer(), + from: Data.from(json.from).toBuffer(), + value: Quantity.from(json.value).toBuffer(), + gasLimit: Quantity.from(json.gas || json.gasLimit).toBuffer(), + gasPrice: Quantity.from(json.gasPrice).toBuffer(), + data: data ? Data.from(data).toBuffer() : null, + to: toAccount, + v: Data.from(json.v).toBuffer(), + r: Data.from(json.r).toBuffer(), + s: Data.from(json.s).toBuffer() + }; + + const tx = new Transaction(options, common, type); + tx._hash = json.hash ? Data.from(json.hash).toBuffer() : null; + tx._from = json.from ? Data.from(json.from).toBuffer() : null; + return tx; + } + + /** + * Encodes the Transaction in order to be used in a database. Can be decoded + * into an identical Transaction via `Transaction.decode(encodedTx)`. + */ + encode() { + const resultJSON = { + hash: Data.from(this.hash()).toString(), + nonce: Quantity.from(this.nonce).toString() || "0x", + from: Data.from(this.from).toString(), + to: Data.from(this.to).toString(), + value: Quantity.from(this.value).toString(), + gas: Quantity.from(this.gasLimit).toString(), + gasPrice: Quantity.from(this.gasPrice).toString(), + data: this.data ? this.data.toString("hex") : null, + v: Quantity.from(this.v).toString(), + r: Quantity.from(this.r).toString(), + s: Quantity.from(this.s).toString(), + _type: this.type + }; + return resultJSON; + } + + isFake() { + return (this.type & Transaction.types.fake) === Transaction.types.fake; + } + + isSigned() { + return (this.type & Transaction.types.signed) === Transaction.types.signed; + } + + /** + * Compares the transaction's nonce value to the given expectedNonce taking in + * to account the type of transaction and comparison rules for each type. + * + * In a signed transaction a nonce of Buffer([]) is the same as Buffer([0]), + * but in a fake transaction Buffer([]) is null and Buffer([0]) is 0. + * + * @param {Buffer} expectedNonce The value of the from account's next nonce. + */ + validateNonce(expectedNonce: any) { + let nonce; + if (this.isSigned() && this.nonce.length === 0) { + nonce = utils.BUFFER_ZERO; + } else { + nonce = this.nonce; + } + return nonce.equals(expectedNonce); + } + + /** + * Signs the transaction and sets the `type` bit for `signed` to 1, + * i.e., `isSigned() === true` + */ + sign(secretKey: Buffer) { + this.type |= Transaction.types.signed; + return sign.call(this, secretKey); + } + + /** + * Returns a JSON-RPC spec compliant representation of this Transaction. + * + * @param {Object} block The block this Transaction appears in. + */ + toJSON(block?: Block) { + let blockHash: Data; + let blockNum: Quantity; + if (block) { + blockHash = block.hash(); + blockNum = block.header.number; + } else { + blockHash = this._blockHash ? Data.from(this._blockHash, 32) : null; + blockNum = this._blockNum ? Quantity.from(this._blockNum) : null; + } + return { + hash: Data.from(this.hash(), 32), + nonce: Quantity.from(this.nonce), + blockHash: blockHash ? blockHash : null, + blockNumber: blockNum ? blockNum : null, + transactionIndex: this._index ? Quantity.from(this._index) : null, + from: Address.from(this.from), + to: this.to.length === 0 ? null : Address.from(this.to), + value: Quantity.from(this.value), + gas: Quantity.from(this.gasLimit), + gasPrice: Quantity.from(this.gasPrice), + input: Data.from(this.data), + v: Quantity.from(this.v), + r: Quantity.from(this.r), + s: Quantity.from(this.s) + }; + } + + /** + * Initializes the receipt and logs + * @param result + * @returns RLP encoded data for use in a transaction trie + */ + fillFromResult(result: RunTxResult, cumulativeGasUsed: bigint) { + const vmResult = result.execResult; + const execException = vmResult.exceptionError; + let status: Buffer; + if (execException) { + status = ZERO_BUFFER; + this.execException = new RuntimeError( + this.hash(), + result, + RETURN_TYPES.TRANSACTION_HASH + ); + } else { + status = ONE_BUFFER; + } + + const receipt = (this.#receipt = TransactionReceipt.fromValues( + status, + Quantity.from(cumulativeGasUsed).toBuffer(), + result.bloom.bitvector, + (this.#logs = vmResult.logs || ([] as TransactionLog[])), + result.createdAddress, + result.gasUsed.toArrayLike(Buffer) + )); + + return receipt.serialize(false); + } + + getReceipt() { + return this.#receipt; + } + + getLogs() { + return this.#logs; + } + + public execException: RuntimeError = null; +} + +export default Transaction; diff --git a/src/chains/ethereum/src/transaction-pool.ts b/src/chains/ethereum/src/transaction-pool.ts new file mode 100644 index 0000000000..e0ff56d6a7 --- /dev/null +++ b/src/chains/ethereum/src/transaction-pool.ts @@ -0,0 +1,319 @@ +import Emittery from "emittery"; +import Blockchain from "./blockchain"; +import { utils } from "@ganache/utils"; +import Transaction from "./things/transaction"; +import { Data, Quantity } from "@ganache/utils"; +import { GAS_LIMIT, INTRINSIC_GAS_TOO_LOW } from "./errors/errors"; +import CodedError, { ErrorCodes } from "./errors/coded-error"; +import { EthereumInternalOptions } from "./options"; +import { Executables } from "./types/executables"; + +function byNonce(values: Transaction[], a: number, b: number) { + return ( + (Quantity.from(values[b].nonce).toBigInt() || 0n) > + (Quantity.from(values[a].nonce).toBigInt() || 0n) + ); +} + +export default class TransactionPool extends Emittery.Typed<{}, "drain"> { + #options: EthereumInternalOptions["miner"]; + + /** + * Minimum price bump percentage to replace an already existing transaction (nonce) + */ + #priceBump: bigint = 10n; + + #blockchain: Blockchain; + constructor( + options: EthereumInternalOptions["miner"], + blockchain: Blockchain + ) { + super(); + this.#blockchain = blockchain; + this.#options = options; + } + public readonly executables: Executables = { + inProgress: new Set(), + pending: new Map() + }; + readonly #origins: Map> = new Map(); + readonly #accountPromises = new Map>(); + + /** + * Inserts a transaction into the pending queue, if executable, or future pool + * if not. + * + * @param transaction + * @param secretKey + * @returns data that can be used to drain the queue + */ + public async prepareTransaction(transaction: Transaction, secretKey?: Data) { + let err: Error; + + err = this.#validateTransaction(transaction); + if (err != null) { + throw err; + } + + const from = Data.from(transaction.from); + let transactionNonce: bigint; + if (secretKey == null || transaction.nonce.length !== 0) { + transactionNonce = Quantity.from(transaction.nonce).toBigInt() || 0n; + if (transactionNonce < 0n) { + throw new Error("Transaction nonce cannot be negative."); + } + } + + const origin = from.toString(); + + // We await the `transactorNoncePromise` async request to ensure we process + // transactions in FIFO order *by account*. We look up accounts because + // ganache fills in missing nonces automatically, and we need to do it in + // order. + // The trick here is that we might actually get the next nonce from the + // account's pending executable transactions, not the account... + // But another transaction might currently be getting the nonce from the + // account, if it is, we need to wait for it to be done doing that. Hence: + let transactorNoncePromise = this.#accountPromises.get(origin); + if (transactorNoncePromise) { + await transactorNoncePromise; + } + + // we should _probably_ cache `highestNonce`, but it's actually a really hard thing to cache as the current highest + // nonce might be invalidated (like if the sender doesn't have enough funds), so we'd have to go back to the previous + // highest nonce... but what if that previous highest nonce was also invalidated?! we have to go back to the... you + // get the picture. + // So... we currently do things sub-optimally: + // if we currently have txs in `executableOriginTransactions`, we iterate over them to find the highest nonce + // and use that. Otherwise, we just fetch it from the database. + // Beware! There might still be race conditions here: + // * if the highest tx executes, which causes it to be removed from the `executableOriginTransactions` heap, + // then a new tx comes in _before_ the block is persisted to the database, the nonce might be of the second + // tx would be too low. + // * rough idea for a fix: transactions have a `finalize` method that is called _after_ the tx is saved. Maybe + // when tx's are executed their nonce is moved to a `highNonceByOrigin` map? We'd check this map in addition to the + // `executableOriginTransactions` map, always taking the highest of the two. + let highestNonce = 0n; + + const origins = this.#origins; + const queuedOriginTransactions = origins.get(origin); + + let isExecutableTransaction = false; + const executables = this.executables.pending; + let executableOriginTransactions = executables.get(origin); + + let length: number; + if ( + executableOriginTransactions && + (length = executableOriginTransactions.length) + ) { + // check if a transaction with the same nonce is in the origin's + // executables queue already. Replace the matching transaction or throw this + // new transaction away as neccessary. + const pendingArray = executableOriginTransactions.array; + const priceBump = this.#priceBump; + const newGasPrice = Quantity.from(transaction.gasPrice).toBigInt(); + // Notice: we're iterating over the raw heap array, which isn't + // necessarily sorted + for (let i = 0; i < length; i++) { + const currentPendingTx = pendingArray[i]; + const thisNonce = Quantity.from(currentPendingTx.nonce).toBigInt(); + if (thisNonce === transactionNonce) { + const gasPrice = Quantity.from(currentPendingTx.gasPrice).toBigInt(); + const thisPricePremium = gasPrice + (gasPrice * priceBump) / 100n; + + // if our new price is `gasPrice * priceBumpPercent` better than our + // oldPrice, throw out the old now. + if (!currentPendingTx.locked && newGasPrice > thisPricePremium) { + isExecutableTransaction = true; + // do an in-place replace without triggering a re-sort because we + // already know where this tranasaction should go in this "byNonce" + // heap. + pendingArray[i] = transaction; + + currentPendingTx.finalize( + "rejected", + new CodedError( + "Transaction replaced by better transaction", + ErrorCodes.TRANSACTION_REJECTED + ) + ); + } else { + throw new CodedError( + "replacement transaction underpriced", + ErrorCodes.TRANSACTION_REJECTED + ); + } + } + if (thisNonce > highestNonce) { + highestNonce = thisNonce; + } + } + if (secretKey && transactionNonce === void 0) { + // if we aren't signed and don't have a transactionNonce yet set it now + transactionNonce = highestNonce + 1n; + transaction.nonce = Quantity.from(transactionNonce).toBuffer(); + isExecutableTransaction = true; + highestNonce = transactionNonce; + } else if (transactionNonce === highestNonce + 1n) { + // if our transaction's nonce is 1 higher than the last transaction in the + // origin's heap we are executable. + isExecutableTransaction = true; + highestNonce = transactionNonce; + } + } else { + // since we don't have any executable transactions at the moment, we need + // to find our nonce from the account itself... + if (!transactorNoncePromise) { + transactorNoncePromise = this.#blockchain.accounts.getNonce(from); + this.#accountPromises.set(origin, transactorNoncePromise); + transactorNoncePromise.then(() => { + this.#accountPromises.delete(origin); + }); + } + const transactor = await transactorNoncePromise; + + const transactorNonce = transactor ? transactor.toBigInt() : 0n; + if (secretKey && transactionNonce === void 0) { + // if we don't have a transactionNonce, just use the account's next + // nonce and mark as executable + transactionNonce = transactorNonce ? transactorNonce : 0n; + highestNonce = transactionNonce; + isExecutableTransaction = true; + transaction.nonce = Quantity.from(transactionNonce).toBuffer(); + } else if (transactionNonce < transactorNonce) { + // it's an error if the transaction's nonce is <= the persisted nonce + throw new Error( + `the tx doesn't have the correct nonce. account has nonce of: ${transactorNonce} tx has nonce of: ${transactionNonce}` + ); + } else if (transactionNonce === transactorNonce) { + isExecutableTransaction = true; + } + } + + // now that we know we have a transaction nonce we can sign the transaction + // (if we have the secret key) + if (secretKey) { + transaction.sign(secretKey.toBuffer()); + } + + if (isExecutableTransaction) { + // if it is executable add it to the executables queue + if (executableOriginTransactions) { + executableOriginTransactions.push(transaction); + } else { + // if we don't yet have an executables queue for this origin make one now + executableOriginTransactions = utils.Heap.from(transaction, byNonce); + executables.set(origin, executableOriginTransactions); + } + + // Now we need to drain any queued transacions that were previously + // not executable due to nonce gaps into the origin's queue... + if (queuedOriginTransactions) { + let nextExpectedNonce = transactionNonce + 1n; + while (true) { + const nextTx = queuedOriginTransactions.peek(); + const nextTxNonce = Quantity.from(nextTx.nonce).toBigInt() || 0n; + if (nextTxNonce !== nextExpectedNonce) { + break; + } + + // we've got a an executable nonce! Put it in the executables queue. + executableOriginTransactions.push(nextTx); + + // And then remove this transaction from its origin's queue + if (!queuedOriginTransactions.removeBest()) { + // removeBest() returns `false` when there are no more items after + // the removed item. Let's do some cleanup when that happens. + origins.delete(origin); + break; + } + + nextExpectedNonce += 1n; + } + } + + return true; + } else { + // otherwise, put it in the future queue + if (queuedOriginTransactions) { + queuedOriginTransactions.push(transaction); + } else { + origins.set(origin, utils.Heap.from(transaction, byNonce)); + } + + return false; + } + } + + public clear() { + this.#origins.clear(); + this.#accountPromises.clear(); + this.executables.pending.clear(); + } + + /** + * Returns the transaction matching the given hash. + * + * This isn't the fastest thing... but querying for pending transactions is + * likely rare, so leaving this slow so other code paths can be faster might + * be okay. + * + * @param transactionHash + */ + public find(transactionHash: Buffer) { + const { pending, inProgress } = this.executables; + + // first search pending transactions + for (let [_, transactions] of this.#origins) { + if (transactions === undefined) continue; + const arr = transactions.array; + for (let i = 0; i < transactions.length; i++) { + const tx = arr[i]; + if (tx.hash().equals(transactionHash)) { + return tx; + } + } + } + + // then transactions eligible for execution + for (let [_, transactions] of pending) { + const arr = transactions.array; + for (let i = 0; i < transactions.length; i++) { + const tx = arr[i]; + if (tx.hash().equals(transactionHash)) { + return tx; + } + } + } + + // and finally transactions that have just been processed, but not yet saved + for (let tx of inProgress) { + if (tx.hash().equals(transactionHash)) { + return tx; + } + } + return null; + } + + readonly drain = () => { + // notify listeners (the blockchain, then the miner, eventually) that we + // have executable transactions ready + this.emit("drain"); + }; + + readonly #validateTransaction = (transaction: Transaction): Error => { + // Check the transaction doesn't exceed the current block limit gas. + if (Quantity.from(transaction.gasLimit) > this.#options.blockGasLimit) { + return new CodedError(GAS_LIMIT, ErrorCodes.INVALID_INPUT); + } + + // Should supply enough intrinsic gas + const gas = transaction.calculateIntrinsicGas(); + if (Quantity.from(transaction.gasLimit).toBigInt() < gas) { + return new CodedError(INTRINSIC_GAS_TOO_LOW, ErrorCodes.INVALID_INPUT); + } + + return null; + }; +} diff --git a/src/chains/ethereum/src/types/executables.ts b/src/chains/ethereum/src/types/executables.ts new file mode 100644 index 0000000000..7a9bcae406 --- /dev/null +++ b/src/chains/ethereum/src/types/executables.ts @@ -0,0 +1,7 @@ +import { utils } from "@ganache/utils"; +import Transaction from "../things/transaction"; + +export type Executables = { + inProgress: Set; + pending: Map>; +}; diff --git a/src/chains/ethereum/src/types/extract-values-from-types.ts b/src/chains/ethereum/src/types/extract-values-from-types.ts new file mode 100644 index 0000000000..e584aa2302 --- /dev/null +++ b/src/chains/ethereum/src/types/extract-values-from-types.ts @@ -0,0 +1 @@ +export type ExtractValuesFromType = { [I in keyof T]: T[I] }[keyof T]; diff --git a/src/chains/ethereum/src/types/filters.ts b/src/chains/ethereum/src/types/filters.ts new file mode 100644 index 0000000000..06dde6fd35 --- /dev/null +++ b/src/chains/ethereum/src/types/filters.ts @@ -0,0 +1,24 @@ +import { Data } from "@ganache/utils"; +import Emittery from "emittery"; +import Tag from "../things/tags"; + +export enum FilterTypes { + log, + block, + pendingTransaction +} +export type Topic = string | string[]; +export type BaseFilterArgs = { address?: string | string[]; topics?: Topic[] }; +export type BlockHashFilterArgs = BaseFilterArgs & { blockHash?: string }; +export type RangeFilterArgs = BaseFilterArgs & { + fromBlock?: string | Tag; + toBlock?: string | Tag; +}; +export type FilterArgs = BlockHashFilterArgs | RangeFilterArgs; + +export type Filter = { + type: FilterTypes; + updates: Data[]; + unsubscribe: Emittery.UnsubscribeFn; + filter: FilterArgs; +}; diff --git a/src/chains/ethereum/src/types/shh.ts b/src/chains/ethereum/src/types/shh.ts new file mode 100644 index 0000000000..7d2ffdc19b --- /dev/null +++ b/src/chains/ethereum/src/types/shh.ts @@ -0,0 +1 @@ +export type WhisperPostObject = any; diff --git a/src/chains/ethereum/src/types/snapshots.ts b/src/chains/ethereum/src/types/snapshots.ts new file mode 100644 index 0000000000..7b7f1d07cd --- /dev/null +++ b/src/chains/ethereum/src/types/snapshots.ts @@ -0,0 +1,24 @@ +import Emittery from "emittery"; +import { Block } from "../things/runtime-block"; + +type SinglyLinkedList = { current: T; next: SinglyLinkedList }; + +export type Snapshot = { + block: Block; + timeAdjustment: number; +}; + +export type Snapshots = { + readonly snaps: Snapshot[]; + + /** + * This is a rudimentary Singly Linked List Node. SLL implementation is up to + * you. + */ + blocks: SinglyLinkedList; + + /** + * Function that should be used to remove the "block" listener + */ + unsubscribeFromBlocks: Emittery.UnsubscribeFn | null; +}; diff --git a/src/chains/ethereum/src/types/subscriptions.ts b/src/chains/ethereum/src/types/subscriptions.ts new file mode 100644 index 0000000000..17adeaa0f2 --- /dev/null +++ b/src/chains/ethereum/src/types/subscriptions.ts @@ -0,0 +1,6 @@ +export type SubscriptionId = string; +export type SubscriptionName = + | "newHeads" + | "newPendingTransactions" + | "syncing" + | "logs"; diff --git a/src/chains/ethereum/src/types/tuple-from-union.ts b/src/chains/ethereum/src/types/tuple-from-union.ts new file mode 100644 index 0000000000..71aa7e40e1 --- /dev/null +++ b/src/chains/ethereum/src/types/tuple-from-union.ts @@ -0,0 +1,36 @@ +type TuplePrepend = [ + NewElement, + ...Tuple +]; + +type Consumer = (value: Value) => void; + +type IntersectionFromUnion = ( + Union extends unknown ? Consumer : never +) extends Consumer + ? ResultIntersection + : never; + +type OverloadedConsumerFromUnion = IntersectionFromUnion< + Union extends unknown ? Consumer : never +>; + +type UnionLast = OverloadedConsumerFromUnion extends ( + a: infer A +) => void + ? A + : never; + +type UnionExcludingLast = Exclude>; + +type TupleFromUnionRec< + RemainingUnion, + CurrentTuple extends readonly unknown[] +> = [RemainingUnion] extends [never] + ? CurrentTuple + : TupleFromUnionRec< + UnionExcludingLast, + TuplePrepend> + >; + +export type TupleFromUnion = TupleFromUnionRec; diff --git a/src/chains/ethereum/src/wallet.ts b/src/chains/ethereum/src/wallet.ts new file mode 100644 index 0000000000..64d5f1106d --- /dev/null +++ b/src/chains/ethereum/src/wallet.ts @@ -0,0 +1,451 @@ +import { utils } from "@ganache/utils"; +import { Data, Quantity } from "@ganache/utils"; +import Address from "./things/address"; +import { privateToAddress } from "ethereumjs-util"; +import Account from "./things/account"; +import secp256k1 from "secp256k1"; +import { mnemonicToSeedSync } from "bip39"; +import HDKey from "hdkey"; +import { alea as rng } from "seedrandom"; +import crypto from "crypto"; +import createKeccakHash from "keccak"; +import { writeFileSync } from "fs"; +import { EthereumInternalOptions } from "./options"; + +//#region Constants +const SCRYPT_PARAMS = { + dklen: 32, + n: 1024, // practically nothing + p: 8, + r: 1 +} as const; +const CIPHER = "aes-128-ctr"; +const WEI = utils.WEI; +//#endregion + +type OmitLastType]> = T extends [ + ...infer A, + infer _L +] + ? A + : never; +type LastType]> = T extends [ + ...infer _A, + infer L +] + ? L + : never; + +type Params = Parameters; +type LastParams = Parameters>; +const scrypt = (...args: OmitLastType) => { + return new Promise( + ( + resolve: (value: LastParams[1]) => void, + reject: (reason: LastParams[0]) => void + ) => { + crypto.scrypt.call( + crypto, + ...args, + (err: LastParams[0], derivedKey: LastParams[1]) => { + if (err) { + return void reject(err); + } + return resolve(derivedKey); + } + ); + } + ); +}; + +const uncompressedPublicKeyToAddress = (uncompressedPublicKey: Buffer) => { + const compresedPublicKey = secp256k1 + .publicKeyConvert(uncompressedPublicKey, false) + .slice(1); + const hasher = createKeccakHash("keccak256"); + (hasher as any)._state.absorb(compresedPublicKey); + return Address.from(hasher.digest().slice(-20)); +}; + +const asUUID = (uuid: Buffer | { length: 16 }) => { + return `${uuid.toString("hex", 0, 4)}-${uuid.toString( + "hex", + 4, + 6 + )}-${uuid.toString("hex", 6, 8)}-${uuid.toString( + "hex", + 8, + 10 + )}-${uuid.toString("hex", 10)}`; +}; + +type ThenArg = T extends PromiseLike ? U : T; +type EncryptType = ThenArg>; + +export default class Wallet { + readonly addresses: string[]; + readonly initialAccounts: Account[]; + readonly knownAccounts = new Set(); + readonly encryptedKeyFiles = new Map(); + readonly unlockedAccounts = new Map(); + readonly lockTimers = new Map(); + + #hdKey: HDKey; + + constructor(opts: EthereumInternalOptions["wallet"]) { + this.#hdKey = HDKey.fromMasterSeed(mnemonicToSeedSync(opts.mnemonic, null)); + + const initialAccounts = (this.initialAccounts = this.#initializeAccounts( + opts + )); + const l = initialAccounts.length; + + const knownAccounts = this.knownAccounts; + const unlockedAccounts = this.unlockedAccounts; + //#region Unlocked Accounts + const givenUnlockedAccounts = opts.unlockedAccounts; + if (givenUnlockedAccounts) { + const ul = givenUnlockedAccounts.length; + for (let i = 0; i < ul; i++) { + let arg = givenUnlockedAccounts[i]; + let address: string; + switch (typeof arg) { + case "string": + // `toLowerCase` so we handle uppercase `0X` formats + const addressOrIndex = arg.toLowerCase(); + if (addressOrIndex.indexOf("0x") === 0) { + address = addressOrIndex; + break; + } else { + // try to convert the arg string to a number. + // don't use parseInt because strings like `"123abc"` parse + // to `123`, and there is probably an error on the user's side we'd + // want to uncover. + const index = ((arg as any) as number) - 0; + // if we don't have a valid number, or the number isn't a valid JS + // integer (no bigints or decimals, please), throw an error. + if (!Number.isSafeInteger(index)) { + throw new Error(`Invalid value in unlocked_accounts: ${arg}`); + } + arg = index; + // not `break`ing here because I want this to fall through to the + // `"number"` case below. + // Refactor it if you want. + // break; // no break, please. + } + case "number": + const account = initialAccounts[arg]; + if (account == null) { + throw new Error( + `Account at index ${arg} not found. Max index available is ${ + l - 1 + }.` + ); + } + address = account.address.toString().toLowerCase(); + break; + default: + throw new Error(`Invalid value specified in unlocked_accounts`); + } + if (unlockedAccounts.has(address)) continue; + // if we don't have the secretKey for an account we use `null` + unlockedAccounts.set(address, null); + } + } + //#endregion + + //#region Configure Known + Unlocked Accounts + const accountsCache = (this.addresses = Array(l)); + for (let i = 0; i < l; i++) { + const account = initialAccounts[i]; + const address = account.address; + const strAddress = address.toString(); + accountsCache[i] = strAddress; + knownAccounts.add(strAddress); + + // if the `secure` option has been set do NOT add these accounts to the + // unlockedAccounts, unless the account was already added to + // unlockedAccounts, in which case we need to add the account's private + // key. + if (opts.secure && !unlockedAccounts.has(strAddress)) continue; + + unlockedAccounts.set(strAddress, account.privateKey); + } + //#endregion + + //#region save accounts to disk + if (opts.accountKeysPath != null) { + const fileData = { + addresses: {} as { [address: string]: string }, + private_keys: {} as { [address: string]: Data } + }; + unlockedAccounts.forEach((privateKey, address) => { + fileData.addresses[address] = address; + fileData.private_keys[address] = privateKey; + }); + writeFileSync(opts.accountKeysPath, JSON.stringify(fileData)); + } + //#endregion + } + + #seedCounter = 0n; + + #randomBytes = (length: number) => { + // Since this is a mock RPC library, the rng doesn't need to be + // cryptographically secure, and determinism is desired. + const buf = Buffer.allocUnsafe(length); + const seed = (this.#seedCounter += 1n); + const rand = rng(seed.toString()); + for (let i = 0; i < length; i++) { + buf[i] = (rand() * 255) | 0; + } + return buf; + }; + + #initializeAccounts = ( + options: EthereumInternalOptions["wallet"] + ): Account[] => { + // convert a potentially fractional balance of Ether to WEI + const balanceParts = options.defaultBalance.toString().split(".", 2); + const significand = BigInt(balanceParts[0]); + const fractionalStr = balanceParts[1] || "0"; + const fractional = BigInt(fractionalStr); + const magnitude = 10n ** BigInt(fractionalStr.length); + const defaultBalanceInWei = + WEI * significand + fractional * (WEI / magnitude); + const etherInWei = Quantity.from(defaultBalanceInWei); + let accounts: Account[]; + + let givenAccounts = options.accounts; + let accountsLength: number; + if (givenAccounts && (accountsLength = givenAccounts.length) !== 0) { + const hdKey = this.#hdKey; + const hdPath = options.hdPath; + accounts = Array(accountsLength); + for (let i = 0; i < accountsLength; i++) { + const account = givenAccounts[i]; + const secretKey = account.secretKey; + let privateKey: Data; + let address: Address; + if (!secretKey) { + const acct = hdKey.derive(hdPath + i); + address = uncompressedPublicKeyToAddress(acct.publicKey); + privateKey = Data.from(acct.privateKey); + accounts[i] = Wallet.createAccount( + Quantity.from(account.balance), + privateKey, + address + ); + } else { + privateKey = Data.from(secretKey); + const a = (accounts[i] = Wallet.createAccountFromPrivateKey( + privateKey + )); + a.balance = Quantity.from(account.balance); + } + } + } else { + const numerOfAccounts = options.totalAccounts; + if (numerOfAccounts) { + accounts = Array(numerOfAccounts); + const hdPath = options.hdPath; + const hdKey = this.#hdKey; + + for (let index = 0; index < numerOfAccounts; index++) { + const acct = hdKey.derive(hdPath + index); + const address = uncompressedPublicKeyToAddress(acct.publicKey); + const privateKey = Data.from(acct.privateKey); + accounts[index] = Wallet.createAccount( + etherInWei, + privateKey, + address + ); + } + } else { + throw new Error( + "Cannot initialize chain: either options.accounts or options.total_accounts must be specified" + ); + } + } + return accounts; + }; + + public async encrypt(privateKey: Data, passphrase: string) { + const random = this.#randomBytes(32 + 16 + 16); + const salt = random.slice(0, 32); // first 32 bytes + const iv = random.slice(32, 32 + 16); // next 16 bytes + const uuid = random.slice(32 + 16); // last 16 bytes + + const derivedKey = await scrypt(passphrase, salt, SCRYPT_PARAMS.dklen, { + ...SCRYPT_PARAMS, + N: SCRYPT_PARAMS.n + }); + const cipher = crypto.createCipheriv(CIPHER, derivedKey.slice(0, 16), iv); + const ciphertext = Buffer.concat([ + cipher.update(privateKey.toBuffer()), + cipher.final() + ]); + const mac = createKeccakHash("keccak256") + .update(Buffer.concat([derivedKey.slice(16, 32), ciphertext])) + .digest(); + return { + crypto: { + cipher: CIPHER, + ciphertext: Data.from(ciphertext), + cipherparams: { + iv: Data.from(iv) + }, + kdf: "scrypt", + kdfParams: { + ...SCRYPT_PARAMS, + salt: Data.from(salt) + }, + mac: Data.from(mac) + }, + id: asUUID(uuid), + version: 3 + }; + } + + public async decrypt(keyfile: EncryptType, passphrase: crypto.BinaryLike) { + const crypt = keyfile.crypto; + + if (crypt.cipher !== CIPHER) { + throw new Error(`keyfile cypher must be "${CIPHER}"`); + } + if (crypt.kdf !== "scrypt") { + throw new Error(`keyfile kdf must be "script"`); + } + + const kdfParams = crypt.kdfParams; + const salt = kdfParams.salt; + const mac = crypt.mac; + const ciphertext = crypt.ciphertext.toBuffer(); + + let derivedKey: Buffer; + let localMac: Buffer; + if (passphrase != null) { + try { + derivedKey = await scrypt( + passphrase, + salt.toBuffer(), + kdfParams.dklen, + { ...kdfParams, N: kdfParams.n } + ); + localMac = createKeccakHash("keccak256") + .update(Buffer.concat([derivedKey.slice(16, 32), ciphertext])) + .digest(); + } catch { + localMac = null; + } + } + + if (!localMac || !mac.toBuffer().equals(localMac)) { + throw new Error("could not decrypt key with given password"); + } + + const decipher = crypto.createDecipheriv( + crypt.cipher, + derivedKey.slice(0, 16), + crypt.cipherparams.iv.toBuffer() + ); + const plaintext = decipher.update(ciphertext); + return plaintext; + } + + public static createAccount( + balance: Quantity, + privateKey: Data, + address: Address + ) { + const account = new Account(address); + account.privateKey = privateKey; + account.balance = balance; + return account; + } + + public static createAccountFromPrivateKey(privateKey: Data) { + const address = Address.from(privateToAddress(privateKey.toBuffer())); + const account = new Account(address); + account.privateKey = privateKey; + return account; + } + + public createRandomAccount(startingSeed: string) { + // create some seeded deterministic psuedo-randomness based on the chain's + // initial starting conditions (`startingSeed`) + const seed = Buffer.concat([ + Buffer.from(startingSeed), + this.#randomBytes(64) + ]); + const acct = HDKey.fromMasterSeed(seed); + const address = uncompressedPublicKeyToAddress(acct.publicKey); + const privateKey = Data.from(acct.privateKey); + return Wallet.createAccount(utils.RPCQUANTITY_ZERO, privateKey, address); + } + + public async unlockAccount( + lowerAddress: string, + passphrase: string, + duration: number + ) { + const encryptedKeyFile = this.encryptedKeyFiles.get(lowerAddress); + if (encryptedKeyFile == null) { + return false; + } + const secretKey = await this.decrypt(encryptedKeyFile, passphrase); + + const existingTimer = this.lockTimers.get(lowerAddress); + if (existingTimer) { + clearTimeout(existingTimer); + } + + // a duration <= 0 will remain unlocked + const durationMs = (duration * 1000) | 0; + if (durationMs > 0) { + const timeout = setTimeout(this.#lockAccount, durationMs, lowerAddress); + utils.unref(timeout); + this.lockTimers.set(lowerAddress, timeout as any); + } + + this.unlockedAccounts.set(lowerAddress, Data.from(secretKey)); + return true; + } + + public async unlockUnknownAccount(lowerAddress: string, duration: number) { + if (this.unlockedAccounts.has(lowerAddress)) { + // already unlocked, return `false` since we didn't do anything + return false; + } + + // if we "know" about this account, it cannot be unlocked this way + if (this.knownAccounts.has(lowerAddress)) { + throw new Error("cannot unlock known/personal account"); + } + + // a duration <= 0 will remain unlocked + const durationMs = (duration * 1000) | 0; + if (durationMs > 0) { + const timeout = setTimeout(this.#lockAccount, durationMs, lowerAddress); + utils.unref(timeout); + this.lockTimers.set(lowerAddress, timeout as any); + } + + // otherwise, unlock it! + this.unlockedAccounts.set(lowerAddress, null); + return true; + } + + #lockAccount = (lowerAddress: string) => { + this.lockTimers.delete(lowerAddress); + this.unlockedAccounts.delete(lowerAddress); + return true; + }; + + public lockAccount(lowerAddress: string) { + if (!this.unlockedAccounts.has(lowerAddress)) return false; + + clearTimeout(this.lockTimers.get(lowerAddress)); + return this.#lockAccount(lowerAddress); + } +} diff --git a/src/chains/ethereum/tests/@types/solc/index.d.ts b/src/chains/ethereum/tests/@types/solc/index.d.ts new file mode 100644 index 0000000000..6b94b3df2c --- /dev/null +++ b/src/chains/ethereum/tests/@types/solc/index.d.ts @@ -0,0 +1,158 @@ +declare module "solc" { + export type Primitive = + | "bool" + | "string" + | "address" + | "uint8" + | "uint16" + | "uint32" + | "uint64" + | "uint128" + | "uint256" + | "int8" + | "int16" + | "int32" + | "int64" + | "int128" + | "int256" + | "bytes" + | "bytes20" + | "bytes32" + | "bool[]" + | "string[]" + | "address[]" + | "uint8[]" + | "uint16[]" + | "uint32[]" + | "uint64[]" + | "uint128[]" + | "uint256[]" + | "int8[]" + | "int16[]" + | "int32[]" + | "int64[]" + | "int128[]" + | "int256[]" + | "bytes[]" + | "bytes20[]" + | "bytes32[]"; + + export interface AbiParameter { + name: string; + type: Primitive; + } + + export interface AbiEventParameter extends AbiParameter { + indexed: boolean; + } + + export interface AbiFunction { + name: string; + type: "function" | "constructor" | "fallback"; + stateMutability: "pure" | "view" | "payable" | "nonpayable"; + constant: boolean; + payable: boolean; + inputs: Array; + outputs: Array; + } + + export interface AbiEvent { + name: string; + type: "event"; + inputs: Array; + anonymous: boolean; + } + + export type Abi = Array; + + interface CompilerInputSourceFile { + keccak256?: string; + urls: string[]; + } + interface CompilerInputSourceCode { + keccak256?: string; + content: string; + } + interface CompilerInput { + language: "Solidity" | "serpent" | "lll" | "assembly"; + settings?: any; + sources: { + [globalName: string]: CompilerInputSourceFile | CompilerInputSourceCode; + }; + } + interface CompilerOutputError { + sourceLocation?: { + file: string; + start: number; + end: number; + }; + type: "TypeError" | "InternalCompilerError" | "Exception"; + component: "general" | "ewasm"; + severity: "error" | "warning"; + message: string; + formattedMessage?: string; + } + interface CompilerOutputEvmBytecode { + object?: string; + opcodes?: string; + sourceMap?: string; + linkReferences?: + | {} + | { + [globalName: string]: { + [name: string]: { start: number; length: number }[]; + }; + }; + } + interface CompilerOutputSources { + [globalName: string]: { + id: number; + ast?: any; + legacyAST?: any; + }; + } + interface CompilerOutputContracts { + [globalName: string]: { + [contractName: string]: { + abi?: Abi; + metadata?: string; + userdoc?: any; + devdoc?: any; + ir?: string; + evm?: { + assembly?: string; + legacyAssembly?: any; + bytecode: CompilerOutputEvmBytecode; + deployedBytecode?: CompilerOutputEvmBytecode; + methodIdentifiers?: { + [methodName: string]: string; + }; + gasEstimates?: { + creation: { + codeDepositCost: string; + executionCost: string; + totalCost: string; + }; + external: { + [functionSignature: string]: string; + }; + internal: { + [functionSignature: string]: string; + }; + }; + }; + ewasm: { + wast?: string; + wasm?: string; + }; + }; + }; + } + interface CompilerOutput { + errors: CompilerOutputError[]; + sources: CompilerOutputSources; + contracts: CompilerOutputContracts; + } + type ReadCallback = (path: string) => { contents?: string; error?: string }; + function compile(input: string): string; +} diff --git a/src/chains/ethereum/tests/api/bzz/bzz.test.ts b/src/chains/ethereum/tests/api/bzz/bzz.test.ts new file mode 100644 index 0000000000..156264e3bc --- /dev/null +++ b/src/chains/ethereum/tests/api/bzz/bzz.test.ts @@ -0,0 +1,28 @@ +import getProvider from "../../helpers/getProvider"; +import assert from "assert"; +import EthereumProvider from "../../../src/provider"; + +describe("api", () => { + describe("bzz", () => { + let provider: EthereumProvider; + before(async function () { + // GitHub Actions' windows-2019 Node v14 environment can sometimes take a + // VERY long time to run this `before`, as it is CURRENTLY the first + // @ganache/ethereum test that mocha runs (alphabetically)... and for some + // reason it is slow. + this.timeout(10000); + + provider = await getProvider(); + }); + + it("bzz_hive stub returns value", async () => { + const result = await provider.send("bzz_hive"); + assert.deepStrictEqual(result, []); + }); + + it("bzz_info stub returns value", async () => { + const result = await provider.send("bzz_info"); + assert.deepStrictEqual(result, []); + }); + }); +}); diff --git a/src/chains/ethereum/tests/api/db/db.test.ts b/src/chains/ethereum/tests/api/db/db.test.ts new file mode 100644 index 0000000000..26a0489706 --- /dev/null +++ b/src/chains/ethereum/tests/api/db/db.test.ts @@ -0,0 +1,32 @@ +import getProvider from "../../helpers/getProvider"; +import assert from "assert"; +import EthereumProvider from "../../../src/provider"; + +describe("api", () => { + describe("db", () => { + let provider: EthereumProvider; + beforeEach(async () => { + provider = await getProvider(); + }); + + it("db_putString", async () => { + const result = await provider.send("db_putString", ["", "", ""]); + assert.deepStrictEqual(result, false); + }); + + it("db_getString", async () => { + const result = await provider.send("db_getString", ["", ""]); + assert.deepStrictEqual(result, ""); + }); + + it("db_putHex", async () => { + const result = await provider.send("db_putHex", ["", "", ""]); + assert.deepStrictEqual(result, false); + }); + + it("db_getHex", async () => { + const result = await provider.send("db_getHex", ["", ""]); + assert.deepStrictEqual(result, "0x00"); + }); + }); +}); diff --git a/src/chains/ethereum/tests/api/eth/contracts/GetCode.sol b/src/chains/ethereum/tests/api/eth/contracts/GetCode.sol new file mode 100644 index 0000000000..49edc1c3cb --- /dev/null +++ b/src/chains/ethereum/tests/api/eth/contracts/GetCode.sol @@ -0,0 +1,4 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.7.4; + +contract GetCode {} diff --git a/src/chains/ethereum/tests/api/eth/contracts/GetStorageAt.sol b/src/chains/ethereum/tests/api/eth/contracts/GetStorageAt.sol new file mode 100644 index 0000000000..2d27d6a107 --- /dev/null +++ b/src/chains/ethereum/tests/api/eth/contracts/GetStorageAt.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.7.4; + +contract GetStorageAt { + uint256 public intValue1 = 123; + address public self = address(0x01); +} diff --git a/src/chains/ethereum/tests/api/eth/contracts/Logs.sol b/src/chains/ethereum/tests/api/eth/contracts/Logs.sol new file mode 100644 index 0000000000..b1aa9045fb --- /dev/null +++ b/src/chains/ethereum/tests/api/eth/contracts/Logs.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.7.4; + +contract Logs { + event Event(uint256 indexed first, uint256 indexed second); + + constructor() { + emit Event(1, 2); + } + + function logNTimes(uint8 n) public { + for (uint8 i = 0; i < n; i++) { + emit Event(i, i); + } + } +} diff --git a/src/chains/ethereum/tests/api/eth/contracts/Reverts.sol b/src/chains/ethereum/tests/api/eth/contracts/Reverts.sol new file mode 100644 index 0000000000..14879bbf76 --- /dev/null +++ b/src/chains/ethereum/tests/api/eth/contracts/Reverts.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.7.4; + +contract Reverts { + function invalidRevertReason() public pure { + assembly { + // revert reason code + mstore(0x80, 0x0000000000000000000000000000000000000000000000000000000008c379a0) + // invalid data + mstore(0xA0, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc0) + // trigger revert, returning the mstore values set above + revert( + 0x9C, /* mem start */ + 0x24 /* mem length */ + ) + } + } +} diff --git a/src/chains/ethereum/tests/api/eth/eth.test.ts b/src/chains/ethereum/tests/api/eth/eth.test.ts new file mode 100644 index 0000000000..5de4379d33 --- /dev/null +++ b/src/chains/ethereum/tests/api/eth/eth.test.ts @@ -0,0 +1,497 @@ +import assert from "assert"; +import EthereumProvider from "../../../src/provider"; +import getProvider from "../../helpers/getProvider"; + +function hex(length: number) { + return `0x${Buffer.allocUnsafe(length).fill(0).toString("hex")}`; +} + +describe("api", () => { + describe("eth", () => { + let provider: EthereumProvider; + let accounts: string[]; + + beforeEach(async () => { + provider = await getProvider(); + accounts = await provider.send("eth_accounts"); + }); + + describe("eth_coinbase", () => { + it("should return correct address", async () => { + const coinbase = await provider.send("eth_coinbase"); + assert.strictEqual(coinbase, "0x" + "0".repeat(40)); + }); + }); + + describe("eth_mining", () => { + it("should return true", async () => { + const result = await provider.send("eth_mining"); + assert.strictEqual(result, true); + }); + }); + + describe("eth_syncing", () => { + it("should return true", async () => { + const result = await provider.send("eth_syncing"); + assert.strictEqual(result, false); + }); + }); + + describe("eth_hashrate", () => { + it("should return hashrate of zero", async () => { + const result = await provider.send("eth_hashrate"); + assert.deepStrictEqual(result, "0x0"); + }); + }); + + describe("eth_protocolVersion", () => { + it("should get ethereum version", async () => { + const result = await provider.send("eth_protocolVersion"); + assert.strictEqual(result, "0x3f", "Network Version should be 63"); + }); + }); + + describe("eth_getCompilers", () => { + it("should get compilers list", async () => { + const result = await provider.send("eth_getCompilers"); + assert.deepStrictEqual(result, []); + }); + }); + + describe("eth_submitWork", () => { + it("should get compilers list", async () => { + const result = await provider.send("eth_submitWork", [ + hex(8), + hex(32), + hex(32) + ]); + assert.deepStrictEqual(result, false); + }); + }); + + describe("eth_getWork", () => { + it("should get compilers list", async () => { + const result = await provider.send("eth_getWork", ["0x0"]); + assert.deepStrictEqual(result, []); + }); + }); + + describe("eth_submitHashrate", () => { + it("should return the status of eth_submitHashrate", async () => { + const result = await provider.send("eth_submitHashrate", [ + hex(32), + hex(32) + ]); + assert.deepStrictEqual(result, false); + }); + }); + + describe("eth_chainId", () => { + it("should return the default chain id", async () => { + const result = await provider.send("eth_chainId"); + assert.deepStrictEqual(result, "0x539"); + }); + + it("should use the default chain id when signing transactions", async () => { + await provider.send("eth_subscribe", ["newHeads"]); + const txHash = await provider.send("eth_sendTransaction", [ + { from: accounts[0], to: accounts[0] } + ]); + await provider.once("message"); + const tx = await provider.send("eth_getTransactionByHash", [txHash]); + assert.strictEqual(tx.v, "0xa95"); + }); + + it("chainid option should change the chain id", async () => { + const provider = await getProvider({ chain: { chainId: 1234 } }); + const result = await provider.send("eth_chainId"); + assert.deepStrictEqual(result, "0x4d2"); + }); + }); + + describe("eth_getBalance", () => { + it("should return initial balance", async () => { + const balance = await provider.send("eth_getBalance", [accounts[0]]); + assert.strictEqual(balance, "0x56bc75e2d63100000"); + }); + + it("should return 0 for non-existent account", async () => { + const balance = await provider.send("eth_getBalance", [ + "0x1234567890123456789012345678901234567890" + ]); + assert.strictEqual(balance, "0x0"); + }); + }); + + describe("eth_getTransactionCount", () => { + it("should get the tranasction count of the block", async () => { + const tx = { + from: accounts[0], + to: accounts[1], + value: 1 + }; + + const txCount1 = await provider.send("eth_getTransactionCount", [ + accounts[0] + ]); + assert.strictEqual(txCount1, "0x0"); + const initialBlockNumber = await provider.send("eth_blockNumber"); + const txCount2 = await provider.send("eth_getTransactionCount", [ + accounts[0], + initialBlockNumber + ]); + assert.strictEqual(txCount2, "0x0"); + + await provider.send("eth_subscribe", ["newHeads"]); + + // send one tx, then check the count + await provider.send("miner_stop"); + await provider.send("eth_sendTransaction", [{ ...tx }]); + await provider.send("miner_start"); + const message1 = await provider.once("message"); + + const txCount3 = await provider.send("eth_getTransactionCount", [ + accounts[0], + message1.data.result.number + ]); + assert.strictEqual(txCount3, "0x1"); + + // send two txs, then check the count + await provider.send("miner_stop"); + await provider.send("eth_sendTransaction", [{ ...tx }]); + await provider.send("eth_sendTransaction", [{ ...tx }]); + await provider.send("miner_start"); + const message2 = await provider.once("message"); + + const txCount4 = await provider.send("eth_getTransactionCount", [ + accounts[0], + message2.data.result.number + ]); + assert.strictEqual(txCount4, "0x3"); + + // the check the count at different block numbers... + + const txCount5 = await provider.send("eth_getTransactionCount", [ + accounts[0], + message1.data.result.number + ]); + assert.strictEqual(txCount5, txCount3); + + const txCount6 = await provider.send("eth_getTransactionCount", [ + accounts[0], + initialBlockNumber + ]); + assert.strictEqual(txCount6, "0x0"); + + const txCount7 = await provider.send("eth_getTransactionCount", [ + accounts[0] + ]); + assert.strictEqual(txCount7, txCount4); + + const txCount8 = await provider.send("eth_getTransactionCount", [ + accounts[0], + "earliest" + ]); + assert.strictEqual(txCount8, "0x0"); + + const txCount9 = await provider.send("eth_getTransactionCount", [ + accounts[0], + "latest" + ]); + assert.strictEqual(txCount9, txCount4); + }); + }); + + describe("eth_blockNumber", () => { + it("should return initial block number of zero", async () => { + const blockNumber = await provider.send("eth_blockNumber"); + assert.strictEqual(parseInt(blockNumber, 10), 0); + }); + + it("should increment the block number after a transaction", async () => { + const tx = { + from: accounts[0], + to: accounts[1], + value: 1 + }; + + const startingBlockNumber = parseInt( + await provider.send("eth_blockNumber") + ); + await provider.send("eth_subscribe", ["newHeads"]); + await provider.send("eth_sendTransaction", [{ ...tx }]); + await provider.once("message"); + const blockx1 = await provider.send("eth_blockNumber"); + assert.strictEqual( + +blockx1, + startingBlockNumber + 1, + "first tx's block number not as expected" + ); + + const awaitFor = count => + new Promise(resolve => { + let counter = 0; + const off = provider.on("message", (_block: any) => { + counter++; + if (counter === count) { + off(); + resolve(void 0); + } + }); + }); + + let wait = awaitFor(4); + await Promise.all([ + provider.send("eth_sendTransaction", [{ ...tx }]), + provider.send("eth_sendTransaction", [{ ...tx }]), + provider.send("eth_sendTransaction", [{ ...tx }]), + provider.send("eth_sendTransaction", [{ ...tx }]) + ]); + + await Promise.all([ + provider.send("eth_sendTransaction", [{ ...tx }]), + provider.send("eth_sendTransaction", [{ ...tx }]), + provider.send("eth_sendTransaction", [{ ...tx }]), + provider.send("eth_sendTransaction", [{ ...tx }]) + ]); + await wait; + wait = awaitFor(4); + const blockx5 = await provider.send("eth_blockNumber"); + assert.strictEqual( + +blockx5, + startingBlockNumber + 5, + "second block's number not as expected" + ); + await wait; + const blockx9 = await provider.send("eth_blockNumber"); + assert.strictEqual( + +blockx9, + startingBlockNumber + 9, + "third block's number not as expected" + ); + }); + }); + + it("eth_getBlockByNumber", async () => { + const _subscriptionId = await provider.send("eth_subscribe", [ + "newHeads" + ]); + await provider.send("eth_sendTransaction", [ + { + from: accounts[0], + to: accounts[1], + value: 1 + } + ]); + const _message = await provider.once("message"); + const blocks = await Promise.all([ + provider.send("eth_getBlockByNumber", ["0x1", true]), + provider.send("eth_getBlockByNumber", ["0x1"]) + ]); + assert(blocks[0].hash, blocks[1].hash); + }); + + it("eth_getBlockByHash", async () => { + const _subscriptionId = await provider.send("eth_subscribe", [ + "newHeads" + ]); + await provider.send("eth_sendTransaction", [ + { + from: accounts[0], + to: accounts[1], + value: 1 + } + ]); + const _message = await provider.once("message"); + const block = await provider.send("eth_getBlockByNumber", ["0x1"]); + + const blocks = await Promise.all([ + provider.send("eth_getBlockByHash", [block.hash, true]), + provider.send("eth_getBlockByHash", [block.hash]) + ]); + assert(blocks[0].hash, blocks[1].hash); + const counts = await Promise.all([ + provider.send("eth_getBlockTransactionCountByNumber", ["0x1"]), + provider.send("eth_getBlockTransactionCountByHash", [blocks[0].hash]) + ]); + + assert(true); + }); + + it("eth_getBlockTransactionCountByHash", async () => { + const _subscriptionId = await provider.send("eth_subscribe", [ + "newHeads" + ]); + await provider.send("eth_sendTransaction", [ + { + from: accounts[0], + to: accounts[1], + value: 1 + } + ]); + const _message = await provider.once("message"); + const block = await provider.send("eth_getBlockByNumber", ["0x1"]); + + const count = await provider.send("eth_getBlockTransactionCountByHash", [ + block.hash + ]); + assert(count, "1"); + }); + + it("eth_getBlockTransactionCountByNumber", async () => { + await provider.send("eth_subscribe", ["newHeads"]); + await provider.send("eth_sendTransaction", [ + { + from: accounts[0], + to: accounts[1], + value: 1 + } + ]); + await provider.once("message"); + + const count = await provider.send( + "eth_getBlockTransactionCountByNumber", + ["0x1"] + ); + assert.strictEqual(count, "0x1"); + }); + + it("eth_sendTransaction bad data (tiny gas limit)", async () => { + await provider + .send("eth_sendTransaction", [ + { + from: accounts[0], + to: accounts[1], + gas: "0x01" + } + ]) + .catch(e => { + assert.strictEqual(e.code, -32000); + assert.strictEqual(e.message, "intrinsic gas too low"); + }); + }); + + it("eth_sendTransaction bad data (huge gas limit)", async () => { + await provider + .send("eth_sendTransaction", [ + { + from: accounts[0], + to: accounts[1], + gas: "0xfffffffff" + } + ]) + .catch(e => { + assert.strictEqual(e.code, -32000); + assert.strictEqual(e.message, "exceeds block gas limit"); + }); + }); + + it("handles block gas limit errors, callback style", done => { + provider.send( + { + jsonrpc: "2.0", + id: "1", + method: "eth_sendTransaction", + params: [ + { + from: accounts[0], + to: accounts[1], + gas: "0xfffffffff" // generates an "exceeds block gas limit" error + } + ] + }, + (e, r) => { + assert.strictEqual(e.message, "exceeds block gas limit"); + assert.strictEqual((r as any).error.code, -32000); + assert.strictEqual((r as any).error.message, e.message); + done(); + } + ); + }); + + it("eth_getTransactionByBlockNumberAndIndex", async () => { + await provider.send("eth_subscribe", ["newHeads"]); + const txHash = await provider.send("eth_sendTransaction", [ + { + from: accounts[0], + to: accounts[1], + value: 1 + } + ]); + await provider.once("message"); + + const tx = await provider.send( + "eth_getTransactionByBlockNumberAndIndex", + ["0x1", "0x0"] + ); + assert.strictEqual( + tx.hash, + "0xab338178ffd130f1b7724a687ef20afcc75d44020184f82127ab1bc59f17d7e2", + "Unexpected transaction hash." + ); + assert.strictEqual( + tx.hash, + txHash, + "eth_getTransactionByBlockNumberAndIndex transaction hash doesn't match tx hash" + ); + }); + + it("eth_getTransactionByBlockHashAndIndex", async () => { + await provider.send("eth_subscribe", ["newHeads"]); + const txHash = await provider.send("eth_sendTransaction", [ + { + from: accounts[0], + to: accounts[1], + value: 1 + } + ]); + const _message = await provider.once("message"); + const block = await provider.send("eth_getBlockByNumber", ["0x1"]); + + const tx = await provider.send("eth_getTransactionByBlockHashAndIndex", [ + block.hash, + "0x0" + ]); + assert.strictEqual( + tx.hash, + "0xab338178ffd130f1b7724a687ef20afcc75d44020184f82127ab1bc59f17d7e2", + "Unexpected transaction hash." + ); + assert.strictEqual( + tx.hash, + txHash, + "eth_getTransactionByBlockNumberAndIndex transaction hash doesn't match tx hash" + ); + }); + + it("eth_getTransactionReceipt", async () => { + await provider.send("eth_subscribe", ["newHeads"]); + const hash = await provider.send("eth_sendTransaction", [ + { + from: accounts[0], + to: accounts[1], + value: 1 + } + ]); + const _message = await provider.once("message"); + + const receipt = await provider.send("eth_getTransactionReceipt", [hash]); + assert(receipt.transactionIndex, "0x0"); + }); + + it("eth_getTransactionByHash", async () => { + await provider.send("eth_subscribe", ["newHeads"]); + const hash = await provider.send("eth_sendTransaction", [ + { + from: accounts[0], + to: accounts[1], + value: 1 + } + ]); + const _message = await provider.once("message"); + + const tx = await provider.send("eth_getTransactionByHash", [hash]); + assert(tx.transactionIndex, "0x0"); + }); + }); +}); diff --git a/src/chains/ethereum/tests/api/eth/getCode.test.ts b/src/chains/ethereum/tests/api/eth/getCode.test.ts new file mode 100644 index 0000000000..7e8fc14d96 --- /dev/null +++ b/src/chains/ethereum/tests/api/eth/getCode.test.ts @@ -0,0 +1,97 @@ +import assert from "assert"; +import EthereumProvider from "../../../src/provider"; +import getProvider from "../../helpers/getProvider"; +import compile from "../../helpers/compile"; +import { join } from "path"; +import { Quantity } from "@ganache/utils"; + +describe("api", () => { + describe("eth", () => { + describe("getCode", () => { + describe("null checks", () => { + let provider: EthereumProvider; + + before(async () => { + provider = await getProvider(); + }); + + after(async () => { + provider && (await provider.disconnect()); + }); + + it("should return 0x for null address", async () => { + const code = await provider.send("eth_getCode", [ + "0x0000000000000000000000000000000000000000" + ]); + assert.strictEqual(code, "0x"); + }); + + it("should return 0x for un-initialized address", async () => { + const code = await provider.send("eth_getCode", [ + "0xabcdefg012345678abcdefg012345678abcdefg0" + ]); + assert.strictEqual(code, "0x"); + }); + + it("should return 0x for existing non-contract address", async () => { + const accounts = await provider.send("eth_accounts"); + const code = await provider.send("eth_getCode", [accounts[0]]); + assert.strictEqual(code, "0x"); + }); + }); + + describe("code checks", () => { + let provider: EthereumProvider; + let accounts: string[]; + let contractAddress: string; + let blockNumber: Quantity; + let contract: ReturnType; + + before(async () => { + contract = compile(join(__dirname, "./contracts/GetCode.sol")); + provider = await getProvider(); + accounts = await provider.send("eth_accounts"); + await provider.send("eth_subscribe", ["newHeads"]); + const transactionHash = await provider.send("eth_sendTransaction", [ + { + from: accounts[0], + data: contract.code, + gas: 3141592 + } + ]); + await provider.once("message"); + const transactionReceipt = await provider.send( + "eth_getTransactionReceipt", + [transactionHash] + ); + contractAddress = transactionReceipt.contractAddress; + assert( + contractAddress !== null, + "Contract wasn't deployed as expected" + ); + + blockNumber = Quantity.from(transactionReceipt.blockNumber); + }); + + it("should return the code at the deployed block number", async () => { + const code = await provider.send("eth_getCode", [ + contractAddress, + blockNumber.toString() + ]); + assert.strictEqual( + code, + `0x${contract.contract.evm.deployedBytecode.object}` + ); + }); + + it("should return the no code at the previous block number", async () => { + const code = await provider.send("eth_getCode", [ + contractAddress, + Quantity.from(blockNumber.toBigInt() - 1n).toString() + ]); + assert.strictEqual(code, "0x"); + }); + }); + }); + }); +}); diff --git a/src/chains/ethereum/tests/api/eth/getStorageAt.test.ts b/src/chains/ethereum/tests/api/eth/getStorageAt.test.ts new file mode 100644 index 0000000000..c2afdd2522 --- /dev/null +++ b/src/chains/ethereum/tests/api/eth/getStorageAt.test.ts @@ -0,0 +1,94 @@ +import getProvider from "../../helpers/getProvider"; +import assert from "assert"; +import EthereumProvider from "../../../src/provider"; +import compile from "../../helpers/compile"; +import { join } from "path"; +import { Quantity } from "@ganache/utils"; +const THIRTY_TWO_BYES = "0".repeat(64); + +describe("api", () => { + describe("eth", () => { + describe("getStorageAt", () => { + let provider: EthereumProvider; + const contract = compile(join(__dirname, "./contracts/GetStorageAt.sol")); + let contractAddress: string; + let contractMethods: any; + + beforeEach(async () => { + provider = await getProvider({ + miner: { defaultTransactionGasLimit: 6721975 } + }); + const accounts = await provider.send("eth_accounts"); + const from = accounts[0]; + + await provider.send("eth_subscribe", ["newHeads"]); + + const transactionHash = await provider.send("eth_sendTransaction", [ + { + from, + data: contract.code + } + ]); + + await provider.once("message"); + + const receipt = await provider.send("eth_getTransactionReceipt", [ + transactionHash + ]); + + contractAddress = receipt.contractAddress; + contractMethods = contract.contract.evm.methodIdentifiers; + }); + + it("returns the value at the hex position", async () => { + const result = await provider.send("eth_getStorageAt", [ + contractAddress, + "0x0" + ]); + assert.strictEqual(BigInt(result), 123n); + const result2 = await provider.send("eth_getStorageAt", [ + contractAddress, + "0x1" + ]); + assert.strictEqual(result2, "0x01"); + }); + + it("returns the value at the 32-byte hex position", async () => { + const result = await provider.send("eth_getStorageAt", [ + contractAddress, + "0x" + THIRTY_TWO_BYES + ]); + assert.strictEqual(BigInt(result), 123n); + const result2 = await provider.send("eth_getStorageAt", [ + contractAddress, + "0x" + THIRTY_TWO_BYES.slice(-1) + "1" + ]); + assert.strictEqual(result2, "0x01"); + }); + + it("returns the value even when hex positions exceeds 32-bytes", async () => { + const thirtyThreeBytePosition = "0x1" + THIRTY_TWO_BYES; + const result = await provider.send("eth_getStorageAt", [ + contractAddress, + thirtyThreeBytePosition + ]); + assert.strictEqual(BigInt(result), 123n); + const thirtyThreeBytePosition2 = "0x" + THIRTY_TWO_BYES + "1"; + const result2 = await provider.send("eth_getStorageAt", [ + contractAddress, + thirtyThreeBytePosition2 + ]); + assert.strictEqual(result2, "0x01"); + }); + + it("rejects when the block doesn't exist", async () => { + await assert.rejects( + provider.send("eth_getStorageAt", [contractAddress, "0x0", "0x2"]), + { + message: "header not found" + } + ); + }); + }); + }); +}); diff --git a/src/chains/ethereum/tests/api/eth/legacyInstamining.test.ts b/src/chains/ethereum/tests/api/eth/legacyInstamining.test.ts new file mode 100644 index 0000000000..f7de64d197 --- /dev/null +++ b/src/chains/ethereum/tests/api/eth/legacyInstamining.test.ts @@ -0,0 +1,81 @@ +import getProvider from "../../helpers/getProvider"; +import assert from "assert"; + +describe("api", () => { + describe("eth", () => { + describe("legacy", () => { + it("when not in legacy mode, does not mine before returning the tx hash", async () => { + const provider = await getProvider({ + miner: { legacyInstamine: false } + }); + const accounts = await provider.send("eth_accounts"); + + const hash = await provider.send("eth_sendTransaction", [ + { + from: accounts[0], + to: accounts[1], + value: 1 + } + ]); + const receipt = await provider.send("eth_getTransactionReceipt", [ + hash + ]); + assert.strictEqual(receipt, null); + }); + + it("when in legacy mode, mines before returns in the tx hash", async () => { + const provider = await getProvider({ + miner: { legacyInstamine: true } + }); + const accounts = await provider.send("eth_accounts"); + + const hash = await provider.send("eth_sendTransaction", [ + { + from: accounts[0], + to: accounts[1], + value: 1 + } + ]); + const receipt = await provider.send("eth_getTransactionReceipt", [ + hash + ]); + assert.notStrictEqual(receipt, null); + }); + + it("handles transaction balance errors, callback style", done => { + getProvider({ + miner: { legacyInstamine: true }, + chain: { vmErrorsOnRPCResponse: true } + }).then(async provider => { + const accounts = await provider.send("eth_accounts"); + + provider.send( + { + jsonrpc: "2.0", + id: "1", + method: "eth_sendTransaction", + params: [ + { + from: accounts[0], + to: accounts[1], + value: "0x76bc75e2d63100000" + } + ] + }, + (e, r) => { + assert( + e.message.includes( + "sender doesn't have enough funds to send tx" + ) + ); + assert.strictEqual(e.message, (r as any).error.message); + assert.strictEqual((r as any).error.code, -32000); + assert.strictEqual(typeof (r as any).error.data.result, "string"); + done(); + } + ); + }); + }); + }); + }); +}); diff --git a/src/chains/ethereum/tests/api/eth/logs.test.ts b/src/chains/ethereum/tests/api/eth/logs.test.ts new file mode 100644 index 0000000000..45e91fc742 --- /dev/null +++ b/src/chains/ethereum/tests/api/eth/logs.test.ts @@ -0,0 +1,488 @@ +import getProvider from "../../helpers/getProvider"; +import assert from "assert"; +import EthereumProvider from "../../../src/provider"; +import compile from "../../helpers/compile"; +import { join } from "path"; +import { promises } from "fs-extra"; + +describe("api", () => { + describe("eth", () => { + describe("logs", () => { + let provider: EthereumProvider; + let contract: ReturnType; + let contractAddress: string; + let accounts: string[]; + + beforeEach(async () => { + contract = compile(join(__dirname, "./contracts/Logs.sol")); + provider = await getProvider(); + accounts = await provider.send("eth_accounts"); + + const subscriptionId = await provider.send("eth_subscribe", [ + "newHeads" + ]); + const transactionHash = await provider.send("eth_sendTransaction", [ + { + from: accounts[0], + data: contract.code, + gas: 3141592 + } + ]); + await provider.once("message"); + const transactionReceipt = await provider.send( + "eth_getTransactionReceipt", + [transactionHash] + ); + contractAddress = transactionReceipt.contractAddress; + await provider.send("eth_unsubscribe", [subscriptionId]); + }); + + describe("eth_subscribe", () => { + describe("logs", () => { + const onceMessageFor = subId => { + return new Promise(resolve => { + provider.on("message", message => { + if (message.data.subscription === subId) { + resolve(message); + } + }); + }); + }; + + it("subscribes and unsubscribes", async () => { + const subscriptionId = await provider.send("eth_subscribe", [ + "logs" + ]); + + assert(subscriptionId != null); + assert.notStrictEqual(subscriptionId, false); + + // subscribe again + const subscriptionId2 = await provider.send("eth_subscribe", [ + "logs" + ]); + + // trigger a log event, we should get two events + const numberOfLogs = 4; + const data = + "0x" + + contract.contract.evm.methodIdentifiers["logNTimes(uint8)"] + + numberOfLogs.toString().padStart(64, "0"); + const tx = { from: accounts[0], to: contractAddress, data }; + const subs = [ + onceMessageFor(subscriptionId), + onceMessageFor(subscriptionId2) + ]; + const txHash = await provider.send("eth_sendTransaction", [ + { ...tx } + ]); + + const [message1, message2] = await Promise.all(subs); + assert.deepStrictEqual(message1.data.result, message2.data.result); + + assert.strictEqual(message1.data.result.length, numberOfLogs); + + const unsubResult = await provider.send("eth_unsubscribe", [ + subscriptionId + ]); + assert.strictEqual(unsubResult, true); + await provider.send("eth_sendTransaction", [{ ...tx }]); + const message = await Promise.race([ + onceMessageFor(subscriptionId), + onceMessageFor(subscriptionId2) + ]); + assert.strictEqual( + message.data.subscription, + subscriptionId2, + "unsubscribe didn't work" + ); + }); + }); + }); + + describe("getLogs", () => { + it("should return a log for the constructor transaction", async () => { + const logs = await provider.send("eth_getLogs", [ + { address: contractAddress } + ]); + assert.strictEqual(logs.length, 1); + }); + + it("should return the logs", async () => { + await provider.send("eth_subscribe", ["newHeads"]); + const numberOfLogs = 4; + const data = + "0x" + + contract.contract.evm.methodIdentifiers["logNTimes(uint8)"] + + numberOfLogs.toString().padStart(64, "0"); + const txHash = await provider.send("eth_sendTransaction", [ + { + from: accounts[0], + to: contractAddress, + gas: 3141592, + data: data + } + ]); + await provider.once("message"); + const txReceipt = await provider.send("eth_getTransactionReceipt", [ + txHash + ]); + assert.deepStrictEqual(txReceipt.logs.length, numberOfLogs); + const logs = await provider.send("eth_getLogs", [ + { address: contractAddress } + ]); + assert.deepStrictEqual(logs, txReceipt.logs); + }); + + it("should filter out other blocks when using `latest`", async () => { + await provider.send("eth_subscribe", ["newHeads"]); + const numberOfLogs = 4; + const data = + "0x" + + contract.contract.evm.methodIdentifiers["logNTimes(uint8)"] + + numberOfLogs.toString().padStart(64, "0"); + await provider.send("eth_sendTransaction", [ + { + from: accounts[0], + to: contractAddress, + gas: 3141592, + data: data + } + ]); + await provider.once("message"); + await provider.send("evm_mine"); + await provider.once("message"); + const logs = await provider.send("eth_getLogs", [ + { address: contractAddress, toBlock: "latest", fromBlock: "latest" } + ]); + assert.strictEqual(logs.length, 0); + }); + + it("should filter appropriately when using fromBlock and toBlock", async () => { + const genesisBlockNumber = "0x0"; + const deployBlockNumber = "0x1"; + const emptyBlockNumber = "0x2"; + await provider.send("evm_mine"); // 0x2 + + await provider.send("eth_subscribe", ["newHeads"]); + const numberOfLogs = 4; + const data = + "0x" + + contract.contract.evm.methodIdentifiers["logNTimes(uint8)"] + + numberOfLogs.toString().padStart(64, "0"); + const txHash = await provider.send("eth_sendTransaction", [ + { + from: accounts[0], + to: contractAddress, + gas: 3141592, + data: data + } + ]); // 0x3 + await provider.once("message"); + const { + blockNumber + } = await provider.send("eth_getTransactionReceipt", [txHash]); + + async function testGetLogs( + fromBlock: string, + toBlock: string, + expected: number, + address: string = contractAddress + ) { + const logs = await provider.send("eth_getLogs", [ + { address, fromBlock, toBlock } + ]); + assert.strictEqual( + logs.length, + expected, + `there should be ${expected} log(s) between the ${fromBlock} block and the ${toBlock} block` + ); + } + + // tests ranges up to latest/blockNumber + await testGetLogs("earliest", "earliest", 0); + await testGetLogs(genesisBlockNumber, genesisBlockNumber, 0); + await testGetLogs("earliest", emptyBlockNumber, 1); + await testGetLogs(genesisBlockNumber, emptyBlockNumber, 1); + await testGetLogs("earliest", "latest", numberOfLogs + 1); + await testGetLogs("earliest", blockNumber, numberOfLogs + 1); + await testGetLogs(genesisBlockNumber, "latest", numberOfLogs + 1); + await testGetLogs(genesisBlockNumber, blockNumber, numberOfLogs + 1); + await testGetLogs(deployBlockNumber, "latest", numberOfLogs + 1); + await testGetLogs(deployBlockNumber, blockNumber, numberOfLogs + 1); + await testGetLogs(emptyBlockNumber, "latest", numberOfLogs); + await testGetLogs(emptyBlockNumber, blockNumber, numberOfLogs); + + // tests variations where latest === blockNumber + await testGetLogs(blockNumber, blockNumber, numberOfLogs); + await testGetLogs(blockNumber, "latest", numberOfLogs); + await testGetLogs("latest", blockNumber, numberOfLogs); + await testGetLogs("latest", "latest", numberOfLogs); + + // mine an extra block + await provider.send("evm_mine"); // 0x3 + const lastBlockNumber = `0x${(parseInt(blockNumber) + 1).toString( + 16 + )}`; + await provider.once("message"); + + // test variations of `earliest` and `0x0` + await testGetLogs(genesisBlockNumber, genesisBlockNumber, 0); + await testGetLogs("earliest", "earliest", 0); + await testGetLogs("earliest", genesisBlockNumber, 0); + await testGetLogs(genesisBlockNumber, "earliest", 0); + + // test misc ranges not already tested + await testGetLogs(genesisBlockNumber, deployBlockNumber, 1); + await testGetLogs("earliest", deployBlockNumber, 1); + await testGetLogs("earliest", "latest", numberOfLogs + 1); + await testGetLogs(genesisBlockNumber, "latest", numberOfLogs + 1); + await testGetLogs(deployBlockNumber, "latest", numberOfLogs + 1); + // test variations involving the last block number + await testGetLogs( + genesisBlockNumber, + lastBlockNumber, + numberOfLogs + 1 + ); + await testGetLogs( + deployBlockNumber, + lastBlockNumber, + numberOfLogs + 1 + ); + await testGetLogs(emptyBlockNumber, lastBlockNumber, numberOfLogs); + await testGetLogs(lastBlockNumber, "latest", 0); + await testGetLogs("latest", lastBlockNumber, 0); + }); + + it("should filter appropriately when using blockHash", async () => { + const genesisBlockNumber = "0x0"; + const deployBlockNumber = "0x1"; + const emptyBlockNumber = "0x2"; + await provider.send("evm_mine"); // 0x2 + + await provider.send("eth_subscribe", ["newHeads"]); + const numberOfLogs = 4; + const data = + "0x" + + contract.contract.evm.methodIdentifiers["logNTimes(uint8)"] + + numberOfLogs.toString().padStart(64, "0"); + const txHash = await provider.send("eth_sendTransaction", [ + { + from: accounts[0], + to: contractAddress, + gas: 3141592, + data: data + } + ]); // 0x3 + await provider.once("message"); + const { blockHash } = await provider.send( + "eth_getTransactionReceipt", + [txHash] + ); + + async function testGetLogs( + blockHash: string, + expected: number, + address: string = contractAddress + ) { + const logs = await provider.send("eth_getLogs", [ + { address, blockHash } + ]); + assert.strictEqual( + logs.length, + expected, + `there should be ${expected} log(s) at the ${blockHash} block` + ); + } + + // tests blockHash + let { + hash: genesisBlockHash + } = await provider.send("eth_getBlockByNumber", [genesisBlockNumber]); + await testGetLogs(blockHash, 4); + await testGetLogs(genesisBlockHash, 0); + let { + hash: deployBlockHash + } = await provider.send("eth_getBlockByNumber", [deployBlockNumber]); + await testGetLogs(deployBlockHash, 1, null); + let { + hash: emptyBlockHash + } = await provider.send("eth_getBlockByNumber", [emptyBlockNumber]); + await testGetLogs(emptyBlockHash, 0); + const invalidBlockHash = "0x123456789"; + await testGetLogs(invalidBlockHash, 0); + + // mine an extra block + await provider.send("evm_mine"); + await provider.once("message"); + + // make sure we still get the right data + await testGetLogs(blockHash, 4); + }); + }); + + describe("eth_newBlockFilter", () => { + it("returns new blocks", async () => { + await provider.send("eth_subscribe", ["newHeads"]); + async function assertNoChanges() { + const noChanges = await provider.send("eth_getFilterChanges", [ + filterId + ]); + assert.strictEqual(noChanges.length, 0); + } + const filterId = await provider.send("eth_newBlockFilter"); + await assertNoChanges(); + await provider.send("evm_mine"); + await provider.once("message"); + const changes1 = await provider.send("eth_getFilterChanges", [ + filterId + ]); + let blockNum = await provider.send("eth_blockNumber"); + let { hash } = await provider.send("eth_getBlockByNumber", [ + blockNum + ]); + assert.strictEqual(changes1[0], hash); + await assertNoChanges(); + await provider.send("evm_mine"); + await provider.once("message"); + blockNum = await provider.send("eth_blockNumber"); + let { hash: hash2 } = await provider.send("eth_getBlockByNumber", [ + blockNum + ]); + await provider.send("evm_mine"); + await provider.once("message"); + blockNum = await provider.send("eth_blockNumber"); + let { hash: hash3 } = await provider.send("eth_getBlockByNumber", [ + blockNum + ]); + + const changes2 = await provider.send("eth_getFilterChanges", [ + filterId + ]); + assert.strictEqual(changes2[0], hash2); + assert.strictEqual(changes2[1], hash3); + await assertNoChanges(); + }); + }); + + describe("eth_newPendingTransactionFilter", () => { + it("returns new pending transactions", async () => { + await provider.send("eth_subscribe", ["newPendingTransactions"]); + async function assertNoChanges() { + const noChanges = await provider.send("eth_getFilterChanges", [ + filterId + ]); + assert.strictEqual(noChanges.length, 0); + } + const filterId = await provider.send( + "eth_newPendingTransactionFilter" + ); + const tx = { from: accounts[0], to: accounts[0] }; + await assertNoChanges(); + provider.send("eth_sendTransaction", [{ ...tx }]); + let hash = await provider.once("message"); + const changes1 = await provider.send("eth_getFilterChanges", [ + filterId + ]); + assert.strictEqual(changes1[0], hash.data.result); + await assertNoChanges(); + provider.send("eth_sendTransaction", [{ ...tx }]); + let hash2 = await provider.once("message"); + provider.send("eth_sendTransaction", [{ ...tx }]); + let hash3 = await provider.once("message"); + + const changes2 = await provider.send("eth_getFilterChanges", [ + filterId + ]); + assert.strictEqual(changes2[0], hash2.data.result); + assert.strictEqual(changes2[1], hash3.data.result); + await assertNoChanges(); + }); + }); + + describe("eth_newFilter", () => { + it("returns new logs", async () => { + await provider.send("eth_subscribe", ["newHeads"]); + async function assertNoChanges() { + const noChanges = await provider.send("eth_getFilterChanges", [ + filterId + ]); + assert.strictEqual(noChanges.length, 0); + } + const filterId = await provider.send("eth_newFilter"); + const numberOfLogs = 1; + const data = + "0x" + + contract.contract.evm.methodIdentifiers["logNTimes(uint8)"] + + numberOfLogs.toString().padStart(64, "0"); + const tx = { from: accounts[0], to: contractAddress, data }; + await assertNoChanges(); + provider.send("eth_sendTransaction", [{ ...tx }]); + let hash = await provider.once("message"); + const changes1 = await provider.send("eth_getFilterChanges", [ + filterId + ]); + assert.strictEqual(changes1.length, 1); + await assertNoChanges(); + provider.send("eth_sendTransaction", [{ ...tx }]); + let hash2 = await provider.once("message"); + provider.send("eth_sendTransaction", [{ ...tx }]); + let hash3 = await provider.once("message"); + + const changes2 = await provider.send("eth_getFilterChanges", [ + filterId + ]); + assert.strictEqual(changes2.length, 2); + await assertNoChanges(); + }); + }); + + describe("eth_newFilter", () => { + it("returns new logs", async () => { + await provider.send("eth_subscribe", ["newHeads"]); + async function assertNoChanges() { + const noChanges = await provider.send("eth_getFilterChanges", [ + filterId + ]); + assert.strictEqual(noChanges.length, 0); + } + const currentBlockNumber = + "0x" + + (parseInt(await provider.send("eth_blockNumber")) + 1).toString(16); + const filterId = await provider.send("eth_newFilter", [ + { fromBlock: currentBlockNumber, toBlock: "0x99" } + ]); + const numberOfLogs = 1; + const data = + "0x" + + contract.contract.evm.methodIdentifiers["logNTimes(uint8)"] + + numberOfLogs.toString().padStart(64, "0"); + const tx = { from: accounts[0], to: contractAddress, data }; + await assertNoChanges(); + provider.send("eth_sendTransaction", [{ ...tx }]); + let hash = await provider.once("message"); + const changes1 = await provider.send("eth_getFilterChanges", [ + filterId + ]); + assert.strictEqual(changes1.length, 1); + await assertNoChanges(); + provider.send("eth_sendTransaction", [{ ...tx }]); + let hash2 = await provider.once("message"); + provider.send("eth_sendTransaction", [{ ...tx }]); + let hash3 = await provider.once("message"); + + const changes2 = await provider.send("eth_getFilterChanges", [ + filterId + ]); + assert.strictEqual(changes2.length, 2); + await assertNoChanges(); + + const allChanges = await provider.send("eth_getFilterLogs", [ + filterId + ]); + assert.deepStrictEqual(allChanges, [...changes1, ...changes2]); + }); + }); + }); + }); +}); diff --git a/src/chains/ethereum/tests/api/eth/sendRawTransaction.test.ts b/src/chains/ethereum/tests/api/eth/sendRawTransaction.test.ts new file mode 100644 index 0000000000..ec60545620 --- /dev/null +++ b/src/chains/ethereum/tests/api/eth/sendRawTransaction.test.ts @@ -0,0 +1,61 @@ +import getProvider from "../../helpers/getProvider"; +import assert from "assert"; +import EthereumProvider from "../../../src/provider"; +import Transaction from "ethereumjs-tx/dist/transaction"; +import Common from "ethereumjs-common"; + +describe("api", () => { + describe("eth", () => { + describe("eth_sendRawTransaction*", () => { + let secretKey = + "0x4c3fc38239e503913706205746ef2dcc54a5ea9971988bfcac136b43e3190841"; + let provider: EthereumProvider; + let accounts: string[]; + const common = Common.forCustomChain( + "mainnet", + { + name: "ganache", + chainId: 1337, + comment: "Local test network", + bootstrapNodes: [] + }, + "petersburg" + ); + + beforeEach(async () => { + provider = await getProvider({ + wallet: { + mnemonic: "sweet treat", + accounts: [{ secretKey, balance: "0xffffff" }] + } + }); + accounts = await provider.send("eth_accounts"); + }); + + it("processes a signed transaction", async () => { + const transaction = new Transaction( + { + value: "0xff", + gasLimit: "0x33450", + to: accounts[0] + }, + { common } + ); + + const secretKeyBuffer = Buffer.from(secretKey.substr(2), "hex"); + transaction.sign(secretKeyBuffer); + + await provider.send("eth_subscribe", ["newHeads"]); + const txHash = await provider.send("eth_sendRawTransaction", [ + transaction.serialize() + ]); + await provider.once("message"); + + const receipt = await provider.send("eth_getTransactionReceipt", [ + txHash + ]); + assert.strictEqual(receipt.transactionHash, txHash); + }); + }); + }); +}); diff --git a/src/chains/ethereum/tests/api/eth/sendTransaction.test.ts b/src/chains/ethereum/tests/api/eth/sendTransaction.test.ts new file mode 100644 index 0000000000..50c518b3c6 --- /dev/null +++ b/src/chains/ethereum/tests/api/eth/sendTransaction.test.ts @@ -0,0 +1,103 @@ +import assert from "assert"; +import getProvider from "../../helpers/getProvider"; +import compile from "../../helpers/compile"; +import { join } from "path"; +import EthereumProvider from "../../../src/provider"; +import { EthereumProviderOptions } from "../../../src/options"; + +describe("api", () => { + describe("eth", () => { + describe("sendTransaction", () => { + describe("contracts", () => { + describe("revert", () => { + async function deployContract( + provider: EthereumProvider, + accounts: string[] + ) { + const contract = compile( + join(__dirname, "./contracts/Reverts.sol") + ); + + const from = accounts[0]; + + await provider.send("eth_subscribe", ["newHeads"]); + + const transactionHash = await provider.send("eth_sendTransaction", [ + { + from, + data: contract.code, + gas: 3141592 + } + ]); + + await provider.once("message"); + + const receipt = await provider.send("eth_getTransactionReceipt", [ + transactionHash + ]); + assert.strictEqual(receipt.blockNumber, "0x1"); + + const contractAddress = receipt.contractAddress; + return { + contract, + contractAddress + }; + } + + it("doesn't crash on badly encoded revert string", async () => { + async function test(opts: EthereumProviderOptions) { + const provider = await getProvider(opts); + const accounts = await provider.send("eth_accounts"); + const { contract, contractAddress } = await deployContract( + provider, + accounts + ); + const contractMethods = contract.contract.evm.methodIdentifiers; + const prom = provider.send("eth_call", [ + { + from: accounts[0], + to: contractAddress, + data: "0x" + contractMethods["invalidRevertReason()"] + } + ]); + + const revertString = + "0x08c379a0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc0"; + if (opts.chain.vmErrorsOnRPCResponse) { + const result = await prom.catch(e => e); + assert.strictEqual( + result.code, + -32000, + "Error code should be -32000" + ); + assert.strictEqual( + result.data.reason, + null, + "The reason is undecodable, and thus should be null" + ); + assert.strictEqual( + result.data.message, + "revert", + "The message should not have a reason string included" + ); + assert.strictEqual( + result.data.result, + revertString, + "The revert reason should be encoded as hex" + ); + } else { + assert.strictEqual( + await prom, + revertString, + "The revert reason should be encoded as hex" + ); + } + } + await test({ chain: { vmErrorsOnRPCResponse: false } }); + await test({ chain: { vmErrorsOnRPCResponse: true } }); + }); + }); + }); + }); + }); +}); diff --git a/src/chains/ethereum/tests/api/eth/sign.test.ts b/src/chains/ethereum/tests/api/eth/sign.test.ts new file mode 100644 index 0000000000..be8db14351 --- /dev/null +++ b/src/chains/ethereum/tests/api/eth/sign.test.ts @@ -0,0 +1,91 @@ +import assert from "assert"; +import { + ecrecover, + pubToAddress, + fromSigned, + hashPersonalMessage +} from "ethereumjs-util"; +import getProvider from "../../helpers/getProvider"; +import { Data } from "@ganache/utils"; + +describe("api", () => { + describe("eth", () => { + describe("sign", () => { + let accounts; + let provider; + + // Load account. + before(async () => { + // This account produces an edge case signature when it signs the hex-encoded buffer: + // '0x07091653daf94aafce9acf09e22dbde1ddf77f740f9844ac1f0ab790334f0627'. (See Issue #190) + const acc = { + balance: "0x0", + secretKey: + "0xe6d66f02cd45a13982b99a5abf3deab1f67cf7be9fee62f0a072cb70896342e4" + }; + provider = await getProvider({ + wallet: { + accounts: [acc] + } + }); + accounts = await provider.send("eth_accounts"); + }); + + it("should produce a signature whose signer can be recovered", async () => { + const msg = Buffer.from("0xffffffffff"); + const msgHash = hashPersonalMessage(msg); + + const address = accounts[0]; + let sgn = await provider.send("eth_sign", [ + address, + Data.from(msg).toString() + ]); + sgn = sgn.slice(2); + + const r = Buffer.from(sgn.slice(0, 64), "hex"); + const s = Buffer.from(sgn.slice(64, 128), "hex"); + const v = parseInt(sgn.slice(128), 16); + const pub = ecrecover( + msgHash, + v, + r, + s, + provider.getOptions().chain.chainId + ); + const addr = fromSigned(pubToAddress(pub)); + const strAddr = "0x" + addr.toString("hex"); + assert.strictEqual(strAddr, accounts[0].toLowerCase()); + }); + + it("should work if ecsign produces 'r' or 's' components that start with 0", async () => { + // This message produces a zero prefixed 'r' component when signed by + // ecsign w/ the account set in this test's 'before' block. + const msgHex = + "0x07091653daf94aafce9acf09e22dbde1ddf77f740f9844ac1f0ab790334f0627"; + const edgeCaseMsg = Data.from(msgHex).toBuffer(); + const msgHash = hashPersonalMessage(edgeCaseMsg); + + let sgn = await provider.send("eth_sign", [accounts[0], msgHex]); + sgn = sgn.slice(2); + + const r = Buffer.from(sgn.slice(0, 64), "hex"); + const s = Buffer.from(sgn.slice(64, 128), "hex"); + const v = parseInt(sgn.slice(128), 16); + const pub = ecrecover( + msgHash, + v, + r, + s, + provider.getOptions().chain.chainId + ); + const addr = fromSigned(pubToAddress(pub)); + const strAddr = "0x" + addr.toString("hex"); + assert.deepStrictEqual(strAddr, accounts[0].toLowerCase()); + }); + + after("shutdown", async () => { + provider && (await provider.disconnect()); + }); + }); + }); +}); diff --git a/src/chains/ethereum/tests/api/eth/signTypedData.test.ts b/src/chains/ethereum/tests/api/eth/signTypedData.test.ts new file mode 100644 index 0000000000..8463e12088 --- /dev/null +++ b/src/chains/ethereum/tests/api/eth/signTypedData.test.ts @@ -0,0 +1,138 @@ +import assert from "assert"; +const createKeccakHash = require("keccak"); +import getProvider from "../../helpers/getProvider"; + +describe("api", () => { + describe("eth", () => { + describe("signTypedData", () => { + let accounts; + let provider; + + // Load account. + before(async () => { + // Account based on https://github.com/ethereum/EIPs/blob/master/assets/eip-712/Example.js + const acc = { + balance: "0x0", + secretKey: createKeccakHash("keccak256").update("cow").digest() + }; + provider = await getProvider({ + wallet: { + accounts: [acc] + } + }); + accounts = await provider.send("eth_accounts"); + }); + + it("should produce a signature whose signer can be recovered", async () => { + const typedData = { + types: { + EIP712Domain: [ + { name: "name", type: "string" }, + { name: "version", type: "string" }, + { name: "chainId", type: "uint256" }, + { name: "verifyingContract", type: "address" } + ], + Person: [ + { name: "name", type: "string" }, + { name: "wallet", type: "address" } + ], + Mail: [ + { name: "from", type: "Person" }, + { name: "to", type: "Person" }, + { name: "contents", type: "string" } + ] + }, + primaryType: "Mail", + domain: { + name: "Ether Mail", + version: "1", + chainId: 1, + verifyingContract: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + message: { + from: { + name: "Cow", + wallet: "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" + }, + to: { + name: "Bob", + wallet: "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" + }, + contents: "Hello, Bob!" + } + }; + + const result = await provider.send("eth_signTypedData", [ + accounts[0], + typedData + ]); + assert.strictEqual( + result, + "0x4355c47d63924e8a72e509b65029052eb6c299d53a04e167c5775fd466751c9d07299936d304c153f6443dfa05f40ff007d72911b6f72307f996231605b915621c" + ); + }); + + it("should produce a signature whose signer can be recovered (for arrays)", async () => { + const typedData = { + types: { + EIP712Domain: [ + { name: "name", type: "string" }, + { name: "version", type: "string" }, + { name: "chainId", type: "uint256" }, + { name: "verifyingContract", type: "address" } + ], + Person: [ + { name: "name", type: "string" }, + { name: "wallets", type: "address[]" } + ], + Mail: [ + { name: "from", type: "Person" }, + { name: "to", type: "Person[]" }, + { name: "contents", type: "string" } + ], + Group: [ + { name: "name", type: "string" }, + { name: "members", type: "Person[]" } + ] + }, + domain: { + name: "Ether Mail", + version: "1", + chainId: 1, + verifyingContract: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + primaryType: "Mail", + message: { + from: { + name: "Cow", + wallets: [ + "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", + "0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF" + ] + }, + to: [ + { + name: "Bob", + wallets: [ + "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB", + "0xB0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57", + "0xB0B0b0b0b0b0B000000000000000000000000000" + ] + } + ], + contents: "Hello, Bob!" + } + }; + + const result = await provider.send("eth_signTypedData", [ + accounts[0], + typedData + ]); + assert.strictEqual( + result, + "0x65cbd956f2fae28a601bebc9b906cea0191744bd4c4247bcd27cd08f8eb6b71c78efdf7a31dc9abee78f492292721f362d296cf86b4538e07b51303b67f749061b" + ); + }); + }); + }); +}); diff --git a/src/chains/ethereum/tests/api/eth/subscribe.test.ts b/src/chains/ethereum/tests/api/eth/subscribe.test.ts new file mode 100644 index 0000000000..1a326693da --- /dev/null +++ b/src/chains/ethereum/tests/api/eth/subscribe.test.ts @@ -0,0 +1,201 @@ +import getProvider from "../../helpers/getProvider"; +import assert from "assert"; +import EthereumProvider from "../../../src/provider"; +import { Quantity } from "@ganache/utils"; + +describe("api", () => { + describe("eth", () => { + describe("eth_subscribe*", () => { + let provider: EthereumProvider; + let accounts: string[]; + const gasLimit = "0x6691b7"; + const time = new Date("2019/03/15 GMT"); + + beforeEach(async () => { + provider = await getProvider({ + chain: { + time + }, + miner: { + blockGasLimit: gasLimit + }, + wallet: { + mnemonic: "sweet treat" + } + }); + accounts = await provider.send("eth_accounts"); + }); + + describe("newHeads", () => { + it("subscribes and unsubscribes", async () => { + const timestamp = ((+time / 1000) | 0) + 1; + const startingBlockNumber = parseInt( + await provider.send("eth_blockNumber") + ); + const subscriptionId = await provider.send("eth_subscribe", [ + "newHeads" + ]); + + assert(subscriptionId != null); + assert.notStrictEqual(subscriptionId, false); + + // subscribe again + const subscriptionId2 = await provider.send("eth_subscribe", [ + "newHeads" + ]); + + // trigger a mine, we should get two events + await provider.send("evm_mine", [timestamp]); + let counter = 0; + + const message = await new Promise(resolve => { + let firstMessage; + provider.on("message", (message: any) => { + counter++; + if (counter === 1) { + firstMessage = message; + } + if (counter === 2) { + assert.deepStrictEqual( + firstMessage.data.result, + message.data.result + ); + resolve(firstMessage); + } + }); + }); + + assert.deepStrictEqual(message, { + type: "eth_subscription", + data: { + result: { + difficulty: "0x0", + extraData: "0x", + gasLimit: gasLimit, + gasUsed: "0x0", + hash: + "0xf821422e084d82d550019e555b656b9113c9af45c4c03fad670caaa9b5d8acde", + logsBloom: `0x${"0".repeat(512)}`, + miner: `0x${"0".repeat(40)}`, + mixHash: `0x${"0".repeat(64)}`, + nonce: "0x0000000000000000", + number: Quantity.from(startingBlockNumber + 1).toString(), + parentHash: + "0x746144f35cfbcc1bb8ea1dcd540b674c81cc25ffec8fa1ec42d444cba9678cc2", + receiptsRoot: + "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + sha3Uncles: + "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + stateRoot: + "0x8281cb204e0242d2d9178e392b60eaf4563ae5ffc4897c9c6cf6e99a4d35aff3", + timestamp: Quantity.from(timestamp).toString(), + transactionsRoot: + "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" + }, + subscription: subscriptionId + } + }); + + // trigger a mine... we should only get a _single_ message this time + const unsubResult = await provider.send("eth_unsubscribe", [ + subscriptionId + ]); + assert.strictEqual(unsubResult, true); + await provider.send("evm_mine", [timestamp]); + await assert.doesNotReject( + new Promise((resolve, reject) => { + provider.on("message", async (message: any) => { + if (subscriptionId2 === message.data.subscription) { + const blockNumber = parseInt( + await provider.send("eth_blockNumber") + ); + assert.strictEqual(blockNumber, startingBlockNumber + 2); + + resolve(void 0); + } else { + reject(new Error("Unsubscribe didn't work!")); + } + }); + }) + ); + }); + }); + + describe("newPendingTransactions", () => { + it("subscribes and unsubscribes", async () => { + const subscriptionId = await provider.send("eth_subscribe", [ + "newPendingTransactions" + ]); + + assert(subscriptionId != null); + assert.notStrictEqual(subscriptionId, false); + + // subscribe again + const subscriptionId2 = await provider.send("eth_subscribe", [ + "newPendingTransactions" + ]); + + let messagePromise = new Promise(resolve => { + let firstMessage; + provider.on("message", (message: any) => { + counter++; + if (counter === 1) { + firstMessage = message; + } + if (counter === 2) { + assert.deepStrictEqual( + firstMessage.data.result, + message.data.result + ); + resolve(firstMessage); + } + }); + }); + + // trigger a pendingTransaction, we should get two events + const tx = { from: accounts[0], to: accounts[0] }; + const txHash = await provider.send("eth_sendTransaction", [ + { ...tx } + ]); + let counter = 0; + + const message = await messagePromise; + + assert.deepStrictEqual(message, { + type: "eth_subscription", + data: { + result: txHash, + subscription: subscriptionId + } + }); + + // trigger a mine... we should only get a _single_ message this time + const unsubResult = await provider.send("eth_unsubscribe", [ + subscriptionId + ]); + assert.strictEqual(unsubResult, true); + messagePromise = new Promise((resolve, reject) => { + provider.on("message", async (message: any) => { + if (subscriptionId2 === message.data.subscription) { + assert.strictEqual(message.data.result, txHash2); + + resolve(message.data.result); + } else { + reject(new Error("Unsubscribe didn't work!")); + } + }); + }); + const txHash2 = await provider.send("eth_sendTransaction", [ + { ...tx } + ]); + await assert.doesNotReject(messagePromise); + }); + }); + + it("returns false for unsubscribe with bad id", async () => { + const unsubResult = await provider.send("eth_unsubscribe", ["0xffff"]); + assert.strictEqual(unsubResult, false); + }); + }); + }); +}); diff --git a/src/chains/ethereum/tests/api/eth/uncles.test.ts b/src/chains/ethereum/tests/api/eth/uncles.test.ts new file mode 100644 index 0000000000..5ee221542a --- /dev/null +++ b/src/chains/ethereum/tests/api/eth/uncles.test.ts @@ -0,0 +1,89 @@ +import getProvider from "../../helpers/getProvider"; +import assert from "assert"; +import EthereumProvider from "../../../src/provider"; + +describe("api", () => { + describe("eth", () => { + describe("eth_getUncle*", () => { + let provider: EthereumProvider; + let accounts: string[]; + + beforeEach(async () => { + provider = await getProvider(); + accounts = await provider.send("eth_accounts"); + }); + + it("eth_getUncleCountByBlockHash", async () => { + await provider.send("eth_subscribe", ["newHeads"]); + await provider.send("eth_sendTransaction", [ + { + from: accounts[0], + to: accounts[1], + value: 1 + } + ]); + await provider.once("message"); + const block = await provider.send("eth_getBlockByNumber", ["0x1"]); + + const count = await provider.send("eth_getUncleCountByBlockHash", [ + block.hash + ]); + assert.strictEqual(count, "0x0"); + }); + + it("eth_getUncleCountByBlockNumber", async () => { + await provider.send("eth_subscribe", ["newHeads"]); + await provider.send("eth_sendTransaction", [ + { + from: accounts[0], + to: accounts[1], + value: 1 + } + ]); + await provider.once("message"); + + const count = await provider.send("eth_getUncleCountByBlockNumber", [ + "0x1" + ]); + assert.strictEqual(count, "0x0"); + }); + + it("eth_getUncleByBlockHashAndIndex", async () => { + await provider.send("eth_subscribe", ["newHeads"]); + await provider.send("eth_sendTransaction", [ + { + from: accounts[0], + to: accounts[1], + value: 1 + } + ]); + await provider.once("message"); + const block = await provider.send("eth_getBlockByNumber", ["0x1"]); + + const result = await provider.send("eth_getUncleByBlockHashAndIndex", [ + block.hash, + "0x0" + ]); + assert.deepStrictEqual(result, null); + }); + + it("eth_getUncleByBlockNumberAndIndex", async () => { + await provider.send("eth_subscribe", ["newHeads"]); + await provider.send("eth_sendTransaction", [ + { + from: accounts[0], + to: accounts[1], + value: 1 + } + ]); + await provider.once("message"); + + const result = await provider.send( + "eth_getUncleByBlockNumberAndIndex", + ["0x1", "0x0"] + ); + assert.deepStrictEqual(result, null); + }); + }); + }); +}); diff --git a/src/chains/ethereum/tests/api/evm/evm.test.ts b/src/chains/ethereum/tests/api/evm/evm.test.ts new file mode 100644 index 0000000000..0b824229ee --- /dev/null +++ b/src/chains/ethereum/tests/api/evm/evm.test.ts @@ -0,0 +1,245 @@ +import getProvider from "../../helpers/getProvider"; +import assert from "assert"; +import { Quantity } from "@ganache/utils"; +import EthereumProvider from "../../../src/provider"; + +function between(x: number, min: number, max: number) { + return x >= min && x <= max; +} + +describe("api", () => { + describe("evm", () => { + describe("evm_setTime", () => { + it("should set the time correctly when difference is greater than 2**31", async () => { + // this test is here to prevent a dev from "optimizing" rounding to use + // bitwise tricks since those won't work on numbers greater than 2**31. + + const provider = await getProvider(); + const bin32 = 2 ** 31; + const now = Date.now(); + // fast forward time by bin32, plus 2 seconds, in case testing is slow + const newTime = bin32 + now + 2; + + const timeAdjustment = await provider.send("evm_setTime", [newTime]); + + // it should return `newTime - now`, floored to the nearest second + const baseLineOffset = Math.floor((newTime - now) / 1000); + assert(between(timeAdjustment, baseLineOffset - 2, baseLineOffset + 2)); + }); + + it("should set the time correctly when given as a hex string", async () => { + const provider = await getProvider(); + const now = Date.now(); + // fast forward time by 10 seconds (plus 2 seconds in case testing is slow) + const newTime = now + 10000 + 2000; + + const timeAdjustment = await provider.send("evm_setTime", [ + `0x${newTime.toString(16)}` + ]); + + // it should return `newTime - now`, floored to the nearest second + const baseLineOffset = Math.floor((newTime - now) / 1000); + assert(between(timeAdjustment, baseLineOffset - 2, baseLineOffset + 2)); + }); + + it("should set the time correctly when given as a Date", async () => { + const provider = await getProvider(); + const now = Date.now(); + // fast forward time by 10 seconds (plus 2 seconds in case testing is slow), then create a new Date object + const newTime = new Date(now + 10000 + 2000); + + const timeAdjustment = await provider.send("evm_setTime", [newTime]); + + // it should return `newTime.getTime() - now`, floored to the nearest second + const baseLineOffset = Math.floor((newTime.getTime() - now) / 1000); + assert(between(timeAdjustment, baseLineOffset - 2, baseLineOffset + 2)); + }); + }); + + describe("evm_increaseTime", () => { + it("should return the `timeAdjustment` value via `evm_increaseTime` when provided as a number", async () => { + const provider = await getProvider(); + const seconds = 10; + const timeAdjustment = await provider.send("evm_increaseTime", [ + seconds + ]); + assert.strictEqual(timeAdjustment, seconds); + }); + + it("should return the `timeAdjustment` value via `evm_increaseTime` when provided as hex string", async () => { + const provider = await getProvider(); + const seconds = 10; + const timeAdjustment = await provider.send("evm_increaseTime", [ + `0x${seconds.toString(16)}` + ]); + assert.strictEqual(timeAdjustment, seconds); + }); + }); + + describe("evm_mine", () => { + it("should mine a block on demand", async () => { + const provider = await getProvider(); + const initialBlock = parseInt(await provider.send("eth_blockNumber")); + await provider.send("evm_mine"); + const currentBlock = parseInt(await provider.send("eth_blockNumber")); + assert.strictEqual(currentBlock, initialBlock + 1); + }); + + it("should mine a block on demand at the specified timestamp", async () => { + const startDate = new Date(2019, 3, 15); + const miningTimestamp = Math.floor( + new Date(2020, 3, 15).getTime() / 1000 + ); + const provider = await getProvider({ chain: { time: startDate } }); + await provider.send("evm_mine", [miningTimestamp]); + const currentBlock = await provider.send("eth_getBlockByNumber", [ + "latest" + ]); + assert.strictEqual(parseInt(currentBlock.timestamp), miningTimestamp); + }); + + it("should mine a block even when mining is stopped", async () => { + const provider = await getProvider(); + const initialBlock = parseInt(await provider.send("eth_blockNumber")); + await provider.send("miner_stop"); + await provider.send("evm_mine"); + const currentBlock = parseInt(await provider.send("eth_blockNumber")); + assert.strictEqual(currentBlock, initialBlock + 1); + }); + + it("should mine a block when in interval mode", async () => { + const provider = await getProvider({ miner: { blockTime: 1000 } }); + const initialBlock = parseInt(await provider.send("eth_blockNumber")); + await provider.send("evm_mine"); + const currentBlock = parseInt(await provider.send("eth_blockNumber")); + assert.strictEqual(currentBlock, initialBlock + 1); + }); + + it("should mine a block when in interval mode even when mining is stopped", async () => { + const provider = await getProvider({ miner: { blockTime: 1000 } }); + const initialBlock = parseInt(await provider.send("eth_blockNumber")); + await provider.send("miner_stop"); + await provider.send("evm_mine"); + const currentBlock = parseInt(await provider.send("eth_blockNumber")); + assert.strictEqual(currentBlock, initialBlock + 1); + }); + }); + + describe("evm_setAccountNonce", () => { + it("should set the nonce forward", async () => { + const provider = await getProvider(); + const [account] = await provider.send("eth_accounts"); + const newCount = Quantity.from(1000); + const initialCount = parseInt( + await provider.send("eth_getTransactionCount", [account]) + ); + assert.strictEqual(initialCount, 0); + const status = await provider.send("evm_setAccountNonce", [ + account, + newCount.toString() + ]); + assert.strictEqual(status, true); + const afterCount = parseInt( + await provider.send("eth_getTransactionCount", [account]) + ); + assert.strictEqual(afterCount, newCount.toNumber()); + }); + }); + + describe("evm_lockUnknownAccount/evm_unlockUnknownAccount", () => { + let accounts: string[], provider: EthereumProvider; + before(async () => { + provider = await getProvider(); + accounts = await provider.send("eth_accounts"); + }); + + it("should unlock any account after server has been started", async () => { + const address = "0x742d35Cc6634C0532925a3b844Bc454e4438f44e"; + const result1 = await provider.send("evm_unlockUnknownAccount", [ + address + ]); + assert.strictEqual(result1, true); + + // should return `false` if account was already locked + const result2 = await provider.send("evm_unlockUnknownAccount", [ + address + ]); + assert.strictEqual(result2, false); + }); + + it("should not unlock any locked personal account", async () => { + const [address] = accounts; + await provider.send("personal_lockAccount", [address]); + try { + await assert.rejects( + provider.send("evm_unlockUnknownAccount", [ + { + message: "cannot unlock known/personal account" + } + ]) + ); + } finally { + // unlock the account + await provider.send("personal_unlockAccount", [address, "", 0]); + } + }); + + it("should lock any unlocked unknown account via evm_lockUnknownAccount", async () => { + const address = "0x842d35Cc6634C0532925a3b844Bc454e4438f44f"; + const unlockResult = await provider.send("evm_unlockUnknownAccount", [ + address + ]); + assert.strictEqual(unlockResult, true); + + const lockResult1 = await provider.send("evm_lockUnknownAccount", [ + address + ]); + assert.strictEqual(lockResult1, true); + + // bonus: also make sure we return false when the account is already locked: + const lockResult2 = await provider.send("evm_lockUnknownAccount", [ + address + ]); + assert.strictEqual(lockResult2, false); + }); + + it("should not lock a known account via evm_lockUnknownAccount", async () => { + await assert.rejects( + provider.send("evm_lockUnknownAccount", [accounts[0]]), + { + message: "cannot lock known/personal account" + } + ); + }); + + it("should not lock a personal account via evm_lockUnknownAccount", async () => { + // create a new personal account + const address = await provider.send("personal_newAccount", [ + "password" + ]); + + // then explicitly unlock it + const result = await provider.send("personal_unlockAccount", [ + address, + "password", + 0 + ]); + assert.strictEqual(result, true); + + // then try to lock it via evm_lockUnknownAccount + await assert.rejects( + provider.send("evm_lockUnknownAccount", [address]), + { + message: "cannot lock known/personal account" + } + ); + }); + + it("should return `false` upon lock if account isn't locked (unknown account)", async () => { + const address = "0x942d35Cc6634C0532925a3b844Bc454e4438f450"; + const result = await provider.send("evm_lockUnknownAccount", [address]); + assert.strictEqual(result, false); + }); + }); + }); +}); diff --git a/src/chains/ethereum/tests/api/evm/snapshot.sol b/src/chains/ethereum/tests/api/evm/snapshot.sol new file mode 100644 index 0000000000..158cf5414d --- /dev/null +++ b/src/chains/ethereum/tests/api/evm/snapshot.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.7.4; + +contract snapshot { + uint256 public n = 42; + + function inc() public { + n += 1; + } +} diff --git a/src/chains/ethereum/tests/api/evm/snapshot.test.ts b/src/chains/ethereum/tests/api/evm/snapshot.test.ts new file mode 100644 index 0000000000..5d72ba744b --- /dev/null +++ b/src/chains/ethereum/tests/api/evm/snapshot.test.ts @@ -0,0 +1,561 @@ +import assert from "assert"; +import { join } from "path"; +import getProvider from "../../helpers/getProvider"; +import compile from "../../helpers/compile"; + +const eth = "0x" + 1000000000000000000n.toString(16); + +describe("api", () => { + describe("evm", () => { + describe("snapshot / revert", () => { + let context = {} as any; + let startingBalance; + let snapshotId; + + beforeEach("Set up provider and deploy a contract", async () => { + const contract = compile(join(__dirname, "./snapshot.sol")); + + const p = await getProvider({ + miner: { defaultTransactionGasLimit: 6721975 } + }); + const accounts = await p.send("eth_accounts"); + const from = accounts[3]; + + await p.send("eth_subscribe", ["newHeads"]); + + const transactionHash = await p.send("eth_sendTransaction", [ + { + from, + data: contract.code + } + ]); + + await p.once("message"); + + const receipt = await p.send("eth_getTransactionReceipt", [ + transactionHash + ]); + assert.strictEqual(receipt.blockNumber, "0x1"); + + const to = receipt.contractAddress; + const methods = contract.contract.evm.methodIdentifiers; + + context.send = p.send.bind(p); + context.accounts = accounts; + context.provider = p; + context.instance = { + n: () => { + const tx = { + to, + data: "0x" + methods["n()"] + }; + return p.send("eth_call", [tx]); + }, + inc: async (tx: any) => { + tx.from ||= accounts[0]; + tx.to = to; + tx.data = "0x" + methods["inc()"]; + const hash = await p.send("eth_sendTransaction", [tx]); + await p.once("message"); + return await p.send("eth_getTransactionByHash", [hash]); + } + }; + }); + + beforeEach("send a transaction then make a checkpoint", async () => { + const { accounts, send, provider } = context; + + await send("eth_sendTransaction", [ + { + from: accounts[0], + to: accounts[1], + value: eth, + gas: 90000 + } + ]); + + await provider.once("message"); + + // Since transactions happen immediately, we can assert the balance. + let balance = await send("eth_getBalance", [accounts[0]]); + balance = BigInt(balance); + + // Assert the starting balance is where we think it is, including tx costs. + assert( + balance > 98900000000000000000 && balance < 99000000000000000000 + ); + startingBalance = balance; + + // Now checkpoint. + snapshotId = await send("evm_snapshot"); + }); + + it("rolls back successfully", async () => { + const { accounts, send, provider } = context; + + // Send another transaction, check the balance, then roll it back to the old one and check the balance again. + const transactionHash = await send("eth_sendTransaction", [ + { + from: accounts[0], + to: accounts[1], + value: eth, + gas: 90000 + } + ]); + + await provider.once("message"); + + let balance = await send("eth_getBalance", [accounts[0]]); + balance = BigInt(balance); + + // Assert the starting balance is where we think it is, including tx costs. + assert( + balance > 97900000000000000000n && balance < 98000000000000000000n + ); + + const status = await send("evm_revert", [snapshotId]); + + assert(status, "Snapshot should have returned true"); + + let revertedBalance = await send("eth_getBalance", [accounts[0]]); + revertedBalance = BigInt(revertedBalance); + + assert( + revertedBalance === startingBalance, + "Should have reverted back to the starting balance" + ); + + const oldReceipt = await send("eth_getTransactionReceipt", [ + transactionHash + ]); + assert.strictEqual( + oldReceipt, + null, + "Receipt should be null as it should have been removed" + ); + }); + + it("returns false when reverting a snapshot that doesn't exist", async () => { + const { send } = context; + + const snapShotId1 = await send("evm_snapshot"); + const snapShotId2 = await send("evm_snapshot"); + const response1 = await send("evm_revert", [snapShotId1]); + assert.strictEqual( + response1, + true, + "Reverting a snapshot that exists does not work" + ); + const response2 = await send("evm_revert", [snapShotId2]); + assert.strictEqual( + response2, + false, + "Reverting a snapshot that no longer exists does not work" + ); + const response3 = await send("evm_revert", [snapShotId1]); + assert.strictEqual( + response3, + false, + "Reverting a snapshot that hasn't already been reverted does not work" + ); + const response4 = await send("evm_revert", [999]); + assert.strictEqual( + response4, + false, + "Reverting a snapshot that has never existed does not work" + ); + }); + + it("checkpoints and reverts without persisting contract storage", async () => { + const { accounts, instance, send } = context; + + const snapShotId = await send("evm_snapshot"); + const n1 = await instance.n(); + assert.strictEqual(parseInt(n1), 42, "Initial n is not 42"); + + await instance.inc({ from: accounts[0] }); + const n2 = await instance.n(); + assert.strictEqual( + parseInt(n2), + 43, + "n is not 43 after first call to `inc`" + ); + + await send("evm_revert", [snapShotId]); + const n3 = await instance.n(); + assert.strictEqual( + parseInt(n3), + 42, + "n is not 42 after reverting snapshot" + ); + + // this is the real test. what happened was that the vm's contract storage + // trie cache wasn't cleared when the vm's stateManager cache was cleared. + await instance.inc({ from: accounts[0] }); + const n4 = await instance.n(); + assert.strictEqual( + parseInt(n4), + 43, + "n is not 43 after calling `inc` again" + ); + }); + + it("evm_revert rejects invalid subscriptionId types without crashing", async () => { + const { send } = context; + const ids = [{ foo: "bar" }, true, false, 0.5, Infinity, -Infinity]; + await Promise.all( + ids.map(id => + assert.rejects( + send("evm_revert", [id]), + /Cannot wrap a .+? as a json-rpc type/, + "evm_revert did not reject as expected" + ) + ) + ); + }); + + it("evm_revert rejects null/undefined subscriptionId values", async () => { + const { send } = context; + const ids = [null, undefined]; + await Promise.all( + ids.map(id => + assert.rejects( + send("evm_revert", [id]), + /invalid snapshotId/, + "evm_revert did not reject as expected" + ) + ) + ); + }); + + it("evm_revert returns false for out-of-range subscriptionId values", async () => { + const { send } = context; + const ids = [-1, Buffer.from([0])]; + const promises = ids.map(id => + send("evm_revert", [id]).then(result => + assert.strictEqual(result, false) + ) + ); + await Promise.all(promises); + }); + + it("removes transactions that are already in processing at the start of evm_revert", async () => { + const { + send, + accounts: [from, to] + } = context; + + const snapShotId = await send("evm_snapshot"); + + // increment value for each transaction so the hashes always differ + let value = 1; + + // send some transactions + const inFlightTxs = [ + send("eth_sendTransaction", [{ from, to, value: value++ }]), + send("eth_sendTransaction", [{ from, to, value: value++ }]) + ]; + // wait for the tx hashes to be returned; this is confirmation that + // they've been accepted by the transaction pool. + const txHashes = await Promise.all(inFlightTxs); + + const getReceipt = (hash: string) => + send("eth_getTransactionReceipt", [hash]); + const getTx = (hash: string) => + send("eth_getTransactionByHash", [hash]); + + const receiptsProm = Promise.all(txHashes.map(getReceipt)); + const transactionsProm = Promise.all(txHashes.map(getTx)); + const [receipts, transactions] = await Promise.all([ + receiptsProm, + transactionsProm + ]); + + // sometimes ganache is REALLY fast and mines and saves the first tx + // before we even get here (which is fine). As long as we have tx that + // is still pending (meaning its receipt is `null`) the test is still + // testing what we want it to. + assert.strictEqual( + receipts.some(r => r === null), + true, + "At least 1 receipt should be null" + ); + + // and that the transations were all accepted + transactions.forEach(transaction => { + assert.notStrictEqual( + transaction, + null, + "Transaction should not be null" + ); + }); + + // revert while these transactions are being mined + await send("evm_revert", [snapShotId]); + + const finalReceiptsProm = Promise.all(txHashes.map(getReceipt)); + const finalTransactionsProm = Promise.all(txHashes.map(getTx)); + const [finalReceipts, finalTransactions] = await Promise.all([ + finalReceiptsProm, + finalTransactionsProm + ]); + + // verify that we don't have any receipts + finalReceipts.forEach(receipt => { + assert.strictEqual(receipt, null, "Receipt should be null"); + }); + + // and we don't have any transactions + finalTransactions.forEach(transaction => { + assert.strictEqual(transaction, null, "Transaction should be null"); + }); + }); + + it("removes transactions that are in pending transactions at the start of evm_revert", async () => { + const { + provider, + send, + accounts: [from, to] + } = context; + + const snapShotId = await send("evm_snapshot"); + + // increment value for each transaction so the hashes always differ + let value = 1; + + // send some transactions + const accountNonce = parseInt( + await send("eth_getTransactionCount", [from]), + 16 + ); + const inFlightTxs = [ + send("eth_sendTransaction", [ + { from, to, value: value++, nonce: accountNonce + 1 } + ]), + send("eth_sendTransaction", [ + { from, to, value: value++, nonce: accountNonce + 2 } + ]) + ]; + // wait for the tx hashes to be returned; this is confirmation that + // they've been accepted by the transaction pool. + const txHashes = await Promise.all(inFlightTxs); + + const getReceipt = (hash: string) => + send("eth_getTransactionReceipt", [hash]); + const getTx = (hash: string) => + send("eth_getTransactionByHash", [hash]); + + const transactions = await Promise.all(txHashes.map(getTx)); + + // and that the transations were all accepted + transactions.forEach(transaction => { + assert.notStrictEqual( + transaction, + null, + "Transaction should not be null" + ); + }); + + // revert while these transactions are pending + await send("evm_revert", [snapShotId]); + + // mine a transaction to fill in the nonce gap (this would normally cause the pending transactions to be mined) + await send("eth_sendTransaction", [ + { from, to, value: value++, nonce: accountNonce } + ]); + await provider.once("message"); + + // and mine one more block just to force the any transactions to be immediately mined + await send("evm_mine"); + + const finalReceiptsProm = Promise.all(txHashes.map(getReceipt)); + const finalTransactionsProm = Promise.all(txHashes.map(getTx)); + const [finalReceipts, finalTransactions] = await Promise.all([ + finalReceiptsProm, + finalTransactionsProm + ]); + + // verify that we don't have any receipts + finalReceipts.forEach(receipt => { + assert.strictEqual(receipt, null, "Receipt should be null"); + }); + + // and we don't have any transactions + finalTransactions.forEach(transaction => { + assert.strictEqual(transaction, null, "Transaction should be null"); + }); + }); + + it("doesn't revert transactions that were added *after* the start of evm_revert", async () => { + const { + provider, + send, + accounts: [from, to] + } = context; + const accountNonce = parseInt( + await send("eth_getTransactionCount", [from]), + 16 + ); + const snapShotId = await send("evm_snapshot"); + + // increment value for each transaction so the hashes always differ + let value = 1; + + // send a transaction so we have something to revert + const revertedTx = await send("eth_sendTransaction", [ + { from, to, value: value++ } + ]); + await provider.once("message"); + + // revert while these transactions are being mined + const revertPromise = send("evm_revert", [snapShotId]); + + const txsMinedProm = new Promise(resolve => { + let count = 0; + const unsub = provider.on("message", m => { + if (++count === 2) { + unsub(); + resolve(null); + } + }); + }); + + // send some transactions + const inFlightTxs = [ + send("eth_sendTransaction", [{ from, to, value: value++ }]), + send("eth_sendTransaction", [{ from, to, value: value++ }]) + ]; + + // these two transactions have nonces that are too high to be executed immediately + const laterTxs = [ + send("eth_sendTransaction", [ + { from, to, value: value++, nonce: accountNonce + 3 } + ]), + send("eth_sendTransaction", [ + { from, to, value: value++, nonce: accountNonce + 3 } + ]) + ]; + + // wait for the tx hashes to be returned; this is confirmation that + // they've been accepted by the transaction pool. + const txHashPromises = Promise.all(inFlightTxs); + + const getReceipt = (hash: string) => + send("eth_getTransactionReceipt", [hash]); + const getTx = (hash: string) => + send("eth_getTransactionByHash", [hash]); + + // wait for the revert to finish up + const result = await Promise.race([revertPromise, txHashPromises]); + assert.strictEqual( + result, + true, + "evm_revert should finish before the transaction hashes are returned" + ); + + // wait for the inFlightTxs to be mined + await txsMinedProm; + const txHashes = await txHashPromises; + const laterHashes = await Promise.all(laterTxs); + + // and mine one more block just to force the any executable transactions + // to be immediately mined + + const gotTxsProm = new Promise(resolve => { + let count = 0; + const unsub = provider.on("message", m => { + if (++count === 3) { + unsub(); + resolve(null); + } + }); + }); + + await send("evm_mine"); + + const finalReceiptsProm = Promise.all(txHashes.map(getReceipt)); + const finalTransactionsProm = Promise.all(txHashes.map(getTx)); + const [finalReceipts, finalTransactions] = await Promise.all([ + finalReceiptsProm, + finalTransactionsProm + ]); + + // verify that we do have the receipts + finalReceipts.forEach(receipt => { + assert.notStrictEqual(receipt, null, "Receipt should not be null"); + }); + + // and we do have the transactions + finalTransactions.forEach(transaction => { + assert.notStrictEqual( + transaction, + null, + "Transaction should not be null" + ); + }); + + const laterTxsReceiptsProm = Promise.all(laterHashes.map(getReceipt)); + const laterTxsTransactionsProm = Promise.all(laterHashes.map(getTx)); + const [laterTxsReceipts, laterTxsTransactions] = await Promise.all([ + laterTxsReceiptsProm, + laterTxsTransactionsProm + ]); + + // verify that we do NOT have the receipts + laterTxsReceipts.forEach(receipt => { + assert.strictEqual(receipt, null, "Receipt should be null"); + }); + + // and we DO have the transactions + laterTxsTransactions.forEach(transaction => { + assert.notStrictEqual( + transaction, + null, + "Transaction should not be null" + ); + }); + + // send one more transaction to fill in the gap + send("eth_sendTransaction", [{ from, to, value: value++ }]); + await gotTxsProm; + + const finalLaterTxsReceiptsProm = Promise.all(txHashes.map(getReceipt)); + const finalLaterTxsTransactionsProm = Promise.all(txHashes.map(getTx)); + const [ + finalLaterTxsReceipts, + finalLaterTxsTransactions + ] = await Promise.all([ + finalLaterTxsReceiptsProm, + finalLaterTxsTransactionsProm + ]); + + // verify that we do have the receipts + finalLaterTxsReceipts.forEach(receipt => { + assert.notStrictEqual(receipt, null, "Receipt should not be null"); + }); + + // and we do have the transactions + finalLaterTxsTransactions.forEach(transaction => { + assert.notStrictEqual( + transaction, + null, + "Transaction should not be null" + ); + }); + + const revertedTxReceipt = await getReceipt(revertedTx); + const revertedTxTransactions = await getTx(revertedTx); + assert.strictEqual( + revertedTxReceipt, + null, + "First transaction should not have a receipt" + ); + assert.strictEqual( + revertedTxTransactions, + null, + "First transaction should not have a tx" + ); + }); + }); + }); +}); diff --git a/src/chains/ethereum/tests/api/miner/miner.test.ts b/src/chains/ethereum/tests/api/miner/miner.test.ts new file mode 100644 index 0000000000..cc2c05e722 --- /dev/null +++ b/src/chains/ethereum/tests/api/miner/miner.test.ts @@ -0,0 +1,131 @@ +import assert from "assert"; +import EthereumProvider from "../../../src/provider"; +import getProvider from "../../helpers/getProvider"; + +describe("api", () => { + describe("miner", () => { + let provider: EthereumProvider; + let accounts: string[]; + + beforeEach(async () => { + provider = await getProvider(); + accounts = await provider.send("eth_accounts"); + }); + + describe("miner_stop/eth_mining/miner_start", () => { + async function testStopStartMining(provider) { + const initialBlockNumber = parseInt( + await provider.send("eth_blockNumber") + ); + const [account] = await provider.send("eth_accounts"); + + // stop the miner + const stopped = await provider.send("miner_stop"); + assert.strictEqual(stopped, true); + + // check that eth_mining returns the correct status + let miningStatus = await provider.send("eth_mining"); + assert.strictEqual(miningStatus, false); + + // send a transaction, and make sure it does *not* get mined + await provider.send("eth_subscribe", ["newHeads"]); + const txHash = await provider.send("eth_sendTransaction", [ + { from: account, to: account, value: 1 } + ]); + const fail = () => + assert.fail( + "No message should have been received while mining was stopped" + ); + provider.on("message", fail); + await new Promise(resolve => setTimeout(resolve, 2000)); + provider.off("message", fail); + + // let's relaly make sure it wasn't mined by checking for a receipt + let receipt = await provider.send("eth_getTransactionReceipt", [ + txHash + ]); + assert.strictEqual(receipt, null); + + // now start the miner back up + const prom = provider.once("message"); + const started = await provider.send("miner_start"); + assert.strictEqual(started, true); + + // check that eth_mining returns the correct status + miningStatus = await provider.send("eth_mining"); + assert.strictEqual(miningStatus, true); + + // wait for the transaction to be mined + await prom; + receipt = await provider.send("eth_getTransactionReceipt", [txHash]); + + // make sure we're on the next block! + assert.strictEqual( + parseInt(receipt.blockNumber), + initialBlockNumber + 1 + ); + } + + it("should stop mining, then mine when started", async () => { + const provider = await getProvider(); + await testStopStartMining(provider); + }).timeout(3000); + + it("should stop mining, then mine when started", async () => { + const provider = await getProvider({ miner: { blockTime: 1 } }); + await testStopStartMining(provider); + }).timeout(4000); + }); + + describe("miner_setEtherbase", () => { + it("sets the etherbase", async () => { + const setState = await provider.send("miner_setEtherbase", [ + accounts[1] + ]); + assert.strictEqual(setState, true); + + const coinbase = await provider.send("eth_coinbase"); + assert.strictEqual(coinbase, accounts[1]); + + await provider.send("eth_subscribe", ["newHeads"]); + const txHash = await provider.send("eth_sendTransaction", [ + { from: accounts[0], to: accounts[0] } + ]); + await provider.once("message"); + const { + status, + blockNumber + } = await provider.send("eth_getTransactionReceipt", [txHash]); + assert.strictEqual(status, "0x1"); + const { miner } = await provider.send("eth_getBlockByNumber", [ + blockNumber + ]); + assert.strictEqual(miner, accounts[1]); + }); + }); + + describe("miner_setGasPrice", () => { + it("sets the gasPrice and uses it as the default price in tranactions", async () => { + const newGasPrice = "0xffff"; + const setState = await provider.send("miner_setGasPrice", [ + newGasPrice + ]); + assert.strictEqual(setState, true); + + const ethGasPrice = await provider.send("eth_gasPrice"); + assert.strictEqual(ethGasPrice, newGasPrice); + + await provider.send("eth_subscribe", ["newHeads"]); + const txHash = await provider.send("eth_sendTransaction", [ + { from: accounts[0], to: accounts[0] } + ]); + await provider.once("message"); + + const { gasPrice } = await provider.send("eth_getTransactionByHash", [ + txHash + ]); + assert.strictEqual(gasPrice, newGasPrice); + }); + }); + }); +}); diff --git a/src/chains/ethereum/tests/api/net/net.test.ts b/src/chains/ethereum/tests/api/net/net.test.ts new file mode 100644 index 0000000000..02d6cc4970 --- /dev/null +++ b/src/chains/ethereum/tests/api/net/net.test.ts @@ -0,0 +1,26 @@ +import assert from "assert"; +import getProvider from "../../helpers/getProvider"; + +describe("api", () => { + describe("net", () => { + it("net_version", async () => { + const roundedTo5Seconds = (num: number) => Math.round(num / 5000) * 5000; + const nowIsh = roundedTo5Seconds(Date.now()); + const provider = await getProvider(); + const netVersion = await provider.send("net_version"); + assert.strictEqual(roundedTo5Seconds(netVersion), nowIsh); + }); + + it("net_listening", async () => { + const provider = await getProvider(); + const netListening = await provider.send("net_listening"); + assert.strictEqual(netListening, true); + }); + + it("net_peerCount", async () => { + const provider = await getProvider(); + const peerCount = await provider.send("net_peerCount"); + assert.strictEqual(peerCount, "0x0"); + }); + }); +}); diff --git a/src/chains/ethereum/tests/api/personal/personal.test.ts b/src/chains/ethereum/tests/api/personal/personal.test.ts new file mode 100644 index 0000000000..c9a62effc2 --- /dev/null +++ b/src/chains/ethereum/tests/api/personal/personal.test.ts @@ -0,0 +1,341 @@ +import assert from "assert"; +import getProvider from "../../helpers/getProvider"; +import { Quantity } from "@ganache/utils"; +import EthereumProvider from "../../../src/provider"; + +describe("api", () => { + describe("personal", () => { + describe("listAccounts", () => { + it("matches eth_accounts", async () => { + const provider = await getProvider({ wallet: { seed: "temet nosce" } }); + const accounts = await provider.send("eth_accounts"); + const personalAccounts = await provider.send("personal_listAccounts"); + assert.deepStrictEqual(personalAccounts, accounts); + }); + }); + + async function testLockedAccountWithPassphraseViaEth_SendTransaction( + provider: EthereumProvider, + newAccount: string, + passphrase: string + ) { + const transaction = { + from: newAccount, + to: newAccount, + gasLimit: 21000, + gasPrice: 0, + value: 0, + nonce: 0 + }; + + // make sure we can't use the account via eth_sendTransaction + await assert.rejects( + provider.send("eth_sendTransaction", [transaction]), + { + message: "authentication needed: password or unlock" + }, + "eth_sendTransaction should have rejected due to locked from account without its passphrase" + ); + + // unlock the account indefinitely + const unlocked = await provider.send("personal_unlockAccount", [ + newAccount, + passphrase, + 0 + ]); + assert.strictEqual(unlocked, true); + + await provider.send("eth_subscribe", ["newHeads"]); + + // send a normal transaction + const transactionHash = await provider.send("eth_sendTransaction", [ + transaction + ]); + await provider.once("message"); + + // ensure sure it worked + const receipt = await provider.send("eth_getTransactionReceipt", [ + transactionHash + ]); + assert.strictEqual( + receipt.status, + "0x1", + "Transaction failed when it should have succeeded" + ); + + // lock the account + const accountLocked = await provider.send("personal_lockAccount", [ + newAccount + ]); + assert.strictEqual(accountLocked, true); + + // make sure it is locked + await assert.rejects( + provider.send("eth_sendTransaction", [ + Object.assign({}, transaction, { nonce: 1 }) + ]), + { + message: "authentication needed: password or unlock" + }, + "personal_lockAccount didn't work" + ); + } + + async function testLockedAccountWithPassphraseViaPersonal_SendTransaction( + provider: EthereumProvider, + newAccount: string, + passphrase: string + ) { + const transaction = { + from: newAccount, + to: newAccount, + gasLimit: 21000, + gasPrice: 0, + value: 0, + nonce: 0 + }; + + // make sure we can't use the account via personal_sendTransaction and no passphrase + await assert.rejects( + provider.send("personal_sendTransaction", [transaction, undefined]), + { + message: "could not decrypt key with given password" + }, + "personal_sendTransaction should have rejected due to locked from account without its passphrase" + ); + + // make sure we can't use the account with bad passphrases + const invalidPassphrases = [ + "this is not my passphrase", + null, + undefined, + Buffer.allocUnsafe(0), + 1, + 0, + Infinity, + NaN + ]; + await Promise.all( + invalidPassphrases.map(invalidPassphrase => { + return assert.rejects( + provider.send("personal_sendTransaction", [ + transaction, + invalidPassphrase as any + ]), + { + message: "could not decrypt key with given password" + }, + "Transaction should have rejected due to locked from account with wrong passphrase" + ); + }) + ); + + // use personal_sendTransaction with the valid passphrase + await provider.send("eth_subscribe", ["newHeads"]); + const transactionHashPromise = provider.send("personal_sendTransaction", [ + transaction, + passphrase + ]); + const msgPromise = transactionHashPromise.then(() => + provider.once("message") + ); + + await assert.rejects( + provider.send("eth_sendTransaction", [ + Object.assign({}, transaction, { nonce: 1 }) + ]), + { + message: "authentication needed: password or unlock" + }, + "personal_sendTransaction should not unlock the while transaction is bring processed" + ); + + const transactionHash = await transactionHashPromise; + await msgPromise; + + const receipt = await provider.send("eth_getTransactionReceipt", [ + transactionHash + ]); + assert.strictEqual( + receipt.status, + "0x1", + "Transaction failed when it should have succeeded" + ); + + // ensure the account is still locked + await assert.rejects( + provider.send("eth_sendTransaction", [ + Object.assign({}, transaction, { nonce: 1 }) + ]), + { + message: "authentication needed: password or unlock" + }, + "personal_sendTransaction should still be locked the after the transaction is processed" + ); + } + + describe("newAccount", () => { + it("generates deterministic accounts", async () => { + const controlProvider = await getProvider(); + const provider = await getProvider(); + const newAccount = await provider.send("personal_newAccount", [""]); + const controlAccount = await controlProvider.send( + "personal_newAccount", + [""] + ); + assert.strictEqual(newAccount, controlAccount); + }); + + it("generates different accounts based on the `seed` option", async () => { + const controlProvider = await getProvider(); + const provider = await getProvider({ wallet: { seed: "temet nosce" } }); + const newAccount = await provider.send("personal_newAccount", [""]); + const controlAccount = await controlProvider.send( + "personal_newAccount", + [""] + ); + assert.notStrictEqual(newAccount, controlAccount); + }); + + it("generates different accounts based on the `mnemonic` option", async () => { + const controlProvider = await getProvider(); + const provider = await getProvider({ + wallet: { mnemonic: "sweet treat" } + }); + const newAccount = await provider.send("personal_newAccount", [""]); + const controlAccount = await controlProvider.send( + "personal_newAccount", + [""] + ); + assert.notStrictEqual(newAccount, controlAccount); + }); + + it("generates different accounts on successive calls", async () => { + const provider = await getProvider(); + const firstNewAccount = await provider.send("personal_newAccount", [ + "" + ]); + const secondNewAccount = await provider.send("personal_newAccount", [ + "" + ]); + assert.notStrictEqual(firstNewAccount, secondNewAccount); + }); + + it("generates different accounts on successive calls based on the seed", async () => { + const controlProvider = await getProvider(); + const provider = await getProvider({ wallet: { seed: "temet nosce" } }); + const firstNewAccount = await provider.send("personal_newAccount", [ + "" + ]); + const secondNewAccount = await provider.send("personal_newAccount", [ + "" + ]); + + await provider.send("personal_newAccount", [""]); + const controlSecondNewAccount = await controlProvider.send( + "personal_newAccount", + [""] + ); + + assert.notStrictEqual( + firstNewAccount, + secondNewAccount, + "First and second generated accounts are the same when they shouldn't be" + ); + assert.notStrictEqual( + secondNewAccount, + controlSecondNewAccount, + "Second account is identical to control's second account when it shouldn't be" + ); + }); + + describe("personal_unlockAccount ➡ eth_sendTransaction ➡ personal_lockAccount", () => { + it("generates locked accounts with passphrase", async () => { + const provider = await getProvider({ miner: { gasPrice: 0 } }); + const passphrase = "this is my passphrase"; + // generate an account + const newAccount = await provider.send("personal_newAccount", [ + passphrase + ]); + + testLockedAccountWithPassphraseViaEth_SendTransaction( + provider, + newAccount, + passphrase + ); + }); + }); + + describe("personal_sendTransaction", () => { + it("generates locked accounts with passphrase", async () => { + const provider = await getProvider({ miner: { gasPrice: 0 } }); + const passphrase = "this is my passphrase"; + // generate an account + const newAccount = await provider.send("personal_newAccount", [ + passphrase + ]); + + testLockedAccountWithPassphraseViaPersonal_SendTransaction( + provider, + newAccount, + passphrase + ); + }); + }); + }); + + describe("personal_importRawKey", () => { + const secretKey = + "0x0123456789012345678901234567890123456789012345678901234567890123"; + const passphrase = "this is my passphrase"; + + it("should return the known account address", async () => { + const provider = await getProvider(); + const newAccount = await provider.send("personal_importRawKey", [ + secretKey, + passphrase + ]); + assert.strictEqual( + newAccount, + "0x14791697260e4c9a71f18484c9f997b308e59325", + "Raw account not imported correctly" + ); + }); + + describe("personal_unlockAccount ➡ eth_sendTransaction ➡ personal_lockAccount", () => { + it("generates locked accounts with passphrase", async () => { + const provider = await getProvider({ miner: { gasPrice: 0 } }); + const passphrase = "this is my passphrase"; + // generate an account + const newAccount = await provider.send("personal_importRawKey", [ + secretKey, + passphrase + ]); + + await testLockedAccountWithPassphraseViaEth_SendTransaction( + provider, + newAccount, + passphrase + ); + }); + }); + + describe("personal_sendTransaction", () => { + it("generates locked accounts with passphrase", async () => { + const provider = await getProvider({ miner: { gasPrice: 0 } }); + // generate an account + const newAccount = await provider.send("personal_importRawKey", [ + secretKey, + passphrase + ]); + + await testLockedAccountWithPassphraseViaPersonal_SendTransaction( + provider, + newAccount, + passphrase + ); + }); + }); + }); + }); +}); diff --git a/src/chains/ethereum/tests/api/rpc/modules.test.ts b/src/chains/ethereum/tests/api/rpc/modules.test.ts new file mode 100644 index 0000000000..d4fe8f4c1b --- /dev/null +++ b/src/chains/ethereum/tests/api/rpc/modules.test.ts @@ -0,0 +1,25 @@ +import getProvider from "../../helpers/getProvider"; +import assert from "assert"; +import EthereumProvider from "../../../src/provider"; +const RPC_MODULES = { + eth: "1.0", + net: "1.0", + rpc: "1.0", + web3: "1.0", + evm: "1.0", + personal: "1.0" +} as const; + +describe("api", () => { + describe("rpc", () => { + let provider: EthereumProvider; + before(async () => { + provider = await getProvider(); + }); + + it("rpc_modules returns the modules", async () => { + const result = await provider.send("rpc_modules"); + assert.deepStrictEqual(result, RPC_MODULES); + }); + }); +}); diff --git a/src/chains/ethereum/tests/api/shh/shh.test.ts b/src/chains/ethereum/tests/api/shh/shh.test.ts new file mode 100644 index 0000000000..51e8053178 --- /dev/null +++ b/src/chains/ethereum/tests/api/shh/shh.test.ts @@ -0,0 +1,62 @@ +import getProvider from "../../helpers/getProvider"; +import assert from "assert"; +import EthereumProvider from "../../../src/provider"; + +describe("api", () => { + describe("shh", () => { + let provider: EthereumProvider; + before(async () => { + provider = await getProvider(); + }); + + it("returns it's shh_newIdentity status", async () => { + const result = await provider.send("shh_newIdentity"); + assert.deepStrictEqual(result, "0x00"); + }); + + it("returns it's shh_hasIdentity status", async () => { + const result = await provider.send("shh_hasIdentity", ["0x0"]); + assert.strictEqual(result, false); + }); + + it("returns false for shh_addToGroup", async () => { + const result = await provider.send("shh_addToGroup", ["0x0"]); + assert.strictEqual(result, false); + }); + + it("returns it's shh_newGroup status", async () => { + const result = await provider.send("shh_newGroup"); + assert.deepStrictEqual(result, "0x00"); + }); + + it("returns false for shh_newFilter", async () => { + const result = await provider.send("shh_newFilter", ["0x0", []]); + assert.strictEqual(result, false); + }); + + it("returns false for shh_uninstallFilter", async () => { + const result = await provider.send("shh_uninstallFilter", ["0x0"]); + assert.strictEqual(result, false); + }); + + it("returns []] for shh_getFilterChanges", async () => { + const result = await provider.send("shh_getFilterChanges", ["0x0"]); + assert.deepStrictEqual(result, []); + }); + + it("returns false for shh_getMessages", async () => { + const result = await provider.send("shh_getMessages", ["0x0"]); + assert.strictEqual(result, false); + }); + + it("returns false for shh_post", async () => { + const result = await provider.send("shh_post", [{}]); + assert.strictEqual(result, false); + }); + + it("returns 2 for shh_version", async () => { + const result = await provider.send("shh_version"); + assert.strictEqual(result, "2"); + }); + }); +}); diff --git a/src/chains/ethereum/tests/api/web3/web3.test.ts b/src/chains/ethereum/tests/api/web3/web3.test.ts new file mode 100644 index 0000000000..a87f8949dd --- /dev/null +++ b/src/chains/ethereum/tests/api/web3/web3.test.ts @@ -0,0 +1,31 @@ +import getProvider from "../../helpers/getProvider"; +import assert from "assert"; +import EthereumProvider from "../../../src/provider"; +const { version } = require("../../../../../packages/ganache/package.json"); + +describe("api", () => { + describe("web3", () => { + let provider: EthereumProvider; + before(async () => { + provider = await getProvider(); + }); + + it("web3_clientVersion returns the client version", async () => { + const result = await provider.send("web3_clientVersion"); + assert.deepStrictEqual( + result, + `Ganache/v${version}/EthereumJS TestRPC/v${version}/ethereum-js` + ); + }); + + it("web3_sha should hash the given input", async () => { + const input = "Tim is a swell guy."; + const result = await provider.send("web3_sha3", [input]); + + assert.strictEqual( + result, + "0xee80d4ac03202e2246d51a596c76a18e1a6d899bed9f05246d998fb656d9bd1f" + ); + }); + }); +}); diff --git a/src/chains/ethereum/tests/contracts/HelloWorld.sol b/src/chains/ethereum/tests/contracts/HelloWorld.sol new file mode 100644 index 0000000000..59e7b6f1d5 --- /dev/null +++ b/src/chains/ethereum/tests/contracts/HelloWorld.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.7.4; + +contract HelloWorld { + uint256 public value; + + event ValueSet(uint256); + + constructor() payable { + value = 5; + } + + function setValue(uint256 val) public { + value = val; + emit ValueSet(val); + } + + function getConstVal() public pure returns (uint8) { + return 123; + } +} diff --git a/src/chains/ethereum/tests/helpers/compile.ts b/src/chains/ethereum/tests/helpers/compile.ts new file mode 100644 index 0000000000..4b64cbbe21 --- /dev/null +++ b/src/chains/ethereum/tests/helpers/compile.ts @@ -0,0 +1,51 @@ +import solc from "solc"; + +{ + // Clean up after solc. Looks like this never really got fixed: + // https://github.com/chriseth/browser-solidity/issues/167 + const listeners = process.listeners("unhandledRejection"); + const solcListener = listeners[listeners.length - 1]; + if (solcListener && solcListener.name === "abort") { + process.removeListener("unhandledRejection", solcListener); + } else { + throw new Error( + "Looks like either the solc listener was finally removed, or they changed the name. Check it!" + ); + } +} + +import { readFileSync } from "fs-extra"; +import { parse } from "path"; + +export default function compile(contractPath: string, contractName?: string) { + const parsedPath = parse(contractPath); + const content = readFileSync(contractPath, { encoding: "utf8" }); + const globalName = parsedPath.base; + contractName ||= parsedPath.name; + + let result = JSON.parse( + solc.compile( + JSON.stringify({ + language: "Solidity", + sources: { + [globalName]: { + content + } + }, + settings: { + outputSelection: { + "*": { + "*": ["*"] + } + } + } + } as solc.CompilerInput) + ) + ) as solc.CompilerOutput; + + const contract = result.contracts[globalName][contractName]; + return { + code: "0x" + contract.evm.bytecode.object, + contract + }; +} diff --git a/src/chains/ethereum/tests/helpers/getProvider.ts b/src/chains/ethereum/tests/helpers/getProvider.ts new file mode 100644 index 0000000000..1814c4f8a3 --- /dev/null +++ b/src/chains/ethereum/tests/helpers/getProvider.ts @@ -0,0 +1,41 @@ +import { utils } from "@ganache/utils"; +import EthereumProvider from "../../src/provider"; +import { EthereumProviderOptions } from "../../src/options"; +const { RequestCoordinator, Executor } = utils; + +const mnemonic = + "into trim cross then helmet popular suit hammer cart shrug oval student"; + +type Writeable = { -readonly [P in keyof T]: T[P] }; + +const getProvider = async ( + options: Writeable = { + wallet: { mnemonic: mnemonic } + } +) => { + options.chain = options.chain || {}; + options.logging = options.logging || { logger: { log: () => {} } }; + + // set `asyncRequestProcessing` to `true` by default + let doAsync = options.chain.asyncRequestProcessing; + doAsync = options.chain.asyncRequestProcessing = + doAsync != null ? doAsync : true; + + // don't write to stdout in tests + if (!options.logging.logger) { + options.logging.logger = { log: () => {} }; + } + + const requestCoordinator = new RequestCoordinator(doAsync ? 0 : 1); + const executor = new Executor(requestCoordinator); + const provider = new EthereumProvider(options, executor); + await new Promise(resolve => { + provider.on("connect", () => { + requestCoordinator.resume(); + resolve(void 0); + }); + }); + return provider; +}; + +export default getProvider; diff --git a/src/chains/ethereum/tests/provider.test.ts b/src/chains/ethereum/tests/provider.test.ts new file mode 100644 index 0000000000..14aaab9fe3 --- /dev/null +++ b/src/chains/ethereum/tests/provider.test.ts @@ -0,0 +1,55 @@ +import assert from "assert"; +import EthereumProvider from "../src/provider"; +import getProvider from "./helpers/getProvider"; +import { JsonRpcTypes } from "@ganache/utils"; +import EthereumApi from "../src/api"; + +describe("provider", () => { + describe("options", () => { + it("generates predictable accounts when given a seed", async () => { + const provider = await getProvider({ wallet: { seed: "temet nosce" } }); + const accounts = await provider.send("eth_accounts"); + assert.strictEqual( + accounts[0], + "0x59ef313e6ee26bab6bcb1b5694e59613debd88da" + ); + }); + }); + + describe("interface", () => { + const networkId = 1234; + let provider: EthereumProvider; + + beforeEach(async () => { + provider = await getProvider({ chain: { networkId } }); + }); + + it("returns things via EIP-1193", async () => { + assert.strictEqual(await provider.send("net_version"), `${networkId}`); + }); + + it("returns things via legacy", async () => { + const jsonRpcRequest: JsonRpcTypes.Request = { + id: "1", + jsonrpc: "2.0", + method: "net_version" + }; + const methods = ["send", "sendAsync"] as const; + return Promise.all( + methods + .map(method => { + return new Promise((resolve, reject) => { + provider[method](jsonRpcRequest, (err: Error, { result }) => { + if (err) return reject(err); + assert.strictEqual(result, `${networkId}`); + resolve(void 0); + }); + }); + }) + .map(async prom => { + assert.strictEqual(await prom, void 0); + }) + ); + }); + }); +}); diff --git a/src/chains/ethereum/tests/temp-tests.test.ts b/src/chains/ethereum/tests/temp-tests.test.ts new file mode 100644 index 0000000000..e5f17da2aa --- /dev/null +++ b/src/chains/ethereum/tests/temp-tests.test.ts @@ -0,0 +1,315 @@ +import os from "os"; +import fs from "fs"; +import assert from "assert"; +import getProvider from "./helpers/getProvider"; +import compile from "./helpers/compile"; +import { join } from "path"; + +/** + * test in here are playground tests or just tests that are in the original + * ganache-core but have yet been properly ported over yet. + */ + +describe("Random tests that are temporary!", () => { + const expectedAddress = "0x604a95c9165bc95ae016a5299dd7d400dddbea9a"; + const mnemonic = + "into trim cross then helmet popular suit hammer cart shrug oval student"; + + it("should respect the BIP99 mnemonic", async () => { + const options = { wallet: { mnemonic } }; + const p = await getProvider(options); + const accounts = await p.send("eth_accounts"); + + assert.strictEqual(accounts[0], expectedAddress); + }); + + it("eth_sendTransaction", async () => { + const options = { wallet: { mnemonic } }; + const p = await getProvider(options); + const accounts = await p.send("eth_accounts"); + const balance1_1 = await p.send("eth_getBalance", [accounts[1]]); + await p.send("eth_subscribe", ["newHeads"]); + await p.send("eth_sendTransaction", [ + { + from: accounts[0], + to: accounts[1], + value: 1 + } + ]); + await p.once("message"); + + const balance1_2 = await p.send("eth_getBalance", [accounts[1]]); + assert.strictEqual(parseInt(balance1_1) + 1, parseInt(balance1_2)); + }); + + it("should create its own mnemonic", async () => { + const p = await getProvider(); + const options = p.getOptions(); + assert.deepStrictEqual(typeof options.wallet.mnemonic, "string"); + }); + + it("shouldn't allow initialization without accounts", async () => { + const options = { wallet: { totalAccounts: 0 } } as any; + await assert.rejects(getProvider(options), { + message: + "Cannot initialize chain: either options.accounts or options.total_accounts must be specified" + }); + + options.wallet.accounts = []; + await assert.rejects(getProvider(options), { + message: + "Cannot initialize chain: either options.accounts or options.total_accounts must be specified" + }); + }); + + it("sets up accounts", async () => { + const privateKey = Buffer.from( + "4646464646464646464646464646464646464646464646464646464646464646", + "hex" + ); + const p = await getProvider({ + wallet: { + accounts: [ + { balance: "0x123", secretKey: "0x" + privateKey.toString("hex") }, + { balance: "0x456" } + ] + } + }); + const accounts = await p.send("eth_accounts"); + assert.strictEqual(accounts.length, 2); + }); + + it("sets errors when unlocked_accounts index is too high", async () => { + await assert.rejects(getProvider({ wallet: { unlockedAccounts: [99] } }), { + message: "Account at index 99 not found. Max index available is 9." + }); + }); + + it("sets errors when unlocked_accounts index is a (big) bigint", async () => { + const bigNumber = BigInt(Number.MAX_SAFE_INTEGER) + 1n; + await assert.rejects( + getProvider({ + wallet: { + unlockedAccounts: [bigNumber.toString()] + } + }), + { + message: `Invalid value in unlocked_accounts: ${bigNumber}` + } + ); + }); + + it("unlocks accounts via unlock_accounts (both string and numbered numbers)", async () => { + const p = await getProvider({ + wallet: { + mnemonic, + secure: true, + unlockedAccounts: ["0", 1] + } + }); + + const accounts = await p.send("eth_accounts"); + const balance1_1 = await p.send("eth_getBalance", [accounts[1]]); + const badSend = async () => { + return p.send("eth_sendTransaction", [ + { + from: accounts[2], + to: accounts[1], + value: 123 + } + ]); + }; + await assert.rejects( + badSend, + "Error: authentication needed: password or unlock" + ); + + await p.send("eth_subscribe", ["newHeads"]); + await p.send("eth_sendTransaction", [ + { + from: accounts[0], + to: accounts[1], + value: 123 + } + ]); + + await p.once("message"); + + const balance1_2 = await p.send("eth_getBalance", [accounts[1]]); + assert.strictEqual(BigInt(balance1_1) + 123n, BigInt(balance1_2)); + + const balance0_1 = await p.send("eth_getBalance", [accounts[0]]); + + await p.send("eth_sendTransaction", [ + { + from: accounts[1], + to: accounts[0], + value: 123 + } + ]); + + await p.once("message"); + + const balance0_2 = await p.send("eth_getBalance", [accounts[0]]); + assert.strictEqual(BigInt(balance0_1) + 123n, BigInt(balance0_2)); + }); + + it("deploys contracts", async () => { + const contract = compile(join(__dirname, "./contracts/HelloWorld.sol")); + + const p = await getProvider({ + miner: { defaultTransactionGasLimit: 6721975 } + }); + const accounts = await p.send("eth_accounts"); + const from = accounts[3]; + + await p.send("eth_subscribe", ["newHeads"]); + + const transactionHash = await p.send("eth_sendTransaction", [ + { + from, + data: contract.code + } + ]); + + await p.once("message"); + + const receipt = await p.send("eth_getTransactionReceipt", [ + transactionHash + ]); + assert.strictEqual(receipt.blockNumber, "0x1"); + + const to = receipt.contractAddress; + const methods = contract.contract.evm.methodIdentifiers; + + const value = await p.send("eth_call", [ + { from, to, data: "0x" + methods["value()"] } + ]); + + const x5 = + "0x0000000000000000000000000000000000000000000000000000000000000005"; + assert.strictEqual(value, x5); + + const constVal = await p.send("eth_call", [ + { from, to, data: "0x" + methods["getConstVal()"] } + ]); + + const x123 = + "0x000000000000000000000000000000000000000000000000000000000000007b"; + assert.strictEqual(constVal, x123); + + const storage = await p.send("eth_getStorageAt", [ + receipt.contractAddress, + 0, + receipt.blockNumber + ]); + assert.strictEqual(storage, "0x05"); + + const raw25 = + "0000000000000000000000000000000000000000000000000000000000000019"; + const x25 = "0x" + raw25; + const hash = await p.send("eth_sendTransaction", [ + { from, to, data: "0x" + methods["setValue(uint256)"] + raw25 } + ]); + await p.once("message"); + const txReceipt = await p.send("eth_getTransactionReceipt", [hash]); + assert.strictEqual(txReceipt.blockNumber, "0x2"); + + const getValueAgain = await p.send("eth_call", [ + { from, to, data: "0x" + methods["value()"] } + ]); + + assert.strictEqual(getValueAgain, x25); + + const storage2 = await p.send("eth_getStorageAt", [ + receipt.contractAddress, + 0, + txReceipt.blockNumber + ]); + assert.strictEqual(storage2, "0x19"); + }); + + it("transfers value", async () => { + const p = await getProvider({ miner: { gasPrice: 0 } }); + const accounts = await p.send("eth_accounts"); + const ONE_ETHER = 1000000000000000000n; + const startingBalance = 100n * ONE_ETHER; + await p.send("eth_subscribe", ["newHeads"]); + await p.send("eth_sendTransaction", [ + { + from: accounts[1], + to: accounts[2], + value: ONE_ETHER + } + ]); + await p.once("message"); + + const balances = ( + await Promise.all([ + p.send("eth_getBalance", [accounts[1]]), + p.send("eth_getBalance", [accounts[2]]) + ]) + ).map(BigInt); + assert.strictEqual(balances[0], startingBalance - ONE_ETHER); + assert.strictEqual(balances[1], startingBalance + ONE_ETHER); + }); + + it("runs eth_call", async () => { + const privateKey = Buffer.from( + "4646464646464646464646464646464646464646464646464646464646464646", + "hex" + ); + const p = await getProvider({ + wallet: { + accounts: [ + { balance: "0x123", secretKey: "0x" + privateKey.toString("hex") }, + { balance: "0x456" } + ] + } + }); + const accounts = await p.send("eth_accounts"); + const result = await p.send("eth_call", [ + { from: accounts[0], to: accounts[0], value: "0x1" } + ]); + assert(result, "0x"); + }); + + describe("options:account_keys_path", () => { + const fileName = join(os.tmpdir(), "/ganache-core-test-accounts.json"); + + function cleanUp() { + try { + fs.unlinkSync(fileName); + } catch (e) { + // ignore error + } + } + afterEach("clean up", () => { + cleanUp(); + }); + it("should create the file by name", async () => { + await getProvider({ wallet: { accountKeysPath: fileName } }); + assert.strictEqual( + fs.existsSync(fileName), + true, + "The account_keys file doesn't exist." + ); + }); + it("should populate the file by descriptor", async () => { + const fd = fs.openSync(fileName, "w"); + try { + await getProvider({ wallet: { accountKeysPath: fd } }); + assert.strictEqual( + fs.existsSync(fileName), + true, + "The account_keys file doesn't exist." + ); + } finally { + fs.closeSync(fd); + } + }); + afterEach("clean up", () => { + cleanUp(); + }); + }); +}); diff --git a/src/chains/ethereum/tests/tsconfig.json b/src/chains/ethereum/tests/tsconfig.json new file mode 100644 index 0000000000..7f41c2548d --- /dev/null +++ b/src/chains/ethereum/tests/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../tsconfig.json", + "include": ["./"] +} diff --git a/src/chains/ethereum/tsconfig.json b/src/chains/ethereum/tsconfig.json new file mode 100644 index 0000000000..0c41d652c6 --- /dev/null +++ b/src/chains/ethereum/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "outDir": "lib" + }, + "include": ["index.ts"], + "typeRoots": ["./node_modules/@types", "./src/@types"] +} diff --git a/src/chains/ethereum/typedoc.json b/src/chains/ethereum/typedoc.json new file mode 100644 index 0000000000..2b106fd177 --- /dev/null +++ b/src/chains/ethereum/typedoc.json @@ -0,0 +1,13 @@ +{ + "name": "@ganache/ethereum", + "mode": "modules", + "module": "commonjs", + "theme": "../../../node_modules/@trufflesuite/typedoc-default-themes/bin/default/", + "includeDeclarations": false, + "ignoreCompilerErrors": true, + "preserveConstEnums": true, + "excludeExternals": true, + "categoryOrder": [], + "exclude": ["**/test/*"], + "target": "ES6" +} diff --git a/src/chains/tezos/.npmignore b/src/chains/tezos/.npmignore new file mode 100644 index 0000000000..115ab4f024 --- /dev/null +++ b/src/chains/tezos/.npmignore @@ -0,0 +1,8 @@ +./index.ts +tests +.nyc_output +coverage +scripts +src +tsconfig.json +typedoc.json diff --git a/src/chains/tezos/LICENSE b/src/chains/tezos/LICENSE new file mode 100644 index 0000000000..39f3b14498 --- /dev/null +++ b/src/chains/tezos/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2019-2020 Truffle Blockchain Group + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/chains/tezos/README.md b/src/chains/tezos/README.md new file mode 100644 index 0000000000..59fe51bb8c --- /dev/null +++ b/src/chains/tezos/README.md @@ -0,0 +1,3 @@ +# `@ganache/tezos` + +This is ganache's Tezos client implementation. diff --git a/src/chains/tezos/index.ts b/src/chains/tezos/index.ts new file mode 100644 index 0000000000..5a788f80bd --- /dev/null +++ b/src/chains/tezos/index.ts @@ -0,0 +1,42 @@ +import Emittery from "emittery"; +import { utils, types } from "@ganache/utils"; +import Provider from "./src/provider"; +import TezosApi from "./src/api"; +import { HttpRequest } from "uWebSockets.js"; + +export type TezosProvider = Provider; +export const TezosProvider = Provider; + +export class TezosConnector + extends Emittery.Typed + implements types.Connector { + provider: Provider; + #api: TezosApi; + + constructor(providerOptions: any, requestCoordinator: utils.Executor) { + super(); + + const api = (this.#api = new TezosApi()); + this.provider = new Provider(providerOptions); + } + + format(result: any) { + return JSON.stringify(result); + } + + formatError(error: any) { + return JSON.stringify(error); + } + + parse(message: Buffer) { + return JSON.parse(message); + } + + handle(payload: any, _connection: HttpRequest): Promise { + return Promise.resolve(123); + } + + close() { + return {}; + } +} diff --git a/src/chains/tezos/npm-shrinkwrap.json b/src/chains/tezos/npm-shrinkwrap.json new file mode 100644 index 0000000000..8228513ad0 --- /dev/null +++ b/src/chains/tezos/npm-shrinkwrap.json @@ -0,0 +1,1931 @@ +{ + "name": "@ganache/tezos", + "version": "0.1.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@koa/cors": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@koa/cors/-/cors-3.1.0.tgz", + "integrity": "sha512-7ulRC1da/rBa6kj6P4g2aJfnET3z8Uf3SWu60cjbtxTA5g8lxRdX/Bd2P92EagGwwAhANeNw8T8if99rJliR6Q==", + "dev": true, + "requires": { + "vary": "^1.1.2" + } + }, + "@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "dev": true + }, + "@trufflesuite/typedoc-default-themes": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@trufflesuite/typedoc-default-themes/-/typedoc-default-themes-0.6.1.tgz", + "integrity": "sha512-/wt3Jp+fD/DsxArEMixt94hDjHlB6R82Xa2ffjoCzlXrF8OSidzmzYkMvuxi53t1PaQvobNY5wMTXUqGnt/HXA==", + "dev": true, + "requires": { + "backbone": "^1.4.0", + "jquery": "^3.4.1", + "lunr": "^2.3.6", + "underscore": "^1.9.1" + } + }, + "@types/node": { + "version": "14.0.27", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.27.tgz", + "integrity": "sha512-kVrqXhbclHNHGu9ztnAwSncIgJv/FaxmzXJvGXNdcCpV1b8u1/Mi6z6m0vwy0LzKeXFTPLH0NzwmoJ3fNCIq0g==", + "dev": true + }, + "JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "dev": true, + "requires": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + } + }, + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "dev": true, + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + } + }, + "agent-base": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.1.tgz", + "integrity": "sha512-01q25QQDwLSsyfhrKbn8yuur+JNw0H+0Y4JiGIKd3z9aYk/w/2kxD/Upc+t2ZBBSUNff50VjPsSW2YxM8QYKVg==", + "dev": true, + "requires": { + "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "ansi-escape-sequences": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/ansi-escape-sequences/-/ansi-escape-sequences-5.1.2.tgz", + "integrity": "sha512-JcpoVp1W1bl1Qn4cVuiXEhD6+dyXKSOgCn2zlzE8inYgCJCBy1aPnUhlz6I4DFum8D4ovb9Qi/iAjUcGvG2lqw==", + "dev": true, + "requires": { + "array-back": "^4.0.0" + } + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=", + "dev": true + }, + "array-back": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-4.0.1.tgz", + "integrity": "sha512-Z/JnaVEXv+A9xabHzN43FiiiWEE7gPCRXMrVmRm00tWbjZRul1iHm7ECzlyNq1p4a4ATXz+G9FJ3GqGOkOV3fg==", + "dev": true + }, + "backbone": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/backbone/-/backbone-1.4.0.tgz", + "integrity": "sha512-RLmDrRXkVdouTg38jcgHhyQ/2zjg7a8E6sz2zxfz21Hh17xDJYUHBZimVIt5fUyS8vbfpeSmTL3gUjTEvUV3qQ==", + "dev": true, + "requires": { + "underscore": ">=1.8.3" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dev": true, + "requires": { + "safe-buffer": "5.1.2" + }, + "dependencies": { + "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==", + "dev": true + } + } + }, + "batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", + "dev": true + }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "byte-size": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/byte-size/-/byte-size-6.2.0.tgz", + "integrity": "sha512-6EspYUCAPMc7E2rltBgKwhG+Cmk0pDm9zDtF1Awe2dczNUL3YpZ8mTs/dueOTS1hqGWBOatqef4jYMGjln7WmA==", + "dev": true + }, + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "dev": true + }, + "cache-content-type": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-content-type/-/cache-content-type-1.0.1.tgz", + "integrity": "sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==", + "dev": true, + "requires": { + "mime-types": "^2.1.18", + "ylru": "^1.2.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "cheerio": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.3.tgz", + "integrity": "sha512-0td5ijfUPuubwLUu0OBoe98gZj8C/AA+RW3v67GPlGOrvxWjZmBXiBCRU+I8VEiNyJzjth40POfHiz2RB3gImA==", + "dev": true, + "requires": { + "css-select": "~1.2.0", + "dom-serializer": "~0.1.1", + "entities": "~1.1.1", + "htmlparser2": "^3.9.1", + "lodash": "^4.15.0", + "parse5": "^3.0.1" + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true + }, + "co-body": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/co-body/-/co-body-6.0.0.tgz", + "integrity": "sha512-9ZIcixguuuKIptnY8yemEOuhb71L/lLf+Rl5JfJEUiDNJk0e02MBt7BPxR2GEh5mw8dPthQYR4jPI/BnS1MQgw==", + "dev": true, + "requires": { + "inflation": "^2.0.0", + "qs": "^6.5.2", + "raw-body": "^2.3.3", + "type-is": "^1.6.16" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "command-line-args": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.1.1.tgz", + "integrity": "sha512-hL/eG8lrll1Qy1ezvkant+trihbGnaKaeEjj6Scyr3DN+RC7iQ5Rz84IeLERfAWDGo0HBSNAakczwgCilDXnWg==", + "dev": true, + "requires": { + "array-back": "^3.0.1", + "find-replace": "^3.0.0", + "lodash.camelcase": "^4.3.0", + "typical": "^4.0.0" + }, + "dependencies": { + "array-back": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", + "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==", + "dev": true + }, + "typical": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz", + "integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==", + "dev": true + } + } + }, + "command-line-usage": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/command-line-usage/-/command-line-usage-6.1.0.tgz", + "integrity": "sha512-Ew1clU4pkUeo6AFVDFxCbnN7GIZfXl48HIOQeFQnkO3oOqvpI7wdqtLRwv9iOCZ/7A+z4csVZeiDdEcj8g6Wiw==", + "dev": true, + "requires": { + "array-back": "^4.0.0", + "chalk": "^2.4.2", + "table-layout": "^1.0.0", + "typical": "^5.2.0" + }, + "dependencies": { + "typical": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", + "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", + "dev": true + } + } + }, + "common-log-format": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/common-log-format/-/common-log-format-1.0.0.tgz", + "integrity": "sha512-fFn/WPNbsTCGTTwdCpZfVZSa5mgqMEkA0gMTRApFSlEsYN+9B2FPfiqch5FT+jsv5IV1RHV3GeZvCa7Qg+jssw==", + "dev": true + }, + "compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "requires": { + "mime-db": ">= 1.43.0 < 2" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "dev": true, + "requires": { + "safe-buffer": "5.1.2" + }, + "dependencies": { + "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==", + "dev": true + } + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true + }, + "cookies": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.8.0.tgz", + "integrity": "sha512-8aPsApQfebXnuI+537McwYsDtjVxGm8gTIzQI3FDW6t5t/DAhERxtnbEPN/8RX+uZthoz4eCOgloXaE5cYyNow==", + "dev": true, + "requires": { + "depd": "~2.0.0", + "keygrip": "~1.1.0" + }, + "dependencies": { + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true + } + } + }, + "copy-to": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/copy-to/-/copy-to-2.0.1.tgz", + "integrity": "sha1-JoD7uAaKSNCGVrYJgJK9r8kG9KU=", + "dev": true + }, + "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 + }, + "create-mixin": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/create-mixin/-/create-mixin-3.0.0.tgz", + "integrity": "sha512-LkdMqnWT9LaqBN4huqpUnMz56Yr1mVSoCduAd2xXefgH/YZP2sXCMAyztXjk4q8hTF/TlcDa+zQW2aTgGdjjKQ==", + "dev": true + }, + "css-select": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", + "dev": true, + "requires": { + "boolbase": "~1.0.0", + "css-what": "2.1", + "domutils": "1.5.1", + "nth-check": "~1.0.1" + } + }, + "css-what": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", + "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==", + "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" + } + }, + "deep-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", + "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", + "dev": true + }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "dev": true + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", + "dev": true + }, + "dom-serializer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", + "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", + "dev": true, + "requires": { + "domelementtype": "^1.3.0", + "entities": "^1.1.1" + } + }, + "domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", + "dev": true + }, + "domhandler": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", + "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "dev": true, + "requires": { + "domelementtype": "1" + } + }, + "domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "dev": true, + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "dev": true + }, + "emittery": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.7.2.tgz", + "integrity": "sha512-A8OG5SR/ij3SsJdWDJdkkSYUjQdCUx6APQXem0SaEePBSRg4eymGYwBkKo1Y6DU+af/Jn2dBQqDBvjnr9Vi8nQ==" + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "dev": true + }, + "entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", + "dev": true + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", + "dev": true + }, + "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 + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "dev": true + }, + "find-replace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz", + "integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==", + "dev": true, + "requires": { + "array-back": "^3.0.1" + }, + "dependencies": { + "array-back": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", + "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==", + "dev": true + } + } + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "dev": true + }, + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "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" + } + }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + }, + "handlebars": { + "version": "4.7.6", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.6.tgz", + "integrity": "sha512-1f2BACcBfiwAfStCKZNrUCgqNZkGsAT7UM3kkYtXuLo0KnaVfjKOyf7PRzB6++aK9STyT1Pd2ZCPe3EGOXleXA==", + "dev": true, + "requires": { + "minimist": "^1.2.5", + "neo-async": "^2.6.0", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4", + "wordwrap": "^1.0.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "highlight.js": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.1.2.tgz", + "integrity": "sha512-Q39v/Mn5mfBlMff9r+zzA+gWxRsCRKwEMvYTiisLr/XUiFI/4puWt0Ojdko3R3JCNWGdOWaA5g/Yxqa23kC5AA==", + "dev": true + }, + "htmlparser2": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", + "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", + "dev": true, + "requires": { + "domelementtype": "^1.3.1", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^3.1.1" + } + }, + "http-assert": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.4.1.tgz", + "integrity": "sha512-rdw7q6GTlibqVVbXr0CKelfV5iY8G2HqEUkhSk297BMbSpSL8crXC+9rjKoMcZZEsksX30le6f/4ul4E28gegw==", + "dev": true, + "requires": { + "deep-equal": "~1.0.1", + "http-errors": "~1.7.2" + }, + "dependencies": { + "http-errors": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", + "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + } + } + }, + "http-errors": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.0.tgz", + "integrity": "sha512-4I8r0C5JDhT5VkvI47QktDW75rNlGVsUf/8hzjCC/wkWI/jdTRmBb9aI7erSG82r1bjKY3F6k28WnsVxB1C73A==", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + }, + "dependencies": { + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + } + } + }, + "http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "dev": true, + "requires": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "dev": true, + "requires": { + "agent-base": "6", + "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "inflation": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/inflation/-/inflation-2.0.0.tgz", + "integrity": "sha1-i0F+R8KPklpFEz2RTKH9OJEH8w8=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "dev": true + }, + "is-docker": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.0.0.tgz", + "integrity": "sha512-pJEdRugimx4fBMra5z2/5iRdZ63OhYV0vr0Dwm5+xtW4D1FvRkB8hamMIhnWfyJeDdyr/aa7BDyNbtG38VxgoQ==", + "dev": true + }, + "is-generator-function": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.7.tgz", + "integrity": "sha512-YZc5EwyO4f2kWCax7oegfuSr9mFz1ZvieNYBEjmukLxgXfBUbxAWGVF7GZf0zidYtoBl3WvC07YK0wT76a+Rtw==", + "dev": true + }, + "is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "requires": { + "is-docker": "^2.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "jquery": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.5.1.tgz", + "integrity": "sha512-XwIBPqcMn57FxfT+Go5pzySnm4KWkT1Tv7gjrpT1srtf8Weynl6R273VJ5GjkRb51IzMp5nbaPjJXMWeju2MKg==", + "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 + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", + "dev": true + }, + "keygrip": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz", + "integrity": "sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==", + "dev": true, + "requires": { + "tsscmp": "1.0.6" + } + }, + "koa": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/koa/-/koa-2.13.0.tgz", + "integrity": "sha512-i/XJVOfPw7npbMv67+bOeXr3gPqOAw6uh5wFyNs3QvJ47tUx3M3V9rIE0//WytY42MKz4l/MXKyGkQ2LQTfLUQ==", + "dev": true, + "requires": { + "accepts": "^1.3.5", + "cache-content-type": "^1.0.0", + "content-disposition": "~0.5.2", + "content-type": "^1.0.4", + "cookies": "~0.8.0", + "debug": "~3.1.0", + "delegates": "^1.0.0", + "depd": "^1.1.2", + "destroy": "^1.0.4", + "encodeurl": "^1.0.2", + "escape-html": "^1.0.3", + "fresh": "~0.5.2", + "http-assert": "^1.3.0", + "http-errors": "^1.6.3", + "is-generator-function": "^1.0.7", + "koa-compose": "^4.1.0", + "koa-convert": "^1.2.0", + "on-finished": "^2.3.0", + "only": "~0.0.2", + "parseurl": "^1.3.2", + "statuses": "^1.5.0", + "type-is": "^1.6.16", + "vary": "^1.1.2" + } + }, + "koa-bodyparser": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/koa-bodyparser/-/koa-bodyparser-4.3.0.tgz", + "integrity": "sha512-uyV8G29KAGwZc4q/0WUAjH+Tsmuv9ImfBUF2oZVyZtaeo0husInagyn/JH85xMSxM0hEk/mbCII5ubLDuqW/Rw==", + "dev": true, + "requires": { + "co-body": "^6.0.0", + "copy-to": "^2.0.1" + } + }, + "koa-compose": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-4.1.0.tgz", + "integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==", + "dev": true + }, + "koa-compress": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/koa-compress/-/koa-compress-3.1.0.tgz", + "integrity": "sha512-0m24/yS/GbhWI+g9FqtvStY+yJwTObwoxOvPok6itVjRen7PBWkjsJ8pre76m+99YybXLKhOJ62mJ268qyBFMQ==", + "dev": true, + "requires": { + "bytes": "^3.0.0", + "compressible": "^2.0.0", + "koa-is-json": "^1.0.0", + "statuses": "^1.0.0" + } + }, + "koa-conditional-get": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/koa-conditional-get/-/koa-conditional-get-2.0.0.tgz", + "integrity": "sha1-pD83I8HQFLcwo07Oit8wuTyCM/I=", + "dev": true + }, + "koa-convert": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/koa-convert/-/koa-convert-1.2.0.tgz", + "integrity": "sha1-2kCHXfSd4FOQmNFwC1CCDOvNIdA=", + "dev": true, + "requires": { + "co": "^4.6.0", + "koa-compose": "^3.0.0" + }, + "dependencies": { + "koa-compose": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-3.2.1.tgz", + "integrity": "sha1-qFzLQLfZhtjlo0Wzoazo6rz1Tec=", + "dev": true, + "requires": { + "any-promise": "^1.1.0" + } + } + } + }, + "koa-etag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/koa-etag/-/koa-etag-3.0.0.tgz", + "integrity": "sha1-nvc4Ld1agqsN6xU0FckVg293HT8=", + "dev": true, + "requires": { + "etag": "^1.3.0", + "mz": "^2.1.0" + } + }, + "koa-is-json": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/koa-is-json/-/koa-is-json-1.0.0.tgz", + "integrity": "sha1-JzwH7c3Ljfaiwat9We52SRRR7BQ=", + "dev": true + }, + "koa-json": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/koa-json/-/koa-json-2.0.2.tgz", + "integrity": "sha1-Nq8U5uofXWRtfESihXAcb4Wk/eQ=", + "dev": true, + "requires": { + "koa-is-json": "1", + "streaming-json-stringify": "3" + } + }, + "koa-morgan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/koa-morgan/-/koa-morgan-1.0.1.tgz", + "integrity": "sha1-CAUuDODYOdPEMXi5CluzQkvvH5k=", + "dev": true, + "requires": { + "morgan": "^1.6.1" + } + }, + "koa-range": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/koa-range/-/koa-range-0.3.0.tgz", + "integrity": "sha1-NYjjSWRzqDmhvSZNKkKx2FvX/qw=", + "dev": true, + "requires": { + "stream-slice": "^0.1.2" + } + }, + "koa-route": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/koa-route/-/koa-route-3.2.0.tgz", + "integrity": "sha1-dimLmaa8+p44yrb+XHmocz51i84=", + "dev": true, + "requires": { + "debug": "*", + "methods": "~1.1.0", + "path-to-regexp": "^1.2.0" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dev": true, + "requires": { + "isarray": "0.0.1" + } + } + } + }, + "koa-send": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/koa-send/-/koa-send-5.0.1.tgz", + "integrity": "sha512-tmcyQ/wXXuxpDxyNXv5yNNkdAMdFRqwtegBXUaowiQzUKqJehttS0x2j0eOZDQAyloAth5w6wwBImnFzkUz3pQ==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "http-errors": "^1.7.3", + "resolve-path": "^1.4.0" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "koa-static": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/koa-static/-/koa-static-5.0.0.tgz", + "integrity": "sha512-UqyYyH5YEXaJrf9S8E23GoJFQZXkBVJ9zYYMPGz919MSX1KuvAcycIuS0ci150HCoPf4XQVhQ84Qf8xRPWxFaQ==", + "dev": true, + "requires": { + "debug": "^3.1.0", + "koa-send": "^5.0.0" + } + }, + "load-module": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/load-module/-/load-module-3.0.0.tgz", + "integrity": "sha512-ZqprfrTx4vfH5+1mgpspPh5JYsNyA193NkMUdb3GwpmVqMczOh8cUDJgZBmEZVlSR42JBGYTUxlBAX9LHIBtIA==", + "dev": true, + "requires": { + "array-back": "^4.0.1" + } + }, + "local-web-server": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/local-web-server/-/local-web-server-4.2.1.tgz", + "integrity": "sha512-v71LZool2w7uYA+tDP5HhfjzUxz5SFfcrPPB/zC98yFFawt7A6fcmAr2MR4Q9AHk/A8oyd/wrhEJBJLndwHxNQ==", + "dev": true, + "requires": { + "lws": "^3.1.0", + "lws-basic-auth": "^2.0.0", + "lws-blacklist": "^3.0.0", + "lws-body-parser": "^2.0.0", + "lws-compress": "^2.0.0", + "lws-conditional-get": "^2.0.0", + "lws-cors": "^3.0.0", + "lws-index": "^2.0.0", + "lws-json": "^2.0.0", + "lws-log": "^2.0.0", + "lws-mime": "^2.0.0", + "lws-range": "^3.0.0", + "lws-request-monitor": "^2.0.0", + "lws-rewrite": "^3.1.1", + "lws-spa": "^3.0.0", + "lws-static": "^2.0.0", + "node-version-matches": "^2.0.1" + } + }, + "lodash": { + "version": "4.17.19", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", + "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==", + "dev": true + }, + "lodash.assignwith": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.assignwith/-/lodash.assignwith-4.2.0.tgz", + "integrity": "sha1-EnqX8CrcQXUalU0ksN4X4QDgOOs=", + "dev": true + }, + "lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=", + "dev": true + }, + "lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=", + "dev": true + }, + "lunr": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.8.tgz", + "integrity": "sha512-oxMeX/Y35PNFuZoHp+jUj5OSEmLCaIH4KTFJh7a93cHBoFmpw2IoPs22VIz7vyO2YUnx2Tn9dzIwO2P/4quIRg==", + "dev": true + }, + "lws": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lws/-/lws-3.1.0.tgz", + "integrity": "sha512-I8rTgZxz8OJL0hjdlDxs6WpcVG7WSyalVHPQXXK+WPNVjm3KhkT5gV0Qmsgm2FRLbRUp15tso80xmDxMsyt7zA==", + "dev": true, + "requires": { + "ansi-escape-sequences": "^5.1.2", + "array-back": "^4.0.1", + "byte-size": "^6.2.0", + "command-line-args": "^5.1.1", + "command-line-usage": "^6.1.0", + "create-mixin": "^3.0.0", + "koa": "^2.11.0", + "load-module": "^3.0.0", + "lodash.assignwith": "^4.2.0", + "node-version-matches": "^2.0.1", + "open": "^7.0.4", + "qrcode-terminal": "^0.12.0", + "reduce-flatten": "^3.0.0", + "typical": "^6.0.0", + "walk-back": "^4.0.0" + } + }, + "lws-basic-auth": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lws-basic-auth/-/lws-basic-auth-2.0.0.tgz", + "integrity": "sha512-zzyoGFLQPuKaQJvHMLmmSyfT6lIvocwcDXllTVW5brD0t0YgHYopILkzja+x+MIlJX/YhNKniaTSasujniYVjw==", + "dev": true, + "requires": { + "basic-auth": "^2.0.1" + } + }, + "lws-blacklist": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lws-blacklist/-/lws-blacklist-3.0.0.tgz", + "integrity": "sha512-KNXGDBmbj+UGfWMBAefe2vrfuWpEQms/9Fd7kfMScTqAKF6nrVoEs4pkxfefArG3bX0bu7jWLyB4tJGma5WC6Q==", + "dev": true, + "requires": { + "array-back": "^4.0.1", + "path-to-regexp": "^6.1.0" + } + }, + "lws-body-parser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lws-body-parser/-/lws-body-parser-2.0.0.tgz", + "integrity": "sha512-QFDzln3sSdKWL9fVNWy2+ZmrKy/XaYRO0/FFB0MBrDCsNnzepeCD4I7rOOfyuphLn42yR8XUpWdcJ3Ii5aauRA==", + "dev": true, + "requires": { + "koa-bodyparser": "^4.2.1" + } + }, + "lws-compress": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lws-compress/-/lws-compress-2.0.0.tgz", + "integrity": "sha512-5qDXI9pukVYWm07WjAOfpItLXKtL8lCHvjmW4RiXULhTRJj1qqBjNcmqReyk8L7NLUKhc+8eqoDDJFKURQEp0w==", + "dev": true, + "requires": { + "koa-compress": "^3.0.0" + } + }, + "lws-conditional-get": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lws-conditional-get/-/lws-conditional-get-2.0.0.tgz", + "integrity": "sha512-U05yDlFJKIYa7gJZYfnc1HIEuXbKpDJztgkvNYyxCqJC28j/k9ORoNnFNOIHpBh/jlPJgV8x7uH34mIxFAryWA==", + "dev": true, + "requires": { + "koa-conditional-get": "^2.0.0", + "koa-etag": "^3.0.0" + } + }, + "lws-cors": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lws-cors/-/lws-cors-3.0.0.tgz", + "integrity": "sha512-diUkoyVZyzLB8LamdtUYYAfJdPAyu/+IjE3ZUcdnNQz9koECe4O2x3SWD7LSV43pd3CKgyiwwSxWJ4hTBZFIvQ==", + "dev": true, + "requires": { + "@koa/cors": "^3.0.0" + } + }, + "lws-index": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lws-index/-/lws-index-2.0.0.tgz", + "integrity": "sha512-qfkeQmKYnd13LmQubzI5LtFV2N8PJQG4QvgSoefoiB3dWre9k2T4C7ajjOTKO8mgSzYpUEREduNcQcLyt62n0g==", + "dev": true, + "requires": { + "serve-index-75lb": "^2.0.1" + } + }, + "lws-json": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lws-json/-/lws-json-2.0.0.tgz", + "integrity": "sha512-vqUFrAQ5BGpkMS2Mm/ZhgvUMi6Tgia7YtESG7pKjNoiSsD+TxncG0nqp8YjUh2xrEzi/SYFc/ed+9ZOl/t0A0g==", + "dev": true, + "requires": { + "koa-json": "^2.0.2" + } + }, + "lws-log": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lws-log/-/lws-log-2.0.0.tgz", + "integrity": "sha512-YveoazSZ0Qb1Tljdm8G8yn9c+mAMXgvLMACZzh5aZIk7p8YJwiXf9r1S+xY7wbXEcKG629KfVO0B5G5gRFcyDQ==", + "dev": true, + "requires": { + "koa-morgan": "^1.0.1", + "stream-log-stats": "^3.0.2" + } + }, + "lws-mime": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lws-mime/-/lws-mime-2.0.0.tgz", + "integrity": "sha512-mfrAgRQ5+hkQ7LJ6EAgwnUeymNeYxwLXZY3UQ6C2hSTr7BqMSzm9k5O0C8wWP2dzdhChzITYKwzWbUnAYVBwtA==", + "dev": true + }, + "lws-range": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lws-range/-/lws-range-3.0.0.tgz", + "integrity": "sha512-7ZhA/LqQnKjolKBo/2BFj9DyDDXcJGY3v05TwYRD0qDGrxW4vuatEjluC3SV7ZO/k4PxDLdxuk+RCgL5t3ThtQ==", + "dev": true, + "requires": { + "koa-range": "^0.3.0" + } + }, + "lws-request-monitor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lws-request-monitor/-/lws-request-monitor-2.0.0.tgz", + "integrity": "sha512-ZTo0/pS42qiejcYlL+wlpurSbDSS0J7pDDohqBx7jjUQkgni2Qd8cPzn/kW8QI82gXgDmdZH+ps0vheLHlgdgg==", + "dev": true, + "requires": { + "byte-size": "^6.2.0" + } + }, + "lws-rewrite": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lws-rewrite/-/lws-rewrite-3.1.1.tgz", + "integrity": "sha512-cOeaPXIlLUVLxS6BZ52QzZVzI8JjCzlWD4RWizB5Hd+0YGO0SPa3Vgk7CIghtAOsSdjtXg/wSOap2H1h+tw8BQ==", + "dev": true, + "requires": { + "array-back": "^4.0.1", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "koa-route": "^3.2.0", + "path-to-regexp": "^6.1.0" + } + }, + "lws-spa": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lws-spa/-/lws-spa-3.0.0.tgz", + "integrity": "sha512-Tz10LfuOTUsRG6z+OCJ/vBN+4LQWoAGIJ1R02CFPrDk0pY3rHezM7/cCpq6Z6dXD+ipdNE8alkVn4zL2M+eVGg==", + "dev": true, + "requires": { + "koa-send": "^5.0.0" + } + }, + "lws-static": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lws-static/-/lws-static-2.0.0.tgz", + "integrity": "sha512-P25A0+IXdkB6Y6gZAG7X0mnaa+FJ8aTiWLUgM5kazaWmruRO7lyhSjitsA3y5TLI3DpPCZn0mWE4SRREujUZLg==", + "dev": true, + "requires": { + "koa-static": "^5.0.0" + } + }, + "marked": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-1.0.0.tgz", + "integrity": "sha512-Wo+L1pWTVibfrSr+TTtMuiMfNzmZWiOPeO7rZsQUY5bgsxpHesBEcIWJloWVTFnrMXnf/TL30eTFSGJddmQAng==", + "dev": true + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "dev": true + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "dev": true + }, + "mime-db": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", + "dev": true + }, + "mime-types": { + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", + "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "dev": true, + "requires": { + "mime-db": "1.44.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "morgan": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", + "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", + "dev": true, + "requires": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.0.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true + } + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "requires": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", + "dev": true + }, + "neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "node-version-matches": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/node-version-matches/-/node-version-matches-2.0.1.tgz", + "integrity": "sha512-oqk6+05FC0dNVY5NuXuhPEMq+m1b9ZjS9SIhVE9EjwCHZspnmjSO8npbKAEieinR8GeEgbecoQcYIvI/Kwcf6Q==", + "dev": true, + "requires": { + "semver": "^6.3.0" + } + }, + "nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "dev": true, + "requires": { + "boolbase": "~1.0.0" + } + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "only": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/only/-/only-0.0.2.tgz", + "integrity": "sha1-Kv3oTQPlC5qO3EROMGEKcCle37Q=", + "dev": true + }, + "open": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/open/-/open-7.1.0.tgz", + "integrity": "sha512-lLPI5KgOwEYCDKXf4np7y1PBEkj7HYIyP2DY8mVDRnx0VIIu6bNrRB0R66TuO7Mack6EnTNLm4uvcl1UoklTpA==", + "dev": true, + "requires": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + } + }, + "parse5": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz", + "integrity": "sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "path-to-regexp": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.1.0.tgz", + "integrity": "sha512-h9DqehX3zZZDCEm+xbfU0ZmwCGFCAAraPJWMXJ4+v32NjZJilVg3k1TcKsRgIb8IQ/izZSaydDc1OhJCZvs2Dw==", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, + "qrcode-terminal": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz", + "integrity": "sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ==", + "dev": true + }, + "qs": { + "version": "6.9.4", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.4.tgz", + "integrity": "sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ==", + "dev": true + }, + "raw-body": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.1.tgz", + "integrity": "sha512-9WmIKF6mkvA0SLmA2Knm9+qj89e+j1zqgyn8aXGd7+nAduPoqgI9lO57SAZNn/Byzo5P7JhXTyg9PzaJbH73bA==", + "dev": true, + "requires": { + "bytes": "3.1.0", + "http-errors": "1.7.3", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "dependencies": { + "http-errors": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", + "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + } + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "dev": true, + "requires": { + "resolve": "^1.1.6" + } + }, + "reduce-flatten": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-3.0.0.tgz", + "integrity": "sha512-eczl8wAYBxJ6Egl6I1ECIF+8z6sHu+KE7BzaEDZTpPXKXfy9SUDQlVYwkRcNTjJLC3Iakxbhss50KuT/R6SYfg==", + "dev": true + }, + "resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-path": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/resolve-path/-/resolve-path-1.4.0.tgz", + "integrity": "sha1-xL2p9e+y/OZSR4c6s2u02DT+Fvc=", + "dev": true, + "requires": { + "http-errors": "~1.6.2", + "path-is-absolute": "1.0.1" + }, + "dependencies": { + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + } + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "serve-index-75lb": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/serve-index-75lb/-/serve-index-75lb-2.0.1.tgz", + "integrity": "sha512-/d9r8bqJlFQcwy0a0nb1KnWAA+Mno+V+VaoKocdkbW5aXKRQd/+4bfnRhQRQr6uEoYwTRJ4xgztOyCJvWcpBpQ==", + "dev": true, + "requires": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.18", + "parseurl": "~1.3.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + } + } + }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", + "dev": true + }, + "shelljs": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.4.tgz", + "integrity": "sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ==", + "dev": true, + "requires": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + } + }, + "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 + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true + }, + "stream-log-stats": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/stream-log-stats/-/stream-log-stats-3.0.2.tgz", + "integrity": "sha512-393j7aeF9iRdHvyANqEQU82UQmpw2CTxgsT83caefh+lOxavVLbVrw8Mr4zjXeZLh2+xeHZMKfVx4T0rJ/EchA==", + "dev": true, + "requires": { + "JSONStream": "^1.3.5", + "ansi-escape-sequences": "^5.1.2", + "byte-size": "^6.2.0", + "common-log-format": "^1.0.0", + "lodash.throttle": "^4.1.1", + "stream-via": "^1.0.4", + "table-layout": "~1.0.0" + } + }, + "stream-slice": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/stream-slice/-/stream-slice-0.1.2.tgz", + "integrity": "sha1-LcT04bk2+xPz6zmi3vGTJ5jQeks=", + "dev": true + }, + "stream-via": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/stream-via/-/stream-via-1.0.4.tgz", + "integrity": "sha512-DBp0lSvX5G9KGRDTkR/R+a29H+Wk2xItOF+MpZLLNDWbEV9tGPnqLPxHEYjmiz8xGtJHRIqmI+hCjmNzqoA4nQ==", + "dev": true + }, + "streaming-json-stringify": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/streaming-json-stringify/-/streaming-json-stringify-3.1.0.tgz", + "integrity": "sha1-gCAEN6mTzDnE/gAmO3s7kDrIevU=", + "dev": true, + "requires": { + "json-stringify-safe": "5", + "readable-stream": "2" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": 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" + } + }, + "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==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "table-layout": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-1.0.1.tgz", + "integrity": "sha512-dEquqYNJiGwY7iPfZ3wbXDI944iqanTSchrACLL2nOB+1r+h1Nzu2eH+DuPPvWvm5Ry7iAPeFlgEtP5bIp5U7Q==", + "dev": true, + "requires": { + "array-back": "^4.0.1", + "deep-extend": "~0.6.0", + "typical": "^5.2.0", + "wordwrapjs": "^4.0.0" + }, + "dependencies": { + "typical": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", + "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", + "dev": true + } + } + }, + "thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "requires": { + "any-promise": "^1.0.0" + } + }, + "thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=", + "dev": true, + "requires": { + "thenify": ">= 3.1.0 < 4" + } + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "dev": true + }, + "tsscmp": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz", + "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==", + "dev": true + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "typedoc": { + "version": "0.17.8", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.17.8.tgz", + "integrity": "sha512-/OyrHCJ8jtzu+QZ+771YaxQ9s4g5Z3XsQE3Ma7q+BL392xxBn4UMvvCdVnqKC2T/dz03/VXSLVKOP3lHmDdc/w==", + "dev": true, + "requires": { + "fs-extra": "^8.1.0", + "handlebars": "^4.7.6", + "highlight.js": "^10.0.0", + "lodash": "^4.17.15", + "lunr": "^2.3.8", + "marked": "1.0.0", + "minimatch": "^3.0.0", + "progress": "^2.0.3", + "shelljs": "^0.8.4", + "typedoc-default-themes": "^0.10.2" + } + }, + "typedoc-default-themes": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/typedoc-default-themes/-/typedoc-default-themes-0.10.2.tgz", + "integrity": "sha512-zo09yRj+xwLFE3hyhJeVHWRSPuKEIAsFK5r2u47KL/HBKqpwdUSanoaz5L34IKiSATFrjG5ywmIu98hPVMfxZg==", + "dev": true, + "requires": { + "lunr": "^2.3.8" + } + }, + "typical": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/typical/-/typical-6.0.1.tgz", + "integrity": "sha512-+g3NEp7fJLe9DPa1TArHm9QAA7YciZmWnfAqEaFrBihQ7epOv9i99rjtgb6Iz0wh3WuQDjsCTDfgRoGnmHN81A==", + "dev": true + }, + "uWebSockets.js": { + "version": "github:uNetworking/uWebSockets.js#3dbec7b56d627193e20705844b6bd10e49848b8c", + "from": "github:uNetworking/uWebSockets.js#v18.4.0", + "dev": true + }, + "uglify-js": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.10.0.tgz", + "integrity": "sha512-Esj5HG5WAyrLIdYU74Z3JdG2PxdIusvj6IWHMtlyESxc7kcDz7zYlYjpnSokn1UbpV0d/QX9fan7gkCNd/9BQA==", + "dev": true, + "optional": true + }, + "underscore": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.10.2.tgz", + "integrity": "sha512-N4P+Q/BuyuEKFJ43B9gYuOj4TQUHXX+j2FqguVOpjkssLUUrnJofCcBccJSCoeturDoZU6GorDTHSvUDlSQbTg==", + "dev": true + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "dev": true + }, + "walk-back": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/walk-back/-/walk-back-4.0.0.tgz", + "integrity": "sha512-kudCA8PXVQfrqv2mFTG72vDBRi8BKWxGgFLwPpzHcpZnSwZk93WMwUDVcLHWNsnm+Y0AC4Vb6MUNRgaHfyV2DQ==", + "dev": true + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + }, + "wordwrapjs": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-4.0.0.tgz", + "integrity": "sha512-Svqw723a3R34KvsMgpjFBYCgNOSdcW3mQFK4wIfhGQhtaFVOJmdYoXgi63ne3dTlWgatVcUc7t4HtQ/+bUVIzQ==", + "dev": true, + "requires": { + "reduce-flatten": "^2.0.0", + "typical": "^5.0.0" + }, + "dependencies": { + "reduce-flatten": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-2.0.0.tgz", + "integrity": "sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w==", + "dev": true + }, + "typical": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", + "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", + "dev": true + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "ylru": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ylru/-/ylru-1.2.1.tgz", + "integrity": "sha512-faQrqNMzcPCHGVC2aaOINk13K+aaBDUPjGWl0teOXywElLjyVAB6Oe2jj62jHYtwsU49jXhScYbvPENK+6zAvQ==", + "dev": true + } + } +} diff --git a/src/chains/tezos/package.json b/src/chains/tezos/package.json new file mode 100644 index 0000000000..00a21f3240 --- /dev/null +++ b/src/chains/tezos/package.json @@ -0,0 +1,54 @@ +{ + "name": "@ganache/tezos", + "version": "0.1.0", + "description": "Ganache's Tezos client implementation", + "author": "David Murdoch (https://davidmurdoch.com)", + "homepage": "https://github.com/trufflesuite/ganache-core/tree/develop/src/chains/tezos#readme", + "license": "MIT", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "source": "src/index.ts", + "directories": { + "lib": "lib", + "test": "tests" + }, + "files": [ + "lib" + ], + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "https://github.com/trufflesuite/ganache-core.git", + "directory": "src/chains/tezos" + }, + "scripts": { + "tsc": "ttsc" + }, + "bugs": { + "url": "https://github.com/trufflesuite/ganache-core/issues" + }, + "keywords": [ + "ganache", + "ganache-tezos", + "tezos", + "blockchain", + "smart contracts", + "dapps", + "michelson", + "tooling" + ], + "dependencies": { + "@ganache/options": "^0.1.0", + "@ganache/utils": "^0.1.0", + "emittery": "0.7.2" + }, + "devDependencies": { + "@trufflesuite/typedoc-default-themes": "0.6.1", + "cheerio": "1.0.0-rc.3", + "local-web-server": "4.2.1", + "typedoc": "0.17.8", + "uWebSockets.js": "github:uNetworking/uWebSockets.js#v18.4.0" + } +} diff --git a/src/chains/tezos/scripts/post-process-docs.js b/src/chains/tezos/scripts/post-process-docs.js new file mode 100644 index 0000000000..ee20d63e8e --- /dev/null +++ b/src/chains/tezos/scripts/post-process-docs.js @@ -0,0 +1,21 @@ +const cheerio = require("cheerio"); +const { readFileSync, writeFileSync } = require("fs"); +const { randomBytes } = require("crypto"); +const $ = cheerio.load(readFileSync("./lib/docs/classes/_api_.tezosapi.html")); + +$(`.tsd-page-title`).after(``); + +$(".runkit-example").each(function () { + const sanitizedCode = $(this) + .text() + .replace(/;(\s+)/gi, ";\n") + .trim(); + $(this).text(""); + const id = randomBytes(4).toString("hex"); + $(this).attr("id", id).html(); + $(this).prepend( + `` + ); +}); + +writeFileSync("./lib/docs/classes/_api_.tezosapi.html", $.html()); diff --git a/src/chains/tezos/src/api.ts b/src/chains/tezos/src/api.ts new file mode 100644 index 0000000000..9133b66e63 --- /dev/null +++ b/src/chains/tezos/src/api.ts @@ -0,0 +1,9 @@ +import { types } from "@ganache/utils"; + +export default class TezosApi implements types.Api { + readonly [index: string]: (...args: any) => Promise; + + async version(): Promise { + return "just an example"; + } +} diff --git a/src/chains/tezos/src/provider.ts b/src/chains/tezos/src/provider.ts new file mode 100644 index 0000000000..22450f7273 --- /dev/null +++ b/src/chains/tezos/src/provider.ts @@ -0,0 +1,18 @@ +import { types } from "@ganache/utils"; +import TezosApi from "./api"; +import Emittery from "emittery"; + +export default class TezosProvider + extends Emittery.Typed< + { request: types.RequestType }, + "ready" | "close" + > + implements types.Provider { + constructor(providerOptions?: any) { + super(); + this.emit("ready"); + } + public async close() { + this.emit("close"); + } +} diff --git a/src/chains/tezos/tests/index.test.ts b/src/chains/tezos/tests/index.test.ts new file mode 100644 index 0000000000..6ea406bc78 --- /dev/null +++ b/src/chains/tezos/tests/index.test.ts @@ -0,0 +1,7 @@ +"use strict"; + +const tezos = require(".."); + +describe("@ganache/tezos", () => { + it("needs tests"); +}); diff --git a/src/chains/tezos/tsconfig.json b/src/chains/tezos/tsconfig.json new file mode 100644 index 0000000000..25d5c3cf7e --- /dev/null +++ b/src/chains/tezos/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "outDir": "lib" + }, + "include": ["index.ts"] +} diff --git a/src/chains/tezos/typedoc.json b/src/chains/tezos/typedoc.json new file mode 100644 index 0000000000..e38eaed876 --- /dev/null +++ b/src/chains/tezos/typedoc.json @@ -0,0 +1,13 @@ +{ + "name": "@ganache/tezos", + "mode": "modules", + "module": "commonjs", + "theme": "../../../node_modules/@trufflesuite/typedoc-default-themes/bin/default/", + "includeDeclarations": false, + "ignoreCompilerErrors": true, + "preserveConstEnums": true, + "excludeExternals": true, + "categoryOrder": [], + "exclude": ["**/test/*"], + "target": "ES6" +} diff --git a/src/packages/cli/.npmignore b/src/packages/cli/.npmignore new file mode 100644 index 0000000000..115ab4f024 --- /dev/null +++ b/src/packages/cli/.npmignore @@ -0,0 +1,8 @@ +./index.ts +tests +.nyc_output +coverage +scripts +src +tsconfig.json +typedoc.json diff --git a/src/packages/cli/LICENSE b/src/packages/cli/LICENSE new file mode 100644 index 0000000000..39f3b14498 --- /dev/null +++ b/src/packages/cli/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2019-2020 Truffle Blockchain Group + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/packages/cli/README.md b/src/packages/cli/README.md new file mode 100644 index 0000000000..2f34bb6a47 --- /dev/null +++ b/src/packages/cli/README.md @@ -0,0 +1,3 @@ +# `@ganache/cli` + +> TODO: description diff --git a/src/packages/cli/args.ts b/src/packages/cli/args.ts new file mode 100644 index 0000000000..81cd18069c --- /dev/null +++ b/src/packages/cli/args.ts @@ -0,0 +1,258 @@ +import yargs from "yargs"; + +export default function (version: string, isDocker: boolean) { + return yargs + .strict() + .option("p", { + group: "Network:", + alias: "port", + type: "number", + default: 8545, + describe: "Port number to listen on" + }) + .option("h", { + group: "Network:", + alias: ["host", "hostname"], + type: "string", + default: isDocker ? "0.0.0.0" : "127.0.0.1", + describe: "Hostname to listen on" + }) + .option("keepAliveTimeout", { + group: "Network:", + type: "number", + default: 5000, + describe: + "The number of milliseconds of inactivity a server needs to wait for additional incoming data, after it has finished writing the last response, before a socket will be destroyed." + }) + .option("a", { + group: "Accounts:", + alias: "accounts", + describe: "Number of accounts to generate at startup", + type: "number", + default: 10 + }) + .option("e", { + group: "Accounts:", + alias: "defaultBalanceEther", + describe: "Amount of ether to assign each test account", + type: "number", + default: 100.0 + }) + .option("account", { + group: "Accounts:", + describe: + "Account data in the form ',', can be specified multiple times. Note that private keys are 64 characters long and must be entered as an 0x-prefixed hex string. Balance can either be input as an integer, or as a 0x-prefixed hex string with either form specifying the initial balance in wei.", + type: "array", + string: true, + demandOption: false + }) + .option("account_keys_path", { + group: "Accounts:", + alias: "acctKeys", + type: "string", + describe: + "saves generated accounts and private keys as JSON object in specified file", + normalize: true, + demandOption: false, + default: null + }) + .option("n", { + group: "Accounts:", + alias: "secure", + describe: + "Lock available accounts by default (good for third party transaction signing)", + type: "boolean", + default: false + }) + .option("u", { + group: "Accounts:", + alias: "unlock", + type: "array", + string: true, + describe: "Comma-separated list of accounts or indices to unlock", + demandOption: false + }) + .option("k", { + group: "Chain:", + alias: "hardfork", + type: "string", + describe: + "Allows users to specify which hardfork should be used. Supported hardforks are `byzantium`, `constantinople`, `petersburg`, `istanbul` and `muirGlacier` (default).", + default: "muirGlacier" + }) + .option("f", { + group: "Chain:", + alias: "fork", + type: "string", + describe: + "Fork from another currently running Ethereum client at a given block. Input should be the HTTP location and port of the other client, e.g. 'http://localhost:8545' or optionally provide a block number 'http://localhost:8545@1599200'", + default: false + }) + .option("forkCacheSize", { + group: "Chain:", + type: "number", + describe: + "The maximum size, in bytes, of the in-memory cache for queries on a chain fork. Defaults to `1_073_741_824` bytes (1 gigabyte). You can set this to `0` to disable caching (not recommended), or to `-1` for unlimited (will be limited by your node process).", + default: 1073741824 + }) + .option("db", { + group: "Chain:", + describe: "Directory of chain database; creates one if it doesn't exist", + type: "string", + normalize: true, + default: null + }) + .option("s", { + group: "Chain:", + alias: "seed", + type: "string", + describe: "Arbitrary data to generate the HD wallet mnemonic to be used", + defaultDescription: "Random value, unless -d is specified", + conflicts: "d", + demandOption: false + }) + .option("hdPath", { + group: "Accounts:", + alias: "hd_path", + describe: `The hierarchical deterministic path to use when generating accounts. Default: "m/44'/60'/0'/0/"`, + type: "string", + demandOption: false + }) + .option("d", { + group: "Chain:", + alias: "deterministic", + describe: + "Generate deterministic addresses based on a pre-defined mnemonic.", + conflicts: "s", + type: "boolean", + default: undefined, + demandOption: false + }) + .option("m", { + group: "Chain:", + alias: "mnemonic", + type: "string", + describe: + "bip39 mnemonic phrase for generating a PRNG seed, which is in turn used for hierarchical deterministic (HD) account generation", + demandOption: false + }) + .option("noVMErrorsOnRPCResponse", { + group: "Chain:", + describe: + "Do not transmit transaction failures as RPC errors. Enable this flag for error reporting behaviour which is compatible with other clients such as geth and Parity.", + type: "boolean", + default: true + }) + .option("b", { + group: "Chain:", + alias: "blockTime", + type: "number", + describe: + "Block time in seconds for automatic mining. Will instantly mine a new block for every transaction if option omitted. Avoid using unless your test cases require a specific mining interval.", + demandOption: false + }) + .option("i", { + group: "Chain:", + alias: "networkId", + type: "number", + describe: "The Network ID ganache-cli will use to identify itself.", + defaultDescription: + "System time at process start or Network ID of forked blockchain if configured.", + demandOption: false + }) + .option("chainId", { + group: "Chain:", + type: "number", + describe: + "The Chain ID ganache-cli will use for `eth_chainId` RPC and the `CHAINID` opcode.", + defaultDescription: + "For legacy reasons, the default is currently `1337` for `eth_chainId` RPC and `1` for the `CHAINID` opcode. This will be fixed in the next major version of ganache-cli and ganache-core!", + demandOption: false + }) + .option("g", { + group: "Chain:", + alias: "gasPrice", + describe: "The price of gas in wei", + type: "number", + default: 20000000000 + }) + .option("l", { + group: "Chain:", + alias: "gasLimit", + describe: "The block gas limit in wei", + type: "number", + default: 0x6691b7 + }) + .option("callGasLimit", { + group: "Chain:", + describe: + 'Sets the transaction gas limit for `eth_call` and `eth_estimateGas` calls. Must be specified as a hex string. Defaults to "0x1fffffffffffff" (Number.MAX_SAFE_INTEGER)', + type: "number", + default: 0x1fffffffffffff + }) + .option("allowUnlimitedContractSize", { + group: "Chain:", + describe: + "Allows unlimited contract sizes while debugging. By enabling this flag, the check within the EVM for contract size limit of 24KB (see EIP-170) is bypassed. Enabling this flag *will* cause ganache-cli to behave differently than production environments.", + type: "boolean", + default: false + }) + .option("t", { + group: "Chain:", + alias: "time", + describe: + "Date (ISO 8601) that the first block should start. Use this feature, along with the evm_increaseTime method to test time-dependent code.", + type: "string", + coerce: arg => { + let timestamp = Date.parse(arg); + if (isNaN(timestamp)) { + throw new Error("Invalid 'time' format"); + } + return new Date(timestamp); + } + }) + .option("debug", { + group: "Other:", + describe: "Output VM opcodes for debugging", + type: "boolean", + default: false + }) + .option("v", { + group: "Other:", + alias: "verbose", + describe: "Log all requests and responses to stdout", + type: "boolean", + default: false + }) + .option("mem", { + group: "Other:", + describe: "Only show memory output, not tx history", + type: "boolean", + default: false + }) + .option("q", { + group: "Other:", + alias: "quiet", + describe: "Run ganache quietly (no logs)", + type: "boolean", + default: false + }) + .showHelpOnFail(false, "Specify -? or --help for available options") + .help("help") + .alias("help", "?") + .wrap(Math.min(120, yargs.terminalWidth())) + .version(version) + .check(argv => { + if (argv.p < 1 || argv.p > 65535) { + throw new Error(`Invalid port number '${argv.p}'`); + } + + if (argv.h.trim() == "") { + throw new Error( + "Cannot leave hostname blank; please provide a hostname" + ); + } + + return true; + }); +} diff --git a/src/packages/cli/cli.ts b/src/packages/cli/cli.ts new file mode 100644 index 0000000000..45f39e01ce --- /dev/null +++ b/src/packages/cli/cli.ts @@ -0,0 +1,261 @@ +#!/usr/bin/env node + +import Ganache from "./index"; +import { $INLINE_JSON } from "ts-transformer-inline-file"; +import { toChecksumAddress } from "ethereumjs-util"; +import args from "./args"; + +const { version: ganacheVersion } = $INLINE_JSON("../core/package.json"); +const { version } = $INLINE_JSON("./package.json"); +const detailedVersion = + "Ganache CLI v" + version + " (ganache-core: " + ganacheVersion + ")"; + +const isDocker = + "DOCKER" in process.env && process.env.DOCKER.toLowerCase() === "true"; + +const argv = args(detailedVersion, isDocker).argv; + +function parseAccounts(accounts: string[]) { + function splitAccount(account: string) { + const accountParts = account.split(","); + return { + secretKey: accountParts[0], + balance: accountParts[1] + }; + } + + if (typeof accounts === "string") return [splitAccount(accounts)]; + else if (!Array.isArray(accounts)) return; + + var ret = []; + for (var i = 0; i < accounts.length; i++) { + ret.push(splitAccount(accounts[i])); + } + return ret; +} + +if (argv.d) { + argv.s = "TestRPC is awesome!"; // Seed phrase; don't change to Ganache, maintain original determinism +} + +if (typeof argv.unlock == "string") { + argv.unlock = [argv.unlock]; +} + +let logger: { + log: (message?: any, ...optionalParams: any[]) => void; +} = console; + +// If quiet argument passed, no output +if (argv.q === true) { + logger = { + log: function () {} + }; +} + +// If the mem argument is passed, only show memory output, +// not transaction history. +if (argv.mem === true) { + logger = { + log: function () {} + }; + + setInterval(function () { + console.log(process.memoryUsage()); + }, 1000); +} + +var options = { + wallet: { + accountKeysPath: argv.account_keys_path, + mnemonic: argv.m, + seed: argv.s, + totalAccounts: argv.a, + defaultBalance: argv.e, + accounts: parseAccounts(argv.account), + unlockedAccounts: argv.unlock, + secure: argv.n, + hdPath: argv.hdPath + } as any, // any type this because we just pass whatever the user gives us + logging: { + debug: argv.debug, + verbose: argv.v, + logger: logger + }, + miner: { + blockTime: argv.b, + gasPrice: argv.g, + blockGasLimit: argv.l, + callGasLimit: argv.callGasLimit + }, + // forking: { + // fork: argv.f, + // forkCacheSize: argv.forkCacheSize + // }, // TODO + chain: { + hardfork: argv.k, + networkId: argv.i, + vmErrorsOnRPCResponse: !argv.noVMErrorsOnRPCResponse, + allowUnlimitedContractSize: argv.allowUnlimitedContractSize, + time: argv.t, + chainId: argv.chainId, + keepAliveTimeout: argv.keepAliveTimeout + } as any, + database: { + dbPath: argv.db + } +}; + +const server = Ganache.server(options); + +console.log(detailedVersion); + +let started = false; +process.on("uncaughtException", function (e) { + if (started) { + console.log(e); + } else { + console.log(e.stack); + } + process.exit(1); +}); + +// See http://stackoverflow.com/questions/10021373/what-is-the-windows-equivalent-of-process-onsigint-in-node-js +if (process.platform === "win32") { + require("readline") + .createInterface({ + input: process.stdin, + output: process.stdout + }) + .on("SIGINT", function () { + process.emit("SIGINT" as any); // TODO: don't abuse process's emit + }); +} + +const closeHandler = async function () { + // graceful shutdown + try { + await server.close(); + process.exit(0); + } catch (err) { + // https://nodejs.org/api/process.html#process_process_exit_code + // writes to process.stdout in Node.js are sometimes asynchronous and may occur over + // multiple ticks of the Node.js event loop. Calling process.exit(), however, forces + // the process to exit before those additional writes to stdout can be performed. + if ((process.stdout as any)._handle) + (process.stdout as any).setBlocking(true); + console.log(err.stack || err); + process.exit(); + } +}; + +process.on("SIGINT", closeHandler); +process.on("SIGTERM", closeHandler); +process.on("SIGHUP", closeHandler); + +async function startGanache(err) { + if (err) { + console.log(err); + return; + } + started = true; + const liveOptions = server.provider.getOptions(); + const accounts = server.provider.getInitialAccounts(); + + console.log(""); + console.log("Available Accounts"); + console.log("=================="); + + var addresses = Object.keys(accounts); + var ethInWei = 1000000000000000000n; + + addresses.forEach(function (address, index) { + var balance = accounts[address].balance; + var strBalance = balance / ethInWei; + var about = balance % ethInWei === 0n ? "" : "~"; + var line = `(${index}) ${toChecksumAddress( + address + )} (${about}${strBalance} ETH)`; + + if (!accounts[address].unlocked) { + line += " 🔒"; + } + + console.log(line); + }); + + console.log(""); + console.log("Private Keys"); + console.log("=================="); + + addresses.forEach(function (address, index) { + console.log("(" + index + ") " + accounts[address].secretKey); + }); + + if (options.wallet.accountKeysPath != null) { + console.log(""); + console.log("Accounts and keys saved to " + options.wallet.accountKeysPath); + } + + if (argv.a == null) { + console.log(""); + console.log("HD Wallet"); + console.log("=================="); + console.log("Mnemonic: " + liveOptions.wallet.mnemonic); + console.log( + "Base HD Path: " + liveOptions.wallet.hdPath + "{account_index}" + ); + } + + if (liveOptions.miner.gasPrice) { + console.log(""); + console.log("Gas Price"); + console.log("=================="); + console.log(liveOptions.miner.gasPrice.toBigInt()); + } + + if (liveOptions.miner.blockGasLimit) { + console.log(""); + console.log("BlockGas Limit"); + console.log("=================="); + console.log(liveOptions.miner.blockGasLimit.toBigInt()); + } + + if (liveOptions.miner.callGasLimit) { + console.log(""); + console.log("Call Gas Limit"); + console.log("=================="); + console.log(liveOptions.miner.callGasLimit.toBigInt()); + } + + // if (options.fork) { + // console.log(""); + // console.log("Forked Chain"); + // console.log("=================="); + // console.log("Location: " + state.blockchain.options.fork); + // console.log( + // "Block: " + to.number(state.blockchain.forkBlockNumber) + // ); + // console.log("Network ID: " + state.net_version); + // console.log( + // "Time: " + (state.blockchain.startTime || new Date()).toString() + // ); + // let maxCacheSize; + // if (options.forkCacheSize === -1) { + // maxCacheSize = "∞"; + // } else { + // maxCacheSize = options.forkCacheSize + " bytes"; + // } + // console.log("Max Cache Size: " + maxCacheSize); + // } + + console.log(""); + console.log("Chain Id"); + console.log("=================="); + console.log(liveOptions.chain.chainId); + + console.log(""); + console.log("Listening on " + argv.h + ":" + argv.p); +} + +server.listen(argv.p, argv.h, startGanache); diff --git a/src/packages/cli/index.ts b/src/packages/cli/index.ts new file mode 100644 index 0000000000..526cebbc45 --- /dev/null +++ b/src/packages/cli/index.ts @@ -0,0 +1,3 @@ +import Ganache from "@ganache/core"; +export { ServerOptions, ProviderOptions } from "@ganache/core"; +export default Ganache; diff --git a/src/packages/cli/npm-shrinkwrap.json b/src/packages/cli/npm-shrinkwrap.json new file mode 100644 index 0000000000..fe02034501 --- /dev/null +++ b/src/packages/cli/npm-shrinkwrap.json @@ -0,0 +1,618 @@ +{ + "name": "@ganache/cli", + "version": "0.1.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@types/bn.js": { + "version": "4.11.6", + "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-4.11.6.tgz", + "integrity": "sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==", + "requires": { + "@types/node": "*" + } + }, + "@types/node": { + "version": "14.14.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.6.tgz", + "integrity": "sha512-6QlRuqsQ/Ox/aJEQWBEJG7A9+u7oSYl3mem/K8IzxXG/kAGbV1YPD9Bg9Zw3vyxC/YP+zONKwy8hGkSt1jxFMw==" + }, + "@types/pbkdf2": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/pbkdf2/-/pbkdf2-3.1.0.tgz", + "integrity": "sha512-Cf63Rv7jCQ0LaL8tNXmEyqTHuIJxRdlS5vMh1mj5voN4+QFhVZnlZruezqpWYDiJ8UTzhP0VmeLXCmBk66YrMQ==", + "requires": { + "@types/node": "*" + } + }, + "@types/secp256k1": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/secp256k1/-/secp256k1-4.0.1.tgz", + "integrity": "sha512-+ZjSA8ELlOp8SlKi0YLB2tz9d5iPNEmOBd+8Rz21wTMdaXQIa9b6TEnD6l5qKOCypE7FSyPyck12qZJxSDNoog==", + "requires": { + "@types/node": "*" + } + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "base-x": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.8.tgz", + "integrity": "sha512-Rl/1AWP4J/zRrk54hhlxH4drNxPJXYUaKffODVI53/dAsV4t9fBxyxYKAVPU1XBHxYwOWP9h9H0hM2MVw4YfJA==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "blakejs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.1.0.tgz", + "integrity": "sha1-ad+S75U6qIylGjLfarHFShVfx6U=" + }, + "bn.js": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.3.tgz", + "integrity": "sha512-GkTiFpjFtUzU9CbMeJ5iazkCzGL3jrhzerzZIuqLABjbwRaFt33I9tUdSNryIptM+RxDet6OKm2WnLXzW51KsQ==" + }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" + }, + "browserify-aes": { + "version": "1.2.0", + "resolved": "https://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" + } + }, + "bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha1-vhYedsNU9veIrkBx9j806MTwpCo=", + "requires": { + "base-x": "^3.0.2" + } + }, + "bs58check": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz", + "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", + "requires": { + "bs58": "^4.0.0", + "create-hash": "^1.1.0", + "safe-buffer": "^5.1.2" + } + }, + "buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=" + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "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" + } + }, + "clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", + "dev": true + }, + "clone-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", + "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=", + "dev": true + }, + "clone-stats": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", + "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", + "dev": true + }, + "cloneable-readable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz", + "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "process-nextick-args": "^2.0.0", + "readable-stream": "^2.3.5" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": 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" + } + }, + "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==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "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 + }, + "create-hash": { + "version": "1.2.0", + "resolved": "https://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": "https://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" + } + }, + "elliptic": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz", + "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==", + "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" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==" + } + } + }, + "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 + }, + "ethereum-cryptography": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", + "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", + "requires": { + "@types/pbkdf2": "^3.0.0", + "@types/secp256k1": "^4.0.1", + "blakejs": "^1.1.0", + "browserify-aes": "^1.2.0", + "bs58check": "^2.1.2", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "hash.js": "^1.1.7", + "keccak": "^3.0.0", + "pbkdf2": "^3.0.17", + "randombytes": "^2.1.0", + "safe-buffer": "^5.1.2", + "scrypt-js": "^3.0.0", + "secp256k1": "^4.0.1", + "setimmediate": "^1.0.5" + } + }, + "ethereumjs-util": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.0.7.tgz", + "integrity": "sha512-vU5rtZBlZsgkTw3o6PDKyB8li2EgLavnAbsKcfsH2YhHH1Le+PP8vEiMnAnvgc1B6uMoaM5GDCrVztBw0Q5K9g==", + "requires": { + "@types/bn.js": "^4.11.3", + "bn.js": "^5.1.2", + "create-hash": "^1.1.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "0.1.6", + "rlp": "^2.2.4" + } + }, + "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" + } + }, + "google-closure-compiler": { + "version": "20201102.0.1", + "resolved": "https://registry.npmjs.org/google-closure-compiler/-/google-closure-compiler-20201102.0.1.tgz", + "integrity": "sha512-Cz+1jOswH0MwMVPu1rRH1xD4KYuY5XW2ox5aXwqaAxevqmirhr36f8wgKPHuVRSovFejW640r6UFwyrOT6U0CA==", + "dev": true, + "requires": { + "chalk": "2.x", + "google-closure-compiler-java": "^20201102.0.1", + "google-closure-compiler-linux": "^20201102.0.1", + "google-closure-compiler-osx": "^20201102.0.1", + "google-closure-compiler-windows": "^20201102.0.1", + "minimist": "1.x", + "vinyl": "2.x", + "vinyl-sourcemaps-apply": "^0.2.0" + } + }, + "google-closure-compiler-java": { + "version": "20201102.0.1", + "resolved": "https://registry.npmjs.org/google-closure-compiler-java/-/google-closure-compiler-java-20201102.0.1.tgz", + "integrity": "sha512-pXJIlyqepHhih0HCbShkAZJyViIxdyd4V7MnCUZEXLIIlygw92e2dC+5XiONDQZgRlF93BPmWCy9jr7wYoW1hQ==", + "dev": true + }, + "google-closure-compiler-linux": { + "version": "20201102.0.1", + "resolved": "https://registry.npmjs.org/google-closure-compiler-linux/-/google-closure-compiler-linux-20201102.0.1.tgz", + "integrity": "sha512-aRbyTGnQoFXchcpEFNrP1p/WIvYOgN3hYKI+MOHWkvwVJBY2P8gpb07hAigO8fj+QKD/SFCl+2pXP+JniWOEqw==", + "dev": true, + "optional": true + }, + "google-closure-compiler-osx": { + "version": "20201102.0.1", + "resolved": "https://registry.npmjs.org/google-closure-compiler-osx/-/google-closure-compiler-osx-20201102.0.1.tgz", + "integrity": "sha512-VguqEAOYI6XYZN6JcLMP8fpsoXk1Z9YuntMjv0IDVydkbZaHYOI4zE39FJhMuWiN7gOzSX2b/BBC6GsSh1F3fw==", + "dev": true, + "optional": true + }, + "google-closure-compiler-windows": { + "version": "20201102.0.1", + "resolved": "https://registry.npmjs.org/google-closure-compiler-windows/-/google-closure-compiler-windows-20201102.0.1.tgz", + "integrity": "sha512-LlynipQi/iP76mjkOu6Rc1mCRuxTAhRvLjq10aGfVjKwpbCAF0Jq2a5k2ygr4xYiINNi2/L2qUw6ObPm9wQCOw==", + "dev": true, + "optional": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "hash-base": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", + "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", + "requires": { + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + } + }, + "hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "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" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "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=" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "keccak": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.1.tgz", + "integrity": "sha512-epq90L9jlFWCW7+pQa6JOnKn2Xgl2mtI664seYR6MHskvI9agt7AnDqmAlp9TqU4/caMYbA08Hi5DMZAl5zdkA==", + "requires": { + "node-addon-api": "^2.0.0", + "node-gyp-build": "^4.2.0" + } + }, + "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" + } + }, + "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=" + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "node-addon-api": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", + "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==" + }, + "node-gyp-build": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.2.3.tgz", + "integrity": "sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg==" + }, + "pbkdf2": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz", + "integrity": "sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg==", + "requires": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "replace-ext": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.1.tgz", + "integrity": "sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==", + "dev": true + }, + "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.2.6", + "resolved": "https://registry.npmjs.org/rlp/-/rlp-2.2.6.tgz", + "integrity": "sha512-HAfAmL6SDYNWPUOJNrM500x4Thn4PZsEy5pijPh40U9WfNk0z15hUYzO9xVIMAdIHdFtD8CBDHd75Td1g36Mjg==", + "requires": { + "bn.js": "^4.11.1" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==" + } + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "scrypt-js": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", + "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==" + }, + "secp256k1": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.2.tgz", + "integrity": "sha512-UDar4sKvWAksIlfX3xIaQReADn+WFnHvbVujpcbr+9Sf/69odMwy2MUsz5CKLQgX9nsIyrjuxL2imVyoNHa3fg==", + "requires": { + "elliptic": "^6.5.2", + "node-addon-api": "^2.0.0", + "node-gyp-build": "^4.2.0" + } + }, + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" + }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://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" + } + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "requires": { + "safe-buffer": "~5.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": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "vinyl": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", + "integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==", + "dev": true, + "requires": { + "clone": "^2.1.1", + "clone-buffer": "^1.0.0", + "clone-stats": "^1.0.0", + "cloneable-readable": "^1.0.0", + "remove-trailing-separator": "^1.0.1", + "replace-ext": "^1.0.0" + } + }, + "vinyl-sourcemaps-apply": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.2.1.tgz", + "integrity": "sha1-q2VJ1h0XLCsbh75cUI0jnI74dwU=", + "dev": true, + "requires": { + "source-map": "^0.5.1" + }, + "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 + } + } + } + } +} diff --git a/src/packages/cli/package.json b/src/packages/cli/package.json new file mode 100644 index 0000000000..529a50f8fd --- /dev/null +++ b/src/packages/cli/package.json @@ -0,0 +1,58 @@ +{ + "name": "@ganache/cli", + "version": "0.1.0", + "description": "", + "author": "David Murdoch", + "homepage": "https://github.com/trufflesuite/ganache-core/tree/develop/src/packages/cli#readme", + "license": "MIT", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "source": "index.ts", + "bin": { + "ganache": "./lib/cli.js", + "ganache-cli": "./lib/cli.js" + }, + "directories": { + "lib": "lib", + "test": "tests" + }, + "files": [ + "lib" + ], + "repository": { + "type": "git", + "url": "https://github.com/trufflesuite/ganache-core.git", + "directory": "src/packages/cli" + }, + "scripts": { + "tsc": "ttsc", + "test": "nyc npm run mocha", + "mocha": "cross-env TS_NODE_COMPILER=ttypescript TS_NODE_FILES=true mocha --exit --check-leaks --throw-deprecation --trace-warnings --require ts-node/register 'tests/**/*.test.ts'" + }, + "bugs": { + "url": "https://github.com/trufflesuite/ganache-core/issues" + }, + "keywords": [ + "ganache", + "ganache-cli", + "ethereum", + "evm", + "blockchain", + "smart contracts", + "dapps", + "solidity", + "vyper", + "fe", + "web3", + "tooling", + "truffle" + ], + "devDependencies": { + "@ganache/core": "^0.1.0", + "google-closure-compiler": "20201102.0.1" + }, + "dependencies": { + "@types/node": "14.14.6", + "ethereumjs-util": "^7.0.7" + } +} diff --git a/src/packages/cli/tests/index.test.ts b/src/packages/cli/tests/index.test.ts new file mode 100644 index 0000000000..e853595762 --- /dev/null +++ b/src/packages/cli/tests/index.test.ts @@ -0,0 +1,6 @@ +import assert from "assert"; +import cli from ".."; + +describe("@ganache/cli", () => { + it("needs tests"); +}); diff --git a/src/packages/cli/tsconfig.json b/src/packages/cli/tsconfig.json new file mode 100644 index 0000000000..081b4a87ef --- /dev/null +++ b/src/packages/cli/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "outDir": "lib" + }, + "include": ["index.ts", "cli.ts"] +} diff --git a/src/packages/core/.npmignore b/src/packages/core/.npmignore new file mode 100644 index 0000000000..115ab4f024 --- /dev/null +++ b/src/packages/core/.npmignore @@ -0,0 +1,8 @@ +./index.ts +tests +.nyc_output +coverage +scripts +src +tsconfig.json +typedoc.json diff --git a/src/packages/core/LICENSE b/src/packages/core/LICENSE new file mode 100644 index 0000000000..39f3b14498 --- /dev/null +++ b/src/packages/core/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2019-2020 Truffle Blockchain Group + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/packages/core/README.md b/src/packages/core/README.md new file mode 100644 index 0000000000..9ae51677af --- /dev/null +++ b/src/packages/core/README.md @@ -0,0 +1,5 @@ +# `@ganache/core` + +The `ganache-core` npm package. + +See [README.md](../../../README.md) for info. diff --git a/src/packages/core/index.ts b/src/packages/core/index.ts new file mode 100644 index 0000000000..debc6e2150 --- /dev/null +++ b/src/packages/core/index.ts @@ -0,0 +1,11 @@ +import Connector from "./src/connector"; +import { ProviderOptions, ServerOptions } from "./src/options"; +import Server from "./src/server"; + +export { ProviderOptions, ServerOptions } from "./src/options"; + +export default { + server: (options?: ServerOptions) => new Server(options), + provider: (options?: ProviderOptions) => + Connector.initialize(options).provider +}; diff --git a/src/packages/core/npm-shrinkwrap.json b/src/packages/core/npm-shrinkwrap.json new file mode 100644 index 0000000000..d73c4cc56d --- /dev/null +++ b/src/packages/core/npm-shrinkwrap.json @@ -0,0 +1,207 @@ +{ + "name": "@ganache/core", + "version": "0.1.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@types/cookiejar": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.2.tgz", + "integrity": "sha512-t73xJJrvdTjXrn4jLS9VSGRbz0nUY3cl2DMGDU48lKl+HR9dbbjW2A9r3g40VA++mQpy6uuHg33gy7du2BKpog==", + "dev": true + }, + "@types/node": { + "version": "14.14.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.6.tgz", + "integrity": "sha512-6QlRuqsQ/Ox/aJEQWBEJG7A9+u7oSYl3mem/K8IzxXG/kAGbV1YPD9Bg9Zw3vyxC/YP+zONKwy8hGkSt1jxFMw==", + "dev": true + }, + "@types/superagent": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-4.1.10.tgz", + "integrity": "sha512-xAgkb2CMWUMCyVc/3+7iQfOEBE75NvuZeezvmixbUw3nmENf2tCnQkW5yQLTYqvXUQ+R6EXxdqKKbal2zM5V/g==", + "dev": true, + "requires": { + "@types/cookiejar": "*", + "@types/node": "*" + } + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, + "cookiejar": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz", + "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==", + "dev": true + }, + "debug": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", + "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "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 + }, + "fast-safe-stringify": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz", + "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==", + "dev": true + }, + "form-data": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.0.tgz", + "integrity": "sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, + "formidable": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.2.tgz", + "integrity": "sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q==", + "dev": true + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "dev": true + }, + "mime": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", + "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==", + "dev": true + }, + "mime-db": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", + "dev": true + }, + "mime-types": { + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", + "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "dev": true, + "requires": { + "mime-db": "1.44.0" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "qs": { + "version": "6.9.4", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.4.tgz", + "integrity": "sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ==", + "dev": true + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", + "dev": true + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "superagent": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-6.1.0.tgz", + "integrity": "sha512-OUDHEssirmplo3F+1HWKUrUjvnQuA+nZI6i/JJBdXb5eq9IyEQwPyPpqND+SSsxf6TygpBEkUjISVRN4/VOpeg==", + "dev": true, + "requires": { + "component-emitter": "^1.3.0", + "cookiejar": "^2.1.2", + "debug": "^4.1.1", + "fast-safe-stringify": "^2.0.7", + "form-data": "^3.0.0", + "formidable": "^1.2.2", + "methods": "^1.1.2", + "mime": "^2.4.6", + "qs": "^6.9.4", + "readable-stream": "^3.6.0", + "semver": "^7.3.2" + } + }, + "uWebSockets.js": { + "version": "github:uNetworking/uWebSockets.js#3dbec7b56d627193e20705844b6bd10e49848b8c", + "from": "github:uNetworking/uWebSockets.js#v18.4.0" + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "ws": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.1.tgz", + "integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA==", + "dev": true + } + } +} diff --git a/src/packages/core/package.json b/src/packages/core/package.json new file mode 100644 index 0000000000..8fdc8eb0c6 --- /dev/null +++ b/src/packages/core/package.json @@ -0,0 +1,61 @@ +{ + "name": "@ganache/core", + "version": "0.1.0", + "description": "A library to create a local blockchain for fast Ethereum development.", + "author": "David Murdoch (https://davidmurdoch.com)", + "homepage": "https://github.com/trufflesuite/ganache-core/tree/develop/src/packages/core#readme", + "license": "MIT", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "source": "index.ts", + "directories": { + "lib": "lib", + "test": "tests" + }, + "files": [ + "lib" + ], + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "https://github.com/trufflesuite/ganache-core.git", + "directory": "src/packages/core" + }, + "scripts": { + "tsc": "ttsc", + "test": "nyc npm run mocha", + "mocha": "cross-env TS_NODE_COMPILER=ttypescript TS_NODE_FILES=true mocha --exit --throw-deprecation --trace-warnings --check-leaks --require ts-node/register 'tests/**/*.test.ts'" + }, + "bugs": { + "url": "https://github.com/trufflesuite/ganache-core/issues" + }, + "keywords": [ + "ganache", + "ganache-core", + "ethereum", + "evm", + "blockchain", + "smart contracts", + "dapps", + "solidity", + "vyper", + "fe", + "web3", + "tooling" + ], + "dependencies": { + "@ganache/ethereum": "^0.1.0", + "@ganache/flavors": "^0.1.0", + "@ganache/options": "^0.1.0", + "@ganache/tezos": "^0.1.0", + "@ganache/utils": "^0.1.0", + "uWebSockets.js": "github:uNetworking/uWebSockets.js#v18.4.0" + }, + "devDependencies": { + "@types/superagent": "4.1.10", + "superagent": "6.1.0", + "ws": "7.3.1" + } +} diff --git a/src/packages/core/src/@types/superagent.d.ts b/src/packages/core/src/@types/superagent.d.ts new file mode 100644 index 0000000000..f1211cd567 --- /dev/null +++ b/src/packages/core/src/@types/superagent.d.ts @@ -0,0 +1,6 @@ +// superagent TS def has issues with these browser APIs not being available in +// node. +// error TS2304: Cannot find name 'XMLHttpRequest' +declare interface XMLHttpRequest {} +// error TS2304: Cannot find name 'Blob' +declare interface Blob {} diff --git a/src/packages/core/src/@types/uWebsockets.js.ts b/src/packages/core/src/@types/uWebsockets.js.ts new file mode 100644 index 0000000000..21eb947df8 --- /dev/null +++ b/src/packages/core/src/@types/uWebsockets.js.ts @@ -0,0 +1,23 @@ +import "uWebSockets.js"; + +enum ListenOptions { + LIBUS_LISTEN_DEFAULT = 0, + LIBUS_LISTEN_EXCLUSIVE_PORT = 1 +} +// uWebSockets.js doesn't include these in its TS def file. +declare module "uWebSockets.js" { + export interface TemplatedApp { + listen( + host: RecognizedString, + port: number, + options: ListenOptions, + cb: (listenSocket: us_listen_socket | false) => void + ): TemplatedApp; + + listen( + port: number, + options: ListenOptions, + cb: (listenSocket: us_listen_socket | false) => void + ): TemplatedApp; + } +} diff --git a/src/packages/core/src/connector.ts b/src/packages/core/src/connector.ts new file mode 100644 index 0000000000..d8203b89f2 --- /dev/null +++ b/src/packages/core/src/connector.ts @@ -0,0 +1,45 @@ +import { utils } from "@ganache/utils"; +import { ConnectorsByName, DefaultFlavor } from "@ganache/flavors"; +import { Options as ProviderOptions } from "@ganache/flavors"; +import { hasOwn } from "@ganache/utils/src/utils"; + +/** + * Loads the connector specified by the given `flavor` + */ +export default { + initialize: ( + providerOptions: ProviderOptions = { + flavor: DefaultFlavor, + chain: { asyncRequestProcessing: true } + } + ) => { + const flavor = providerOptions.flavor || DefaultFlavor; + + // Set up our request coordinator to either use FIFO or or async request processing. + // The RequestCoordinator _can_ be used to coordinate the number of requests being processed, but we don't use it + // for that (yet), instead of "all" (0) or just 1 as we are doing here: + const asyncRequestProcessing = + "chain" in providerOptions + ? providerOptions.chain.asyncRequestProcessing + : (providerOptions as any).asyncRequestProcessing; + const requestCoordinator = new utils.RequestCoordinator( + asyncRequestProcessing ? 0 : 1 + ); + + // The Executor is responsible for actually executing the method on the chain/API. + // It performs some safety checks to ensure "safe" method execution before passing it + // to a RequestCoordinator. + const executor = new utils.Executor(requestCoordinator); + + const connector = new ConnectorsByName[flavor]( + providerOptions as any, + executor + ); + + // The request coordinator is initialized in a "paused" state; when the provider is ready we unpause. + // This lets us accept queue requests before we've even fully initialized. + connector.on("ready", requestCoordinator.resume); + + return connector; + } +}; diff --git a/src/packages/core/src/options/index.ts b/src/packages/core/src/options/index.ts new file mode 100644 index 0000000000..b36205c05c --- /dev/null +++ b/src/packages/core/src/options/index.ts @@ -0,0 +1,36 @@ +import { Options as FlavorOptions } from "@ganache/flavors"; +import { ServerConfig, ServerOptions } from "./server-options"; +import { + Defaults, + Definitions, + ExternalConfig, + InternalConfig, + OptionsConfig +} from "@ganache/options"; + +export type ProviderOptions = FlavorOptions; + +export type Options = { + server: ServerConfig; +}; + +export type ServerOptions = Partial< + { + [K in keyof Options]: ExternalConfig; + } +> & + ProviderOptions; + +export type InternalOptions = { + [K in keyof Options]: InternalConfig; +}; + +export type ServerDefaults = { + [K in keyof Options]: Definitions; +}; + +export const serverDefaults: Defaults = { + server: ServerOptions +}; + +export const serverOptionsConfig = new OptionsConfig(serverDefaults); diff --git a/src/packages/core/src/options/server-options.ts b/src/packages/core/src/options/server-options.ts new file mode 100644 index 0000000000..02d2015bbf --- /dev/null +++ b/src/packages/core/src/options/server-options.ts @@ -0,0 +1,67 @@ +import { Definitions } from "@ganache/options"; + +export type ServerConfig = { + options: { + /** + * Enable a websocket server. This is `true` by default. + * + * @default true + */ + readonly ws: { + type: boolean; + hasDefault: true; + legacy: { + /** + * @deprecated Use server.ws instead. + */ + ws: boolean; + }; + }; + + /** + * Wether or not websockets should response with binary data (ArrayBuffers) or + * strings. + * + * Default is "auto", which responds using the same format as the incoming + * message that triggered the response. + * + * @default "auto" + */ + readonly wsBinary: { + type: boolean | "auto"; + hasDefault: true; + }; + + /** + * @obsolete Option removed in v3 + */ + readonly keepAliveTimeout: { + type: void; + legacy: { + /** + * @obsolete Option removed in v3 + */ + keepAliveTimeout: void; + }; + }; + }; +}; +const normalize = (rawInput: T) => rawInput; + +export const ServerOptions: Definitions = { + ws: { + normalize, + default: () => true, + legacyName: "ws" + }, + wsBinary: { + normalize, + default: () => "auto" + }, + keepAliveTimeout: { + normalize: () => { + throw new Error("`keepAliveTimeout` was removed in v3"); + }, + legacyName: "keepAliveTimeout" + } +}; diff --git a/src/packages/core/src/server.ts b/src/packages/core/src/server.ts new file mode 100644 index 0000000000..226297f28e --- /dev/null +++ b/src/packages/core/src/server.ts @@ -0,0 +1,172 @@ +import { InternalOptions, ServerOptions, serverOptionsConfig } from "./options"; + +import uWS, { TemplatedApp, us_listen_socket } from "uWebSockets.js"; +import { Connectors } from "@ganache/flavors"; +import Connector from "./connector"; +import WebsocketServer, { WebSocketCapableFlavor } from "./servers/ws-server"; +import HttpServer from "./servers/http-server"; + +type Providers = Connectors["provider"]; + +const DEFAULT_HOST = "127.0.0.1"; + +type Callback = (err: Error | null) => void; + +/** + * Server ready state constants. + * + * These are bit flags. This means that you can check if the status is: + * * open: `status === Status.open` + * * opening: `status === Status.opening` + * * open || opening: `status & Status.open !== 0` or `status & Status.opening !== 0` + * * closed: `status === Status.closed` + * * closing: `status === Status.closing` + * * open || closing: `status & Status.closed !== 0` or `status & Status.closing !== 0` + */ +export enum Status { + /** + * The connection is open and ready to communicate. + */ + open = 1, + /** + * The connection is not yet open. + */ + opening = 3, + /** + * The connection is closed. + */ + closed = 4, + /** + * The connection is in the process of closing. + */ + closing = 12 +} + +export default class Server { + #app: TemplatedApp; + #httpServer: HttpServer; + #listenSocket?: us_listen_socket; + #options: InternalOptions; + #connector: Connectors; + #status = Status.closed; + #websocketServer: WebsocketServer | null = null; + + public get provider(): Providers { + return this.#connector.provider; + } + + public get status() { + return this.#status; + } + + constructor(serverOptions: ServerOptions = { flavor: "ethereum" }) { + const opts = (this.#options = serverOptionsConfig.normalize(serverOptions)); + const connector = (this.#connector = Connector.initialize(serverOptions)); + + const _app = (this.#app = uWS.App()); + + if (this.#options.server.ws) { + this.#websocketServer = new WebsocketServer( + _app, + connector as WebSocketCapableFlavor, + opts.server + ); + } + this.#httpServer = new HttpServer(_app, connector); + } + + listen(port: number): Promise; + listen(port: number, host: string): Promise; + listen(port: number, callback: Callback): void; + listen(port: number, host: string, callback: Callback): void; + listen( + port: number, + host?: string | Callback, + callback?: Callback + ): void | Promise { + let hostname: string = null; + if (typeof host === "function") { + callback = host; + hostname = null; + } + const callbackIsFunction = typeof callback === "function"; + const status = this.#status; + if (status === Status.closing) { + // if closing + const err = new Error(`Cannot start server while it is closing.`); + return callbackIsFunction + ? process.nextTick(callback!, err) + : Promise.reject(err); + } else if (status & Status.open) { + // if open or opening + const err = new Error(`Server is already open on port: ${port}.`); + return callbackIsFunction + ? process.nextTick(callback!, err) + : Promise.reject(err); + } + + this.#status = Status.opening; + + const promise = new Promise( + (resolve: (listenSocket: false | uWS.us_listen_socket) => void) => { + // Make sure we have *exclusive* use of this port. + // https://github.com/uNetworking/uSockets/commit/04295b9730a4d413895fa3b151a7337797dcb91f#diff-79a34a07b0945668e00f805838601c11R51 + const LIBUS_LISTEN_EXCLUSIVE_PORT = 1; + hostname + ? (this.#app as any).listen( + hostname, + port, + LIBUS_LISTEN_EXCLUSIVE_PORT, + resolve + ) + : this.#app.listen(port as any, LIBUS_LISTEN_EXCLUSIVE_PORT, resolve); + } + ).then(listenSocket => { + if (listenSocket) { + this.#status = Status.open; + this.#listenSocket = listenSocket; + if (callbackIsFunction) callback!(null); + } else { + this.#status = Status.closed; + const err = new Error( + `listen EADDRINUSE: address already in use ${ + hostname || DEFAULT_HOST + }:${port}.` + ); + if (callbackIsFunction) callback!(err); + else throw err; + } + }); + + if (!callbackIsFunction) { + return promise; + } + } + + public async close() { + if (this.#status === Status.opening) { + // if opening + throw new Error(`Cannot close server while it is opening.`); + } else if (this.#status & Status.closed) { + // if closed or closing + throw new Error(`Server is already closed or closing.`); + } + + const _listenSocket = this.#listenSocket; + this.#status = Status.closing; + this.#listenSocket = void 0; + // close the socket to prevent any more connections + uWS.us_listen_socket_close(_listenSocket); + // close all the connected websockets: + const ws = this.#websocketServer; + if (ws) { + ws.close(); + } + + // and do all http cleanup, if any + this.#httpServer.close(); + await this.#connector.close(); + this.#status = Status.closed; + this.#app = void 0; + } +} diff --git a/src/packages/core/src/servers/http-server.ts b/src/packages/core/src/servers/http-server.ts new file mode 100644 index 0000000000..481f2321ed --- /dev/null +++ b/src/packages/core/src/servers/http-server.ts @@ -0,0 +1,216 @@ +import { + TemplatedApp, + HttpResponse, + HttpRequest, + RecognizedString +} from "uWebSockets.js"; +import ContentTypes from "./utils/content-types"; +import HttpResponseCodes from "./utils/http-response-codes"; +import { Connectors } from "@ganache/flavors"; + +type HttpMethods = "GET" | "OPTIONS" | "POST"; + +const noop = () => {}; + +/** + * uWS doesn't let us use the request after the request method has completed. + * But we can't set headers until after the statusCode is set. But we don't + * know the status code until the provider returns asynchronously. + * So this does request-related work immediately and returns a function to do the + * rest of the work later. + * @param method + * @param request + */ +function prepareCORSResponseHeaders(method: HttpMethods, request: HttpRequest) { + // https://fetch.spec.whatwg.org/#http-requests + const origin = request.getHeader("origin"); + const acrh = request.getHeader("access-control-request-headers"); + return (response: HttpResponse) => { + const isCORSRequest = origin !== ""; + if (isCORSRequest) { + // OPTIONS preflight requests need a little extra treatment + if (method === "OPTIONS") { + // we only allow POST requests, so it doesn't matter which method the request is asking for + response.writeHeader("Access-Control-Allow-Methods", "POST"); + // echo all requested access-control-request-headers back to the response + if (acrh !== "") { + response.writeHeader("Access-Control-Allow-Headers", acrh); + } + + // Make browsers and compliant clients cache the OPTIONS preflight response for 10 + // minutes (this is the maximum time Chromium allows) + response.writeHeader("Access-Control-Max-Age", "600"); // seconds + } + + // From the spec: https://fetch.spec.whatwg.org/#http-responses + // "For a CORS-preflight request, request’s credentials mode is always "omit", + // but for any subsequent CORS requests it might not be. Support therefore + // needs to be indicated as part of the HTTP response to the CORS-preflight request as well.", so this + // header is added to all requests. + // Additionally, https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials, + // states that there aren't any HTTP Request headers that indicate you whether or not Request.withCredentials + // is set. Because web3@1.0.0-beta.35-? always sets `request.withCredentials = true` while Safari requires it be + // returned even when no credentials are set in the browser this header must always be return on all requests. + // (I've found that Chrome and Firefox don't actually require the header when credentials aren't set) + // Regression Commit: https://github.com/ethereum/web3.js/pull/1722 + // Open Web3 Issue: https://github.com/ethereum/web3.js/issues/1802 + response.writeHeader("Access-Control-Allow-Credentials", "true"); + + // From the spec: "It cannot be reliably identified as participating in the CORS protocol + // as the `Origin` header is also included for all requests whose method is neither + // `GET` nor `HEAD`." + // Explicitly set the origin instead of using *, since credentials + // can't be used in conjunction with *. This will always be set + /// for valid preflight requests. + response.writeHeader("Access-Control-Allow-Origin", origin); + } + }; +} + +function sendResponse( + response: HttpResponse, + statusCode: HttpResponseCodes, + contentType?: RecognizedString, + data?: RecognizedString, + writeHeaders: (response: HttpResponse) => void = noop +): void { + response.cork(() => { + response.writeStatus(statusCode); + writeHeaders(response); + if (contentType) { + response.writeHeader("Content-Type", contentType); + } + response.end(data); + }); +} + +export default class HttpServer { + #connector: Connectors; + constructor(app: TemplatedApp, connector: Connectors) { + this.#connector = connector; + + // JSON-RPC routes... + app.post("/", this.#handlePost).options("/", this.#handleOptions); + + // because Easter Eggs are fun... + app.get("/418", response => { + sendResponse( + response, + HttpResponseCodes.IM_A_TEAPOT, + ContentTypes.PLAIN, + "418 I'm a teapot" + ); + }); + + // fallback routes... + app.any("/*", (response, request) => { + const connectionHeader = request.getHeader("connection"); + if (connectionHeader && connectionHeader.toLowerCase() === "upgrade") { + // if we got here it means the websocket server wasn't enabled but + // a client tried to connect via websocket. This is a Bad Request. + sendResponse( + response, + HttpResponseCodes.BAD_REQUEST, + ContentTypes.PLAIN, + "400 Bad Request" + ); + } else { + // all other requests don't mean anything to us, so respond with `404 NOT FOUND`... + sendResponse( + response, + HttpResponseCodes.NOT_FOUND, + ContentTypes.PLAIN, + "404 Not Found" + ); + } + }); + } + + #handlePost = (response: HttpResponse, request: HttpRequest) => { + // handle JSONRPC post requests... + const writeHeaders = prepareCORSResponseHeaders("POST", request); + + // TODO(perf): pre-allocate the buffer if we know the Content-Length + let buffer: Buffer; + let aborted = false; + response.onAborted(() => { + aborted = true; + }); + response.onData((message: ArrayBuffer, isLast: boolean) => { + const chunk = Buffer.from(message); + if (isLast) { + const connector = this.#connector; + let payload: ReturnType; + try { + const message = buffer ? Buffer.concat([buffer, chunk]) : chunk; + payload = connector.parse(message); + } catch (e) { + sendResponse( + response, + HttpResponseCodes.BAD_REQUEST, + ContentTypes.PLAIN, + "400 Bad Request: " + e.message, + writeHeaders + ); + return; + } + + connector + .handle(payload, request) + .then(({ value }) => value) + .then(result => { + if (aborted) { + // if the request has been aborted don't try sending (it'll + // cause an `Unhandled promise rejection` if we try) + return; + } + const data = connector.format(result, payload); + sendResponse( + response, + HttpResponseCodes.OK, + ContentTypes.JSON, + data, + writeHeaders + ); + }) + .catch(error => { + if (aborted) { + // if the request has been aborted don't try sending (it'll + // cause an `Unhandled promise rejection` if we try) + return; + } + const data = connector.formatError(error, payload); + sendResponse( + response, + HttpResponseCodes.OK, + ContentTypes.JSON, + data, + writeHeaders + ); + }); + } else { + if (buffer) { + buffer = Buffer.concat([buffer, chunk]); + } else { + buffer = Buffer.concat([chunk]); + } + } + }); + }; + + #handleOptions = (response: HttpResponse, request: HttpRequest) => { + // handle CORS preflight requests... + const writeHeaders = prepareCORSResponseHeaders("OPTIONS", request); + // OPTIONS responses don't have a body, so respond with `204 No Content`... + sendResponse( + response, + HttpResponseCodes.NO_CONTENT, + void 0, + "", + writeHeaders + ); + }; + public close() { + // currently a no op. + } +} diff --git a/src/packages/core/src/servers/utils/content-types.ts b/src/packages/core/src/servers/utils/content-types.ts new file mode 100644 index 0000000000..debf586fc9 --- /dev/null +++ b/src/packages/core/src/servers/utils/content-types.ts @@ -0,0 +1,5 @@ +enum ContentTypes { + PLAIN = "text/plain", + JSON = "application/json" +} +export default ContentTypes; diff --git a/src/packages/core/src/servers/utils/http-response-codes.ts b/src/packages/core/src/servers/utils/http-response-codes.ts new file mode 100644 index 0000000000..d0deaa7dad --- /dev/null +++ b/src/packages/core/src/servers/utils/http-response-codes.ts @@ -0,0 +1,9 @@ +enum HttpResponseCodes { + OK = "200", + NO_CONTENT = "204", + BAD_REQUEST = "400", + NOT_FOUND = "404", + METHOD_NOT_ALLOWED = "405", + IM_A_TEAPOT = "418" +} +export default HttpResponseCodes; diff --git a/src/packages/core/src/servers/utils/websocket-close-codes.ts b/src/packages/core/src/servers/utils/websocket-close-codes.ts new file mode 100644 index 0000000000..f4fac84005 --- /dev/null +++ b/src/packages/core/src/servers/utils/websocket-close-codes.ts @@ -0,0 +1,11 @@ +enum WebSocketCloseCodes { + // CLOSE_NORMAL = 1000, + /** + * Indicates that an endpoint is "going away", such as a server going down or + * a browser having navigated away from a page. + */ + CLOSE_GOING_AWAY = 1001 + // CLOSE_PROTOCOL_ERROR = 1002, + // CLOSE_ABNORMAL = 1006 +} +export default WebSocketCloseCodes; diff --git a/src/packages/core/src/servers/ws-server.ts b/src/packages/core/src/servers/ws-server.ts new file mode 100644 index 0000000000..792da2d0e3 --- /dev/null +++ b/src/packages/core/src/servers/ws-server.ts @@ -0,0 +1,123 @@ +import uWS, { TemplatedApp, WebSocket } from "uWebSockets.js"; +import WebSocketCloseCodes from "./utils/websocket-close-codes"; +import { ServerOptions } from "../options"; +import * as Flavors from "@ganache/flavors"; +import { PromiEvent } from "@ganache/utils"; + +type MergePromiseT = Promise ? X : never>; + +type HandlesWebSocketSignature = (payload: any, connection: WebSocket) => any; + +type WebSocketCapableFlavorMap = { + [k in keyof Flavors.ConnectorsByName]: Flavors.ConnectorsByName[k]["handle"] extends HandlesWebSocketSignature + ? Flavors.ConnectorsByName[k] + : never; +}; + +export type WebSocketCapableFlavor = { + [k in keyof WebSocketCapableFlavorMap]: WebSocketCapableFlavorMap[k]; +}[keyof WebSocketCapableFlavorMap]; + +export type GanacheWebSocket = WebSocket & { closed?: boolean }; + +export type WebsocketServerOptions = Pick; + +export default class WebsocketServer { + #connections = new Map void>>(); + constructor( + app: TemplatedApp, + connector: WebSocketCapableFlavor, + options: WebsocketServerOptions + ) { + const connections = this.#connections; + const wsBinary = options.wsBinary; + const autoBinary = wsBinary === "auto"; + app.ws("/", { + /* WS Options */ + compression: uWS.SHARED_COMPRESSOR, // Zero memory overhead compression + maxPayloadLength: 16 * 1024, // 128 Kibibits + idleTimeout: 120, // in seconds + + /* Handlers */ + open: (ws: GanacheWebSocket) => { + ws.closed = false; + connections.set(ws, new Set()); + }, + + message: async ( + ws: GanacheWebSocket, + message: ArrayBuffer, + isBinary: boolean + ) => { + let payload: ReturnType; + const useBinary = autoBinary ? isBinary : (wsBinary as boolean); + try { + payload = connector.parse(Buffer.from(message)); + } catch (err) { + const response = connector.formatError(err, payload); + ws.send(response, useBinary, true); + return; + } + + let response: uWS.RecognizedString; + + try { + const { value } = await connector.handle(payload, ws); + + // The socket may have closed while we were waiting for the response + // Don't bother trying to send to it if it was. + if (ws.closed) return; + + const resultEmitter = value as MergePromiseT; + const result = await resultEmitter; + if (ws.closed) return; + + response = connector.format(result, payload); + + // if the result is an emitter listen to its `"message"` event + if (resultEmitter instanceof PromiEvent) { + resultEmitter.on("message", (result: any) => { + // note: we _don't_ need to check if `ws.closed` here because when + // `ws.closed` is set we remove this event handler anyway. + const message = JSON.stringify({ + jsonrpc: "2.0", + method: result.type, + params: result.data + }); + ws.send(message, isBinary, true); + }); + + // keep track of listeners to dispose off when the ws disconnects + connections.get(ws).add(resultEmitter.dispose); + } + } catch (err) { + // ensure the connector's `handle` fn doesn't throw outside of a Promise + + if (ws.closed) return; + response = connector.formatError(err, payload); + } + + ws.send(response, useBinary, true); + }, + + drain: (ws: WebSocket) => { + // This is there so tests can detect if a small amount of backpressure + // is happening and that things will still work if it does. We actually + // don't do anything to manage excessive backpressure. + // TODO: handle back pressure for real! + // options.logger.log("WebSocket backpressure: " + ws.getBufferedAmount()); + }, + + close: (ws: GanacheWebSocket) => { + ws.closed = true; + connections.get(ws).forEach(dispose => dispose()); + connections.delete(ws); + } + }); + } + close() { + this.#connections.forEach((_, ws) => + ws.end(WebSocketCloseCodes.CLOSE_GOING_AWAY, "Server closed by client") + ); + } +} diff --git a/src/packages/core/tests/connector.test.ts b/src/packages/core/tests/connector.test.ts new file mode 100644 index 0000000000..4828110351 --- /dev/null +++ b/src/packages/core/tests/connector.test.ts @@ -0,0 +1,148 @@ +import assert from "assert"; +import Ganache from "../"; +import { Provider as EthereumProvider } from "@ganache/ethereum"; + +describe("connector", () => { + it("works without passing options", async () => { + assert.doesNotThrow(() => Ganache.provider()); + }); + + it("it logs when `options.verbose` is `true`", async () => { + const logger = { log: (_msg: string) => {} }; + const p = Ganache.provider({ logging: { logger, verbose: true } }); + + logger.log = msg => { + assert.strictEqual( + msg, + " > net_version: undefined", + "doesn't work when no params" + ); + }; + await p.send("net_version"); + + return new Promise(async resolve => { + logger.log = msg => { + const expected = + " > web3_sha3: [\n" + ' > "Tim is a swell guy."\n' + " > ]"; + assert.strictEqual(msg, expected, "doesn't work with params"); + resolve(); + }; + await p.send("web3_sha3", ["Tim is a swell guy."]); + }); + }); + + it("it processes requests asyncronously when `asyncRequestProcessing` is default (true)", async () => { + const p = Ganache.provider(); + const accounts = await p.send("eth_accounts"); + // `eth_accounts` should always be faster than eth_getBalance; eth_accounts + // should return before eth_getBalance because of the + // `asyncRequestProcessing` flag. + const calA = p.send("eth_getBalance", [accounts[0]]); + const callB = p.send("eth_accounts"); + const result = await Promise.race([calA, callB]); + assert(Array.isArray(result)); + assert.strictEqual(result.length, 10); + }); + + it("it processes requests syncronously when `asyncRequestProcessing` is `false`", async () => { + const p = Ganache.provider({ chain: { asyncRequestProcessing: false } }); + const accounts = await p.send("eth_accounts"); + // eth_getBalance should return first even though eth_accounts is faster; + // eth_getBalance should return before eth_accounts because of the + // `asyncRequestProcessing` flag. + const calA = p.send("eth_getBalance", [accounts[0]]); + const callB = p.send("eth_accounts"); + const result = await Promise.race([calA, callB]); + assert.strictEqual(result, "0x56bc75e2d63100000"); + }); + + // duck punch a property that shouldn't appear on the API. we test this + // to make sure that 3rd party API implementations can't shoot themselves + // in the foot on accident + it.skip("TODO: allow 'injecting' our own engine or API into a provider!", async () => { + const p = Ganache.provider(); + // this won't work becase ganache uses _real_ private properties that can't + // be duck punched. This test is supposed to ensure that _real_ non-function + // own properties (and __proto__ properties) can't be executed. + (p as any)._engine._api.__proto__.illegalProperty = true; + await assert.rejects(p.send("illegalProperty" as any, []), { + message: "`The method illegalProperty does not exist/is not available`" + }); + }); + + it("rejects invalid rpc methods", async () => { + const p = Ganache.provider(); + + const illegalMethodNames = [ + "toString", + "toValue", + "__proto__", + "prototype", + "notAFunction", + "", + " ", + "constructor" + ] as const; + await Promise.all( + illegalMethodNames.map(methodName => { + assert.rejects(() => p.send(methodName as any), { + message: `The method ${methodName} does not exist/is not available` + }); + }) + ); + + // make sure we reject non-strings over the classical send interface + const circular: any = {}; + circular.circular = circular; + const illegalMethodTypes = [ + 123, + // just cast as string to make TS let me test weird stuff... + (Buffer.from([1]) as unknown) as string, + null, + void 0, + {}, + [], + { foo: "bar" }, + [1, 2], + new Date(), + Infinity, + NaN, + circular + ] as const; + await Promise.all( + illegalMethodTypes.map(method => { + return assert.rejects( + new Promise((resolve, reject) => { + p.send( + { + id: "1", + jsonrpc: "2.0", + method + }, + (err, result): void => { + if (err) { + reject(err); + } else { + resolve(result); + } + } + ); + }), + { + message: `The method ${method} does not exist/is not available` + } + ); + }) + ); + + // make sure we reject non-strings over the legacy send interface + illegalMethodTypes.map(methodType => { + assert.throws(() => p.send(methodType), { + message: + "No callback provided to provider's send function. As of " + + "web3 1.0, provider.send is no longer synchronous and must be " + + "passed a callback as its final argument." + }); + }); + }); +}); diff --git a/src/packages/core/tests/helpers/getProvider.ts b/src/packages/core/tests/helpers/getProvider.ts new file mode 100644 index 0000000000..01a35543be --- /dev/null +++ b/src/packages/core/tests/helpers/getProvider.ts @@ -0,0 +1,13 @@ +import Ganache from "../../src/"; +import { ProviderOptions } from "@ganache/options"; +import { EthereumProvider } from "@ganache/ethereum"; + +const mnemonic = + "into trim cross then helmet popular suit hammer cart shrug oval student"; +const getProvider = ( + options: ProviderOptions = { flavor: "ethereum", mnemonic } +) => { + return Ganache.provider(options) as EthereumProvider; +}; + +export default getProvider; diff --git a/src/packages/core/tests/interface.test.ts b/src/packages/core/tests/interface.test.ts new file mode 100644 index 0000000000..ad9f658680 --- /dev/null +++ b/src/packages/core/tests/interface.test.ts @@ -0,0 +1,16 @@ +import Ganache from ".."; +import * as assert from "assert"; + +describe("interface", () => { + it("has an interface", () => { + assert.ok(Ganache.server); + assert.ok(Ganache.provider); + assert.strictEqual(Object.keys(Ganache).length, 2); + + // in v3 these two properties were *removed* because it was confusing. + // these tests are kinda unncessary, but I just want to intent to be + // explicit. + assert.strictEqual("Server" in Ganache, false); + assert.strictEqual("Provider" in Ganache, false); + }); +}); diff --git a/src/packages/core/tests/server.test.ts b/src/packages/core/tests/server.test.ts new file mode 100644 index 0000000000..50897ef5de --- /dev/null +++ b/src/packages/core/tests/server.test.ts @@ -0,0 +1,881 @@ +import Ganache from "../"; +import * as assert from "assert"; +import request from "superagent"; +import WebSocket from "ws"; +import Server, { Status } from "../src/server"; +import http from "http"; +// https://github.com/sindresorhus/into-stream/releases/tag/v6.0.0 +import intoStream = require("into-stream"); +import { PromiEvent } from "@ganache/utils"; +import { promisify } from "util"; +import { ServerOptions } from "../src/options"; + +const IS_WINDOWS = process.platform === "win32"; + +describe("server", () => { + const port = 5234; + const networkId = 1234; + const jsonRpcJson: any = { + jsonrpc: "2.0", + id: "1", + method: "net_version", + params: [] + }; + const logger = { + log: (_message: string) => {} + }; + let s: Server; + + async function setup( + options: ServerOptions = { + chain: { + networkId + }, + logging: { + logger + } + } + ) { + s = Ganache.server(options); + return s.listen(port); + } + + async function teardown() { + // if the server is open or opening, try to close it. + if (s && s.status & Status.open) { + await s.close(); + } + } + + describe("http", () => { + async function simpleTest() { + const response = await request + .post("http://localhost:" + port) + .send(jsonRpcJson); + assert.strictEqual(response.status, 200); + + const json = JSON.parse(response.text); + assert.strictEqual(json.result, `${networkId}`); + return response; + } + + it("returns its status", async () => { + const s = Ganache.server(); + try { + assert.strictEqual(s.status, Status.closed); + const pendingListen = s.listen(port); + assert.strictEqual(s.status, Status.opening); + assert.ok( + s.status & Status.opening, + "Bitmask broken: can't be used to determine `open || closed` state" + ); + await pendingListen; + assert.strictEqual(s.status, Status.open); + assert.ok( + s.status & Status.open, + "Bitmask broken: can't be used to determine `open || closed` state" + ); + const pendingClose = s.close(); + assert.strictEqual(s.status, Status.closing); + assert.ok( + s.status & Status.closing, + "Bitmask broken: can't be used to determine `closed || closing` state" + ); + await pendingClose; + assert.strictEqual(s.status, Status.closed); + assert.ok( + s.status & Status.closed, + "Bitmask broken: can't be used to determine `closed || closing` state" + ); + } catch (e) { + // in case of failure, make sure we properly shut things down + if (s.status & Status.open) { + await s.close().catch(e => e); + } + throw e; + } + }); + + it("returns the net_version", async () => { + await setup(); + try { + await simpleTest(); + } finally { + await teardown(); + } + }); + + it("returns the net_version over a legacy-style connection listener", done => { + s = Ganache.server({ + chain: { networkId } + }); + s.listen(port, async () => { + try { + await simpleTest(); + } finally { + await teardown(); + } + done(); + }); + }); + + it("fails to `.listen()` twice, Promise", async () => { + await setup(); + try { + // the call to `setup()` above calls `listen()` already. if we call it + // again it should fail. + await assert.rejects(s.listen(port), { + message: `Server is already open on port: ${port}.` + }); + } finally { + await teardown(); + } + }); + + it("fails to `.listen()` twice, Callback", async () => { + await setup(); + try { + // the call to `setup()` above calls `listen()` already. if we call it + // again it should fail. + const listen = promisify(s.listen.bind(s)); + await assert.rejects(listen(port), { + message: `Server is already open on port: ${port}.` + }); + } finally { + await teardown(); + } + }); + + it("fails to `.close()` if server is closed", async () => { + await setup(); + try { + await s.close(); + assert.rejects(s.close(), { + message: "Server is already closed or closing." + }); + } finally { + await teardown(); + } + }); + + it("fails to `.close()` if server is closed", async () => { + await setup(); + try { + s.close(); + assert.rejects(s.close(), { + message: "Server is already closed or closing." + }); + } finally { + await teardown(); + } + }); + + it("fails to listen if the socket is already in use by 3rd party, Promise", async () => { + const server = http.createServer(); + server.listen(port); + + try { + await assert.rejects(setup, { + message: `listen EADDRINUSE: address already in use 127.0.0.1:${port}.` + }); + } finally { + await teardown(); + server.close(); + } + }); + + it("fails to listen if the socket is already in use by 3rd party, Callback", async () => { + const server = http.createServer(); + server.listen(port); + + try { + const s = Ganache.server(); + const listen = promisify(s.listen.bind(s)); + await assert.rejects(listen(port), { + message: `listen EADDRINUSE: address already in use 127.0.0.1:${port}.` + }); + } finally { + await teardown(); + server.close(); + } + }); + + // skip on Windows until https://github.com/uNetworking/uSockets/pull/101 is merged + (IS_WINDOWS ? xit : it)( + "fails to listen if the socket is already in use by Ganache", + async () => { + await setup(); + const s2 = Ganache.server(); + + try { + await assert.rejects(s2.listen(port), { + message: `listen EADDRINUSE: address already in use 127.0.0.1:${port}.` + }); + } catch (e) { + // in case of failure, make sure we properly shut things down + if (s2.status & Status.open) { + await s2.close().catch(e => e); + } + throw e; + } finally { + await teardown(); + } + } + ); + + it("rejects if listen called while server is closing, Promise", async () => { + await setup(); + try { + const closer = s.close(); + await assert.rejects(s.listen(4444), { + message: "Cannot start server while it is closing." + }); + await closer; + } finally { + await teardown(); + } + }); + + it("rejects if listen called while server is closing, Callback", async () => { + await setup(); + try { + const closer = s.close(); + const listen = promisify(s.listen.bind(s)); + await assert.rejects(listen(4444), { + message: "Cannot start server while it is closing." + }); + await closer; + } finally { + await teardown(); + } + }); + + it("rejects if close is called while opening", async () => { + const pendingSetup = setup(); + try { + await assert.rejects(s.close(), { + message: "Cannot close server while it is opening." + }); + } finally { + await pendingSetup; + await teardown(); + } + }); + + it("does not start a websocket server when `ws` is false", async () => { + await setup({ + server: { + ws: false + } + }); + try { + const ws = new WebSocket("ws://localhost:" + port); + + await assert.rejects( + new Promise((resolve, reject) => { + ws.on("open", resolve); + ws.on("error", reject); + }), + { + message: "Unexpected server response: 400" + } + ); + } finally { + await teardown(); + } + }); + + it("handles chunked requests (note: doesn't test `transfer-encoding: chunked`)", async () => { + await setup(); + try { + const req = request.post("http://localhost:" + port); + const json = JSON.stringify(jsonRpcJson); + + // we have to set the content-length because we can't use + // `Transfer-Encoding: chunked` with uWebSockets.js as of v15.9.0 + req.set("Content-Length", json.length.toString()); + + await new Promise((resolve, reject) => { + req.on("response", response => { + const json = JSON.parse(response.text); + assert.strictEqual(json.result, `${networkId}`); + resolve(void 0); + }); + req.on("error", () => { + reject(); + }); + + const readableStream = intoStream(json); + // make sure the data is sent as tiny pieces. + (readableStream as any)._readableState.highWaterMark = 8; + readableStream.pipe(req as any); + }); + } finally { + await teardown(); + } + }); + + it("returns 200/OK for RPC errors over HTTP", async () => { + await setup(); + const jsonRpcJson: any = { + jsonrpc: "2.0", + id: "1", + method: "eth_subscribe", + params: [] + }; + try { + const response = await request + .post("http://localhost:" + port) + .send(jsonRpcJson); + assert.strictEqual(response.status, 200); + assert.strictEqual( + JSON.parse(response.text).error.message, + "notifications not supported" + ); + } finally { + await teardown(); + } + }); + + it("handles batched json-rpc requests/responses", async () => { + await setup(); + const jsonRpcJson: any = [ + { + jsonrpc: "2.0", + id: "1", + method: "net_version", + params: [] + }, + { + jsonrpc: "2.0", + id: "2", + method: "eth_chainId", + params: [] + }, + { + jsonrpc: "2.0", + id: "3", + method: "eth_subscribe", // this one fails over HTTP + params: ["newHeads"] + } + ]; + try { + const response = await request + .post("http://localhost:" + port) + .send(jsonRpcJson); + const json = JSON.parse(response.text); + assert.deepStrictEqual(json[0], { + jsonrpc: "2.0", + id: "1", + result: "1234" + }); + assert.deepStrictEqual(json[1], { + jsonrpc: "2.0", + id: "2", + result: "0x539" + }); + assert.deepStrictEqual(json[2].jsonrpc, "2.0"); + assert.deepStrictEqual(json[2].id, "3"); + assert.deepStrictEqual(json[2].error.code, -32004); + assert.deepStrictEqual( + json[2].error.message, + "notifications not supported" + ); + } finally { + await teardown(); + } + }); + + it("returns a teapot (easter egg)", async () => { + await setup(); + try { + const result = await request + .get("http://localhost:" + port + "/418") + .catch(e => e); + assert.strictEqual(result.status, 418); + assert.strictEqual(result.message, "I'm a Teapot"); + } finally { + await teardown(); + } + }); + + it("returns 404 for bad routes", async () => { + await setup(); + const methods = [ + "get", + "post", + "head", + "options", + "put", + "delete", + "patch", + "trace" + ] as const; + try { + const requests = methods.map(async method => { + const result = await request[method]( + "http://localhost:" + port + "/there-is-no-spoon" + ).catch((e: any) => e); + assert.strictEqual(result.status, 404); + assert.strictEqual(result.message, "Not Found"); + }); + await Promise.all(requests); + } finally { + await teardown(); + } + }); + + it("doesn't crash when the request is aborted while waiting for repsonse", async () => { + await setup(); + + try { + const provider = s.provider; + const oldRequestRaw = (provider as any)._requestRaw; + const req = request.post("http://localhost:" + port); + const abortPromise = new Promise(resolve => { + (provider as any)._requestRaw = () => { + // abort the request object after intercepting the request + req.abort(); + return new Promise(innerResolve => { + // It takes 2 passes of the event loop to register the `abort` + // server-side: + setImmediate(setImmediate, () => { + // resolve the `provider.send` to make sure the server can + // handle _not_ responding to a request that has been aborted: + innerResolve({ value: Promise.resolve() as any }); + // and finally, resolve the `abort` promise: + resolve(void 0); + }); + }); + }; + }); + const result = await req.send(jsonRpcJson).catch(e => e); + assert.strictEqual(result.code, "ABORTED", "Response was not aborted"); + + // wait for the server to react to the requesrt's `abort` + await abortPromise; + + provider._requestRaw = oldRequestRaw; + + // now make sure we are still up and running: + await simpleTest(); + } finally { + await teardown(); + } + }); + + it("server closes when told to", async () => { + await setup(); + + try { + await s.close(); + const req = request.post("http://localhost:" + port); + await assert.rejects(req.send(jsonRpcJson), { + code: "ECONNREFUSED" + }); + } finally { + await teardown(); + } + }); + + describe("CORS", () => { + const optionsHeaders = [ + "Access-Control-Allow-Methods", + "Access-Control-Allow-Headers", + "Access-Control-Max-Age" + ] as const; + const baseHeaders = [ + "Access-Control-Allow-Credentials", + "Access-Control-Allow-Origin" + ] as const; + const allCorsHeaders = [...optionsHeaders, ...baseHeaders] as const; + + it("does not return CORS headers for non-CORS requests", async () => { + await setup(); + try { + const resp = await simpleTest(); + allCorsHeaders.forEach(header => { + assert.strictEqual( + resp.header[header.toLowerCase()], + void 0, + `Non-CORS response should not contain header ${header}` + ); + }); + } finally { + await teardown(); + } + }); + + it("returns only base CORS headers for post request with origin header", async () => { + await setup(); + const origin = "origin"; + try { + const resp = await request + .post("http://localhost:" + port) + .set("origin", origin) + .send(jsonRpcJson); + assert.strictEqual(resp.status, 200); + assert.strictEqual( + resp.header["access-control-allow-credentials"], + "true" + ); + assert.strictEqual( + resp.header["access-control-allow-origin"], + origin + ); + optionsHeaders.forEach(header => { + assert.strictEqual( + resp.header[header.toLowerCase()], + void 0, + `Non-CORS response should not contain header ${header}` + ); + }); + } finally { + await teardown(); + } + }); + + it("returns all CORS headers for request options request with origin header", async () => { + await setup(); + const origin = "origin"; + try { + const resp = await request + .options("http://localhost:" + port) + .set("origin", origin) + .send(jsonRpcJson); + assert.strictEqual(resp.status, 204); + assert.strictEqual( + resp.header["access-control-allow-methods"], + "POST" + ); + assert.strictEqual( + resp.header["access-control-allow-origin"], + origin + ); + assert.strictEqual(resp.header["access-control-max-age"], "600"); + assert.strictEqual(resp.header["content-length"], "0"); + assert.strictEqual( + resp.header["access-control-allow-credentials"], + "true" + ); + } finally { + await teardown(); + } + }); + + it("echos Access-Control-Request-Headers for options request", async () => { + await setup(); + const origin = "origin"; + const acrh = "origin, content-length, x-random"; + try { + const resp = await request + .options("http://localhost:" + port) + .set("origin", origin) + .set("Access-Control-Request-Headers", acrh) + .send(jsonRpcJson); + + assert.strictEqual(resp.status, 204); + assert.strictEqual(resp.header["access-control-allow-headers"], acrh); + } finally { + await teardown(); + } + }); + }); + }); + + describe("websocket", () => { + beforeEach(setup); + afterEach(teardown); + + it("returns the net_version over a websocket", async () => { + const ws = new WebSocket("ws://localhost:" + port); + + const response: any = await new Promise(resolve => { + ws.on("open", () => { + ws.send(JSON.stringify(jsonRpcJson)); + }); + ws.on("message", resolve); + }); + const json = JSON.parse(response); + assert.strictEqual(json.result, `${networkId}`); + }); + + it("returns the net_version over a websocket as binary", async () => { + const ws = new WebSocket("ws://localhost:" + port); + const response: any = await new Promise(resolve => { + ws.on("open", () => { + const strToAB = (str: string) => + new Uint8Array(str.split("").map(c => c.charCodeAt(0))).buffer; + ws.send(strToAB(JSON.stringify(jsonRpcJson))); + }); + ws.on("message", resolve); + }); + assert.strictEqual( + response.constructor, + Buffer, + "response doesn't seem to be a Buffer as expect" + ); + const json = JSON.parse(response); + assert.strictEqual( + json.result, + `${networkId}`, + "Binary data result is not as expected" + ); + }); + + it("doesn't crash when sending bad data over http", async () => { + await assert.rejects( + request.post("http://localhost:" + port).send("This is _not_ pudding."), + { + message: "Bad Request" + } + ); + + const response = await request + .post("http://localhost:" + port) + .send(jsonRpcJson); + const json = JSON.parse(response.text); + assert.strictEqual(json.result, `${networkId}`); + }); + + it("doesn't crash when sending bad data over websocket", async () => { + const ws = new WebSocket("ws://localhost:" + port); + const result = await new Promise(resolve => { + ws.on("open", () => { + ws.on("message", resolve); + ws.send("What is it?"); + }); + }); + const json = JSON.parse(result); + assert.strictEqual(json.error.code, -32700); + }); + + it("doesn't crash when the connection is closed while a request is in flight", async () => { + const provider = s.provider; + provider._requestRaw = (async () => { + // close our websocket after intercepting the request + await s.close(); + return { value: Promise.resolve(void 0) }; + }) as any; + + const ws = new WebSocket("ws://localhost:" + port); + return new Promise((resolve, reject) => { + ws.on("open", () => { + // If we get a message that means things didn't get closed as they + // should have OR they are closing too late for some reason and + // this test isn't testing anything. + ws.on("message", () => + reject("Got a message when we shouldn't have!") + ); + + // make sure we leave enough time for things to crash if it does end + // up crashing. + ws.on("close", () => setImmediate(resolve)); + + // The RPC request method doesn't matter since we're duck punching our + // provider.send method anyway. + ws.send(JSON.stringify(jsonRpcJson)); + }); + }); + }); + + it("handles PromiEvent messages", async () => { + const provider = s.provider; + const message = "I hope you get this message"; + const oldRequestRaw = provider._requestRaw.bind(provider); + provider._requestRaw = (async () => { + const promiEvent = new PromiEvent(resolve => { + const subId = "0xsubscriptionId"; + resolve(subId); + setImmediate(() => + promiEvent.emit("message", { + data: { subscription: subId, result: message } + }) + ); + }); + return { value: promiEvent }; + }) as any; + + const ws = new WebSocket("ws://localhost:" + port); + const result = await new Promise(resolve => { + ws.on("open", () => { + ws.on("message", data => { + const { result, params } = JSON.parse(data.toString()); + // ignore the initial response + if (result === "0xsubscriptionId") return; + + resolve(params.result); + }); + + const subscribeJson: any = { + jsonrpc: "2.0", + id: "1", + method: "eth_subscribe", + params: [] + }; + ws.send(JSON.stringify(subscribeJson)); + }); + }); + + assert.strictEqual(result, message); + + provider._requestRaw = oldRequestRaw; + }); + + it("handles batched json-rpc requests/responses", async () => { + const jsonRpcJson: any = [ + { + jsonrpc: "2.0", + id: "1", + method: "net_version", + params: [] + }, + { + jsonrpc: "2.0", + id: "2", + method: "eth_chainId", + params: [] + }, + { + jsonrpc: "2.0", + id: "3", + method: "eth_subscribe", // this one works here in WS-land + params: ["newHeads"] + } + ]; + + const ws = new WebSocket("ws://localhost:" + port); + const response: any = await new Promise(resolve => { + ws.on("open", () => { + ws.send(JSON.stringify(jsonRpcJson)); + }); + ws.on("message", resolve); + }); + ws.close(); + + const json = JSON.parse(response); + assert.deepStrictEqual(json, [ + { + jsonrpc: "2.0", + id: "1", + result: "1234" + }, + { + jsonrpc: "2.0", + id: "2", + result: "0x539" + }, + { + jsonrpc: "2.0", + id: "3", + result: "0x1" + } + ]); + }); + + it("handles invalid json-rpc JSON", async () => { + const ws = new WebSocket("ws://localhost:" + port); + const response = await new Promise(resolve => { + ws.on("open", () => { + ws.send(JSON.stringify(null)); + }); + ws.on("message", data => { + resolve(JSON.parse(data.toString())); + }); + }); + assert.strictEqual(response.error.code, -32700); + }); + + it("doesn't crash when the connection is closed while a subscription is in flight", async () => { + const provider = s.provider; + let promiEvent: PromiEvent; + provider._requestRaw = (async () => { + promiEvent = new PromiEvent(resolve => { + resolve("0xsubscriptionId"); + }); + return { value: promiEvent }; + }) as any; + + const ws = new WebSocket("ws://localhost:" + port); + return new Promise((resolve, reject) => { + ws.on("open", () => { + // If we get a message that means things didn't get closed as they + // should have OR they are closing too late for some reason and + // this test isn't testing anything. + ws.on("message", data => { + if (JSON.parse(data.toString()).result === "0xsubscriptionId") { + // close our websocket after intercepting the request + s.close(); + // then attempt to send a message back right after closing: + promiEvent.emit("message", "I hope you don't get this message"); + return; + } + // the above message should never be received + reject("Got a subscription message when we shouldn't have!"); + }); + + // make sure we leave enough time for things to crash if it does end + // up crashing. + ws.on("close", () => setImmediate(resolve)); + + const subscribeJson: any = { + jsonrpc: "2.0", + id: "1", + method: "eth_subscribe", + params: [] + }; + ws.send(JSON.stringify(subscribeJson)); + }); + }); + }); + + // TODO: actually handle backpressure! + it.skip("can handle backpressure", async () => { + { + // create tons of data to force websocket backpressure + const huge = {}; + for (let i = 0; i < 1e6; i++) huge["prop_" + i] = { i }; + s.provider._requestRaw = (async () => { + return { value: Promise.resolve(huge) }; + }) as any; + } + + const ws = new WebSocket("ws://localhost:" + port); + const oldLog = logger.log; + try { + let gotBackpressure = false; + // duck punch `logger.log` so we can intercept logs + logger.log = (message: string) => { + if (message.indexOf("WebSocket backpressure: ") === 0) { + gotBackpressure = true; + } + }; + return await new Promise((resolve, reject) => { + ws.on("open", () => { + ws.on("message", _message => { + if (gotBackpressure) { + resolve(); + } else { + reject( + new Error( + "Possible false positive: Didn't detect backpressure" + + " before receiving a message. Ensure `s.provider.send` is" + + " sending enough data." + ) + ); + } + }); + + // The RPC request method doesn't matter since we're duck punching + // our provider.send method anyway. + ws.send(JSON.stringify(jsonRpcJson)); + }); + }); + } finally { + // put the original logger back so other tests that might rely on it + // don't break. + logger.log = oldLog; + } + }).timeout(10000); + }); +}); diff --git a/test/local/testdb/!trie_db!0x08ac839d755e4a25bcbc47a4012219db100ef0a340307869393f9df55ebd470c b/src/packages/core/tests/testdb/!trie_db!0x08ac839d755e4a25bcbc47a4012219db100ef0a340307869393f9df55ebd470c similarity index 100% rename from test/local/testdb/!trie_db!0x08ac839d755e4a25bcbc47a4012219db100ef0a340307869393f9df55ebd470c rename to src/packages/core/tests/testdb/!trie_db!0x08ac839d755e4a25bcbc47a4012219db100ef0a340307869393f9df55ebd470c diff --git a/test/local/testdb/!trie_db!0x6990c157721aea0e000dc63d12ccdd1938364fb8e72dcdda25d6baa70992b80f b/src/packages/core/tests/testdb/!trie_db!0x6990c157721aea0e000dc63d12ccdd1938364fb8e72dcdda25d6baa70992b80f similarity index 100% rename from test/local/testdb/!trie_db!0x6990c157721aea0e000dc63d12ccdd1938364fb8e72dcdda25d6baa70992b80f rename to src/packages/core/tests/testdb/!trie_db!0x6990c157721aea0e000dc63d12ccdd1938364fb8e72dcdda25d6baa70992b80f diff --git a/test/local/testdb/!trie_db!0xb0108c95b74533d6862f59a730bb0282bbaaadd8ac1b94510b69a499527c3505 b/src/packages/core/tests/testdb/!trie_db!0xb0108c95b74533d6862f59a730bb0282bbaaadd8ac1b94510b69a499527c3505 similarity index 100% rename from test/local/testdb/!trie_db!0xb0108c95b74533d6862f59a730bb0282bbaaadd8ac1b94510b69a499527c3505 rename to src/packages/core/tests/testdb/!trie_db!0xb0108c95b74533d6862f59a730bb0282bbaaadd8ac1b94510b69a499527c3505 diff --git a/test/local/testdb/!trie_db!0xf173aa08e820d7e4b4bb0c243a1661770198770b14029eed74f0eb473c79cd83 b/src/packages/core/tests/testdb/!trie_db!0xf173aa08e820d7e4b4bb0c243a1661770198770b14029eed74f0eb473c79cd83 similarity index 100% rename from test/local/testdb/!trie_db!0xf173aa08e820d7e4b4bb0c243a1661770198770b14029eed74f0eb473c79cd83 rename to src/packages/core/tests/testdb/!trie_db!0xf173aa08e820d7e4b4bb0c243a1661770198770b14029eed74f0eb473c79cd83 diff --git a/src/packages/core/tsconfig.json b/src/packages/core/tsconfig.json new file mode 100644 index 0000000000..25d5c3cf7e --- /dev/null +++ b/src/packages/core/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "outDir": "lib" + }, + "include": ["index.ts"] +} diff --git a/src/packages/flavors/.npmignore b/src/packages/flavors/.npmignore new file mode 100644 index 0000000000..115ab4f024 --- /dev/null +++ b/src/packages/flavors/.npmignore @@ -0,0 +1,8 @@ +./index.ts +tests +.nyc_output +coverage +scripts +src +tsconfig.json +typedoc.json diff --git a/src/packages/flavors/LICENSE b/src/packages/flavors/LICENSE new file mode 100644 index 0000000000..39f3b14498 --- /dev/null +++ b/src/packages/flavors/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2019-2020 Truffle Blockchain Group + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/packages/flavors/README.md b/src/packages/flavors/README.md new file mode 100644 index 0000000000..e1b8783f7e --- /dev/null +++ b/src/packages/flavors/README.md @@ -0,0 +1,3 @@ +# `@ganache/flavors` + +Ganache's flavors enumeration and TypeScript types diff --git a/src/packages/flavors/index.ts b/src/packages/flavors/index.ts new file mode 100644 index 0000000000..49fe84df43 --- /dev/null +++ b/src/packages/flavors/index.ts @@ -0,0 +1,25 @@ +// import {TezosConnector} from "@ganache/tezos"; +import * as Ethereum from "@ganache/ethereum"; + +export const DefaultFlavor = Ethereum.FlavorName; + +export type ConnectorsByName = { + [Ethereum.FlavorName]: Ethereum.Connector; + // [Tezos.FlavorName]: Tezos.Connector +}; + +export const ConnectorsByName = { + [Ethereum.FlavorName]: Ethereum.Connector + // [Tezos.FlavorName]: Tezos.Connector +}; + +export type Connectors = { + [K in keyof ConnectorsByName]: ConnectorsByName[K]; +}[keyof ConnectorsByName]; + +export type Providers = Ethereum.Provider /*| Tezos.Provider */; + +export type Options = { + flavor?: typeof Ethereum.FlavorName; +} & Ethereum.ProviderOptions; +// | [Tezos.FlavorName]: Tezos.ProviderOptions; diff --git a/src/packages/flavors/npm-shrinkwrap.json b/src/packages/flavors/npm-shrinkwrap.json new file mode 100644 index 0000000000..2fc171fdb6 --- /dev/null +++ b/src/packages/flavors/npm-shrinkwrap.json @@ -0,0 +1,245 @@ +{ + "name": "@ganache/flavors", + "version": "0.1.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", + "dev": true + }, + "clone-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", + "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=", + "dev": true + }, + "clone-stats": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", + "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", + "dev": true + }, + "cloneable-readable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz", + "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "process-nextick-args": "^2.0.0", + "readable-stream": "^2.3.5" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "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 + }, + "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 + }, + "google-closure-compiler": { + "version": "20201102.0.1", + "resolved": "https://registry.npmjs.org/google-closure-compiler/-/google-closure-compiler-20201102.0.1.tgz", + "integrity": "sha512-Cz+1jOswH0MwMVPu1rRH1xD4KYuY5XW2ox5aXwqaAxevqmirhr36f8wgKPHuVRSovFejW640r6UFwyrOT6U0CA==", + "dev": true, + "requires": { + "chalk": "2.x", + "google-closure-compiler-java": "^20201102.0.1", + "google-closure-compiler-linux": "^20201102.0.1", + "google-closure-compiler-osx": "^20201102.0.1", + "google-closure-compiler-windows": "^20201102.0.1", + "minimist": "1.x", + "vinyl": "2.x", + "vinyl-sourcemaps-apply": "^0.2.0" + } + }, + "google-closure-compiler-java": { + "version": "20201102.0.1", + "resolved": "https://registry.npmjs.org/google-closure-compiler-java/-/google-closure-compiler-java-20201102.0.1.tgz", + "integrity": "sha512-pXJIlyqepHhih0HCbShkAZJyViIxdyd4V7MnCUZEXLIIlygw92e2dC+5XiONDQZgRlF93BPmWCy9jr7wYoW1hQ==", + "dev": true + }, + "google-closure-compiler-linux": { + "version": "20201102.0.1", + "resolved": "https://registry.npmjs.org/google-closure-compiler-linux/-/google-closure-compiler-linux-20201102.0.1.tgz", + "integrity": "sha512-aRbyTGnQoFXchcpEFNrP1p/WIvYOgN3hYKI+MOHWkvwVJBY2P8gpb07hAigO8fj+QKD/SFCl+2pXP+JniWOEqw==", + "dev": true, + "optional": true + }, + "google-closure-compiler-osx": { + "version": "20201102.0.1", + "resolved": "https://registry.npmjs.org/google-closure-compiler-osx/-/google-closure-compiler-osx-20201102.0.1.tgz", + "integrity": "sha512-VguqEAOYI6XYZN6JcLMP8fpsoXk1Z9YuntMjv0IDVydkbZaHYOI4zE39FJhMuWiN7gOzSX2b/BBC6GsSh1F3fw==", + "dev": true, + "optional": true + }, + "google-closure-compiler-windows": { + "version": "20201102.0.1", + "resolved": "https://registry.npmjs.org/google-closure-compiler-windows/-/google-closure-compiler-windows-20201102.0.1.tgz", + "integrity": "sha512-LlynipQi/iP76mjkOu6Rc1mCRuxTAhRvLjq10aGfVjKwpbCAF0Jq2a5k2ygr4xYiINNi2/L2qUw6ObPm9wQCOw==", + "dev": true, + "optional": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": 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" + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "replace-ext": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.1.tgz", + "integrity": "sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==", + "dev": true + }, + "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==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "vinyl": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", + "integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==", + "dev": true, + "requires": { + "clone": "^2.1.1", + "clone-buffer": "^1.0.0", + "clone-stats": "^1.0.0", + "cloneable-readable": "^1.0.0", + "remove-trailing-separator": "^1.0.1", + "replace-ext": "^1.0.0" + } + }, + "vinyl-sourcemaps-apply": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.2.1.tgz", + "integrity": "sha1-q2VJ1h0XLCsbh75cUI0jnI74dwU=", + "dev": true, + "requires": { + "source-map": "^0.5.1" + }, + "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 + } + } + } + } +} diff --git a/src/packages/flavors/package.json b/src/packages/flavors/package.json new file mode 100644 index 0000000000..060097bda8 --- /dev/null +++ b/src/packages/flavors/package.json @@ -0,0 +1,51 @@ +{ + "name": "@ganache/flavors", + "version": "0.1.0", + "description": "Ganache's flavors enumeration and TypeScript types", + "author": "David Murdoch (https://davidmurdoch.com)", + "homepage": "https://github.com/trufflesuite/ganache-core/tree/develop/src/packages/flavors#readme", + "license": "MIT", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "source": "index.ts", + "directories": { + "lib": "lib", + "test": "tests" + }, + "files": [ + "lib" + ], + "repository": { + "type": "git", + "url": "https://github.com/trufflesuite/ganache-core.git", + "directory": "src/packages/flavors" + }, + "scripts": { + "tsc": "ttsc" + }, + "bugs": { + "url": "https://github.com/trufflesuite/ganache-core/issues" + }, + "keywords": [ + "ganache", + "ganache-flavors", + "ethereum", + "evm", + "blockchain", + "smart contracts", + "dapps", + "solidity", + "vyper", + "fe", + "web3", + "tooling" + ], + "dependencies": { + "@ganache/ethereum": "^0.1.0", + "@ganache/tezos": "^0.1.0", + "@ganache/utils": "^0.1.0" + }, + "devDependencies": { + "google-closure-compiler": "20201102.0.1" + } +} diff --git a/src/packages/flavors/tests/index.test.ts b/src/packages/flavors/tests/index.test.ts new file mode 100644 index 0000000000..81945f1965 --- /dev/null +++ b/src/packages/flavors/tests/index.test.ts @@ -0,0 +1,5 @@ +import assert from "assert"; + +describe("@ganache/flavors", () => { + it("needs tests"); +}); diff --git a/src/packages/flavors/tsconfig.json b/src/packages/flavors/tsconfig.json new file mode 100644 index 0000000000..25d5c3cf7e --- /dev/null +++ b/src/packages/flavors/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "outDir": "lib" + }, + "include": ["index.ts"] +} diff --git a/src/packages/ganache/.npmignore b/src/packages/ganache/.npmignore new file mode 100644 index 0000000000..115ab4f024 --- /dev/null +++ b/src/packages/ganache/.npmignore @@ -0,0 +1,8 @@ +./index.ts +tests +.nyc_output +coverage +scripts +src +tsconfig.json +typedoc.json diff --git a/src/packages/ganache/LICENSE b/src/packages/ganache/LICENSE new file mode 100644 index 0000000000..39f3b14498 --- /dev/null +++ b/src/packages/ganache/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2019-2020 Truffle Blockchain Group + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/packages/ganache/README.md b/src/packages/ganache/README.md new file mode 100644 index 0000000000..8c29560519 --- /dev/null +++ b/src/packages/ganache/README.md @@ -0,0 +1,3 @@ +# `ganache` + +> TODO: description diff --git a/src/packages/ganache/index.ts b/src/packages/ganache/index.ts new file mode 100644 index 0000000000..edc13ead4d --- /dev/null +++ b/src/packages/ganache/index.ts @@ -0,0 +1,6 @@ +import Ganache from "@ganache/cli"; + +export default { + server: Ganache.server, + provider: Ganache.provider +}; diff --git a/src/packages/ganache/npm-shrinkwrap.json b/src/packages/ganache/npm-shrinkwrap.json new file mode 100644 index 0000000000..51019d29c1 --- /dev/null +++ b/src/packages/ganache/npm-shrinkwrap.json @@ -0,0 +1,2362 @@ +{ + "name": "ganache", + "version": "3.0.0-internal.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@types/eslint": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.2.4.tgz", + "integrity": "sha512-YCY4kzHMsHoyKspQH+nwSe+70Kep7Vjt2X+dZe5Vs2vkRudqtoFoUIv1RlJmZB8Hbp7McneupoZij4PadxsK5Q==", + "dev": true, + "requires": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "@types/eslint-scope": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.0.tgz", + "integrity": "sha512-O/ql2+rrCUe2W2rs7wMR+GqPRcgB6UiqN5RhrR5xruFlY7l9YLMn0ZkDzjoHLeiFkR8MCQZVudUuuvQ2BLC9Qw==", + "dev": true, + "requires": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "@types/estree": { + "version": "0.0.45", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.45.tgz", + "integrity": "sha512-jnqIUKDUqJbDIUxm0Uj7bnlMnRm1T/eZ9N+AVMqhPgzrba2GhGG5o/jCTwmdPK709nEZsGoMzXEDUjcXHa3W0g==", + "dev": true + }, + "@types/json-schema": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz", + "integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==", + "dev": true + }, + "@types/node": { + "version": "14.14.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.6.tgz", + "integrity": "sha512-6QlRuqsQ/Ox/aJEQWBEJG7A9+u7oSYl3mem/K8IzxXG/kAGbV1YPD9Bg9Zw3vyxC/YP+zONKwy8hGkSt1jxFMw==", + "dev": true + }, + "@webassemblyjs/ast": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", + "integrity": "sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA==", + "dev": true, + "requires": { + "@webassemblyjs/helper-module-context": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/wast-parser": "1.9.0" + } + }, + "@webassemblyjs/floating-point-hex-parser": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz", + "integrity": "sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA==", + "dev": true + }, + "@webassemblyjs/helper-api-error": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz", + "integrity": "sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw==", + "dev": true + }, + "@webassemblyjs/helper-buffer": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz", + "integrity": "sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA==", + "dev": true + }, + "@webassemblyjs/helper-code-frame": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz", + "integrity": "sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA==", + "dev": true, + "requires": { + "@webassemblyjs/wast-printer": "1.9.0" + } + }, + "@webassemblyjs/helper-fsm": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz", + "integrity": "sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw==", + "dev": true + }, + "@webassemblyjs/helper-module-context": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz", + "integrity": "sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0" + } + }, + "@webassemblyjs/helper-wasm-bytecode": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz", + "integrity": "sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw==", + "dev": true + }, + "@webassemblyjs/helper-wasm-section": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz", + "integrity": "sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-buffer": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/wasm-gen": "1.9.0" + } + }, + "@webassemblyjs/ieee754": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz", + "integrity": "sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg==", + "dev": true, + "requires": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "@webassemblyjs/leb128": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.9.0.tgz", + "integrity": "sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw==", + "dev": true, + "requires": { + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/utf8": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.9.0.tgz", + "integrity": "sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w==", + "dev": true + }, + "@webassemblyjs/wasm-edit": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz", + "integrity": "sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-buffer": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/helper-wasm-section": "1.9.0", + "@webassemblyjs/wasm-gen": "1.9.0", + "@webassemblyjs/wasm-opt": "1.9.0", + "@webassemblyjs/wasm-parser": "1.9.0", + "@webassemblyjs/wast-printer": "1.9.0" + } + }, + "@webassemblyjs/wasm-gen": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz", + "integrity": "sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/ieee754": "1.9.0", + "@webassemblyjs/leb128": "1.9.0", + "@webassemblyjs/utf8": "1.9.0" + } + }, + "@webassemblyjs/wasm-opt": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz", + "integrity": "sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-buffer": "1.9.0", + "@webassemblyjs/wasm-gen": "1.9.0", + "@webassemblyjs/wasm-parser": "1.9.0" + } + }, + "@webassemblyjs/wasm-parser": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz", + "integrity": "sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-api-error": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/ieee754": "1.9.0", + "@webassemblyjs/leb128": "1.9.0", + "@webassemblyjs/utf8": "1.9.0" + } + }, + "@webassemblyjs/wast-parser": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz", + "integrity": "sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/floating-point-hex-parser": "1.9.0", + "@webassemblyjs/helper-api-error": "1.9.0", + "@webassemblyjs/helper-code-frame": "1.9.0", + "@webassemblyjs/helper-fsm": "1.9.0", + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/wast-printer": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz", + "integrity": "sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/wast-parser": "1.9.0", + "@xtuc/long": "4.2.2" + } + }, + "@webpack-cli/info": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.1.0.tgz", + "integrity": "sha512-uNWSdaYHc+f3LdIZNwhdhkjjLDDl3jP2+XBqAq9H8DjrJUvlOKdP8TNruy1yEaDfgpAIgbSAN7pye4FEHg9tYQ==", + "dev": true, + "requires": { + "envinfo": "^7.7.3" + } + }, + "@webpack-cli/serve": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.1.0.tgz", + "integrity": "sha512-7RfnMXCpJ/NThrhq4gYQYILB18xWyoQcBey81oIyVbmgbc6m5ZHHyFK+DyH7pLHJf0p14MxL4mTsoPAgBSTpIg==", + "dev": true + }, + "@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true + }, + "abstract-leveldown": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-6.2.3.tgz", + "integrity": "sha512-BsLm5vFMRUrrLeCcRc+G0t2qOaTzpoJQLOubq2XM72eNpjF5UdU5o/5NvlNhx95XHcAvcl8OMXr4mlg/fRgUXQ==", + "requires": { + "buffer": "^5.5.0", + "immediate": "^3.2.3", + "level-concat-iterator": "~2.0.0", + "level-supports": "~1.0.0", + "xtend": "~4.0.0" + }, + "dependencies": { + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + } + } + }, + "acorn": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.0.4.tgz", + "integrity": "sha512-XNP0PqF1XD19ZlLKvB7cMmnZswW4C/03pRHgirB30uSJTaS3A3V1/P4sS3HPvFmjoriPCJQs+JDSbm4bL1TxGQ==", + "dev": true + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true + }, + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "array-back": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-4.0.1.tgz", + "integrity": "sha512-Z/JnaVEXv+A9xabHzN43FiiiWEE7gPCRXMrVmRm00tWbjZRul1iHm7ECzlyNq1p4a4ATXz+G9FJ3GqGOkOV3fg==", + "dev": true + }, + "array-filter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-1.0.0.tgz", + "integrity": "sha1-uveeYubvTCpMC4MSMtr/7CUfnYM=", + "dev": true + }, + "asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } + } + }, + "assert": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-2.0.0.tgz", + "integrity": "sha512-se5Cd+js9dXJnu6Ag2JFc00t+HmHOen+8Q+L7O9zI0PqQXr20uk2J0XQqMxZEeo5U50o8Nvmmx7dZrl+Ufr35A==", + "dev": true, + "requires": { + "es6-object-assign": "^1.1.0", + "is-nan": "^1.2.1", + "object-is": "^1.0.1", + "util": "^0.12.0" + } + }, + "available-typed-arrays": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz", + "integrity": "sha512-XWX3OX8Onv97LMk/ftVyBibpGwY5a8SmuxZPzeOxqmuEqUCOM9ZE+uIaD1VNJ5QnvU2UQusvmKbuM1FR8QWGfQ==", + "dev": true, + "requires": { + "array-filter": "^1.0.0" + } + }, + "base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" + }, + "big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true + }, + "bigint-buffer": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/bigint-buffer/-/bigint-buffer-1.1.5.tgz", + "integrity": "sha512-trfYco6AoZ+rKhKnxA0hgX0HAbVP/s808/EuDSe2JDzUnCp/xAsli35Orvk67UrTEcwuxZqYZDmfA2RXJgxVvA==", + "requires": { + "bindings": "^1.3.0" + } + }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "requires": { + "file-uri-to-path": "1.0.0" + } + }, + "bn.js": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.3.tgz", + "integrity": "sha512-GkTiFpjFtUzU9CbMeJ5iazkCzGL3jrhzerzZIuqLABjbwRaFt33I9tUdSNryIptM+RxDet6OKm2WnLXzW51KsQ==", + "dev": true + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" + }, + "browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "dev": true, + "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-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "dev": true, + "requires": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "dev": true, + "requires": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "browserify-rsa": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "randombytes": "^2.0.1" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } + } + }, + "browserify-sign": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz", + "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==", + "dev": true, + "requires": { + "bn.js": "^5.1.1", + "browserify-rsa": "^4.0.1", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.5.3", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.5", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + } + }, + "browserslist": { + "version": "4.14.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.14.6.tgz", + "integrity": "sha512-zeFYcUo85ENhc/zxHbiIp0LGzzTrE2Pv2JhxvS7kpUb9Q9D38kUX6Bie7pGutJ/5iF5rOxE7CepAuWD56xJ33A==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001154", + "electron-to-chromium": "^1.3.585", + "escalade": "^3.1.1", + "node-releases": "^1.1.65" + } + }, + "buffer": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.1.tgz", + "integrity": "sha512-rVAXBwEcEoYtxnHSO5iWyhzV/O1WMtkUYWlfdLS7FjU4PnSJJHEfHXi/uHPI5EwltmOA794gN3bm3/pzuctWjQ==", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.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=", + "dev": true + }, + "call-bind": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.0.tgz", + "integrity": "sha512-AEXsYIyyDY3MCzbwdhzG3Jx1R0J2wetQyUynn6dYHAO+bg8l1k7jwZtRv4ryryFs7EP+NDlikJlVe59jr0cM2w==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.0" + } + }, + "caniuse-lite": { + "version": "1.0.30001154", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001154.tgz", + "integrity": "sha512-y9DvdSti8NnYB9Be92ddMZQrcOe04kcQtcxtBx4NkB04+qZ+JUWotnXBJTmxlKudhxNTQ3RRknMwNU2YQl/Org==", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "chrome-trace-event": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz", + "integrity": "sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "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==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "colorette": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.1.tgz", + "integrity": "sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw==", + "dev": true + }, + "command-line-usage": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/command-line-usage/-/command-line-usage-6.1.0.tgz", + "integrity": "sha512-Ew1clU4pkUeo6AFVDFxCbnN7GIZfXl48HIOQeFQnkO3oOqvpI7wdqtLRwv9iOCZ/7A+z4csVZeiDdEcj8g6Wiw==", + "dev": true, + "requires": { + "array-back": "^4.0.0", + "chalk": "^2.4.2", + "table-layout": "^1.0.0", + "typical": "^5.2.0" + } + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "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 + }, + "create-ecdh": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", + "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "elliptic": "^6.5.3" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } + } + }, + "create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dev": true, + "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": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dev": true, + "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" + } + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "crypto-browserify": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "dev": true, + "requires": { + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0", + "randomfill": "^1.0.3" + } + }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "des.js": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", + "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } + } + }, + "electron-to-chromium": { + "version": "1.3.588", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.588.tgz", + "integrity": "sha512-0zr+ZfytnLeJZxGgmEpPTcItu5Mm4A5zHPZXLfHcGp0mdsk95rmD7ePNewYtK1yIdLbk8Z1U2oTRRfOtR4gbYg==", + "dev": true + }, + "elliptic": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz", + "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==", + "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" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==" + } + } + }, + "emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "enhanced-resolve": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.3.1.tgz", + "integrity": "sha512-G1XD3MRGrGfNcf6Hg0LVZG7GIKcYkbfHa5QMxt1HDUTdYoXH0JR1xXyg+MaKLF73E9A27uWNVxvFivNRYeUB6w==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.4", + "tapable": "^2.0.0" + } + }, + "enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "requires": { + "ansi-colors": "^4.1.1" + } + }, + "envinfo": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.7.3.tgz", + "integrity": "sha512-46+j5QxbPWza0PB1i15nZx0xQ4I/EfQxg9J8Had3b408SV63nEtor2e+oiY63amTo9KTuh2a3XLObNwduxYwwA==", + "dev": true + }, + "errno": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", + "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", + "dev": true, + "requires": { + "prr": "~1.0.1" + } + }, + "es-abstract": { + "version": "1.18.0-next.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz", + "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-negative-zero": "^2.0.0", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "es6-object-assign": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz", + "integrity": "sha1-wsNYJlYkfDnqEHyx5mUrb58kUjw=", + "dev": true + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "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 + }, + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + } + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "events": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.2.0.tgz", + "integrity": "sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg==", + "dev": true + }, + "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==", + "dev": true, + "requires": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "execa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", + "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + } + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "foreach": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", + "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", + "dev": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "get-intrinsic": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.0.1.tgz", + "integrity": "sha512-ZnWP+AmS1VUaLgTRy47+zKtjTxz+0xMpx3I52i+aalBK1QP19ggLF3Db89KJX7kjfOfP2eoa01qc++GwPgufPg==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } + }, + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true + }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true + }, + "hash-base": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", + "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", + "dev": true, + "requires": { + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + } + }, + "hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "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" + } + }, + "human-signals": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", + "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", + "dev": true + }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" + }, + "immediate": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.3.0.tgz", + "integrity": "sha512-HR7EVodfFUdQCTIeySw+WDRFJlPcLOJbXfwwZ7Oom6tjsvZ3bOkCDJHehQC3nxJrv7+f9XecwazynjU8e4Vw3Q==" + }, + "import-local": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.2.tgz", + "integrity": "sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA==", + "dev": true, + "requires": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "interpret": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", + "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", + "dev": true + }, + "is-arguments": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", + "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==", + "dev": true + }, + "is-callable": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz", + "integrity": "sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==", + "dev": true + }, + "is-core-module": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.1.0.tgz", + "integrity": "sha512-YcV7BgVMRFRua2FqQzKtTDMz8iCuLEyGKjr70q8Zm1yy2qKcurbFEd79PAdHV77oL3NrAaOVQIbMmiHQCHB7ZA==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-date-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", + "dev": true + }, + "is-generator-function": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.7.tgz", + "integrity": "sha512-YZc5EwyO4f2kWCax7oegfuSr9mFz1ZvieNYBEjmukLxgXfBUbxAWGVF7GZf0zidYtoBl3WvC07YK0wT76a+Rtw==", + "dev": true + }, + "is-nan": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.0.tgz", + "integrity": "sha512-z7bbREymOqt2CCaZVly8aC4ML3Xhfi0ekuOnjO2L8vKdl+CttdVoGZQhd4adMFAsxQ5VeRVwORs4tU8RH+HFtQ==", + "dev": true, + "requires": { + "define-properties": "^1.1.3" + } + }, + "is-negative-zero": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.0.tgz", + "integrity": "sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE=", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", + "dev": true + }, + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "is-typed-array": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.3.tgz", + "integrity": "sha512-BSYUBOK/HJibQ30wWkWold5txYwMUXQct9YHAQJr8fSwvZoiglcqB0pd7vEN23+Tsi9IUEjztdOSzl4qLVYGTQ==", + "dev": true, + "requires": { + "available-typed-arrays": "^1.0.0", + "es-abstract": "^1.17.4", + "foreach": "^2.0.5", + "has-symbols": "^1.0.1" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", + "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + } + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "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 + }, + "jest-worker": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", + "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", + "dev": true, + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^7.0.0" + } + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json5": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", + "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "keccak": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.1.tgz", + "integrity": "sha512-epq90L9jlFWCW7+pQa6JOnKn2Xgl2mtI664seYR6MHskvI9agt7AnDqmAlp9TqU4/caMYbA08Hi5DMZAl5zdkA==", + "requires": { + "node-addon-api": "^2.0.0", + "node-gyp-build": "^4.2.0" + }, + "dependencies": { + "node-gyp-build": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.2.3.tgz", + "integrity": "sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg==" + } + } + }, + "level-concat-iterator": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/level-concat-iterator/-/level-concat-iterator-2.0.1.tgz", + "integrity": "sha512-OTKKOqeav2QWcERMJR7IS9CUo1sHnke2C0gkSmcR7QuEtFNLLzHQAvnMw8ykvEcv0Qtkg0p7FOwP1v9e5Smdcw==" + }, + "level-js": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/level-js/-/level-js-5.0.2.tgz", + "integrity": "sha512-SnBIDo2pdO5VXh02ZmtAyPP6/+6YTJg2ibLtl9C34pWvmtMEmRTWpra+qO/hifkUtBTOtfx6S9vLDjBsBK4gRg==", + "dev": true, + "requires": { + "abstract-leveldown": "~6.2.3", + "buffer": "^5.5.0", + "inherits": "^2.0.3", + "ltgt": "^2.1.2" + }, + "dependencies": { + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + } + } + }, + "level-supports": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/level-supports/-/level-supports-1.0.1.tgz", + "integrity": "sha512-rXM7GYnW8gsl1vedTJIbzOrRv85c/2uCMpiiCzO2fndd06U/kUXEEU9evYn4zFggBOg36IsBW8LzqIpETwwQzg==", + "requires": { + "xtend": "^4.0.2" + } + }, + "leveldown": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/leveldown/-/leveldown-5.6.0.tgz", + "integrity": "sha512-iB8O/7Db9lPaITU1aA2txU/cBEXAt4vWwKQRrrWuS6XDgbP4QZGj9BL2aNbwb002atoQ/lIotJkfyzz+ygQnUQ==", + "requires": { + "abstract-leveldown": "~6.2.1", + "napi-macros": "~2.0.0", + "node-gyp-build": "~4.1.0" + } + }, + "leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true + }, + "loader-runner": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.1.0.tgz", + "integrity": "sha512-oR4lB4WvwFoC70ocraKhn5nkKSs23t57h9udUgw8o0iH8hMXeEoRuUgfcvgUwAJ1ZpRqBvcou4N2SMvM1DwMrA==", + "dev": true + }, + "loader-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", + "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "dev": true + }, + "ltgt": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ltgt/-/ltgt-2.2.1.tgz", + "integrity": "sha1-81ypHEk/e3PaDgdJUwTxezH4fuU=", + "dev": true + }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "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==", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "memory-fs": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", + "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", + "dev": true, + "requires": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": 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" + } + }, + "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==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + } + }, + "miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } + } + }, + "mime-db": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", + "dev": true + }, + "mime-types": { + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", + "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "dev": true, + "requires": { + "mime-db": "1.44.0" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "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=" + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "napi-macros": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-macros/-/napi-macros-2.0.0.tgz", + "integrity": "sha512-A0xLykHtARfueITVDernsAWdtIMbOJgKgcluwENp3AlsKN/PloyO10HtmoqnFAQAcxPkgZN7wdfPfEd0zNGxbg==" + }, + "neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "node-addon-api": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", + "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==" + }, + "node-gyp-build": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.1.1.tgz", + "integrity": "sha512-dSq1xmcPDKPZ2EED2S6zw/b9NKsqzXRE6dVr8TVQnI3FJOTteUMuqF3Qqs6LZg+mLGYJWqQzMbIjMtJqTv87nQ==" + }, + "node-loader": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/node-loader/-/node-loader-1.0.2.tgz", + "integrity": "sha512-myxAxpyMR7knjA4Uzwf3gjxaMtxSWj2vpm9o6AYWWxQ1S3XMBNeG2vzYcp/5eW03cBGfgSxyP+wntP8qhBJNhQ==", + "dev": true, + "requires": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + } + }, + "node-releases": { + "version": "1.1.65", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.65.tgz", + "integrity": "sha512-YpzJOe2WFIW0V4ZkJQd/DGR/zdVwc/pI4Nl1CZrBO19FdRcSTmsuhdttw9rsTzzJLrNcSloLiBbEYx1C4f6gpA==", + "dev": true + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + }, + "object-inspect": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", + "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", + "dev": true + }, + "object-is": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.3.tgz", + "integrity": "sha512-teyqLvFWzLkq5B9ki8FVWA902UER2qkxmdA4nLf+wjOLAWgxzCWZNCxpDq9MvE8MmhWNr+I8w3BN49Vx36Y6Xg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.1" + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "os-browserify": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", + "dev": true + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "parse-asn1": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", + "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", + "dev": true, + "requires": { + "asn1.js": "^5.2.0", + "browserify-aes": "^1.0.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3", + "safe-buffer": "^5.1.1" + } + }, + "path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "pbkdf2": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz", + "integrity": "sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg==", + "dev": true, + "requires": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "picomatch": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "dev": true + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + } + }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", + "dev": true + }, + "public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } + } + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "dev": true, + "requires": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "rechoir": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.0.tgz", + "integrity": "sha512-ADsDEH2bvbjltXEP+hTIAmeFekTFK0V2BTxMkok6qILyAJEXV0AFfoWcAq4yfll5VdIMd/RVXq0lR+wQi5ZU3Q==", + "dev": true, + "requires": { + "resolve": "^1.9.0" + } + }, + "reduce-flatten": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-2.0.0.tgz", + "integrity": "sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w==", + "dev": true + }, + "resolve": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.18.1.tgz", + "integrity": "sha512-lDfCPaMKfOJXjy0dPayzPdF1phampNWr3qFCjAu+rw/qbQmr5jWH5xN2hwh9QKfw9E5v4hwV7A+jrCmL8yjjqA==", + "dev": true, + "requires": { + "is-core-module": "^2.0.0", + "path-parse": "^1.0.6" + } + }, + "resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "requires": { + "resolve-from": "^5.0.0" + } + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + }, + "ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "schema-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", + "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.6", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + }, + "secp256k1": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.2.tgz", + "integrity": "sha512-UDar4sKvWAksIlfX3xIaQReADn+WFnHvbVujpcbr+9Sf/69odMwy2MUsz5CKLQgX9nsIyrjuxL2imVyoNHa3fg==", + "requires": { + "elliptic": "^6.5.2", + "node-addon-api": "^2.0.0", + "node-gyp-build": "^4.2.0" + }, + "dependencies": { + "node-gyp-build": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.2.3.tgz", + "integrity": "sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg==" + } + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "serialize-javascript": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", + "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-loader": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/shebang-loader/-/shebang-loader-0.0.1.tgz", + "integrity": "sha1-pAAEldRMzu++xjQ157FphWn6Uuw=", + "dev": true + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", + "dev": true + }, + "source-list-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", + "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", + "dev": true + }, + "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.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "stream-browserify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", + "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==", + "dev": true, + "requires": { + "inherits": "~2.0.4", + "readable-stream": "^3.5.0" + } + }, + "string.prototype.trimend": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.2.tgz", + "integrity": "sha512-8oAG/hi14Z4nOVP0z6mdiVZ/wqjDtWSLygMigTzAb+7aPEDTleeFf+WrF+alzecxIRkckkJVn+dTlwzJXORATw==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.1" + } + }, + "string.prototype.trimstart": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.2.tgz", + "integrity": "sha512-7F6CdBTl5zyu30BJFdzSTlSlLPwODC23Od+iLoVH8X6+3fvDPPuBVVj9iaB1GOsSTSIgVfsfm27R2FGrAPznWg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.1" + } + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "table-layout": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-1.0.1.tgz", + "integrity": "sha512-dEquqYNJiGwY7iPfZ3wbXDI944iqanTSchrACLL2nOB+1r+h1Nzu2eH+DuPPvWvm5Ry7iAPeFlgEtP5bIp5U7Q==", + "dev": true, + "requires": { + "array-back": "^4.0.1", + "deep-extend": "~0.6.0", + "typical": "^5.2.0", + "wordwrapjs": "^4.0.0" + } + }, + "tapable": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.0.0.tgz", + "integrity": "sha512-bjzn0C0RWoffnNdTzNi7rNDhs1Zlwk2tRXgk8EiHKAOX1Mag3d6T0Y5zNa7l9CJ+EoUne/0UHdwS8tMbkh9zDg==", + "dev": true + }, + "terser": { + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.3.8.tgz", + "integrity": "sha512-zVotuHoIfnYjtlurOouTazciEfL7V38QMAOhGqpXDEg6yT13cF4+fEP9b0rrCEQTn+tT46uxgFsTZzhygk+CzQ==", + "dev": true, + "requires": { + "commander": "^2.20.0", + "source-map": "~0.7.2", + "source-map-support": "~0.5.19" + }, + "dependencies": { + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "dev": true + } + } + }, + "terser-webpack-plugin": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.0.3.tgz", + "integrity": "sha512-zFdGk8Lh9ZJGPxxPE6jwysOlATWB8GMW8HcfGULWA/nPal+3VdATflQvSBSLQJRCmYZnfFJl6vkRTiwJGNgPiQ==", + "dev": true, + "requires": { + "jest-worker": "^26.6.1", + "p-limit": "^3.0.2", + "schema-utils": "^3.0.0", + "serialize-javascript": "^5.0.1", + "source-map": "^0.6.1", + "terser": "^5.3.8" + }, + "dependencies": { + "p-limit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.0.2.tgz", + "integrity": "sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + } + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "ts-loader": { + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-8.0.7.tgz", + "integrity": "sha512-ooa4wxlZ9TOXaJ/iVyZlWsim79Ul4KyifSwyT2hOrbQA6NZJypsLOE198o8Ko+JV+ZHnMArvWcl4AnRqpCU/Mw==", + "dev": true, + "requires": { + "chalk": "^2.3.0", + "enhanced-resolve": "^4.0.0", + "loader-utils": "^1.0.2", + "micromatch": "^4.0.0", + "semver": "^6.0.0" + }, + "dependencies": { + "enhanced-resolve": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.3.0.tgz", + "integrity": "sha512-3e87LvavsdxyoCfGusJnrZ5G8SLPOFeHSNpZI/ATL9a5leXo2k0w6MKnbqhdBad9qTobSfB20Ld7UmgoNbAZkQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "memory-fs": "^0.5.0", + "tapable": "^1.0.0" + } + }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + } + }, + "tapable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", + "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", + "dev": true + } + } + }, + "ts-node": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.0.0.tgz", + "integrity": "sha512-/TqB4SnererCDR/vb4S/QvSZvzQMJN8daAslg7MeaiHvD8rDZsSfXmNeNumyZZzMned72Xoq/isQljYSt8Ynfg==", + "dev": true, + "requires": { + "arg": "^4.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "source-map-support": "^0.5.17", + "yn": "3.1.1" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "ttypescript": { + "version": "1.5.12", + "resolved": "https://registry.npmjs.org/ttypescript/-/ttypescript-1.5.12.tgz", + "integrity": "sha512-1ojRyJvpnmgN9kIHmUnQPlEV1gq+VVsxVYjk/NfvMlHSmYxjK5hEvOOU2MQASrbekTUiUM7pR/nXeCc8bzvMOQ==", + "dev": true, + "requires": { + "resolve": ">=1.9.0" + } + }, + "typescript": { + "version": "4.1.1-rc", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.1-rc.tgz", + "integrity": "sha512-tgNcFrLIjlaMWEc7bKC0bxLNIt8BIAauY/HLUOQDyTP75HGskETtXOt46x4EKAHRKhWVLMc7yM02puTHa/yhCA==", + "dev": true + }, + "typical": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", + "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", + "dev": true + }, + "uri-js": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz", + "integrity": "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "util": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.3.tgz", + "integrity": "sha512-I8XkoQwE+fPQEhy9v012V+TSdH2kp9ts29i20TaaDUXsg7x/onePbhFJUExBfv/2ay1ZOp/Vsm3nDlmnFGSAog==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "safe-buffer": "^5.1.2", + "which-typed-array": "^1.1.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "v8-compile-cache": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz", + "integrity": "sha512-gTpR5XQNKFwOd4clxfnhaqvfqMpqEwr4tOtCyz4MtYZX2JYhfr1JvBFKdS+7K/9rfpZR3VLX+YWBbKoxCgS43Q==", + "dev": true + }, + "watchpack": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.0.1.tgz", + "integrity": "sha512-vO8AKGX22ZRo6PiOFM9dC0re8IcKh8Kd/aH2zeqUc6w4/jBGlTy2P7fTC6ekT0NjVeGjgU2dGC5rNstKkeLEQg==", + "dev": true, + "requires": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + } + }, + "webpack": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.4.0.tgz", + "integrity": "sha512-udpYTyqz8toTTdaOsL2QKPLeZLt2IEm9qY7yTXuFEQhKu5bk0yQD9BtAdVQksmz4jFbbWOiWmm3NHarO0zr/ng==", + "dev": true, + "requires": { + "@types/eslint-scope": "^3.7.0", + "@types/estree": "^0.0.45", + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-module-context": "1.9.0", + "@webassemblyjs/wasm-edit": "1.9.0", + "@webassemblyjs/wasm-parser": "1.9.0", + "acorn": "^8.0.4", + "browserslist": "^4.14.5", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.3.1", + "eslint-scope": "^5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.4", + "json-parse-better-errors": "^1.0.2", + "loader-runner": "^4.1.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "pkg-dir": "^4.2.0", + "schema-utils": "^3.0.0", + "tapable": "^2.0.0", + "terser-webpack-plugin": "^5.0.3", + "watchpack": "^2.0.0", + "webpack-sources": "^2.1.1" + } + }, + "webpack-cli": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.2.0.tgz", + "integrity": "sha512-EIl3k88vaF4fSxWSgtAQR+VwicfLMTZ9amQtqS4o+TDPW9HGaEpbFBbAZ4A3ZOT5SOnMxNOzROsSTPiE8tBJPA==", + "dev": true, + "requires": { + "@webpack-cli/info": "^1.1.0", + "@webpack-cli/serve": "^1.1.0", + "colorette": "^1.2.1", + "command-line-usage": "^6.1.0", + "commander": "^6.2.0", + "enquirer": "^2.3.6", + "execa": "^4.1.0", + "import-local": "^3.0.2", + "interpret": "^2.2.0", + "leven": "^3.1.0", + "rechoir": "^0.7.0", + "v8-compile-cache": "^2.2.0", + "webpack-merge": "^4.2.2" + }, + "dependencies": { + "commander": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.0.tgz", + "integrity": "sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==", + "dev": true + } + } + }, + "webpack-merge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.2.2.tgz", + "integrity": "sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g==", + "dev": true, + "requires": { + "lodash": "^4.17.15" + } + }, + "webpack-sources": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-2.2.0.tgz", + "integrity": "sha512-bQsA24JLwcnWGArOKUxYKhX3Mz/nK1Xf6hxullKERyktjNMC4x8koOeaDNTA2fEJ09BdWLbM/iTW0ithREUP0w==", + "dev": true, + "requires": { + "source-list-map": "^2.0.1", + "source-map": "^0.6.1" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-typed-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.2.tgz", + "integrity": "sha512-KT6okrd1tE6JdZAy3o2VhMoYPh3+J6EMZLyrxBQsZflI1QCZIxMrIYLkosd8Twf+YfknVIHmYQPgJt238p8dnQ==", + "dev": true, + "requires": { + "available-typed-arrays": "^1.0.2", + "es-abstract": "^1.17.5", + "foreach": "^2.0.5", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.1", + "is-typed-array": "^1.1.3" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", + "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + } + } + }, + "wordwrapjs": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-4.0.0.tgz", + "integrity": "sha512-Svqw723a3R34KvsMgpjFBYCgNOSdcW3mQFK4wIfhGQhtaFVOJmdYoXgi63ne3dTlWgatVcUc7t4HtQ/+bUVIzQ==", + "dev": true, + "requires": { + "reduce-flatten": "^2.0.0", + "typical": "^5.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true + } + } +} diff --git a/src/packages/ganache/package.json b/src/packages/ganache/package.json new file mode 100644 index 0000000000..06ce1ecce1 --- /dev/null +++ b/src/packages/ganache/package.json @@ -0,0 +1,80 @@ +{ + "name": "ganache", + "version": "3.0.0-internal.0", + "description": "A library and cli to create a local blockchain for fast Ethereum development.", + "author": "David Murdoch", + "homepage": "https://github.com/trufflesuite/ganache-core/tree/develop/src/packages/ganache#readme", + "license": "MIT", + "main": "dist/node/ganache.min.js", + "browser": "dist/web/ganache.min.js", + "types": "lib/index.d.ts", + "source": "index.ts", + "bin": { + "ganache": "dist/cli/ganache.min.js", + "ganache-cli": "dist/cli/ganache.min.js" + }, + "directories": { + "lib": "lib", + "test": "tests" + }, + "files": [ + "dist" + ], + "repository": { + "type": "git", + "url": "https://github.com/trufflesuite/ganache-core.git", + "directory": "src/packages/ganache" + }, + "scripts": { + "build": "webpack", + "tsc": "ttsc", + "test": "nyc npm run mocha", + "mocha": "cross-env TS_NODE_COMPILER=ttypescript TS_NODE_FILES=true mocha --exit --check-leaks --throw-deprecation --trace-warnings --require ts-node/register 'tests/**/*.test.ts'" + }, + "bugs": { + "url": "https://github.com/trufflesuite/ganache-core/issues" + }, + "keywords": [ + "ganache", + "ethereum", + "evm", + "blockchain", + "smart contracts", + "dapps", + "solidity", + "vyper", + "fe", + "web3", + "tooling", + "truffle" + ], + "devDependencies": { + "@ganache/cli": "^0.1.0", + "@ganache/flavors": "^0.1.0", + "@types/node": "14.14.6", + "assert": "2.0.0", + "buffer": "6.0.1", + "crypto-browserify": "3.12.0", + "events": "3.2.0", + "level-js": "5.0.2", + "node-loader": "1.0.2", + "os-browserify": "0.3.0", + "path-browserify": "1.0.1", + "process": "0.11.10", + "shebang-loader": "0.0.1", + "stream-browserify": "3.0.0", + "ts-loader": "8.0.7", + "ts-node": "9.0.0", + "ttypescript": "1.5.12", + "typescript": "4.1.1-rc", + "util": "0.12.3", + "webpack": "5.4.0", + "webpack-cli": "4.2.0" + }, + "dependencies": { + "bigint-buffer": "1.1.5", + "keccak": "3.0.1", + "leveldown": "5.6.0", + "secp256k1": "4.0.2" + } +} diff --git a/src/packages/ganache/src/cli.ts b/src/packages/ganache/src/cli.ts new file mode 100644 index 0000000000..138f2163f5 --- /dev/null +++ b/src/packages/ganache/src/cli.ts @@ -0,0 +1,3 @@ +#!/usr/bin/env node + +import "@ganache/cli/cli"; diff --git a/src/packages/ganache/tests/index.test.ts b/src/packages/ganache/tests/index.test.ts new file mode 100644 index 0000000000..70ad885d99 --- /dev/null +++ b/src/packages/ganache/tests/index.test.ts @@ -0,0 +1,6 @@ +import assert from "assert"; +import ganache from ".."; + +describe("@ganache/ganache", () => { + it("needs tests"); +}); diff --git a/src/packages/ganache/tsconfig.json b/src/packages/ganache/tsconfig.json new file mode 100644 index 0000000000..2bb885156a --- /dev/null +++ b/src/packages/ganache/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "outDir": "lib" + }, + "include": ["index.ts", "src/cli.ts"] +} diff --git a/src/packages/ganache/webpack.config.ts b/src/packages/ganache/webpack.config.ts new file mode 100644 index 0000000000..a6ce6ef258 --- /dev/null +++ b/src/packages/ganache/webpack.config.ts @@ -0,0 +1,5 @@ +import webpackBrowser from "./webpack/webpack.browser.config"; +import webpackNode from "./webpack/webpack.node.config"; +import webpackCli from "./webpack/webpack.cli.config"; + +export default [webpackBrowser, webpackNode, webpackCli]; diff --git a/src/packages/ganache/webpack/polyfills/browser-bigint-buffer.ts b/src/packages/ganache/webpack/polyfills/browser-bigint-buffer.ts new file mode 100644 index 0000000000..8df9d9f3a0 --- /dev/null +++ b/src/packages/ganache/webpack/polyfills/browser-bigint-buffer.ts @@ -0,0 +1,4 @@ +export function toBigIntBE(buf: Buffer) { + // TODO(perf): this is slow. Can we make it fast in browserland? + return BigInt(`0x${buf.toString("hex")}`); +} diff --git a/src/packages/ganache/webpack/polyfills/browser-tmp-promise.ts b/src/packages/ganache/webpack/polyfills/browser-tmp-promise.ts new file mode 100644 index 0000000000..5e35d343b0 --- /dev/null +++ b/src/packages/ganache/webpack/polyfills/browser-tmp-promise.ts @@ -0,0 +1,364 @@ +declare const indexedDB: any; + +import { resolve, isAbsolute, basename, join, relative } from "path"; +import { randomBytes, pseudoRandomBytes } from "crypto"; + +export interface TmpNameOptions { + dir?: string; + name?: string; + postfix?: string; + prefix?: string; + template?: string; + tmpdir?: string; + tries?: number; +} + +export interface DirOptions extends TmpNameOptions { + keep?: boolean; + mode?: number; + unsafeCleanup?: boolean; +} + +const // the random characters to choose from + RANDOM_CHARS = + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", + TEMPLATE_PATTERN = /XXXXXX/, + DEFAULT_TRIES = 3, + _removeObjects = []; + +let _gracefulCleanup = false; + +/** + * The garbage collector. + * + * @private + */ +async function _garbageCollector() { + /* istanbul ignore else */ + if (!_gracefulCleanup) return; + + // the function being called removes itself from _removeObjects, + // loop until _removeObjects is empty + while (_removeObjects.length) { + try { + _removeObjects[0](); + } catch (e) { + // already removed? + } + } +} + +/** + * Random name generator based on crypto. + * Adapted from http://blog.tompawlak.org/how-to-generate-random-values-nodejs-javascript + * + * @param {number} howMany + * @returns {string} the generated random name + * @private + */ +function _randomChars(howMany: number) { + let value = [], + rnd = null; + + // make sure that we do not fail because we ran out of entropy + try { + rnd = randomBytes(howMany); + } catch (e) { + rnd = pseudoRandomBytes(howMany); + } + + for (var i = 0; i < howMany; i++) { + value.push(RANDOM_CHARS[rnd[i] % RANDOM_CHARS.length]); + } + + return value.join(""); +} + +/** + * Helper which determines whether a string s is blank, that is undefined, or empty or null. + * + * @private + * @param {string} s + * @returns {Boolean} true whether the string s is blank, false otherwise + */ +function _isBlank(s: string) { + return s === null || _isUndefined(s) || !s.trim(); +} + +/** + * Checks whether the `obj` parameter is defined or not. + * + * @param {Object} obj + * @returns {boolean} true if the object is undefined + * @private + */ +function _isUndefined(obj: Object) { + return typeof obj === "undefined"; +} + +/** + * Generates a new temporary name. + * + * @param {Object} opts + * @returns {string} the new random name according to opts + * @private + */ +function _generateTmpName(options: TmpNameOptions) { + const tmpDir = options.tmpdir; + + /* istanbul ignore else */ + if (!_isUndefined(options.name)) + return join(tmpDir, options.dir, options.name); + + /* istanbul ignore else */ + if (!_isUndefined(options.template)) + return join(tmpDir, options.dir, options.template).replace( + TEMPLATE_PATTERN, + _randomChars(6) + ); + + // prefix and postfix + const name = [ + options.prefix ? options.prefix : "tmp", + "-", + _randomChars(12), + options.postfix ? "-" + options.postfix : "" + ].join(""); + + return join(tmpDir, options.dir, name); +} + +function _indexedDbExists(name: string) { + return new Promise(resolve => { + const req = indexedDB.open(name); + let existed = true; + req.onsuccess = function () { + req.result.close(); + if (!existed) indexedDB.deleteDatabase(name); + resolve(existed); + }; + req.onupgradeneeded = function () { + existed = false; + }; + }); +} + +/** + * Gets a temporary file name. + * + * @param {(Options)} opts options + */ +function tmpName(options: TmpNameOptions) { + const opts = _parseArguments(options); + + _assertAndSanitizeOptions(opts); + + let tries = opts.tries; + + return (async function _getUniqueName() { + const name = _generateTmpName(opts); + + // check whether the path exists then retry if needed + const exists = await _indexedDbExists(name); + if (exists) { + if (tries-- > 0) return _getUniqueName(); + + throw new Error( + "Could not get a unique tmp filename, max tries reached " + name + ); + } + + return name; + })(); +} + +function _prepareTmpDirRemoveCallback(name: string, options: DirOptions) { + const removeCallback = () => { + return new Promise((resolve, reject) => { + const request = indexedDB.deleteDatabase(name); + request.onerror = function (e) { + reject(); + }; + + request.onsuccess = function () { + resolve(void 0); + }; + }); + }; + if (!options.keep) _removeObjects.unshift(removeCallback); + return removeCallback; +} + +function _assertAndSanitizeOptions(options: DirOptions) { + options.tmpdir = _getTmpDir(options); + + const tmpDir = options.tmpdir; + + if (!_isUndefined(options.name)) + _assertIsRelative(options.name, "name", tmpDir); + + if (!_isUndefined(options.dir)) _assertIsRelative(options.dir, "dir", tmpDir); + + if (!_isUndefined(options.template)) { + _assertIsRelative(options.template, "template", tmpDir); + if (!options.template.match(TEMPLATE_PATTERN)) + throw new Error(`Invalid template, found "${options.template}".`); + } + + if ( + (!_isUndefined(options.tries) && isNaN(options.tries)) || + options.tries < 0 + ) + throw new Error(`Invalid tries, found "${options.tries}".`); + + options.tries = _isUndefined(options.name) + ? options.tries || DEFAULT_TRIES + : 1; + options.keep = !!options.keep; + + // sanitize dir, also keep (multiple) blanks if the user, purportedly sane, requests us to + options.dir = _isUndefined(options.dir) + ? "" + : relative(tmpDir, _resolvePath(options.dir, tmpDir)); + options.template = _isUndefined(options.template) + ? undefined + : relative(tmpDir, _resolvePath(options.template, tmpDir)); + // sanitize further if template is relative to options.dir + options.template = _isBlank(options.template) + ? undefined + : relative(options.dir, options.template); + + options.name = _isUndefined(options.name) + ? undefined + : _sanitizeName(options.name); + options.prefix = _isUndefined(options.prefix) ? "" : options.prefix; + options.postfix = _isUndefined(options.postfix) ? "" : options.postfix; +} + +/** + * Resolve the specified path name in respect to tmpDir. + * + * The specified name might include relative path components, e.g. ../ + * so we need to resolve in order to be sure that is is located inside tmpDir + * + * @param name + * @param tmpDir + * @returns {string} + * @private + */ +function _resolvePath(name: string, tmpDir: string) { + const sanitizedName = _sanitizeName(name); + if (sanitizedName.startsWith(tmpDir)) { + return resolve(sanitizedName); + } else { + return resolve(join(tmpDir, sanitizedName)); + } +} + +/** + * Sanitize the specified path name by removing all quote characters. + * + * @param name + * @returns {string} + * @private + */ +function _sanitizeName(name: string) { + if (_isBlank(name)) { + return name; + } + return name.replace(/["']/g, ""); +} + +/** + * Asserts whether specified name is relative to the specified tmpDir. + * + * @param {string} name + * @param {string} option + * @param {string} tmpDir + * @throws {Error} + * @private + */ +function _assertIsRelative( + name: string, + option: "name" | "dir" | "template", + tmpDir: string +) { + if (option === "name") { + // assert that name is not absolute and does not contain a path + if (isAbsolute(name)) + throw new Error( + `${option} option must not contain an absolute path, found "${name}".` + ); + // must not fail on valid . or .. or similar such constructs + let _basename = basename(name); + if (_basename === ".." || _basename === "." || _basename !== name) + throw new Error( + `${option} option must not contain a path, found "${name}".` + ); + } else { + // if (option === 'dir' || option === 'template') { + // assert that dir or template are relative to tmpDir + if (isAbsolute(name) && !name.startsWith(tmpDir)) { + throw new Error( + `${option} option must be relative to "${tmpDir}", found "${name}".` + ); + } + let resolvedPath = _resolvePath(name, tmpDir); + if (!resolvedPath.startsWith(tmpDir)) + throw new Error( + `${option} option must be relative to "${tmpDir}", found "${resolvedPath}".` + ); + } +} + +function _parseArguments(options: DirOptions): DirOptions { + if (_isUndefined(options)) { + return {}; + } + // copy options so we do not leak the changes we make internally + const actualOptions = {}; + for (const key of Object.getOwnPropertyNames(options)) { + actualOptions[key] = options[key]; + } + + return actualOptions; +} + +/** + * Creates a temporary directory. + * + * @param {(Options)} opts the options + */ +export async function dir(options: DirOptions) { + const opts = _parseArguments(options); + // gets a temporary filename + const path = await tmpName(opts); + return { + path, + cleanup: _prepareTmpDirRemoveCallback(path, options) + }; +} + +/** + * Sets the graceful cleanup. + * + * If graceful cleanup is set, tmp will remove all controlled temporary objects on process exit, otherwise the + * temporary objects will remain in place, waiting to be cleaned up on system restart or otherwise scheduled temporary + * object removals. + */ +export function setGracefulCleanup() { + _gracefulCleanup = true; +} + +/** + * Returns the currently configured tmp dir. + * + * @private + * @param {?Options} options + * @returns {string} the currently configured tmp dir + */ +function _getTmpDir(options: TmpNameOptions) { + return resolve(_sanitizeName((options && options.tmpdir) || "/tmp")); +} + +// window.addEventListener("beforeunload", _garbageCollector); diff --git a/src/packages/ganache/webpack/webpack.browser.config.ts b/src/packages/ganache/webpack/webpack.browser.config.ts new file mode 100644 index 0000000000..ac8ae380e2 --- /dev/null +++ b/src/packages/ganache/webpack/webpack.browser.config.ts @@ -0,0 +1,41 @@ +import base from "./webpack.common.config"; +import webpack from "webpack"; +import path from "path"; +import merge from "webpack-merge"; + +const config: webpack.Configuration = merge({}, base, { + resolve: { + extensions: [".tsx", ".ts", ".js"], + fallback: { + //#region node polyfills + util: require.resolve("util/"), + crypto: require.resolve("crypto-browserify"), + path: require.resolve("path-browserify"), + assert: require.resolve("assert/"), + stream: require.resolve("stream-browserify/"), + os: require.resolve("os-browserify/browser"), + process: require.resolve("process/browser"), + events: require.resolve("events/"), + buffer: require.resolve("buffer/"), + fs: false + //#endregion node polyfills + }, + alias: { + "tmp-promise": require.resolve("./polyfills/browser-tmp-promise"), + "bigint-buffer": require.resolve("./polyfills/browser-bigint-buffer"), + // replace leveldown with a browser version + leveldown: require.resolve("level-js/"), + // browser version can't start a server, so just remove the websocket server since it can't work anyway + "uWebSockets.js": false + } + }, + output: { + path: path.resolve(__dirname, "../", "dist", "web") + }, + plugins: [ + new webpack.ProvidePlugin({ Buffer: ["buffer", "Buffer"] }), + new webpack.ProvidePlugin({ process: ["process"] }) + ] +}); + +export default config; diff --git a/src/packages/ganache/webpack/webpack.cli.config.ts b/src/packages/ganache/webpack/webpack.cli.config.ts new file mode 100644 index 0000000000..3c78cea72c --- /dev/null +++ b/src/packages/ganache/webpack/webpack.cli.config.ts @@ -0,0 +1,31 @@ +import base from "./webpack.common.config"; +import webpack from "webpack"; +import path from "path"; +import merge from "webpack-merge"; + +const config: webpack.Configuration = merge({}, base, { + entry: "./src/cli.ts", + target: "node10.7", + output: { + path: path.resolve(__dirname, "../", "dist", "cli") + }, + externals: ["bigint-buffer", "leveldown", "secp256k1", "keccak"], + plugins: [ + new webpack.BannerPlugin({ banner: "#!/usr/bin/env node", raw: true }) + ], + module: { + rules: [ + { + // webpack load native modules + test: /\.node$/, + loader: "node-loader" + }, + { + test: /cli.ts$/, + use: "shebang-loader" + } + ] + } +}); + +export default config; diff --git a/src/packages/ganache/webpack/webpack.common.config.ts b/src/packages/ganache/webpack/webpack.common.config.ts new file mode 100644 index 0000000000..be145ce551 --- /dev/null +++ b/src/packages/ganache/webpack/webpack.common.config.ts @@ -0,0 +1,41 @@ +import webpack from "webpack"; + +// inlines files, like package.json +import packageJsonTransformer from "ts-transformer-inline-file/transformer"; + +const base: webpack.Configuration = { + entry: "./index.ts", + devtool: "source-map", + module: { + rules: [ + { + test: /\.tsx?$/, + use: [ + { + loader: "ts-loader", + options: { + getCustomTransformers: program => ({ + before: [packageJsonTransformer(program)] + }) + } + } + ], + exclude: /node_modules/ + } + ] + }, + resolve: { + extensions: [".tsx", ".ts", ".js"] + }, + output: { + filename: "ganache.min.js", + library: "Ganache", + libraryExport: "default", + libraryTarget: "umd" + }, + stats: { + colors: true + } +}; + +export default base; diff --git a/src/packages/ganache/webpack/webpack.node.config.ts b/src/packages/ganache/webpack/webpack.node.config.ts new file mode 100644 index 0000000000..babd9f0162 --- /dev/null +++ b/src/packages/ganache/webpack/webpack.node.config.ts @@ -0,0 +1,23 @@ +import base from "./webpack.common.config"; +import webpack from "webpack"; +import path from "path"; +import merge from "webpack-merge"; + +const config: webpack.Configuration = merge({}, base, { + target: "node10.7", + output: { + path: path.resolve(__dirname, "../", "dist", "node") + }, + externals: ["leveldown", "secp256k1", "keccak"], + module: { + rules: [ + { + // webpack load native modules + test: /\.node$/, + loader: "node-loader" + } + ] + } +}); + +export default config; diff --git a/src/packages/options/.npmignore b/src/packages/options/.npmignore new file mode 100644 index 0000000000..115ab4f024 --- /dev/null +++ b/src/packages/options/.npmignore @@ -0,0 +1,8 @@ +./index.ts +tests +.nyc_output +coverage +scripts +src +tsconfig.json +typedoc.json diff --git a/src/packages/options/LICENSE b/src/packages/options/LICENSE new file mode 100644 index 0000000000..39f3b14498 --- /dev/null +++ b/src/packages/options/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2019-2020 Truffle Blockchain Group + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/packages/options/README.md b/src/packages/options/README.md new file mode 100644 index 0000000000..177621cbe7 --- /dev/null +++ b/src/packages/options/README.md @@ -0,0 +1,3 @@ +# `@ganache/options` + +Ganache's server/provider options TypeScript types diff --git a/src/packages/options/index.ts b/src/packages/options/index.ts new file mode 100644 index 0000000000..7a9075164c --- /dev/null +++ b/src/packages/options/index.ts @@ -0,0 +1,11 @@ +export * from "./src/base"; +export { + Options, + LegacyOptions, + Legacy, + OptionName, + OptionRawType +} from "./src/getters"; +export * from "./src/exclusive"; +export * from "./src/definition"; +export * from "./src/create"; diff --git a/src/packages/options/npm-shrinkwrap.json b/src/packages/options/npm-shrinkwrap.json new file mode 100644 index 0000000000..7f40e07aef --- /dev/null +++ b/src/packages/options/npm-shrinkwrap.json @@ -0,0 +1,387 @@ +{ + "name": "@ganache/options", + "version": "0.1.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@types/node": { + "version": "11.11.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-11.11.6.tgz", + "integrity": "sha512-Exw4yUWMBXM3X+8oqzJNRqZSwUAaS4+7NdvHqQuFi/d+synz++xmX3QIf+BFqneW8N31R8Ky+sikfZUXq07ggQ==" + }, + "@types/seedrandom": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@types/seedrandom/-/seedrandom-2.4.28.tgz", + "integrity": "sha512-SMA+fUwULwK7sd/ZJicUztiPs8F1yCPwF3O23Z9uQ32ME5Ha0NmDK9+QTsYE4O2tHXChzXomSWWeIhCnoN1LqA==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "bip39": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/bip39/-/bip39-3.0.2.tgz", + "integrity": "sha512-J4E1r2N0tUylTKt07ibXvhpT2c5pyAFgvuA5q1H9uDy6dEGpjV8jmymh3MTYJDLCNbIVClSB9FbND49I6N24MQ==", + "requires": { + "@types/node": "11.11.6", + "create-hash": "^1.1.0", + "pbkdf2": "^3.0.9", + "randombytes": "^2.0.1" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "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" + } + }, + "clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", + "dev": true + }, + "clone-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", + "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=", + "dev": true + }, + "clone-stats": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", + "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", + "dev": true + }, + "cloneable-readable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz", + "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "process-nextick-args": "^2.0.0", + "readable-stream": "^2.3.5" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": 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" + } + }, + "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==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "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 + }, + "create-hash": { + "version": "1.2.0", + "resolved": "https://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": "https://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" + } + }, + "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 + }, + "google-closure-compiler": { + "version": "20201102.0.1", + "resolved": "https://registry.npmjs.org/google-closure-compiler/-/google-closure-compiler-20201102.0.1.tgz", + "integrity": "sha512-Cz+1jOswH0MwMVPu1rRH1xD4KYuY5XW2ox5aXwqaAxevqmirhr36f8wgKPHuVRSovFejW640r6UFwyrOT6U0CA==", + "dev": true, + "requires": { + "chalk": "2.x", + "google-closure-compiler-java": "^20201102.0.1", + "google-closure-compiler-linux": "^20201102.0.1", + "google-closure-compiler-osx": "^20201102.0.1", + "google-closure-compiler-windows": "^20201102.0.1", + "minimist": "1.x", + "vinyl": "2.x", + "vinyl-sourcemaps-apply": "^0.2.0" + } + }, + "google-closure-compiler-java": { + "version": "20201102.0.1", + "resolved": "https://registry.npmjs.org/google-closure-compiler-java/-/google-closure-compiler-java-20201102.0.1.tgz", + "integrity": "sha512-pXJIlyqepHhih0HCbShkAZJyViIxdyd4V7MnCUZEXLIIlygw92e2dC+5XiONDQZgRlF93BPmWCy9jr7wYoW1hQ==", + "dev": true + }, + "google-closure-compiler-linux": { + "version": "20201102.0.1", + "resolved": "https://registry.npmjs.org/google-closure-compiler-linux/-/google-closure-compiler-linux-20201102.0.1.tgz", + "integrity": "sha512-aRbyTGnQoFXchcpEFNrP1p/WIvYOgN3hYKI+MOHWkvwVJBY2P8gpb07hAigO8fj+QKD/SFCl+2pXP+JniWOEqw==", + "dev": true, + "optional": true + }, + "google-closure-compiler-osx": { + "version": "20201102.0.1", + "resolved": "https://registry.npmjs.org/google-closure-compiler-osx/-/google-closure-compiler-osx-20201102.0.1.tgz", + "integrity": "sha512-VguqEAOYI6XYZN6JcLMP8fpsoXk1Z9YuntMjv0IDVydkbZaHYOI4zE39FJhMuWiN7gOzSX2b/BBC6GsSh1F3fw==", + "dev": true, + "optional": true + }, + "google-closure-compiler-windows": { + "version": "20201102.0.1", + "resolved": "https://registry.npmjs.org/google-closure-compiler-windows/-/google-closure-compiler-windows-20201102.0.1.tgz", + "integrity": "sha512-LlynipQi/iP76mjkOu6Rc1mCRuxTAhRvLjq10aGfVjKwpbCAF0Jq2a5k2ygr4xYiINNi2/L2qUw6ObPm9wQCOw==", + "dev": true, + "optional": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "hash-base": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", + "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", + "requires": { + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "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" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "pbkdf2": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz", + "integrity": "sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg==", + "requires": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "replace-ext": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.1.tgz", + "integrity": "sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==", + "dev": true + }, + "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" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "seedrandom": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz", + "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==" + }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://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" + } + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "vinyl": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", + "integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==", + "dev": true, + "requires": { + "clone": "^2.1.1", + "clone-buffer": "^1.0.0", + "clone-stats": "^1.0.0", + "cloneable-readable": "^1.0.0", + "remove-trailing-separator": "^1.0.1", + "replace-ext": "^1.0.0" + } + }, + "vinyl-sourcemaps-apply": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.2.1.tgz", + "integrity": "sha1-q2VJ1h0XLCsbh75cUI0jnI74dwU=", + "dev": true, + "requires": { + "source-map": "^0.5.1" + }, + "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 + } + } + } + } +} diff --git a/src/packages/options/package.json b/src/packages/options/package.json new file mode 100644 index 0000000000..4b5bdb1259 --- /dev/null +++ b/src/packages/options/package.json @@ -0,0 +1,51 @@ +{ + "name": "@ganache/options", + "version": "0.1.0", + "description": "Ganache's server/provider options TypeScript types", + "author": "David Murdoch (https://davidmurdoch.com)", + "homepage": "https://github.com/trufflesuite/ganache-core/tree/develop/src/packages/options#readme", + "license": "MIT", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "source": "index.ts", + "directories": { + "lib": "lib" + }, + "files": [ + "lib" + ], + "repository": { + "type": "git", + "url": "https://github.com/trufflesuite/ganache-core.git", + "directory": "src/packages/options" + }, + "scripts": { + "tsc": "ttsc" + }, + "bugs": { + "url": "https://github.com/trufflesuite/ganache-core/issues" + }, + "keywords": [ + "ganache", + "ganache-options", + "ethereum", + "evm", + "blockchain", + "smart contracts", + "dapps", + "solidity", + "vyper", + "fe", + "web3", + "tooling" + ], + "dependencies": { + "@ganache/utils": "^0.1.0", + "bip39": "3.0.2", + "seedrandom": "3.0.5" + }, + "devDependencies": { + "@types/seedrandom": "2.4.28", + "google-closure-compiler": "20201102.0.1" + } +} diff --git a/src/packages/options/src/base.ts b/src/packages/options/src/base.ts new file mode 100644 index 0000000000..5f5cc59789 --- /dev/null +++ b/src/packages/options/src/base.ts @@ -0,0 +1,21 @@ +export namespace Base { + export type Option = { + rawType?: unknown; + type: unknown; + hasDefault?: true; + legacy?: { + [name: string]: unknown; + }; + }; + + export type ExclusiveGroupOptionName = string; + export type ExclusiveGroup = ExclusiveGroupOptionName[]; + + export type Config = { + options: { + [optionName: string]: Option; + }; + + exclusiveGroups?: ExclusiveGroup[]; + }; +} diff --git a/src/packages/options/src/create.ts b/src/packages/options/src/create.ts new file mode 100644 index 0000000000..45426ec33f --- /dev/null +++ b/src/packages/options/src/create.ts @@ -0,0 +1,84 @@ +import { Definitions, ExternalConfig, InternalConfig } from "./definition"; +import { Base } from "./base"; +import { UnionToTuple } from "./exclusive"; + +import { utils } from "@ganache/utils"; + +const hasOwn = utils.hasOwn; + +type Options = { [key: string]: Base.Config }; + +export type ProviderOptions = Partial< + { + [K in keyof O]: ExternalConfig; + } +>; + +export type InternalOptions = { + [K in keyof O]: InternalConfig; +}; + +export type Defaults = { + [K in keyof O]: Definitions; +}; + +function fill(defaults: any, options: any, target: any, namespace: any) { + const def = defaults[namespace]; + const config = (target[namespace] = target[namespace] || {}); + + if (hasOwn(options, namespace)) { + const namespaceOptions = options[namespace]; + + const keys = Object.keys(def); + for (let i = 0, l = keys.length; i < l; i++) { + const key = keys[i]; + const propDefinition = def[key]; + let value = namespaceOptions[key]; + if (value !== undefined) { + config[key] = propDefinition.normalize(namespaceOptions[key]); + } else { + const legacyName = propDefinition.legacyName || key; + value = options[legacyName]; + if (value !== undefined) { + config[key] = propDefinition.normalize(value); + } else if (hasOwn(propDefinition, "default")) { + config[key] = propDefinition.default(config); + } + } + } + } else { + const keys = Object.keys(def); + for (let i = 0, l = keys.length; i < l; i++) { + const key = keys[i]; + const propDefinition = def[key]; + + const legacyName = propDefinition.legacyName || key; + const value = options[legacyName]; + if (value !== undefined) { + config[key] = propDefinition.normalize(value); + } else if (hasOwn(propDefinition, "default")) { + config[key] = propDefinition.default(config); + } + } + } +} + +export class OptionsConfig { + #defaults: Defaults; + #namespaces: UnionToTuple>; + + constructor(defaults: Defaults) { + this.#defaults = defaults; + this.#namespaces = Object.keys(defaults) as UnionToTuple>; + } + + normalize(options: ProviderOptions) { + const defaults = this.#defaults; + + const out = {} as InternalOptions; + this.#namespaces.forEach(namespace => { + fill(defaults, options, out, namespace as keyof Defaults); + }); + return out; + } +} diff --git a/src/packages/options/src/definition.ts b/src/packages/options/src/definition.ts new file mode 100644 index 0000000000..217c99b6ed --- /dev/null +++ b/src/packages/options/src/definition.ts @@ -0,0 +1,44 @@ +import { Base } from "./base"; +import { ExclusiveGroupUnionAndUnconstrainedPlus } from "./exclusive"; +import { + Legacy, + OptionHasDefault, + OptionHasLegacy, + OptionName, + OptionRawType, + OptionType +} from "./getters"; + +//#region Definition helpers +type Normalize< + C extends Base.Config, + N extends OptionName = OptionName +> = (rawInput: OptionRawType) => OptionType; + +export type ExternalConfig = Partial< + ExclusiveGroupUnionAndUnconstrainedPlus +>; + +export type InternalConfig< + C extends Base.Config +> = ExclusiveGroupUnionAndUnconstrainedPlus; + +type UnionToIntersection = (U extends any ? (k: U) => void : never) extends ( + k: infer I +) => void + ? I + : never; + +export type Definitions = { + [N in OptionName]: { + readonly normalize: Normalize; + } & (void extends OptionHasDefault + ? {} + : { readonly default: (config: InternalConfig) => OptionType }) & + (void extends OptionHasLegacy + ? {} + : { + readonly legacyName: UnionToIntersection>; + }); +}; +//#endregion Definition helpers diff --git a/src/packages/options/src/exclusive.ts b/src/packages/options/src/exclusive.ts new file mode 100644 index 0000000000..51a89e6466 --- /dev/null +++ b/src/packages/options/src/exclusive.ts @@ -0,0 +1,186 @@ +import { Base } from "./base"; +import { + OptionName, + Option, + ExclusiveGroupIndex, + ExclusiveGroups, + Options, + OptionType, + OptionRawType +} from "./getters"; + +//#region options not part of exclusive groups +type UnconstrainedOptions = Omit< + Options, + ExclusiveGroupOptionName +>; +type UnconstrainedOptionName = string & + keyof UnconstrainedOptions; +type UnconstrainedOptionsByType< + C extends Base.Config, + T extends "type" | "rawType" +> = { + [N in UnconstrainedOptionName]: T extends "type" + ? OptionType + : OptionRawType; +}; +//#endregion options not part of exclusive groups + +//#region exclusive group options helpers +type ExclusiveGroupOptionPairs< + C extends Base.Config, + G extends unknown[] +> = G extends [] + ? [] + : G extends [infer N, ...infer R] + ? [ + [N, ExclusiveGroupOptionNameOption], + ...ExclusiveGroupOptionPairs + ] + : never; + +type ExclusiveGroupOptionNameOption< + C extends Base.Config, + N +> = N extends OptionName ? Option : never; +type PairsToMapping = T extends [] + ? {} + : T extends [[infer N, infer O], ...infer R] + ? { + [N_ in string & N]: O; + } & + PairsToMapping + : never; + +type RequireOnly = Pick & Partial>; + +type ExclusiveGroupOptionalUnionByName< + C extends Base.Config, + GRP extends ExclusiveGroup, + M extends OptionName, + T extends "rawType" | "type" +> = { + [K in keyof RequireOnly, M>]: K extends M + ? T extends "type" + ? OptionType + : OptionRawType + : never; +}; + +type Combine< + C extends Base.Config, + O extends unknown, + GRP extends ExclusiveGroup, + T extends "rawType" | "type" +> = { + [N in keyof GRP]: GRP[N] extends OptionName + ? { + [Key in keyof (ExclusiveGroupOptionalUnionByName & + UnconstrainedOptionsByType & + O)]: Key extends keyof ExclusiveGroupOptionalUnionByName< + C, + GRP, + GRP[N], + T + > + ? ExclusiveGroupOptionalUnionByName[Key] + : Key extends keyof UnconstrainedOptionsByType + ? UnconstrainedOptionsByType[Key] + : Key extends keyof O + ? O[Key] + : never; + } + : never; +} extends { [n: number]: infer I } + ? I + : never; + +type IsNeverType = [T] extends [never] ? true : never; +export type ExclusiveGroupUnionAndUnconstrainedPlus< + C extends Base.Config, + T extends "rawType" | "type", + GRPS extends ExclusiveGroups = ExclusiveGroups, + O extends unknown[] = [] +> = GRPS extends [infer GRP, ...infer Rest] + ? GRP extends ExclusiveGroup + ? Rest extends any[] + ? O extends [] + ? // first time through + ExclusiveGroupUnionAndUnconstrainedPlus< + C, + T, + Rest, + UnionToTuple> + > + : // recurse + ExclusiveGroupUnionAndUnconstrainedPlus< + C, + T, + Rest, + UnionToTuple< + { + // iterate over each object in the O tuple. + // Omit makes it include only the indexes, but + // TypeScript will treat it as an object now, so we `UnionToTuple` + // to turn it back into a Tuple + [OK in keyof Omit]: Combine; + } extends { [n: number]: infer I } + ? I + : never + > + > + : never + : never + : O extends { [n: number]: infer I } + ? // if there are no exclusiveGroups `I` is `never` so we return `C` + // directly + true extends IsNeverType + ? { + [Key in keyof UnconstrainedOptionsByType< + C, + T + >]: UnconstrainedOptionsByType[Key]; + } + : I + : never; + +//#region UnionToTuple +export type UnionToTuple = ( + (T extends any ? (t: T) => T : never) extends infer U + ? (U extends any ? (u: U) => any : never) extends (v: infer V) => any + ? V + : never + : never +) extends (_: any) => infer W + ? [...UnionToTuple>, W] + : []; +//#endregion + +//#endregion exclusive group options helpers + +//#region exclusive groups +type ExclusiveGroup< + C extends Base.Config, + K extends ExclusiveGroupIndex = ExclusiveGroupIndex +> = ExclusiveGroups[K]; + +type ExclusiveGroupOptionName< + C extends Base.Config, + K extends ExclusiveGroupIndex = ExclusiveGroupIndex +> = Extract, DeepTupleToUnion>>; + +type DeepTupleToUnion = T extends [] // empty tuple case (base case) + ? never + : T extends [infer N, ...infer R] // inductive case + ? N extends unknown[] + ? DeepTupleToUnion | DeepTupleToUnion + : N | DeepTupleToUnion + : never; // we should never hit this case +//#endregion exclusive groups + +//#region options separated by exclusive group +type ExclusiveGroupOptionsByGroup< + C extends Base.Config, + G extends ExclusiveGroup +> = PairsToMapping>; +//#endregion diff --git a/src/packages/options/src/getters.ts b/src/packages/options/src/getters.ts new file mode 100644 index 0000000000..6897d2eb30 --- /dev/null +++ b/src/packages/options/src/getters.ts @@ -0,0 +1,55 @@ +import { Base } from "./base"; + +//#region getters for buckets +export type Options = C["options"]; +export type ExclusiveGroups = C["exclusiveGroups"]; +//#endregion + +//#region getters for keys +export type OptionName = keyof Options; +export type ExclusiveGroupIndex = number & + keyof ExclusiveGroups; +//#endregion + +//#region getters for individual things +export type Option< + C extends Base.Config, + N extends OptionName = OptionName +> = Options[N]; +//#endregion + +//#region getters for option +export type OptionRawType< + C extends Base.Config, + N extends OptionName = OptionName +> = void extends Option["rawType"] + ? Option["type"] + : Option["rawType"]; + +export type OptionType< + C extends Base.Config, + N extends OptionName = OptionName +> = Option["type"]; + +export type OptionHasDefault< + C extends Base.Config, + N extends OptionName = OptionName +> = Option["hasDefault"]; + +export type OptionHasLegacy< + C extends Base.Config, + N extends OptionName = OptionName +> = Option["legacy"]; + +export type LegacyOptions = { + [K in keyof C["options"]]: C["options"][K] extends { legacy: any } + ? K + : never; +}[keyof C["options"]]; + +export type Legacy> = Option< + C, + N +>["legacy"]; + +//#endregion getters for option diff --git a/src/packages/options/tests/index.test.ts b/src/packages/options/tests/index.test.ts new file mode 100644 index 0000000000..2288012cc3 --- /dev/null +++ b/src/packages/options/tests/index.test.ts @@ -0,0 +1,7 @@ +"use strict"; + +const options = require(".."); + +describe("@ganache/options", () => { + it("needs tests"); +}); diff --git a/src/packages/options/tsconfig.json b/src/packages/options/tsconfig.json new file mode 100644 index 0000000000..25d5c3cf7e --- /dev/null +++ b/src/packages/options/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "outDir": "lib" + }, + "include": ["index.ts"] +} diff --git a/src/packages/promise-queue/.npmignore b/src/packages/promise-queue/.npmignore new file mode 100644 index 0000000000..115ab4f024 --- /dev/null +++ b/src/packages/promise-queue/.npmignore @@ -0,0 +1,8 @@ +./index.ts +tests +.nyc_output +coverage +scripts +src +tsconfig.json +typedoc.json diff --git a/src/packages/promise-queue/LICENSE b/src/packages/promise-queue/LICENSE new file mode 100644 index 0000000000..39f3b14498 --- /dev/null +++ b/src/packages/promise-queue/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2019-2020 Truffle Blockchain Group + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/packages/promise-queue/README.md b/src/packages/promise-queue/README.md new file mode 100644 index 0000000000..f9807bd8c9 --- /dev/null +++ b/src/packages/promise-queue/README.md @@ -0,0 +1,3 @@ +# `@ganache/promise-queue` + +A queue that resolves Promise instances in first-in first-out order. diff --git a/src/packages/promise-queue/index.ts b/src/packages/promise-queue/index.ts new file mode 100644 index 0000000000..2ff0738700 --- /dev/null +++ b/src/packages/promise-queue/index.ts @@ -0,0 +1,108 @@ +import Emittery from "emittery"; +import Entry from "./src/entry"; + +const emitteryMethods = ["emit", "once"] as const; + +/** + * Creates a FIFO queue to that ensures promises are _resolved_ in the order + * they were added. + * + * This is different than a FIFO queue that _executes_ functions that + * return promises; this queue is for the promises themselves. + * + * @example + * ```javascript + * const queue = new PromiseQueue(); + * + * const slow = new Promise(resolve => setTimeout(resolve, 1000, "slow")); + * const fast = Promise.resolve("fast"); + * + * await Promise.race([ + * queue.add(slow), + * queue.add(fast) + * ]); // returns "slow" + * + * Additionally, the queued promise chain can be cleared via `queue.clear(value)`. + * This will cause the chain of promises to all resolve immediately with the + * given value. * + * + * * note: whatever the promise starting doing when it was created will still + * happen, no promises are aborted; rather, the return value is ignored. + * ``` + */ +@Emittery.mixin(Symbol.for("emittery"), emitteryMethods) +class PromiseQueue { + /** + * Returns true if there are promises pending in the queue + */ + public isBusy() { + return this.#queue.length !== 0; + } + + // TODO(perf): a singly linked list is probably a better option here + readonly #queue: Entry[] = []; + + #tryResolve = (queue: Entry[], entry: Entry) => { + // if this is now the highest priority entry, resolve the outer + // Promise + if (entry === queue[0]) { + queue.shift(); + entry.resolve(entry.value); + // then try resolving the rest + this.#tryResolveChain(queue); + } else { + entry.resolved = true; + } + }; + + /** + * Adds the promise to the end of the queue. + * @param promise + * @returns a promise that resolves with the given promise's result. If the + * queue was `clear`ed before the promise could be shifted off the return + * value will be the `value` passed to `clear`. + */ + add(promise: Promise) { + const queue = this.#queue; + const entry: Entry = new Entry(promise, queue, this.#tryResolve); + queue.push(entry); + return entry.promise; + } + + /** + * Clears all promises from the queue and sets their resolved values to the + * given value. + */ + clear(value: T) { + // remove all entrys from the queue and mark them. + const cancelledQueue = this.#queue.splice(0); + cancelledQueue.forEach(entry => { + entry.queue = cancelledQueue; + entry.value = value; + }); + } + + /** + * Removes all _resolved_ promises from the front of the chain of promises. + */ + #tryResolveChain = (queue: Entry[]) => { + let first = queue[0]; + while (first && first.resolved) { + queue.shift(); + first.resolve(first.value); + first = queue[0]; + } + + // if there is nothing left to do emit `"idle"` + if (queue.length === 0) { + this.emit("idle"); + } + }; +} + +interface PromiseQueue + extends Pick { + emittery: Emittery; +} + +export default PromiseQueue; diff --git a/src/packages/promise-queue/npm-shrinkwrap.json b/src/packages/promise-queue/npm-shrinkwrap.json new file mode 100644 index 0000000000..3f55a05c8c --- /dev/null +++ b/src/packages/promise-queue/npm-shrinkwrap.json @@ -0,0 +1,13 @@ +{ + "name": "@ganache/promise-queue", + "version": "0.1.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "emittery": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.7.2.tgz", + "integrity": "sha512-A8OG5SR/ij3SsJdWDJdkkSYUjQdCUx6APQXem0SaEePBSRg4eymGYwBkKo1Y6DU+af/Jn2dBQqDBvjnr9Vi8nQ==" + } + } +} diff --git a/src/packages/promise-queue/package.json b/src/packages/promise-queue/package.json new file mode 100644 index 0000000000..4fdf5beb6c --- /dev/null +++ b/src/packages/promise-queue/package.json @@ -0,0 +1,49 @@ +{ + "name": "@ganache/promise-queue", + "version": "0.1.0", + "description": "A queue that resolves Promise instances in first-in first-out order.", + "author": "David Murdoch (https://davidmurdoch.com)", + "homepage": "https://github.com/trufflesuite/ganache-core/tree/develop/src/packages/promise-queue#readme", + "license": "MIT", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "source": "index.ts", + "directories": { + "lib": "lib", + "test": "tests" + }, + "files": [ + "lib" + ], + "repository": { + "type": "git", + "url": "https://github.com/trufflesuite/ganache-core.git", + "directory": "src/packages/promise-queue" + }, + "scripts": { + "tsc": "ttsc", + "test": "nyc npm run mocha", + "mocha": "cross-env TS_NODE_COMPILER=ttypescript TS_NODE_FILES=true mocha --exit --require ts-node/register --recursive --check-leaks 'tests/**.ts'" + }, + "bugs": { + "url": "https://github.com/trufflesuite/ganache-core/issues" + }, + "keywords": [ + "ganache", + "ganache-promise-queue", + "ethereum", + "evm", + "blockchain", + "smart contracts", + "dapps", + "solidity", + "vyper", + "fe", + "web3", + "tooling" + ], + "dependencies": { + "emittery": "0.7.2" + }, + "devDependencies": {} +} diff --git a/src/packages/promise-queue/src/entry.ts b/src/packages/promise-queue/src/entry.ts new file mode 100644 index 0000000000..5b59c0cb77 --- /dev/null +++ b/src/packages/promise-queue/src/entry.ts @@ -0,0 +1,25 @@ +export default class Entry { + public readonly promise: Promise; + public resolve: (value: T | PromiseLike) => void; + + public value: T | Promise; + public queue: Entry[]; + + public resolved = false; + public onSetteled: (queue: Entry[], entry: Entry) => void; + + constructor( + promise: Promise, + queue: Entry[], + onSetteled: (queue: Entry[], entry: Entry) => void + ) { + this.value = promise; + this.queue = queue; + this.onSetteled = onSetteled; + const _onSetteled = () => this.onSetteled(this.queue, this); + promise.then(_onSetteled, _onSetteled); + this.promise = new Promise(resolve => { + this.resolve = resolve; + }); + } +} diff --git a/src/packages/promise-queue/tests/index.test.ts b/src/packages/promise-queue/tests/index.test.ts new file mode 100644 index 0000000000..d7f1dac8c2 --- /dev/null +++ b/src/packages/promise-queue/tests/index.test.ts @@ -0,0 +1,6 @@ +import assert from "assert"; +import PromiseQueue from ".."; + +describe("@ganache/promise-queue", () => { + it("needs tests"); +}); diff --git a/src/packages/promise-queue/tsconfig.json b/src/packages/promise-queue/tsconfig.json new file mode 100644 index 0000000000..25d5c3cf7e --- /dev/null +++ b/src/packages/promise-queue/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "outDir": "lib" + }, + "include": ["index.ts"] +} diff --git a/src/packages/utils/.npmignore b/src/packages/utils/.npmignore new file mode 100644 index 0000000000..115ab4f024 --- /dev/null +++ b/src/packages/utils/.npmignore @@ -0,0 +1,8 @@ +./index.ts +tests +.nyc_output +coverage +scripts +src +tsconfig.json +typedoc.json diff --git a/src/packages/utils/LICENSE b/src/packages/utils/LICENSE new file mode 100644 index 0000000000..39f3b14498 --- /dev/null +++ b/src/packages/utils/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2019-2020 Truffle Blockchain Group + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/packages/utils/README.md b/src/packages/utils/README.md new file mode 100644 index 0000000000..0368a05105 --- /dev/null +++ b/src/packages/utils/README.md @@ -0,0 +1,3 @@ +# `@ganache/utils` + +Utility functions for @ganache packages. diff --git a/src/packages/utils/index.ts b/src/packages/utils/index.ts new file mode 100644 index 0000000000..236ca0179c --- /dev/null +++ b/src/packages/utils/index.ts @@ -0,0 +1,7 @@ +export * as types from "./src/types"; +export * as utils from "./src/utils"; +export * as JsonRpc from "./src/things/json-rpc"; +export * from "./src/things/json-rpc/json-rpc-quantity"; +export * from "./src/things/json-rpc/json-rpc-data"; +export { default as JsonRpcTypes } from "./src/things/jsonrpc"; +export { default as PromiEvent } from "./src/things/promievent"; diff --git a/src/packages/utils/npm-shrinkwrap.json b/src/packages/utils/npm-shrinkwrap.json new file mode 100644 index 0000000000..5499a0fbaa --- /dev/null +++ b/src/packages/utils/npm-shrinkwrap.json @@ -0,0 +1,42 @@ +{ + "name": "@ganache/utils", + "version": "0.1.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "bigint-buffer": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/bigint-buffer/-/bigint-buffer-1.1.5.tgz", + "integrity": "sha512-trfYco6AoZ+rKhKnxA0hgX0HAbVP/s808/EuDSe2JDzUnCp/xAsli35Orvk67UrTEcwuxZqYZDmfA2RXJgxVvA==", + "optional": true, + "requires": { + "bindings": "^1.3.0" + } + }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "optional": true, + "requires": { + "file-uri-to-path": "1.0.0" + } + }, + "emittery": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.7.2.tgz", + "integrity": "sha512-A8OG5SR/ij3SsJdWDJdkkSYUjQdCUx6APQXem0SaEePBSRg4eymGYwBkKo1Y6DU+af/Jn2dBQqDBvjnr9Vi8nQ==" + }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "optional": true + }, + "uWebSockets.js": { + "version": "github:uNetworking/uWebSockets.js#3dbec7b56d627193e20705844b6bd10e49848b8c", + "from": "github:uNetworking/uWebSockets.js#v18.4.0", + "dev": true + } + } +} diff --git a/src/packages/utils/package.json b/src/packages/utils/package.json new file mode 100644 index 0000000000..e3baa76ca1 --- /dev/null +++ b/src/packages/utils/package.json @@ -0,0 +1,55 @@ +{ + "name": "@ganache/utils", + "version": "0.1.0", + "description": "Utility functions for @ganache packages", + "author": "David Murdoch (https://davidmurdoch.com)", + "homepage": "https://github.com/trufflesuite/ganache-core/tree/develop/src/packages/utils#readme", + "license": "MIT", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "source": "index.ts", + "directories": { + "lib": "lib", + "test": "tests" + }, + "files": [ + "lib" + ], + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "https://github.com/trufflesuite/ganache-core.git", + "directory": "src/packages/utils" + }, + "scripts": { + "tsc": "ttsc" + }, + "bugs": { + "url": "https://github.com/trufflesuite/ganache-core/issues" + }, + "keywords": [ + "ganache", + "ganache-utils", + "ethereum", + "evm", + "blockchain", + "smart contracts", + "dapps", + "solidity", + "vyper", + "fe", + "web3", + "tooling" + ], + "dependencies": { + "emittery": "0.7.2" + }, + "devDependencies": { + "uWebSockets.js": "github:uNetworking/uWebSockets.js#v18.4.0" + }, + "optionalDependencies": { + "bigint-buffer": "1.1.5" + } +} diff --git a/src/packages/utils/src/things/json-rpc/index.ts b/src/packages/utils/src/things/json-rpc/index.ts new file mode 100644 index 0000000000..d7dd7bea53 --- /dev/null +++ b/src/packages/utils/src/things/json-rpc/index.ts @@ -0,0 +1 @@ +export { JsonRpcType } from "./json-rpc-base-types"; diff --git a/src/packages/utils/src/things/json-rpc/json-rpc-base-types.ts b/src/packages/utils/src/things/json-rpc/json-rpc-base-types.ts new file mode 100644 index 0000000000..8de25c06ec --- /dev/null +++ b/src/packages/utils/src/things/json-rpc/json-rpc-base-types.ts @@ -0,0 +1,108 @@ +import { bigIntToBuffer } from "../../utils"; +import { uintToBuffer } from "../../utils"; + +import { BUFFER_EMPTY } from "../../utils/constants"; + +export const strCache = new WeakMap(); +export const bufCache = new WeakMap(); +export const toStrings = new WeakMap(); +export const toBuffers = new WeakMap(); + +const inspect = Symbol.for("nodejs.util.inspect.custom"); + +export class BaseJsonRpcType< + T extends number | bigint | string | Buffer = + | number + | bigint + | string + | Buffer +> { + public [Symbol.toStringTag]: string; + + protected value: T; + // used to make console.log debugging a little easier + private [inspect](_depth: number, _options: any): T { + return this.value; + } + constructor(value: T) { + const self = this; + if (Buffer.isBuffer(value)) { + toStrings.set(this, () => value.toString("hex")); + bufCache.set(this, value); + self[Symbol.toStringTag] = "Buffer"; + } else { + const type = typeof value; + switch (type) { + case "number": + if ((value as number) % 1 !== 0) { + throw new Error("`Cannot wrap a decimal value as a json-rpc type`"); + } + toStrings.set(this, () => (value as number).toString(16)); + toBuffers.set(this, () => uintToBuffer(value as number)); + break; + case "bigint": + toStrings.set(this, () => (value as bigint).toString(16)); + toBuffers.set(this, () => bigIntToBuffer(value as bigint)); + break; + case "string": { + // handle hex-encoded string + if ((value as string).indexOf("0x") === 0) { + strCache.set(this, (value as string).toLowerCase()); + toBuffers.set(this, () => { + let fixedValue = (value as string).slice(2); + if (fixedValue.length % 2 === 1) { + fixedValue = "0" + fixedValue; + } + return Buffer.from(fixedValue, "hex"); + }); + } else { + throw new Error( + `cannot convert string value "${value}" into type \`${this.constructor.name}\`; strings must be hex-encoded and prefixed with "0x".` + ); + } + break; + } + default: + // handle undefined/null + if (value == null) { + // This is a weird thing that returns undefined/null for a call + // to toString(). + this.toString = () => value as string; + bufCache.set(this, BUFFER_EMPTY); + break; + } + throw new Error(`Cannot wrap a "${type}" as a json-rpc type`); + } + self[Symbol.toStringTag] = type; + } + + this.value = value; + } + + toString(): string | null { + let str = strCache.get(this); + if (str === void 0) { + str = "0x" + toStrings.get(this)(); + strCache.set(this, str); + } + return str; + } + toBuffer(): Buffer { + let buf = bufCache.get(this); + if (buf === void 0) { + buf = toBuffers.get(this)(); + bufCache.set(this, buf); + } + return buf; + } + valueOf(): T | null { + return this.value; + } + toJSON(): string | null { + return this.toString(); + } +} + +export type JsonRpcType< + T extends number | bigint | string | Buffer +> = BaseJsonRpcType; diff --git a/src/packages/utils/src/things/json-rpc/json-rpc-data.ts b/src/packages/utils/src/things/json-rpc/json-rpc-data.ts new file mode 100644 index 0000000000..e0c06a4001 --- /dev/null +++ b/src/packages/utils/src/things/json-rpc/json-rpc-data.ts @@ -0,0 +1,58 @@ +import { BaseJsonRpcType } from "./json-rpc-base-types"; +import { strCache, toStrings } from "./json-rpc-base-types"; + +function validateByteLength(byteLength?: number) { + if (typeof byteLength !== "number" || byteLength < 0) { + throw new Error(`byteLength must be a number greater than 0`); + } +} +const byteLengths = new WeakMap(); +export class Data extends BaseJsonRpcType { + constructor(value: string | Buffer, byteLength?: number) { + if (typeof value === "bigint") { + throw new Error(`Cannot create a ${typeof value} as a Data`); + } + super(value); + if (byteLength !== void 0) { + validateByteLength(byteLength); + byteLengths.set(this, byteLength | 0); + } + } + public toString(byteLength?: number): string { + const str = strCache.get(this) as string; + if (str !== void 0) { + return str; + } else { + let str = toStrings.get(this)() as string; + let length = str.length; + if (length % 2 === 1) { + length++; + str = `0${str}`; + } + + if (byteLength !== void 0) { + validateByteLength(byteLength); + } else { + byteLength = byteLengths.get(this); + } + if (byteLength !== void 0) { + const strLength = byteLength * 2; + const padBy = strLength - length; + if (padBy < 0) { + // if our hex-encoded data is longer than it should be, truncate it: + str = str.slice(0, strLength); + } else if (padBy > 0) { + // if our hex-encoded data is shorter than it should be, pad it: + str = "0".repeat(padBy) + str; + } + } + return `0x${str}`; + } + } + public static from( + value: T, + byteLength?: number + ) { + return new Data(value, byteLength); + } +} diff --git a/src/packages/utils/src/things/json-rpc/json-rpc-quantity.ts b/src/packages/utils/src/things/json-rpc/json-rpc-quantity.ts new file mode 100644 index 0000000000..4d085941ea --- /dev/null +++ b/src/packages/utils/src/things/json-rpc/json-rpc-quantity.ts @@ -0,0 +1,89 @@ +import { BaseJsonRpcType } from "./json-rpc-base-types"; +import { toBigIntBE } from "bigint-buffer"; + +export class Quantity extends BaseJsonRpcType { + _nullable: boolean = false; + public static from( + value: number | bigint | string | Buffer, + nullable = false + ) { + if (value instanceof Quantity) return value; + const q = new Quantity(value); + q._nullable = nullable; + return q; + } + public toString(): string | null { + if (Buffer.isBuffer(this.value)) { + let val = this.value.toString("hex").replace(/^(?:0+(.+?))?$/, "$1"); + + if (val === "") { + if (this._nullable) { + return null; + } + // RPC Quantities must represent `0` as `0x0` + val = "0"; + } + return "0x" + val; + } else { + return super.toString(); + } + } + public toBigInt(): bigint | null { + const value = this.value; + + // TODO(perf): memoize this stuff + if (Buffer.isBuffer(value)) { + // Parsed as BE. + + // This doesn't handle negative values. We may need to add logic to handle + // it because it is possible values returned from the VM could be negative + // and stored in a buffer. + + const length = value.byteLength; + if (length === 0) { + return null; + } + // Buffers that are 6 bytes or less can be converted with built-in methods + if (length <= 6) { + return BigInt(value.readUIntBE(0, length)); + } + + let view: DataView; + // Buffers that are 7 bytes need to be padded to 8 bytes + if (length === 7) { + const padded = new Uint8Array(8); + // set byte 0 to 0, and bytes 1-8 to the value's 7 bytes: + padded.set(value, 1); + view = new DataView(padded.buffer); + } else if (length === 8) { + view = new DataView(value.buffer, value.byteOffset, length); + } else { + // TODO: toBigIntBE is a native lib with no pure JS fallback yet. + return toBigIntBE(value); + // TODO: handle bigint's stored as Buffers that are this big? + // It's not too hard. + // throw new Error(`Cannot convert Buffer of length ${length} to bigint!`); + } + return view.getBigUint64(0) as bigint; + } else { + return value != null ? BigInt(value) : 0n; + } + } + public toNumber() { + return typeof this.value === "number" + ? this.value + : Number(this.toBigInt()); + } + valueOf(): bigint { + const value = this.value; + if (value === null) { + return value as null; + } else if (value === undefined) { + return value as undefined; + } else { + return this.toBigInt(); + } + } +} + +export default Quantity; diff --git a/src/packages/utils/src/things/jsonrpc.ts b/src/packages/utils/src/things/jsonrpc.ts new file mode 100644 index 0000000000..41185fd18d --- /dev/null +++ b/src/packages/utils/src/things/jsonrpc.ts @@ -0,0 +1,77 @@ +import { Api, KnownKeys } from "../types"; +type JSError = Error; + +namespace JsonRpc { + const jsonrpc = "2.0"; + type JsonRpc = { + readonly id: string; + readonly jsonrpc: string; + toString(): string; + }; + export type Request = JsonRpc & { + readonly id: string; + readonly jsonrpc: string; + readonly method: KnownKeys; + readonly params?: any[]; + }; + export type Response = JsonRpc & { + readonly result: any; + }; + export type Error = JsonRpc & { + readonly error: { + readonly [key: string]: unknown; + readonly code: number; + readonly message: any; + }; + readonly result?: any; + }; + export const Request = (json: any): Request => { + return { + id: json.id, + jsonrpc, + method: json.method, + params: json.params + }; + }; + export const Response = (id: string, result: any): Response => { + return { + id: id, + jsonrpc, + result + }; + }; + export const Error = ( + id: string, + error: T, + result?: unknown + ): Error => { + type E = { [K in keyof T]: K extends string ? T[K] : never }; + // Error objects are weird, `message` isn't included in the property names, + // so it is pulled out separately. + const details = { message: error.message } as E; + Object.getOwnPropertyNames(error).forEach(name => { + if (typeof name === "string") { + details[name] = error[name]; + } + }); + if (typeof details.code !== "number") { + details.code = -32700; // JSON-RPC Parse error + } + if (result !== undefined) { + return { + id, + jsonrpc, + error: details, + result + }; + } else { + return { + id, + jsonrpc, + error: details + }; + } + }; +} + +export default JsonRpc; diff --git a/src/packages/utils/src/things/promievent.ts b/src/packages/utils/src/things/promievent.ts new file mode 100644 index 0000000000..4e2dbde175 --- /dev/null +++ b/src/packages/utils/src/things/promievent.ts @@ -0,0 +1,124 @@ +import Emittery from "emittery"; + +// PromiEvent's `resolve and `reject` need to return a PromiEvent, not just a +// Promise +declare var Promise: { + /** + * Attaches a callback for only the rejection of the Promise. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A Promise for the completion of the callback. + */ + catch( + onrejected?: + | ((reason: any) => TResult | PromiseLike) + | undefined + | null + ): PromiEvent; + + /** + * Creates a new resolved promievent for the provided value. + * @param value A promise. + * @returns A promievent whose internal state matches the provided promise. + */ + resolve(value: T | PromiseLike): PromiEvent; + + /** + * Creates a new resolved promievent. + * @returns A resolved promievent. + */ + resolve(): PromiEvent; +} & PromiseConstructor; + +const emitteryMethods = [ + "clearListeners", + "once", + "on", + "emit", + "onAny" +] as const; + +@Emittery.mixin(Symbol.for("emittery"), emitteryMethods) +class PromiEvent extends Promise { + constructor( + executor: ( + resolve: (value?: T | PromiseLike) => void, + reject: (reason?: any) => void + ) => void + ) { + super(executor); + } + + /** + * Attaches a callback for only the rejection of the Promise. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A PromiEvent for the completion of the callback. + */ + catch( + onrejected?: + | ((reason: any) => TResult | PromiseLike) + | undefined + | null + ) { + const prom = new PromiEvent((resolve, reject) => { + this.onAny((eventName, eventData) => { + return prom.emit(eventName, eventData); + }); + const p = super.catch(onrejected); + p.then(resolve, reject); + }); + return prom; + } + + /** + * Creates a new resolved promievent. + * @returns A resolved promievent. + */ + static resolve(): PromiEvent; + /** + * Creates a new resolved promievent for the provided value. + * @param value A promise. + * @returns A promievent whose internal state matches the provided promise. + */ + static resolve(value: T | PromiseLike): PromiEvent; + static resolve(value?: T | PromiseLike) { + return new PromiEvent(resolve => { + resolve(value); + }); + } + + /** + * Used to immediately clear all event listeners on the instance and prevent + * any additional binding or emission from the Emitter. + * + * Once disposed no listeners can be bound to this emitter. + * + * Note: `dispose` is pre-bound to the `this`, making it possible to pass the + * method around detached from it's context. + */ + public dispose = () => { + if (!this.clearListeners) throw new Error("PromiEvent already disposed"); + + this.clearListeners(); + + // Ensure that once disposed no listeners can be bound to this emitter. + const fn = () => { + throw new Error("PromiEvent bound after dispose"); + }; + emitteryMethods + .filter(m => m !== "emit") + .forEach(methodName => { + Object.defineProperty(this, methodName, { + enumerable: false, + value: fn + }); + }); + }; +} + +interface PromiEvent + extends Promise, + Pick { + emittery: Emittery; +} + +export default PromiEvent; diff --git a/src/packages/utils/src/types/api.ts b/src/packages/utils/src/types/api.ts new file mode 100644 index 0000000000..c4405ffc15 --- /dev/null +++ b/src/packages/utils/src/types/api.ts @@ -0,0 +1,13 @@ +/** + * Base implementation for an API. + * All properties must be `async` callable or return a `Promise` + */ +class ApiBase { + readonly [index: string]: (...args: unknown[]) => Promise; +} + +/** + * Defines the interface for a API. + * All properties must be `async` callable or return a `Promise` + */ +export interface Api extends ApiBase {} diff --git a/src/packages/utils/src/types/connector.ts b/src/packages/utils/src/types/connector.ts new file mode 100644 index 0000000000..498bb94876 --- /dev/null +++ b/src/packages/utils/src/types/connector.ts @@ -0,0 +1,53 @@ +import { Provider } from "./provider"; +import { RecognizedString, WebSocket, HttpRequest } from "uWebSockets.js"; +import { Api } from "./api"; +import { KnownKeys } from "../types"; +import Emittery from "emittery"; + +/** + * Connects an arbitrary public chain provider to ganache-core + */ +export interface Connector< + ApiImplementation extends Api, + RequestFormat, + ResponseFormat +> extends Emittery.Typed { + provider: Provider; + + /** + * Parses a raw message into something that can be handled by `handle` + * @param message + */ + parse(message: Buffer): RequestFormat; + + /** + * Handles a parsed message + * @param payload + */ + handle: + | (( + payload: RequestFormat, + connection: HttpRequest + ) => Promise<{ value: ReturnType]> }>) + | (( + payload: RequestFormat[], + connection: HttpRequest + ) => Promise<{ value: ReturnType]>[] }>) + | (( + payload: RequestFormat, + connection: WebSocket + ) => Promise<{ value: ReturnType]> }>) + | (( + payload: RequestFormat[], + connection: WebSocket + ) => Promise<{ value: ReturnType]>[] }>); + + /** + * Formats the response (returned from `handle`) + * @param response + * @param payload + */ + format(result: ResponseFormat, payload: RequestFormat): RecognizedString; + + close(): void; +} diff --git a/src/packages/utils/src/types/index.ts b/src/packages/utils/src/types/index.ts new file mode 100644 index 0000000000..4e5c3e9163 --- /dev/null +++ b/src/packages/utils/src/types/index.ts @@ -0,0 +1,23 @@ +export * from "./connector"; +export * from "./provider"; +export * from "./api"; + +import { Api } from "./api"; + +export type KnownKeys = { + [K in keyof T]: string extends K ? never : number extends K ? never : K; +} extends { [_ in keyof T]: infer U } + ? U + : never; + +export type RequestType = (eventDetails: { + api: T; + method: KnownKeys; + params?: Parameters; +}) => ReturnType; + +declare global { + interface JSON { + parse(text: Buffer, reviver?: (key: any, value: any) => any): any; + } +} diff --git a/src/packages/utils/src/types/provider.ts b/src/packages/utils/src/types/provider.ts new file mode 100644 index 0000000000..09a872400e --- /dev/null +++ b/src/packages/utils/src/types/provider.ts @@ -0,0 +1,3 @@ +import { Api } from "./api"; + +export interface Provider {} diff --git a/src/packages/utils/src/utils/bigint-to-buffer.ts b/src/packages/utils/src/utils/bigint-to-buffer.ts new file mode 100644 index 0000000000..7670043227 --- /dev/null +++ b/src/packages/utils/src/utils/bigint-to-buffer.ts @@ -0,0 +1,54 @@ +import { uintToBuffer } from "./uint-to-buffer"; + +const MAX_UINT32 = 0xffffffffn; +const allocUnsafe = Buffer.allocUnsafe; + +let _bigIntToBuffer: (val: bigint) => Buffer; +/** + * Returns the number of bytes contained in this given `value`. + * @param value + */ +function bigIntByteLength(value: bigint) { + let length = 1; + while ((value >>= 8n)) length++; + return length; +} + +const MAX_SAFE_INTEGER = BigInt(Number.MAX_SAFE_INTEGER); +try { + const { toBufferBE } = require("bigint-buffer"); + + // force fallback if only `toBufferBE` is missing (this can happen if toBufferBE isn't polyfilled for the browser, + // which, at the time of this writing... it isn't) + if (!toBufferBE) throw new Error("Missing function `toBufferBE`!"); + + _bigIntToBuffer = (value: bigint) => { + if (value <= MAX_SAFE_INTEGER) { + return uintToBuffer(Number(value)); + } else { + const size = bigIntByteLength(value); + return toBufferBE(value, size); + } + }; +} catch (e) { + _bigIntToBuffer = (value: bigint): Buffer => { + if (value <= MAX_SAFE_INTEGER) { + // if this value can be handled as a JS number safely, convert it that way + return uintToBuffer(Number(value)); + } else { + let length = bigIntByteLength(value); + const buf = allocUnsafe(length); + do { + // process 1 byte at a time + buf[--length] = Number(value & 0xffffffffn); + value >>= 8n; + } while (length); + return buf; + } + }; +} + +/** + * Converts a bigint to a Buffer (Big Endian) + */ +export const bigIntToBuffer = _bigIntToBuffer; diff --git a/src/packages/utils/src/utils/constants.ts b/src/packages/utils/src/utils/constants.ts new file mode 100644 index 0000000000..a065b48e57 --- /dev/null +++ b/src/packages/utils/src/utils/constants.ts @@ -0,0 +1,12 @@ +import { Quantity } from "../things/json-rpc/json-rpc-quantity"; + +export const BUFFER_256_ZERO = Buffer.allocUnsafe(256).fill(0); +export const ACCOUNT_ZERO = BUFFER_256_ZERO.slice(0, 20); +export const BUFFER_EMPTY = Buffer.allocUnsafe(0); +export const BUFFER_ZERO = BUFFER_256_ZERO.slice(0, 1); +export const BUFFER_32_ZERO = BUFFER_256_ZERO.slice(0, 32); + +export const RPCQUANTITY_EMPTY = Quantity.from(BUFFER_EMPTY, true); +export const RPCQUANTITY_ZERO = Quantity.from(BUFFER_ZERO); +export const RPCQUANTITY_ONE = Quantity.from(1n); +export const WEI = 1000000000000000000n as const; diff --git a/src/packages/utils/src/utils/executor.ts b/src/packages/utils/src/utils/executor.ts new file mode 100644 index 0000000000..92fab8b1da --- /dev/null +++ b/src/packages/utils/src/utils/executor.ts @@ -0,0 +1,54 @@ +import { hasOwn } from "./has-own"; +import { KnownKeys, Api } from "../types"; +import { RequestCoordinator } from "./request-coordinator"; + +export class Executor { + #requestCoordinator: RequestCoordinator; + + /** + * The Executor handles execution of methods on the given API + */ + constructor(requestCoordinator: RequestCoordinator) { + this.#requestCoordinator = requestCoordinator; + } + + /** + * Executes the method with the given methodName on the API + * @param methodName The name of the JSON-RPC method to execute. + * @param params The params to pass to the JSON-RPC method. + */ + public execute>( + api: T, + methodName: M, + params: Parameters + ) { + // The methodName is user-entered data and can be all sorts of weird hackery + // Make sure we only accept what we expect to avoid headache and heartache + if (typeof methodName === "string") { + // Only allow executing our *own* methods. We allow: + // * functions added to the Instance by the class, e.g., + // class SomeClass { + // method = () => {} // api.hasOwnProperty("method") === true + // } + // * Or by the class' prototype: + // class SomeClass { + // method(){} // api.__proto__.hasOwnProperty("method") === true + // } + if ( + (hasOwn(api.__proto__, methodName) && methodName !== "constructor") || + hasOwn(api, methodName) + ) { + // cast methodName from `KnownKeys & string` back to KnownKeys so our return type isn't weird. + const fn = api[methodName as M]; + // just double check, in case a API breaks the rules and adds non-fns + // to their API interface. + if (typeof fn === "function") { + // queue up this method for actual execution: + return this.#requestCoordinator.queue(fn, api, params); + } + } + } + + throw new Error(`The method ${methodName} does not exist/is not available`); + } +} diff --git a/src/packages/utils/src/utils/has-own.ts b/src/packages/utils/src/utils/has-own.ts new file mode 100644 index 0000000000..5ba52c0fbd --- /dev/null +++ b/src/packages/utils/src/utils/has-own.ts @@ -0,0 +1,17 @@ +/** + * /** + * Determines whether an object has a property with the specified name. + * + * Safe for use on user-supplied data. + * + * @param obj The object that will be checked. + * @param v A property name. + * @returns `true` if the object has a property with the specified name, + * otherwise false. + */ +export const hasOwn: ( + obj: X, + prop: Y +) => obj is X extends Record + ? X & Required> + : never = {}.hasOwnProperty.call.bind({}.hasOwnProperty) as any; diff --git a/src/packages/utils/src/utils/heap.ts b/src/packages/utils/src/utils/heap.ts new file mode 100644 index 0000000000..e09d35f7d1 --- /dev/null +++ b/src/packages/utils/src/utils/heap.ts @@ -0,0 +1,210 @@ +type Comparator = (values: T[], a: number, b: number) => boolean; + +export class Heap { + public length: number = 0; + public array: T[] = []; + protected readonly less: Comparator; + + /** + * Creates a priority-queue heap where the highest priority element, + * as determined by the `less` function, is at the tip/root of the heap. + * To read the highest priority element without removing it call peek(). To + * read and remove the element call `shift()` + * @param size the size of the heap + * @param less the comparator function + */ + constructor(less: Comparator) { + this.less = less; + } + + public init(array: T[]) { + this.array = array; + const length = (this.length = array.length); + for (let i = ((length / 2) | 0) - 1; i >= 0; ) { + this.down(i--, length); + } + } + + /** + * Pushes a new element onto the heap + * @param value + */ + public push(value: T) { + const i = this.length++; + this.array[i] = value; + this.up(i); + } + + public size() { + return this.length; + } + + /** + * Return the current best element. Does not remove it + */ + public peek(): T { + return this.array[0]; + } + + public clear() { + this.length = this.array.length = 0; + } + + /** + * Removes and returns the element with the highest priority from the heap. + * The complexity is O(log n) where n = this.size(). + * @returns the element with the highest priority. returns `undefined` if + * there are no more elements in the heap. + */ + public shift(): T | undefined { + const length = this.length; + + // if we are empty or about to be empty... + if (length <= 1) { + if (length === 0) return; + const element = this.array[0]; + // finally, clear the array + this.clear(); + return element; + } + // otherwise... + + // remember the best element + const array = this.array; + const best = array[0]; + const newLength = (this.length = length - 1); + // put our last element at the start of the heap + array[0] = array[newLength]; + // then sort from the new first element to the second to last element + this.down(0, newLength); + return best; + } + + /** + * Removes the highest priority element from the queue, replacing it with + * the new element. This is equivalent to, but faster than, calling + * `replace(0, newValue);`. + * If you call this on an empty heap (`this.size() === 0`) you may find + * unexpected behavior. + * @param newValue + */ + public replaceBest(newValue: T) { + this.array[0] = newValue; + this.down(0, this.length); + } + + /** + * Replaces the element at position `i` with the `newValue`. If the element at + * position `i` doesn't exist, or if `i < 0` or `i > this.size()` you may + * find unexpected behavior. + * @param i + * @param newValue + */ + public replace(i: number, newValue: T) { + this.array[i] = newValue; + this.fix(i); + } + + /** + * Removes the element at position `i`. + * The complexity is O(log n) where n = this.size(). + * @param i the element to remove + */ + public remove(i: number) { + const newLength = --this.length; + if (newLength !== i) { + this.swap(i, newLength); + if (!this.down(i, newLength)) { + this.up(i); + } + } + } + + /** + * Removes the element with the highest priority from the heap + * The complexity is O(log n) where n = this.size(). + * @returns `true` when there are more elements in the queue, `false` when the + * last element was just removed. Calling `removeBest` when there are no more + * elements in the queue will return `true`. So don't do that. + */ + public removeBest() { + const array = this.array; + const length = this.length; + if (length === 1) { + // finally, clear the array + this.length = array.length = 0; + return false; + } + + const newLength = --this.length; + // put our last element at the start of the heap + array[0] = array[newLength]; + // then sort from the new first element to the second to last element + this.down(0, newLength); + return true; + } + + /** + * Re-establishes the heap ordering after the element at index `i` changes + * its value. Changing the value of the element at index `i` and then + * calling fix is equivalent to, but faster than, calling + * `remove(i); push(newValue);`. + * The complexity is O(log n) where n = this.size(). + * @param i + */ + public fix(i: number) { + if (!this.down(i, this.length)) { + this.up(i); + } + } + + private up(j: number) { + const less = this.less.bind(null, this.array); + for (let i: number; (i = ((j - 1) / 2) | 0), i !== j && less(j, i); j = i) { + this.swap(i, j); + } + } + + private down(i0: number, l: number): boolean { + const less = this.less.bind(null, this.array); + let i = i0; + for (let j1: number; (j1 = 2 * i + 1) < l; ) { + let j = j1; // left child + let j2 = j1 + 1; + if (j2 < l && less(j2, j1)) { + j = j2; // = 2 * i + 2 // right child + } + if (!less(j, i)) { + break; + } + this.swap(i, j); + i = j; + } + return i > i0; + } + + /** + * Swaps the elements in the heap + * @param i The first element + * @param j The second element + */ + private swap(i: number, j: number) { + const array = this.array; + const first = array[i]; + array[i] = array[j]; + array[j] = first; + } + + /** + * Heap initialization helper for when you only know of a single item for the + * heap. + * @param item + * @param less + */ + public static from(item: T, less: Comparator) { + const heap = new Heap(less); + heap.array = [item]; + heap.length = 1; + return heap; + } +} diff --git a/src/packages/utils/src/utils/index.ts b/src/packages/utils/src/utils/index.ts new file mode 100644 index 0000000000..e2ab466a84 --- /dev/null +++ b/src/packages/utils/src/utils/index.ts @@ -0,0 +1,8 @@ +export * from "./bigint-to-buffer"; +export * from "./executor"; +export * from "./heap"; +export * from "./request-coordinator"; +export * from "./unref"; +export * from "./has-own"; +export * from "./uint-to-buffer"; +export * from "./constants"; diff --git a/src/packages/utils/src/utils/request-coordinator.ts b/src/packages/utils/src/utils/request-coordinator.ts new file mode 100644 index 0000000000..b70f9409eb --- /dev/null +++ b/src/packages/utils/src/utils/request-coordinator.ts @@ -0,0 +1,102 @@ +const noop = () => {}; + +/** + * Responsible for managing global concurrent requests. + */ +export class RequestCoordinator { + /** + * The number of concurrent requests. Set to null for no limit. + */ + public limit: number; + + /** + * The pending requests. You can't do anything with this array. + */ + public readonly pending: ((...args: any) => Promise)[] = []; + + /** + * The number of tasks currently being processed. + */ + public runningTasks: number = 0; + #paused: boolean = true; + public get paused(): boolean { + return this.#paused; + } + + /** + * Promise-based FIFO queue. + * @param limit The number of requests that can be processed at a time. + * Default value is is no limit (`0`). + */ + constructor(limit: number) { + this.limit = limit; + } + + /** + * Pause processing. This will *not* cancel any promises that are currently + * running. + */ + public pause = () => { + this.#paused = true; + }; + + /** + * Resume processing. + */ + public resume = () => { + this.#paused = false; + this.#process(); + }; + + #process = () => { + // if we aren't paused and the number of things we're processing is under + // our limit and we have things to process: do it! + while ( + !this.paused && + this.pending.length > 0 && + (!this.limit || this.runningTasks < this.limit) + ) { + const current = this.pending.shift(); + this.runningTasks++; + current() + // By now, we've resolved the fn's `value` by sending it to the parent scope. + // But over here, we're also waiting for this fn's _value_ to settle _itself_ (it might be a promise) before + // continuing through the `pending` queue. Because we wait for it again here, it could potentially throw here, + // in which case we just need to catch it and throw the result away. We could probably use + // `Promise.allSettled([current()]).finally` to do this instead of the `current().catch(noop).finally`. /shrug + .catch(noop) + .finally(() => { + this.runningTasks--; + this.#process(); + }); + } + }; + + /** + * Insert a new function into the queue. + */ + public queue = unknown>( + fn: T, + thisArgument: any, + argumentsList: Parameters + ) => { + return new Promise<{ value: ReturnType }>((resolve, reject) => { + // const executor is `async` to force the return value into a Promise. + const executor = async () => { + try { + const value = Reflect.apply( + fn, + thisArgument, + argumentsList || [] + ) as ReturnType; + resolve({ value }); + return value; + } catch (e) { + reject(e); + } + }; + this.pending.push(executor); + this.#process(); + }); + }; +} diff --git a/src/packages/utils/src/utils/uint-to-buffer.ts b/src/packages/utils/src/utils/uint-to-buffer.ts new file mode 100644 index 0000000000..69288622be --- /dev/null +++ b/src/packages/utils/src/utils/uint-to-buffer.ts @@ -0,0 +1,179 @@ +const MAX_UINT32 = 0xffffffff; + +/** + * This is just Node's `Buffer.allocUnsafe`. I'm documenting it extra here to + * draw attention to it. It is much faster the `Buffer.alloc(size)` because it + * doesn't initialize its memory first. It's safe for us to use below because we + * guarantee that we will fill every octet ourselves. + * + * Allocates a new buffer of {size} octets, leaving memory not initialized, so + * the contents of the newly created Buffer are unknown and may contain + * sensitive data. + * + * @param {number} size count of octets to allocate + */ +const allocUnsafe = Buffer.allocUnsafe; + +/** + * Converts positive whole numbers that are 32 bits of fewer to a Buffer. Any + * more bits and who knows what will happen!?!1?! + * + * @param num A positive whole number less than 33 bits wide, i.e. a uint32. + * @returns an optimally sized buffer holding `num` in big-endian order (LSB is + * the _last_ value in the Buffer) + */ +function uint32ToBuf(num: number) { + let buf: Buffer; + + /** `lsb` holds the Least Significant *byte* of `num`. It *technically* holds + * all of `num`'s bytes but because of how UInt8Arrays (and thus Buffers) + * work, only the least significant byte of each value gets used. */ + const lsb = num; + + // shift the first 8 least signficant bits off current num, if it's non-zero + // our value contains at least 2 bytes! + if ((num >>>= 8)) { + /** `second` now holds the second most least significant byte in its + * "first" (right most) 8 bits */ + const second = num; + + // shift the next 8 least signficant bits off current num, if it's non-zero + // our value contains at least 3 bytes! + if ((num >>>= 8)) { + /** `third` now holds the third most least significant byte in its + * "first" (right most) 8 bits */ + const third = num; + if ((num >>>= 8)) { + // since we have all 4 bytes, create a 4 byte Buffer and fill it with + // our values! + buf = allocUnsafe(4); + // `num` here is just what is left after shifting off the 3 other bytes + // like we did above + buf[0] = num; + buf[1] = third; + buf[2] = second; + buf[3] = lsb; + } else { + // since we only have 3 bytes, create a 3 byte Buffer and fill it with + // our values! + buf = allocUnsafe(3); + buf[0] = third; + buf[1] = second; + buf[2] = lsb; + } + } else { + // since we only have 2 bytes, create a 2 byte Buffer and fill it with + // our values! + buf = allocUnsafe(2); + buf[0] = second; + buf[1] = lsb; + } + } else { + // We only have 1 byte, create a 1 byte Buffer and fill it with our only + // value, lsb! + buf = allocUnsafe(1); + buf[0] = lsb; + } + + // finally, return our optimally-sized Buffer! + return buf; +} + +/** + * Converts positive whole numbers less than or equal to + * `Number.MAX_SAFE_INTEGER` to a Buffer. If your value is less than 2**32 you + * should use `uint32ToBuf` instead. + * + * @param num A positive whole number <= `Number.MAX_SAFE_INTEGER` + * @returns an optimally sized buffer holding `num` in big-endian order (LSB is + * the _last_ value in the Buffer) + */ +function uintWideToBuf(num: number) { + // This function is similar to `uint32ToBuf`, but splits the number into its + // 32 lowest bits and its 32 highest bits. We have to do this because numeric + // Bitwise operations can only operate on 32 bit-wide values. + // There are some differences, but if you first grasp `uint32ToBuf`, you can + // handle this just fine. + + let buf: Buffer; + + /** If we are in this function we are probably > 32 bits wide, so we need to + * first convert this value to BigInt in order to shift off those high bits. + * Now that I'm documenting this, we could probably just subtract `2**32` from + * `num` to avoid the conversion overhead (BigInts are slower than numbers) */ + let hi = Number(BigInt(num) >> 32n); + + const hiLsb = hi; + let offset = 0; + + // the high bits determine the size of the Buffer, so we compute the high bits + // first + if ((hi >>>= 8)) { + const six = hi; + if ((hi >>>= 8)) { + const five = hi; + if ((hi >>>= 8)) { + buf = allocUnsafe(8); + buf[0] = hi; // msb + buf[1] = five; + buf[2] = six; + buf[3] = hiLsb; + offset = 7; + } else { + buf = allocUnsafe(7); + buf[0] = five; // msb + buf[1] = six; + buf[2] = hiLsb; + offset = 6; + } + } else { + buf = allocUnsafe(6); + buf[0] = six; // msb + buf[1] = hiLsb; + offset = 5; + } + } else { + buf = allocUnsafe(5); + buf[0] = hiLsb; // msb + offset = 4; + } + + // set the low bytes: + let lo = num & MAX_UINT32; + const lsb = lo; + if ((lo >>>= 8)) { + const two = lo; + if ((lo >>>= 8)) { + const one = lo; + buf[offset - 3] = lo >>>= 8; + buf[offset - 2] = one; + buf[offset - 1] = two; + buf[offset] = lsb; + } else { + buf[offset - 3] = 0; + buf[offset - 2] = 0; + buf[offset - 1] = two; + buf[offset] = lsb; + } + } else { + buf[offset - 3] = 0; + buf[offset - 2] = 0; + buf[offset - 1] = 0; + buf[offset] = lsb; + } + return buf; +} + +/** + * Converts a JavaScript number, treated as a Whole Number (0, 1, 2, 3, 4, ...) + * less than 64 bits wide, to a Buffer. + * + * Numbers that are negative, fractional, or greater than 64 bits wide will + * return very unexpected results. Numbers that are greater than + * `Number.MAX_SAFE_INTEGER` will return unexpected results. + * + * @param num A positive whole number <= `Number.MAX_SAFE_INTEGER` + */ +export function uintToBuffer(num: number) { + return num > MAX_UINT32 ? uintWideToBuf(num) : uint32ToBuf(num); +} diff --git a/src/packages/utils/src/utils/unref.ts b/src/packages/utils/src/utils/unref.ts new file mode 100644 index 0000000000..30d50e2b55 --- /dev/null +++ b/src/packages/utils/src/utils/unref.ts @@ -0,0 +1,16 @@ +/** + * In node, calling `unref(timer)` on a running timer ensures that the timer + * does not require that the Node.js event remain active. If there is no other + * activity keeping the event loop running, the process may exit before the + * timer's callback is invoked. + * @param timer + * @returns `true` if the timer could be `unref`ed, otherwise returns `false` + */ +export function unref(timer: NodeJS.Timeout | number): timer is NodeJS.Timeout { + if (typeof timer === "object" && typeof timer.unref === "function") { + timer.unref(); + return true; + } else { + return false; + } +} diff --git a/src/packages/utils/tests/utils.test.ts b/src/packages/utils/tests/utils.test.ts new file mode 100644 index 0000000000..b56cab343c --- /dev/null +++ b/src/packages/utils/tests/utils.test.ts @@ -0,0 +1,7 @@ +"use strict"; + +const utils = require(".."); + +describe("@ganache/utils", () => { + it("needs tests"); +}); diff --git a/src/packages/utils/tsconfig.json b/src/packages/utils/tsconfig.json new file mode 100644 index 0000000000..25d5c3cf7e --- /dev/null +++ b/src/packages/utils/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "outDir": "lib" + }, + "include": ["index.ts"] +} diff --git a/test/.eslintrc b/test/.eslintrc deleted file mode 100644 index b2916b91a7..0000000000 --- a/test/.eslintrc +++ /dev/null @@ -1,5 +0,0 @@ -{ - "env": { - "mocha": true - } -} \ No newline at end of file diff --git a/test/contracts/call/Call.sol b/test/contracts/call/Call.sol deleted file mode 100644 index 83f2e6070b..0000000000 --- a/test/contracts/call/Call.sol +++ /dev/null @@ -1,14 +0,0 @@ -pragma solidity ^0.6.0; - -contract Call { - constructor() public {} - - function theAnswerToLifeTheUniverseAndEverything() public pure returns (int256) { - return 42; - } - - function causeReturnValueOfUndefined() public pure returns (bool) { - require(false); - return true; - } -} diff --git a/test/contracts/chainId/ChainId.sol b/test/contracts/chainId/ChainId.sol deleted file mode 100644 index 195b87c3c6..0000000000 --- a/test/contracts/chainId/ChainId.sol +++ /dev/null @@ -1,11 +0,0 @@ -pragma solidity ^0.6.0; - -contract ChainId { - function getChainId() pure external returns (uint256) { - uint256 id; - assembly { - id := chainid() - } - return id; - } -} diff --git a/test/contracts/constantinople/ConstantinopleContract.sol b/test/contracts/constantinople/ConstantinopleContract.sol deleted file mode 100644 index c8a71889e2..0000000000 --- a/test/contracts/constantinople/ConstantinopleContract.sol +++ /dev/null @@ -1,10 +0,0 @@ -pragma solidity ^0.6.0; - -// Changes to this file will make tests fail. -contract ConstantinopleContract { - function test(uint8 shift) pure public returns (bytes32 value) { - assembly { - value := shl(shift, 1) - } - } -} diff --git a/test/contracts/customContracts/LargeContract.sol b/test/contracts/customContracts/LargeContract.sol deleted file mode 100644 index a9f6f19823..0000000000 --- a/test/contracts/customContracts/LargeContract.sol +++ /dev/null @@ -1,8 +0,0 @@ -pragma solidity ^0.6.0; - -// Changes to this file will make tests fail. -contract LargeContract { - function LargeFunction() public pure returns (string memory big) { - big = "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; - } -} diff --git a/test/contracts/debug/DebugContract.sol b/test/contracts/debug/DebugContract.sol deleted file mode 100644 index 62f59211a1..0000000000 --- a/test/contracts/debug/DebugContract.sol +++ /dev/null @@ -1,27 +0,0 @@ -pragma solidity ^0.6.0; - -// Changes to this file will make tests fail. -contract DebugContract { - uint public value = 5; - uint public otherValue = 5; - uint public currentBlock = 0; - - function setValue(uint _val) public { - value = _val; - otherValue += _val; - currentBlock = block.number; - } - - function callSetValueTwice() public { - setValue(1); - setValue(2); - } - - function get() public view returns (uint) { - return value; - } - - function getOther() public view returns (uint) { - return otherValue; - } -} diff --git a/test/contracts/debug/DebugContractStorage.sol b/test/contracts/debug/DebugContractStorage.sol deleted file mode 100644 index e60faaf18e..0000000000 --- a/test/contracts/debug/DebugContractStorage.sol +++ /dev/null @@ -1,20 +0,0 @@ -pragma solidity ^0.6.0; - -import "./DebugContract.sol"; - -contract DebugContractStorage { - DebugContract debugContract = new DebugContract(); - - function set() public { - debugContract.setValue(1); - debugContract.setValue(2); - } - - function getValue() public view returns(uint) { - return debugContract.get(); - } - - function getOtherValue() public view returns(uint) { - return debugContract.getOther(); - } -} diff --git a/test/contracts/events/EventTest.sol b/test/contracts/events/EventTest.sol deleted file mode 100644 index 7dcccaf1bf..0000000000 --- a/test/contracts/events/EventTest.sol +++ /dev/null @@ -1,8 +0,0 @@ -pragma solidity ^0.6.0; -contract EventTest { - event ExampleEvent(uint indexed first, uint indexed second); - - function triggerEvent(uint _first, uint _second) public { - emit ExampleEvent(_first, _second); - } -} diff --git a/test/contracts/examples/Example.sol b/test/contracts/examples/Example.sol deleted file mode 100644 index 2957e4bfb0..0000000000 --- a/test/contracts/examples/Example.sol +++ /dev/null @@ -1,20 +0,0 @@ -pragma solidity ^0.6.0; - -contract Example { - uint public value; - - event ValueSet(uint); - - constructor() public payable { - value = 5; - } - - function setValue(uint val) public { - value = val; - emit ValueSet(val); - } - - function destruct() public { - selfdestruct(msg.sender); - } -} diff --git a/test/contracts/examples/Example2.sol b/test/contracts/examples/Example2.sol deleted file mode 100644 index 1e5202cce7..0000000000 --- a/test/contracts/examples/Example2.sol +++ /dev/null @@ -1,6 +0,0 @@ -pragma solidity ^0.6.0; -contract Example2 { - constructor() public { - revert("I am not suppose to work"); - } -} diff --git a/test/contracts/forking/Debug.sol b/test/contracts/forking/Debug.sol deleted file mode 100644 index 19b12f24a0..0000000000 --- a/test/contracts/forking/Debug.sol +++ /dev/null @@ -1,19 +0,0 @@ -pragma solidity ^0.6.0; - -contract Debug { - uint public value; - - constructor() public { - value = 1; - } - - function test() public mod() returns (uint) { - value = value + 1; - return value; - } - - modifier mod() { - require(value < 2); - _; - } -} diff --git a/test/contracts/forking/IntraBlockCache.sol b/test/contracts/forking/IntraBlockCache.sol deleted file mode 100644 index 25fbdc394b..0000000000 --- a/test/contracts/forking/IntraBlockCache.sol +++ /dev/null @@ -1,19 +0,0 @@ -pragma solidity ^0.6.0; - -contract Test2 { - // Just make sure there really is some code here - uint256 foo; - function test() external { - foo = 1337; - } -} - -contract IntraBlockCache { - function deploy() external { - Test2 x = new Test2(); - address addr = address(x); - uint32 size; - assembly { size := extcodesize(addr) } - require(size > 0, 'extcodesize is broken'); - } -} diff --git a/test/contracts/forking/Snapshot.sol b/test/contracts/forking/Snapshot.sol deleted file mode 100644 index 52c4d460be..0000000000 --- a/test/contracts/forking/Snapshot.sol +++ /dev/null @@ -1,13 +0,0 @@ -pragma solidity ^0.6.0; - -contract Snapshot { - uint public value; - - constructor() public { - value = 0; - } - - function test() public { - value = value + 1; - } -} diff --git a/test/contracts/forking/StorageDelete.sol b/test/contracts/forking/StorageDelete.sol deleted file mode 100644 index c40a5b70b4..0000000000 --- a/test/contracts/forking/StorageDelete.sol +++ /dev/null @@ -1,18 +0,0 @@ -pragma solidity ^0.6.0; - -contract StorageDelete { - bool entered; - - constructor() public { - entered = true; - } - - function test() public nonReentrant {} - - modifier nonReentrant() { - require(entered, "re-entered"); - entered = false; - _; - entered = true; - } -} diff --git a/test/contracts/gas/ContractFactory.sol b/test/contracts/gas/ContractFactory.sol deleted file mode 100644 index b2fb09682c..0000000000 --- a/test/contracts/gas/ContractFactory.sol +++ /dev/null @@ -1,41 +0,0 @@ -pragma solidity ^0.6.0; - -contract ContractFactory5{ - constructor() public { - } -} -contract ContractFactory4{ - address[] addresses; - constructor() public { - for(uint i=0; i < 2; i++){ - addresses.push(address(new ContractFactory5())); - } - } -} -contract ContractFactory2{ - address public test2; - constructor() public { - test2 = address(new ContractFactory3()); - } -} - -contract ContractFactory3{ - address public test2; - address[] addresses; - constructor() public { - test2 = address(new ContractFactory4()); - - for(uint i=0; i < 2; i++){ - addresses.push(address(new ContractFactory4())); - } - } -} - -contract ContractFactory{ - event UsefulEvent(address factory1, address factory2); - function createInstance() public{ - address test2 = address(new ContractFactory2()); - address test3 = address(new ContractFactory4()); - emit UsefulEvent(test2, test3); - } -} diff --git a/test/contracts/gas/CreateTwo.sol b/test/contracts/gas/CreateTwo.sol deleted file mode 100644 index b863f897c9..0000000000 --- a/test/contracts/gas/CreateTwo.sol +++ /dev/null @@ -1,15 +0,0 @@ -pragma solidity ^0.6.0; - -contract CreateTwo { - event RelayAddress(address addr, uint256 salt); - function deploy(bytes memory code, uint256 salt) public { - address addr; - assembly { - addr := create2(0, add(code, 0x20), mload(code), salt) - if iszero(addr) { - revert(0, 0) - } - } - emit RelayAddress(addr, salt); - } -} diff --git a/test/contracts/gas/Donation.sol b/test/contracts/gas/Donation.sol deleted file mode 100644 index e2b7a28d32..0000000000 --- a/test/contracts/gas/Donation.sol +++ /dev/null @@ -1,63 +0,0 @@ -pragma solidity ^0.6.0; - -contract Donation { - address owner; - event fundMoved(address _to, uint _amount); - modifier onlyowner { if (msg.sender == owner) _; } - address[] _giver; - uint[] _values; - - constructor() public { - owner = msg.sender; - } - - function donate() public payable { - addGiver(msg.value); - } - - function moveFund(address _to, uint _amount) public onlyowner { - uint _balance = address(this).balance; - address payable inst = address(new Fib()); - bool _tosendbal = inst.send(_amount); - if (_amount <= _balance) { - if (_tosendbal) { - emit fundMoved(_to, _amount); - } else { - revert(); - } - } else { - revert(); - } - } - - function moveFund2(address payable _to, uint _amount) public onlyowner { - uint _balance = address(this).balance; - if (_amount <= _balance) { - require(_to.send(_amount)); - emit fundMoved(_to, _amount); - } else { - revert(); - } - } - - function addGiver(uint _amount) internal { - _giver.push(msg.sender); - _values.push(_amount); - } -} - -contract Fib { - constructor() public {} - uint public value = 0; - - fallback() external payable { - calc(5); - } - - function calc(uint index) internal returns(uint){ - if (index <= 1) { - return 1; - } - return calc(index - 1) + calc(index - 2); - } -} diff --git a/test/contracts/gas/EstimateGas.sol b/test/contracts/gas/EstimateGas.sol deleted file mode 100644 index f8861afe76..0000000000 --- a/test/contracts/gas/EstimateGas.sol +++ /dev/null @@ -1,117 +0,0 @@ -pragma solidity ^0.6.0; - -// From https://github.com/ethereumjs/testrpc/issues/58 -contract EstimateGas { - event Add(bytes32 name, bytes32 description, uint value, address owner); - - struct Test { - bytes32 name; - bytes32 description; - uint[] balances; - mapping(address => uint) owners; - } - - uint256 public x; - uint256 public y; - function reset() public { - x = 0; - y = 1; - } - function initialSettingOfX() public { - x = 1; - } - function triggerRsclearRefund() public { - x = gasleft(); - reset(); - } - function triggerRsclearRefundForX() public { - reset(); - x = gasleft(); - } - function triggerRsclearRefundForY() public { - y = gasleft(); - reset(); - } - function triggerRselfdestructRefund() public { - selfdestruct(msg.sender); - } - function triggerAllRefunds() public { - triggerRsclearRefund(); - triggerRselfdestructRefund(); - } - - // https://github.com/trufflesuite/ganache-cli/issues/294 - mapping (uint => uint) public uints; - // Sets the uints[1] slot to a value; - function store(uint _uint) public { uints[1] = _uint;} - function clear() public { delete uints[1]; } - - mapping(bytes32 => uint) index; - Test[] tests; - - constructor() public { - tests.push(); - } - - function add(bytes32 _name, bytes32 _description, uint _value) public returns(bool) { - if (index[_name] != 0) { - return false; - } - uint pos = tests.length; - tests.push(); - tests[pos].name = _name; - tests[pos].description = _description; - tests[pos].balances.push(); - tests[pos].balances.push(_value); - tests[pos].owners[msg.sender] = 1; - index[_name] = pos; - emit Add(_name, _description, _value, msg.sender); - return true; - } - - function transfer(address _to, uint _value, bytes32 _name) public returns(bool) { - uint pos = index[_name]; - if (pos == 0) { - return false; - } - - uint posFrom = tests[pos].owners[msg.sender]; - if (posFrom == 0) { - return false; - } - - if (tests[pos].balances[posFrom] < _value) { - return false; - } - - uint posTo = tests[pos].owners[_to]; - if (posTo == 0) { - uint posBal = tests[pos].balances.length; - tests[pos].balances.push(); - tests[pos].owners[_to] = posBal; - posTo = posBal; - } - - if (tests[pos].balances[posTo] + _value < tests[pos].balances[posTo]) { - return false; - } - tests[pos].balances[posFrom] -= _value; - tests[pos].balances[posTo] += _value; - - return true; - } - - function currentBlock() public view returns (uint) { - return block.number; - } - - uint public counter; - function runsOutOfGas() public { - consumesGas(); - } - function consumesGas() public { - for(uint i = 0; i < 100000; i++){ - counter = i; - } - } -} diff --git a/test/contracts/gas/Fib.sol b/test/contracts/gas/Fib.sol deleted file mode 100644 index 40c2006b66..0000000000 --- a/test/contracts/gas/Fib.sol +++ /dev/null @@ -1,22 +0,0 @@ -pragma solidity ^0.6.0; - -contract Fib { - constructor() public {} - uint public value = 0; - - fallback() external payable { - Bif bif = new Bif(); - value = bif.calc(5); - } -} - -contract Bif { - constructor() public {} - - function calc(uint index) public returns(uint){ - if (index <= 1) { - return 1; - } - return calc(index - 1) + calc(index - 2); - } -} diff --git a/test/contracts/gas/GasLeft.sol b/test/contracts/gas/GasLeft.sol deleted file mode 100644 index 01403a3a4e..0000000000 --- a/test/contracts/gas/GasLeft.sol +++ /dev/null @@ -1,8 +0,0 @@ -pragma solidity ^0.6.0; -contract GasLeft { - uint x = 0; - function checkGas() public { - require(gasleft() > 100000, "Need 100000 gas"); - x = 1; - } -} diff --git a/test/contracts/gas/NonZero.sol b/test/contracts/gas/NonZero.sol deleted file mode 100644 index 40b4774857..0000000000 --- a/test/contracts/gas/NonZero.sol +++ /dev/null @@ -1,19 +0,0 @@ -pragma solidity ^0.6.0; -contract Target { - fallback () external payable { } -} -contract NonZero { - Target private theInstance; - constructor() public { - theInstance = new Target(); - } - function doCall() external payable { - address(theInstance).call.value(msg.value).gas(123456)(""); - } - function doTransfer() external payable { - address(theInstance).transfer(msg.value); - } - function doSend() external payable { - address(theInstance).send(msg.value); - } -} diff --git a/test/contracts/gas/SendContract.sol b/test/contracts/gas/SendContract.sol deleted file mode 100644 index 7cd5be42c3..0000000000 --- a/test/contracts/gas/SendContract.sol +++ /dev/null @@ -1,22 +0,0 @@ -// FROM: https://github.com/trufflesuite/ganache-cli/issues/585 -pragma solidity ^0.6.0; -contract SendContract { - function Send() public payable{ - - } - - fallback () external payable{ - } - - function getBalance() public view returns (uint balance){ - balance = address(this).balance; - return balance; - } - - function transfer(address payable[] memory receiver, uint256 amount) public payable returns(bool){ - for(uint i = 0; i < receiver.length; i++){ - receiver[i].transfer(amount); - } - return true; - } -} diff --git a/test/contracts/gas/TestDepth.sol b/test/contracts/gas/TestDepth.sol deleted file mode 100644 index cbabaf5eee..0000000000 --- a/test/contracts/gas/TestDepth.sol +++ /dev/null @@ -1,17 +0,0 @@ -pragma solidity ^0.6.0; - -contract TestDepth { - uint256 public x = 1; - - function depth(uint256 y) public { - // bool result; - if (y > 0) { - (bool result,) = address(this).delegatecall(abi.encodeWithSignature("depth(uint256)", --y)); - require(result, ""); - } - else { - // Save the remaining gas in storage so that we can access it later - x = gasleft(); - } - } -} diff --git a/test/contracts/misc/Oracle.sol b/test/contracts/misc/Oracle.sol deleted file mode 100644 index 72a93abdbd..0000000000 --- a/test/contracts/misc/Oracle.sol +++ /dev/null @@ -1,15 +0,0 @@ -pragma solidity ^0.6.0; - -contract Oracle{ - bytes32 public blockhash0; - uint public lastBlock; - constructor() public { - blockhash0 = blockhash(0); - } - function currentBlock() public view returns (uint) { - return block.number; - } - function setCurrentBlock() public { - lastBlock = block.number; - } -} diff --git a/test/contracts/revert/Revert.sol b/test/contracts/revert/Revert.sol deleted file mode 100644 index 6739b45f71..0000000000 --- a/test/contracts/revert/Revert.sol +++ /dev/null @@ -1,13 +0,0 @@ -pragma solidity ^0.6.0; - -contract Revert { - uint public value; - - event ValueSet(uint); - - function alwaysReverts(uint val) public { - value = val; - emit ValueSet(val); - revert(); - } -} diff --git a/test/contracts/runtime/RuntimeError.sol b/test/contracts/runtime/RuntimeError.sol deleted file mode 100644 index ad1f0a760c..0000000000 --- a/test/contracts/runtime/RuntimeError.sol +++ /dev/null @@ -1,22 +0,0 @@ -pragma solidity ^0.6.0; - -// Changes to this file will make tests fail. -contract RuntimeError { - function error() public { - for (uint i = 0; i < 3; ) { - i++; - } - revert(); - } - - function errorWithMessage() public { - for (uint i = 0; i < 3; ) { - i++; - } - revert("Message"); - } - - function success() public { - - } -} diff --git a/test/contracts/snapshotting/snapshot.sol b/test/contracts/snapshotting/snapshot.sol deleted file mode 100644 index 970787674a..0000000000 --- a/test/contracts/snapshotting/snapshot.sol +++ /dev/null @@ -1,9 +0,0 @@ -pragma solidity ^0.6.0; - -contract snapshot { - uint public n = 42; - - function inc() public { - n += 1; - } -} diff --git a/test/contracts/transaction-data/TransactionData.sol b/test/contracts/transaction-data/TransactionData.sol deleted file mode 100644 index 9688eb782f..0000000000 --- a/test/contracts/transaction-data/TransactionData.sol +++ /dev/null @@ -1,7 +0,0 @@ -pragma solidity ^0.6.0; - -contract TransactionData { - fallback () external payable { - require(msg.data.length == 0, "msg.data.length was 0"); - } -} diff --git a/test/helpers/contract/bootstrap.js b/test/helpers/contract/bootstrap.js deleted file mode 100644 index 2284850562..0000000000 --- a/test/helpers/contract/bootstrap.js +++ /dev/null @@ -1,27 +0,0 @@ -const { compileAndDeploy } = require("./compileAndDeploy"); -const initializeTestProvider = require("../web3/initializeTestProvider"); - -/** - * @param {Object} contractRef Object containing contract files and subdirectory path - * @param {Object} providerOptions Provider options - * @param {Object} compilerOptions Compiler options - * @returns {Object} abi, accounts, bytecode, contract, instance, provider, receipt, sources, web3 - */ -const bootstrap = async(contractRef = {}, providerOptions = {}, hardfork) => { - const { accounts, provider, send, web3 } = await initializeTestProvider(providerOptions); - const { contractFiles, contractSubdirectory } = contractRef; - const [mainContractName, ...subContractNames] = contractFiles; - const testAssets = await compileAndDeploy( - mainContractName, - subContractNames, - contractSubdirectory, - web3, - providerOptions, - accounts, - hardfork - ); - - return Object.assign({ provider, send, web3 }, testAssets); -}; - -module.exports = bootstrap; diff --git a/test/helpers/contract/compileAndDeploy.js b/test/helpers/contract/compileAndDeploy.js deleted file mode 100644 index 754a00a550..0000000000 --- a/test/helpers/contract/compileAndDeploy.js +++ /dev/null @@ -1,123 +0,0 @@ -const solc = require("solc"); -const { join } = require("path"); -const { readFileSync } = require("fs"); - -/** - * Compile the specified contract(s) - * @param {String} mainContractName Name of the main contract (without .sol extension) - * @param {Array|String} contractFileNames List of imported contracts - * @param {String} contractPath Path to contracts directory - * @returns {Object} context: abi, bytecode, sources - */ -function compile(mainContractName, contractFileNames = [], contractSubdirectory, hardfork = "petersburg") { - const contractPath = join(__dirname, "..", "..", "contracts", `${contractSubdirectory}/`); - const selectedContracts = [mainContractName].concat(contractFileNames); - - const contractSources = selectedContracts.map((contractName) => { - const _contractName = `${contractName.replace(/\.sol$/i, "")}.sol`; - return { [_contractName]: { content: readFileSync(join(contractPath, _contractName), "utf8") } }; - }); - - const input = { - language: "Solidity", - sources: Object.assign({}, ...contractSources), - settings: { - outputSelection: { - "*": { - "*": ["*"] - } - } - } - }; - input.settings.evmVersion = hardfork === "muirGlacier" ? "istanbul" : hardfork; - - const result = JSON.parse(solc.compile(JSON.stringify(input))); - - if (result.errors && result.errors.some((error) => error.severity === "error")) { - const errorMessages = result.errors.map((error) => error.formattedMessage); - throw new Error(`Could not compile test contracts:\n${errorMessages.join("")}`); - } - - const _mainContractName = mainContractName.endsWith(".sol") - ? mainContractName.replace(/\.sol$/i, "") - : mainContractName; - const compiledMainContract = result.contracts[`${_mainContractName}.sol`][`${_mainContractName}`]; - const bytecode = `0x${compiledMainContract.evm.bytecode.object}`; - const abi = compiledMainContract.abi; - - return { - abi, - bytecode, - input, - result - }; -} - -/** - * Deploy a compiled contract - * @param {String} abi contract ABI - * @param {String} bytecode contract bytecode - * @param {Object} web3 Web3 interface - * @param {Object} options Provider options - * @param {Array} existingAccounts Existing accounts - * @returns {Object} context: abi, accounts, bytecode, contract, instance, receipt - */ -async function deploy(abi, bytecode, web3, options = {}, existingAccounts = []) { - let accounts, block, receipt; - - if (existingAccounts.length) { - block = await web3.eth.getBlock("latest"); - accounts = existingAccounts; - } else { - const initialAssets = [web3.eth.getAccounts(), web3.eth.getBlock("latest")]; - [accounts, block] = await Promise.all(initialAssets); - } - - const gas = options.gas || block.gasLimit; - const contract = new web3.eth.Contract(abi); - const instance = await contract - .deploy({ data: bytecode, arguments: options.constructorArguments }) - .send({ from: accounts[0], gas }) - .on("receipt", (rcpt) => { - receipt = rcpt; - }); - - return { - abi, - accounts, - bytecode, - contract, - instance, - receipt - }; -} - -/** - * Compile and deploy the specified contract(s) - * @param {String} mainContractName Name of the main contract (without .sol extension) - * @param {Array|String} contractFileNames List of imported contracts - * @param {String} contractPath Path to contracts directory - * @param {Object} web3 Web3 interface - * @param {Object} options Provider options - * @param {Array} accounts Predetermined accounts - * @returns {Object} context: abi, accounts, bytecode, contract, instance, receipt, sources - */ -async function compileAndDeploy( - mainContractName, - contractFileNames = [], - contractPath, - web3, - web3Options = {}, - accounts = [], - hardfork -) { - const { abi, bytecode, options, result } = compile(mainContractName, contractFileNames, contractPath, hardfork); - const context = await deploy(abi, bytecode, web3, web3Options, accounts); - return Object.assign(context, options, result); -} - -module.exports = { - compile, - compileAndDeploy, - deploy -}; diff --git a/test/helpers/contract/singleFileCompile.js b/test/helpers/contract/singleFileCompile.js deleted file mode 100644 index 237570d191..0000000000 --- a/test/helpers/contract/singleFileCompile.js +++ /dev/null @@ -1,23 +0,0 @@ -const { readFileSync } = require("fs"); -const { compile } = require("solc"); -const { join } = require("path"); - -module.exports = function singleFileCompile(filepath, contractName) { - const filename = `${contractName}.sol`; - const input = { - language: "Solidity", - sources: {}, - settings: { - outputSelection: { - "*": { - "*": ["*"] - } - } - } - }; - const source = readFileSync(join(__dirname, "../../..", filepath, filename), { encoding: "utf8" }); - input.sources[filename] = { - content: source - }; - return { result: JSON.parse(compile(JSON.stringify(input))), source }; -}; diff --git a/test/helpers/utils/create-signed-tx.js b/test/helpers/utils/create-signed-tx.js deleted file mode 100644 index a911923bcc..0000000000 --- a/test/helpers/utils/create-signed-tx.js +++ /dev/null @@ -1,18 +0,0 @@ -const Transaction = require("../../../lib/utils/transaction"); - -module.exports = function createSignedTx(privateKey, params) { - function _createAndSign(params) { - const tx = new Transaction(params); - tx.sign(privateKey); - return tx; - } - - const validBuffer = privateKey && Buffer.isBuffer(privateKey) && privateKey.length === 32; - const validParams = params && typeof params === "object"; - - if (validBuffer) { - return validParams ? _createAndSign(params) : (params) => _createAndSign(params); - } - - throw new Error("Please use args: privateKey(Buffer), params(Object) "); -}; diff --git a/test/helpers/utils/generateRandomInteger.js b/test/helpers/utils/generateRandomInteger.js deleted file mode 100644 index 5e21206f64..0000000000 --- a/test/helpers/utils/generateRandomInteger.js +++ /dev/null @@ -1,10 +0,0 @@ -/** - * Generates a random integer - * @param {Number} max Maximum integer to randomly generate - * @returns {Number} Returns a random integer between 0 and `max` - */ -const randomInteger = (max) => { - return Math.floor(Math.random() * (max + 1)); -}; - -module.exports = randomInteger; diff --git a/test/helpers/utils/rpc.js b/test/helpers/utils/rpc.js deleted file mode 100644 index b5973977d9..0000000000 --- a/test/helpers/utils/rpc.js +++ /dev/null @@ -1,25 +0,0 @@ -const { promisify } = require("util"); - -/** - * Generic RPC method - * @param {Object} provider Ganache provider - * @returns {Function} Send method - */ -const generateSend = function(provider) { - /** - * Generic RPC method - * @param {String} method JSON RPC method - * @param {...*} params JSON RPC parameters - * @returns {Promise} Response object - */ - return function(method = "", ...params) { - return promisify(provider.send.bind(provider))({ - id: `${new Date().getTime()}`, - jsonrpc: "2.0", - method, - params: [...params] - }); - }; -}; - -module.exports = generateSend; diff --git a/test/helpers/utils/sleep.js b/test/helpers/utils/sleep.js deleted file mode 100644 index 7ecace6d85..0000000000 --- a/test/helpers/utils/sleep.js +++ /dev/null @@ -1,12 +0,0 @@ -/** - * Sleep for a period of time in milliseconds - * @param {number} milliseconds Number of milliseconds to wait - * @returns {Promise} Asynchronous timeout for a period specified in millieseconds - */ -const sleep = async(milliseconds) => { - return new Promise((resolve) => { - setTimeout(resolve, milliseconds); - }); -}; - -module.exports = sleep; diff --git a/test/helpers/utils/toBytesHexString.js b/test/helpers/utils/toBytesHexString.js deleted file mode 100644 index 2928333883..0000000000 --- a/test/helpers/utils/toBytesHexString.js +++ /dev/null @@ -1,16 +0,0 @@ -const to = require("../../../lib/utils/to"); - -/** - * Converts a string into a hex string - * @param {string} message - * @returns {string} Hex representation of the `message` - */ -const toBytesHexString = (message) => { - const bytes = Array.prototype.map.call(message, (character) => { - return character.codePointAt(0); - }); - - return to.hex(Buffer.from(bytes)); -}; - -module.exports = toBytesHexString; diff --git a/test/helpers/web3/initializeTestProvider.js b/test/helpers/web3/initializeTestProvider.js deleted file mode 100644 index f14c981be7..0000000000 --- a/test/helpers/web3/initializeTestProvider.js +++ /dev/null @@ -1,26 +0,0 @@ -const Ganache = require(process.env.TEST_BUILD - ? "../../../build/ganache.core." + process.env.TEST_BUILD + ".js" - : "../../../index.js"); -const Web3 = require("web3"); -const generateSend = require("../utils/rpc"); - -/** - * Initialize Ganache provider with `options` - * @param {Object} options - Ganache provider options - * @returns {Object} accounts, provider, send, web3 Object - */ -const initializeTestProvider = async(options = {}, provider = null) => { - provider = provider || options.provider || Ganache.provider(options); - const send = generateSend(provider); - const web3 = new Web3(provider); - const accounts = await web3.eth.getAccounts(); - - return { - accounts, - provider, - send, - web3 - }; -}; - -module.exports = initializeTestProvider; diff --git a/test/local/CallLibrary.sol b/test/local/CallLibrary.sol deleted file mode 100644 index a570b96ea4..0000000000 --- a/test/local/CallLibrary.sol +++ /dev/null @@ -1,15 +0,0 @@ -pragma solidity ^0.6.0; - -import "./Library.sol"; - -contract CallLibrary { - address originalSender = msg.sender; - - function callExternalLibraryFunction() public view returns (bool) { - address sender = Library.callCheckMsgSender(); - if (sender == originalSender){ - return true; - } - return false; - } -} diff --git a/test/local/LargeContract.sol b/test/local/LargeContract.sol deleted file mode 100644 index 4b830add0c..0000000000 --- a/test/local/LargeContract.sol +++ /dev/null @@ -1,8 +0,0 @@ -pragma solidity ^0.6.0; - -// Changes to this file will make tests fail. -contract LargeContract { - function LargeFunction() public pure returns (string) { - return "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; - } -} diff --git a/test/local/Library.sol b/test/local/Library.sol deleted file mode 100644 index 20859e3885..0000000000 --- a/test/local/Library.sol +++ /dev/null @@ -1,14 +0,0 @@ -pragma solidity ^0.6.0; - -library Library { - function checkMsgSender() internal view returns (address) { - address sender = msg.sender; - return sender; - } - - // this function should call the checkMsgSender function - function callCheckMsgSender() external view returns (address) { - address sender = checkMsgSender(); - return sender; - } -} diff --git a/test/local/accounts.js b/test/local/accounts.js deleted file mode 100644 index b83b5efcf0..0000000000 --- a/test/local/accounts.js +++ /dev/null @@ -1,363 +0,0 @@ -const assert = require("assert"); -const initializeTestProvider = require("../helpers/web3/initializeTestProvider"); -const { BN } = require("ethereumjs-util"); -var Ganache = require(process.env.TEST_BUILD - ? "../../build/ganache.core." + process.env.TEST_BUILD + ".js" - : "../../index.js"); -const genSend = require("../helpers/utils/rpc"); -const Account = require("ethereumjs-account").default; -const { promisify } = require("util"); -const utils = require("ethereumjs-util"); - -describe("Accounts", () => { - const expectedAddress = "0x604a95C9165Bc95aE016a5299dd7d400dDDBEa9A"; - const badAddress = "0x1234567890123456789012345678901234567890"; - const mnemonic = "into trim cross then helmet popular suit hammer cart shrug oval student"; - - it("should respect the BIP99 mnemonic", async() => { - const options = { mnemonic }; - const { accounts } = await initializeTestProvider(options); - - assert.strictEqual(accounts[0], expectedAddress); - }); - - it("should lock all accounts when specified", async() => { - const options = { - mnemonic, - secure: true - }; - - const { accounts, web3 } = await initializeTestProvider(options); - - await Promise.all( - accounts.map((account) => { - const tx = web3.eth.sendTransaction({ - from: account, - to: badAddress, - value: web3.utils.toWei("1", "ether"), - gasLimit: 90000 - }); - return assert.rejects(tx, /signer account is locked/, "should not be able to unlock the count"); - }) - ); - }); - - it("should unlock specified accounts, in conjunction with --secure", async() => { - const options = { - mnemonic, - secure: true, - unlocked_accounts: [expectedAddress] - }; - - const { accounts, web3 } = await initializeTestProvider(options); - - await Promise.all( - accounts.map((account) => { - const tx = web3.eth.sendTransaction({ - from: account, - to: badAddress, - value: web3.utils.toWei("1", "ether"), - gasLimit: 90000 - }); - - if (account === expectedAddress) { - return assert.doesNotReject(tx, /signer account is locked/, "should not be able to unlock the count"); - } else { - return assert.rejects(tx, /signer account is locked/, "should not be able to unlock the count"); - } - }) - ); - }); - - it("should unlock specified accounts, in conjunction with --secure, using array indexes", async() => { - const accountIndexToUnlock = 5; - const options = { - mnemonic, - secure: true, - unlocked_accounts: [accountIndexToUnlock] - }; - - const { accounts, web3 } = await initializeTestProvider(options); - const unlockedAccount = accounts[accountIndexToUnlock]; - - await Promise.all( - accounts.map((account) => { - const tx = web3.eth.sendTransaction({ - from: account, - to: badAddress, - value: web3.utils.toWei("1", "ether"), - gasLimit: 90000 - }); - - if (account === unlockedAccount) { - return assert.doesNotReject(tx, /signer account is locked/, "should not be able to unlock the count"); - } else { - return assert.rejects(tx, /signer account is locked/, "should not be able to unlock the count"); - } - }) - ); - }); - - it("should unlock accounts even if private key isn't managed by the testrpc (impersonation)", async() => { - const options = { - mnemonic, - secure: true, - unlocked_accounts: [0, badAddress], - gasPrice: 0 - }; - - const { web3 } = await initializeTestProvider(options); - - // Set up: give second address some ether - await web3.eth.sendTransaction({ - from: expectedAddress, - to: badAddress, - value: web3.utils.toWei("10", "ether"), - gasLimit: 90000 - }); - - // Now we should be able to send a transaction from second address without issue. - await web3.eth.sendTransaction({ - from: badAddress, - to: expectedAddress, - value: web3.utils.toWei("5", "ether"), - gasLimit: 90000 - }); - - // And for the heck of it let's check the balance just to make sure it went through - const balance = await web3.eth.getBalance(badAddress); - let balanceInEther = web3.utils.fromWei(balance, "ether"); - balanceInEther = parseFloat(balanceInEther); - assert.strictEqual(balanceInEther, 5); - }); - - it("errors when we try to sign a transaction from an account we're impersonating", async function() { - const options = { - mnemonic, - secure: true, - unlocked_accounts: [0, badAddress] - }; - - const { web3 } = await initializeTestProvider(options); - - assert.rejects( - () => web3.eth.sign("some data", badAddress), - /cannot sign data; no private key/, - "should not be able to sign a transaction with an impersonated account" - ); - }); - - it("should create a 2 accounts when passing an object to provider", async() => { - const options = { - accounts: [{ balance: "0x12" }, { balance: "0x13" }] - }; - - const { accounts } = await initializeTestProvider(options); - - assert.strictEqual(accounts.length, 2, "The number of accounts created should be 2"); - }); - - it("should create the correct number of accounts as specified by total_accounts", async() => { - const options = { - total_accounts: 7 - }; - - const { accounts } = await initializeTestProvider(options); - - assert.strictEqual(accounts.length, 7, "The number of accounts created should be 7"); - }); - - it("should respect the default_balance_ether option", async() => { - const options = { - default_balance_ether: 1.23456 - }; - - const { accounts, web3 } = await initializeTestProvider(options); - - await Promise.all( - accounts.map((account) => - web3.eth.getBalance(account).then((balance) => { - const balanceInEther = web3.utils.fromWei(balance, "Ether"); - assert.strictEqual(balanceInEther, "1.23456"); - }) - ) - ); - }); - - describe("Should handle large nonces", function() { - let provider; - let accounts; - let from; - let send; - let currentNonce; - let startingBlockNumber; - const sendTransaction = (payload) => send("eth_sendTransaction", payload); - const getTransactionByHash = (payload) => send("eth_getTransactionByHash", payload); - - beforeEach("set up provider", async function() { - provider = Ganache.provider(); - send = genSend(provider); - const { result: _accounts } = await send("eth_accounts"); - accounts = _accounts; - from = accounts[9]; - }); - - async function setUp(initialNonce) { - // Hack to seed the state with an account with a very high nonce - const stateManager = provider.manager.state.blockchain.vm.stateManager; - const putAccount = promisify(stateManager.putAccount.bind(stateManager)); - await putAccount( - utils.toBuffer(from), - new Account({ - balance: "0xffffffffffffffffffff", - nonce: new BN(initialNonce), - address: from - }) - ); - // we need to mine a block for the putAccount to take effect - await send("evm_mine"); - - const { result: count } = await send("eth_getTransactionCount", from, "latest"); - currentNonce = new BN(count.slice(2), "hex"); - assert.strictEqual(currentNonce.toNumber(), initialNonce, "nonce is not equal to" + initialNonce); - const { - result: { number: blockNumber } - } = await send("eth_getBlockByNumber", "latest", false); - startingBlockNumber = parseInt(blockNumber, 16); - assert.strictEqual(startingBlockNumber, 1, "latest block number is not expected (1)"); - } - - async function runTests(intervalMining = true) { - const expectedNonce = new BN(currentNonce); - let expectedBlockNum = startingBlockNumber + 1; - - if (intervalMining) { - // mimic interval mining without out having to actually - // configure ganache to mine on an interval (slowing tests down) - // by just stopping the miner and then mining on command - await send("miner_stop"); - } - // create some transactions that will increment the nonce - const tx = { value: 1, from, to: from }; - // send of our transactions and get their tx info once ready - const pendingHashes = Array(3) - .fill(tx) - .map(sendTransaction); - if (intervalMining) { - await send("evm_mine"); - } - const pendingTransactions = pendingHashes.map((tx) => tx.then(({ result }) => getTransactionByHash(result))); - await Promise.all( - pendingTransactions.map((tx) => - tx.then(({ result }) => { - const nonce = new BN(result.nonce.slice(2), "hex"); - const blockNum = parseInt(result.blockNumber, 16); - assert.strictEqual(nonce.toString(10), expectedNonce.toString(10), "Tx nonce is not as expected"); - assert.strictEqual(blockNum, expectedBlockNum, "Tx blockNumber is not as expected"); - expectedNonce.iaddn(1); - // the block number must be different for each treansaction is we - // we are instamining. This ensures we are testing the right code branch - if (!intervalMining) { - expectedBlockNum++; - } - }) - ) - ); - } - - it("should handle nonces greater than 255 (interval)", async function() { - await setUp(255); - await runTests(true); - }); - - it("should handle nonces greater than 255 (instamining)", async function() { - await setUp(255); - await runTests(false); - }); - - it("should handle nonces greater than MAX_SAFE_INTEGER (interval)", async function() { - await setUp(Number.MAX_SAFE_INTEGER); - await runTests(true); - }); - - it("should handle nonces greater than MAX_SAFE_INTEGER (instamining)", async function() { - await setUp(Number.MAX_SAFE_INTEGER); - await runTests(false); - }); - }); - - describe("evm_lockUnknownAccount/evm_unlockUnknownAccount", () => { - let accounts, send; - before(async() => { - const context = await initializeTestProvider(); - accounts = context.accounts; - send = context.send; - }); - - it("should unlock any account after server has been started", async() => { - const address = "0x742d35Cc6634C0532925a3b844Bc454e4438f44e"; - const { result: result1 } = await send("evm_unlockUnknownAccount", address); - assert.strictEqual(result1, true); - - // should return `false` if account was already locked - const { result: result2 } = await send("evm_unlockUnknownAccount", address); - assert.strictEqual(result2, false); - }); - - it("should not unlock any locked personal account", async() => { - const [address] = accounts; - await send("personal_lockAccount", address); - try { - await assert.rejects( - send("evm_unlockUnknownAccount", { - message: "cannot unlock known/personal account" - }) - ); - } finally { - // unlock the account - await send("personal_unlockAccount", address, "", 0); - } - }); - - it("should lock any unlocked unknown account via evm_lockUnknownAccount", async() => { - const address = "0x842d35Cc6634C0532925a3b844Bc454e4438f44f"; - const { result: unlockResult } = await send("evm_unlockUnknownAccount", address); - assert.strictEqual(unlockResult, true); - - const { result: lockResult1 } = await send("evm_lockUnknownAccount", address); - assert.strictEqual(lockResult1, true); - - // bonus: also make sure we return false when the account is already locked: - const { result: lockResult2 } = await send("evm_lockUnknownAccount", address); - assert.strictEqual(lockResult2, false); - }); - - it("should not lock a known account via evm_lockUnknownAccount", async() => { - await assert.rejects( - send("evm_lockUnknownAccount", accounts[0], { - message: "cannot lock known/personal account" - }) - ); - }); - - it("should not lock a personal account via evm_lockUnknownAccount", async() => { - // create a new personal account - const { result: address } = await send("personal_newAccount", "password"); - - // then explicitly unlock it - const { result } = await send("personal_unlockAccount", address, "password", 0); - assert.strictEqual(result, true); - - // then try to lock it via evm_lockUnknownAccount - await assert.rejects(send("evm_lockUnknownAccount", address), { - message: "cannot lock known/personal account" - }); - }); - - it("should return `false` upon lock if account isn't locked (unknown account)", async() => { - const address = "0x942d35Cc6634C0532925a3b844Bc454e4438f450"; - const { result } = await send("evm_lockUnknownAccount", address); - assert.strictEqual(result, false); - }); - }); -}); diff --git a/test/local/bad_input.js b/test/local/bad_input.js deleted file mode 100644 index d901131a99..0000000000 --- a/test/local/bad_input.js +++ /dev/null @@ -1,213 +0,0 @@ -var Web3 = require("web3"); -var Ganache = require(process.env.TEST_BUILD - ? "../../build/ganache.core." + process.env.TEST_BUILD + ".js" - : "../../index.js"); -var assert = require("assert-match"); -var regex = require("assert-match/matchers").regex; - -var tests = function(web3) { - var accounts; - - // The second request, after the first in each of these tests, - // informs us whether or not the provider crashed. - function secondRequest(callback) { - web3.eth.getAccounts(callback); - } - - describe("bad input", function() { - before(function(done) { - web3.eth.getAccounts(function(err, accs) { - if (err) { - return done(err); - } - accounts = accs; - done(); - }); - }); - - it("recovers after to address that isn't a string", function(done) { - var provider = web3.currentProvider; - - provider.send( - { - jsonrpc: "2.0", - method: "eth_sendTransaction", - params: [ - { - value: "0x0", - gas: "0xf4240", - from: accounts[0], - // Buffers have been sent in the past - to: { - type: "Buffer", - data: [ - // ... - ] - }, - data: "0xe1fa8e84666f6f0000000000000000000000000000000000000000000000000000000000" - } - ], - id: 2 - }, - function() { - // Ignore any errors, but make sure we can make the second request - secondRequest(done); - } - ); - }); - - it("recovers after bad nonce (too high)", function(done) { - var provider = web3.currentProvider; - - var request = { - jsonrpc: "2.0", - method: "eth_sendTransaction", - params: [ - { - value: "0x10000000", - gas: "0xf4240", - from: accounts[0], - to: accounts[1], - nonce: "0xffffffff" // too big nonce - } - ], - id: 2 - }; - - provider.send(request, function(err, result) { - if (err) { - assert( - err.message.indexOf( - "the tx doesn't have the correct nonce. account has nonce of: 0 tx has nonce of: 4294967295" - ) >= 0 - ); - } - // We're supposed to get an error the first time. Let's assert we get the right one. - // Note that if using the Ganache as a provider, err will be non-null when there's - // an error. However, when using it as a server it won't be. In both cases, however, - // result.error should be set with the same error message. We'll check for that. - assert( - result.error.message.indexOf( - "the tx doesn't have the correct nonce. account has nonce of: 0 tx has nonce of: 4294967295" - ) >= 0 - ); - - delete request.params[0].nonce; - provider.send(request, done); - }); - }); - - it("recovers after bad nonce (too low)", function(done) { - var provider = web3.currentProvider; - - var request = { - jsonrpc: "2.0", - method: "eth_sendTransaction", - params: [ - { - value: "0x10000000", - gas: "0xf4240", - from: accounts[0], - to: accounts[1], - nonce: "0x0" // too low nonce - } - ], - id: 2 - }; - - provider.send(request, function(err, result) { - if (err) { - assert( - /the tx doesn't have the correct nonce. account has nonce of: 1 tx has nonce of: 0/.test(err.message), - `Expected incorrect nonce error, got '${err.message}', instead.` - ); - } - // We're supposed to get an error the first time. Let's assert we get the right one. - // Note that if using the Ganache as a provider, err will be non-null when there's - // an error. However, when using it as a server it won't be. In both cases, however, - // result.error should be set with the same error message. We'll check for that. - assert( - /the tx doesn't have the correct nonce. account has nonce of: 1 tx has nonce of: 0/.test( - result.error.message - ), - `Expected incorrect nonce error, got '${result.error.message}', instead.` - ); - - delete request.params[0].nonce; - provider.send(request, done); - }); - }); - - it("recovers after bad balance", function(done) { - web3.eth.getBalance(accounts[0], function(_, balance) { - var provider = web3.currentProvider; - - var request = { - jsonrpc: "2.0", - method: "eth_sendTransaction", - params: [ - { - value: "0x1000000000000000000000000000", - gas: "0xf4240", - from: accounts[0], - to: accounts[1] - } - ], - id: 2 - }; - - provider.send(request, function(err, result) { - if (err) { - assert.deepEqual( - err.message, - regex( - /sender doesn't have enough funds to send tx. The upfront cost is: \d+ and the sender's account only has: \d+/ - ), - `Unexpected error message. Got ${err.message}.` - ); - } - // We're supposed to get an error the first time. Let's assert we get the right one. - // Note that if using the Ganache as a provider, err will be non-null when there's - // an error. However, when using it as a server it won't be. In both cases, however, - // result.error should be set with the same error message. We'll check for that. - assert.deepEqual( - result.error.message, - regex( - /sender doesn't have enough funds to send tx. The upfront cost is: \d+ and the sender's account only has: \d+/ - ), - `Unexpected error message. Got ${result.error.message}.` - ); - - request.params[0].value = "0x5"; - provider.send(request, done); - }); - }); - }); - }); -}; - -describe("Provider:", function() { - var web3 = new Web3(); - web3.setProvider(Ganache.provider({})); - tests(web3); -}); - -describe("Server:", function(done) { - var web3 = new Web3(); - var port = 12345; - var server; - - before("Initialize Ganache server", function(done) { - server = Ganache.server({}); - server.listen(port, function() { - web3.setProvider(new Web3.providers.HttpProvider("http://localhost:" + port)); - done(); - }); - }); - - after("Shutdown server", function(done) { - server.close(done); - }); - - tests(web3); -}); diff --git a/test/local/block-tags.js b/test/local/block-tags.js deleted file mode 100644 index 3edd641157..0000000000 --- a/test/local/block-tags.js +++ /dev/null @@ -1,113 +0,0 @@ -const assert = require("assert"); -const initializeTestProvider = require("../helpers/web3/initializeTestProvider"); -const compile = require("../helpers/contract/singleFileCompile"); - -describe("Block Tags", function() { - let context; - const initialState = {}; - - before("Setting up web3", async function() { - this.timeout(10000); - - const options = { - mnemonic: "candy maple velvet cake sugar cream honey rich smooth crumble sweet treat", - time: new Date(0) // Testing features that rely on determinate conditions - }; - - context = await initializeTestProvider(options); - }); - - before("Stop automatic miner", async function() { - await context.send("miner_stop"); - }); - - before("Get initial balance, nonce and block number", async function() { - const { accounts, web3 } = context; - - const results = [ - web3.eth.getBalance(accounts[0]), - web3.eth.getTransactionCount(accounts[0]), - web3.eth.getBlockNumber() - ]; - - const [balance, nonce, blockNumber] = await Promise.all(results); - - Object.assign(initialState, { - balance, - blockNumber, - nonce - }); - }); - - before("Make a transaction that changes the balance, code and nonce", async function() { - const { accounts, web3 } = context; - const { result } = compile("./test/contracts/examples/", "Example"); - const contractPromise = web3.eth.sendTransaction({ - from: accounts[0], - data: "0x" + result.contracts["Example.sol"].Example.evm.bytecode.object, - gas: 3141592 - }); - contractPromise.on("transactionHash", () => { - context.send("evm_mine", 1); - }); - const { contractAddress } = await contractPromise; - - initialState.contractAddress = contractAddress; - }); - - it("should return the initial nonce at the previous block number", async function() { - const { accounts, web3 } = context; - const { blockNumber, nonce } = initialState; - let testNonce = await web3.eth.getTransactionCount(accounts[0], blockNumber); - assert.strictEqual(testNonce, nonce); - - // Check that the nonce incremented with the block number, just to be sure. - testNonce = await web3.eth.getTransactionCount(accounts[0], blockNumber + 1); - assert.strictEqual(testNonce, nonce + 1); - }); - - it("should return the initial balance at the previous block number", async function() { - const { accounts, web3 } = context; - const { balance, blockNumber } = initialState; - let testBalance = await web3.eth.getBalance(accounts[0], blockNumber); - assert.strictEqual(testBalance, balance); - - // Check that the balance incremented with the block number, just to be sure. - testBalance = await web3.eth.getBalance(accounts[0], blockNumber + 1); - const initialBalanceInEther = parseFloat(web3.utils.fromWei(balance, "ether")); - const balanceInEther = parseFloat(web3.utils.fromWei(testBalance, "ether")); - assert(balanceInEther < initialBalanceInEther); - }); - - it("should return the no code at the previous block number", async function() { - const { web3 } = context; - const { contractAddress, blockNumber } = initialState; - - let code = await web3.eth.getCode(contractAddress, blockNumber); - assert.strictEqual(code, "0x"); - - // Check that the code incremented with the block number, just to be sure. - code = await web3.eth.getCode(contractAddress, blockNumber + 1); - assert.notStrictEqual(code, "0x"); - assert(code.length > 20); // Just because we don't know the actual code we're supposed to get back - }); - - it("should produce correct tx and receipt root when the block contains 1 (or more) tx's", async function() { - const { web3 } = context; - const { blockNumber } = initialState; - - const block = await web3.eth.getBlock(blockNumber + 1, false); - assert.strictEqual(block.transactions.length, 1, "should have one tx in the block."); - assert.notStrictEqual(block.transactionsRoot, block.receiptsRoot, "Trie roots should not be equal."); - assert.strictEqual( - block.transactionsRoot, - "0x1d790d78fb45a013f23735cdb280258bd773392ea52913c939ed48eb916362db", - "Should produce correct transactionsRoot" - ); - assert.strictEqual( - block.receiptsRoot, - "0x9b620919250d3ae8b3096dcda9152491c92f98ca0e7a8cfc756a46a88e49156a", - "Should produce correct receiptsRoot" - ); - }); -}); diff --git a/test/local/call.js b/test/local/call.js deleted file mode 100644 index 47e404e3e6..0000000000 --- a/test/local/call.js +++ /dev/null @@ -1,54 +0,0 @@ -const assert = require("assert"); -const bootstrap = require("../helpers/contract/bootstrap"); - -describe("eth_call", function() { - const contractRef = { - contractFiles: ["EstimateGas"], - contractSubdirectory: "gas" - }; - - it("should use the call gas limit if no call gas limit is specified in the call", async function() { - const context = await bootstrap(contractRef, { - callGasLimit: "0x6691b7" - }); - const { accounts, instance } = context; - - const name = "0x54696d"; // Byte code for "Tim" - const description = "0x4120677265617420677579"; // Byte code for "A great guy" - const value = 5; - - // this call uses more than the default transaction gas limit and will - // therefore fail if the block gas limit isn't used for calls - const status = await instance.methods.add(name, description, value).call({ from: accounts[0] }); - - assert.strictEqual(status, true); - }).timeout(4000); - - it("should use max call gas limit if no gas limit is specified in the provider or the call", async function() { - const contractRef = { - contractFiles: ["EstimateGas"], - contractSubdirectory: "gas" - }; - const context = await bootstrap(contractRef); - const { accounts, instance } = context; - - const name = "0x54696d"; // Byte code for "Tim" - const description = "0x4120677265617420677579"; // Byte code for "A great guy" - const value = 5; - - // this call uses more than the default transaction gas limit and will - // therefore fail if the maxUInt64 limit isn't used for calls - const status = await instance.methods.add(name, description, value).call({ from: accounts[0] }); - - assert.strictEqual(status, true); - }); - - it("should use the current block number via `eth_call`", async() => { - const context = await bootstrap(contractRef); - - const actualBlockNumber = await context.web3.eth.getBlockNumber(); - // should read the block number, too - const callBlockNumber = await context.instance.methods.currentBlock().call(); - assert.strictEqual(parseInt(callBlockNumber, 10), actualBlockNumber); - }); -}); diff --git a/test/local/call/undefined.js b/test/local/call/undefined.js deleted file mode 100644 index 7a70fc7bc2..0000000000 --- a/test/local/call/undefined.js +++ /dev/null @@ -1,88 +0,0 @@ -const assert = require("assert"); -const bootstrap = require("../../helpers/contract/bootstrap"); - -describe("Undefined", () => { - describe("Calls", () => { - let context; - - before("Setting up web3 and contract", async function() { - this.timeout(10000); - - const contractRef = { - contractFiles: ["Call"], - contractSubdirectory: "call" - }; - - const ganacheProviderOptions = { - vmErrorsOnRPCResponse: false - }; - - context = await bootstrap(contractRef, ganacheProviderOptions); - }); - - it("should return `0x` when eth_call fails (web3.eth call)", async() => { - const { instance, web3 } = context; - - // test raw JSON RPC value: - const result = await web3.eth.call({ - to: instance._address, - data: instance.methods.causeReturnValueOfUndefined()._method.signature - }); - assert.strictEqual(result, "0x"); - }); - - it("should throw due to returned value of `0x` when eth_call fails (compiled contract call)", async() => { - const { instance } = context; - await assert.rejects( - () => instance.methods.causeReturnValueOfUndefined().call(), - /Returned values aren't valid, did it run Out of Gas\?/, - "web3 should throw when receiving a return value of 0x" - ); - }); - - it("should return a value when contract and method exists at block (web3.eth.call)", async() => { - const { instance, web3 } = context; - - const params = { - to: instance._address, - data: instance.methods.theAnswerToLifeTheUniverseAndEverything()._method.signature - }; - // test raw JSON RPC value: - const result = await web3.eth.call(params, "latest"); - assert.strictEqual( - result, - "0x000000000000000000000000000000000000000000000000000000000000002a", - "it should return 42 (as hex)" - ); - }); - - it("should return a value when contract and method exists at block (compiled contract call)", async() => { - const { instance } = context; - const result = await instance.methods.theAnswerToLifeTheUniverseAndEverything().call(); - assert.strictEqual(result, "42"); - }); - - it("should return 0x when contract doesn't exist at block", async() => { - const { instance, web3 } = context; - - const params = { - to: instance._address, - data: instance.methods.theAnswerToLifeTheUniverseAndEverything()._method.signature - }; - const result = await web3.eth.call(params, "earliest"); - - assert.strictEqual(result, "0x"); - }); - - it("should return 0x when method doesn't exist at block", async() => { - const { instance, web3 } = context; - const params = { - to: instance._address, - data: "0x01234567" - }; - const result = await web3.eth.call(params, "latest"); - - assert.strictEqual(result, "0x"); - }); - }); -}); diff --git a/test/local/chainId.js b/test/local/chainId.js deleted file mode 100644 index 0fde0f4411..0000000000 --- a/test/local/chainId.js +++ /dev/null @@ -1,52 +0,0 @@ -const assert = require("assert"); -const initializeTestProvider = require("../helpers/web3/initializeTestProvider"); -const { compile } = require("../helpers/contract/compileAndDeploy"); - -describe("Chain Id option", function() { - const contract = {}; - - before("compile contract", async function() { - this.timeout(10000); - const contractSubdirectory = "chainId"; - const contractFilename = "ChainId"; - const subcontractFiles = []; - const { abi, bytecode } = await compile(contractFilename, subcontractFiles, contractSubdirectory, "istanbul"); - contract.abi = abi; - contract.bytecode = bytecode; - }); - - describe("Allow Unlimited Contract Size", function() { - let context; - - before("Setup provider to allow unlimited contract size", async function() { - const ganacheOptions = { - _chainId: 1, - _chainIdRpc: 1 - }; - - context = await initializeTestProvider(ganacheOptions); - }); - - before("deploy contract", async function() { - const chainIdContract = new context.web3.eth.Contract(contract.abi); - contract.deployed = await chainIdContract - .deploy({ - data: contract.bytecode - }) - .send({ - from: context.accounts[0], - gas: 3141592 - }); - }); - - it("chainid opcode should match options", async function() { - const chainId = await contract.deployed.methods.getChainId().call(); - assert.strictEqual(chainId, "1"); - }); - - it("chain id rpc should match options", async function() { - const chainId = await context.web3.eth.getChainId(); - assert.strictEqual(chainId, 1); - }); - }); -}); diff --git a/test/local/cors.js b/test/local/cors.js deleted file mode 100644 index c7b0e4891f..0000000000 --- a/test/local/cors.js +++ /dev/null @@ -1,131 +0,0 @@ -const assert = require("assert"); -const Ganache = require("../../index.js"); -const request = require("request"); -const pify = require("pify"); - -const customRequestHeader = "X-PINGOTHER"; - -function test(host, port) { - describe("CORS", () => { - it("should set response headers correctly in a preflight request", (done) => { - const req = request.options( - { - url: "http://" + host + ":" + port, - headers: { - "Access-Control-Request-Headers": customRequestHeader, - // delete isn't supported by ganache.server, but we want to test that - // we respond with the headers we do support (POST) - "Access-Control-Request-Method": "DELETE", - Origin: "https://localhost:3000" - } - }, - function(error, response) { - if (error) { - return done(error); - } - - const allowHeader = response.headers["access-control-allow-headers"]; - const methodHeader = response.headers["access-control-allow-methods"]; - const contentLengthHeader = response.headers["content-length"]; - const statusCode = response.statusCode; - - assert.strictEqual( - allowHeader, - customRequestHeader, - "Access-Control-Allow-Headers should be equals to Access-Control-Request-Headers" - ); - assert.strictEqual(methodHeader, "POST", "Access-Control-Allow-Methods should be equals to 'POST'"); - assert.strictEqual( - contentLengthHeader, - "0", - "Content-Length header should be equal to 0 for browser compatibility reasons" - ); - assert.strictEqual(statusCode, 204, "response.statusCode should be '204'"); - - done(); - } - ); - - req.destroy(); - }); - - it("should set response.Access-Control-Allow-Origin to equal request.Origin if request.Origin is set", (done) => { - const origin = "https://localhost:3000"; - const req = request.options( - { - url: "http://" + host + ":" + port, - headers: { - Origin: origin, - "Access-Control-Request-Method": "POST" - } - }, - function(error, response) { - if (error) { - return done(error); - } - - let accessControlAllowOrigin = ""; - const hasOwnProperty = Object.prototype.hasOwnProperty; - if (hasOwnProperty.call(response.headers, "access-control-allow-origin")) { - accessControlAllowOrigin = response.headers["access-control-allow-origin"]; - } - - assert.strictEqual( - accessControlAllowOrigin, - origin, - "response.Access-Control-Allow-Origin should equal request.Origin if request.Origin is set." - ); - - done(); - } - ); - - req.destroy(); - }); - - it("should set Access-Control-Allow-Credentials=true if the Origin is set.", (done) => { - const origin = "https://localhost:3000"; - const req = request.options( - { - url: "http://" + host + ":" + port, - headers: { - Origin: origin, - "Access-Control-Request-Method": "POST" - } - }, - function(error, response) { - if (error) { - return done(error); - } - - assert.strictEqual( - response.headers["access-control-allow-credentials"], - "true", - "response.Access-Control-Allow-Origin should equal request.Origin if request.Origin is set." - ); - - done(); - } - ); - - req.destroy(); - }); - }); -} - -describe("HTTP Server:", function() { - const host = "localhost"; - const port = 12345; - let server; - - before("Initialize Ganache server", async function() { - server = Ganache.server(); - await pify(server.listen)(port); - }); - - after("Shutdown server", async function() { - await pify(server.close)(); - }); - - test(host, port); -}); diff --git a/test/local/debug/debug.js b/test/local/debug/debug.js deleted file mode 100644 index 6eb1cada89..0000000000 --- a/test/local/debug/debug.js +++ /dev/null @@ -1,288 +0,0 @@ -const assert = require("assert"); -const bootstrap = require("../../helpers/contract/bootstrap"); -const { promisify } = require("util"); -var Ganache = require(process.env.TEST_BUILD - ? "../../../build/ganache.core." + process.env.TEST_BUILD + ".js" - : "../../../index.js"); - -// Thanks solc. At least this works! -// This removes solc's overzealous uncaughtException event handler. -process.removeAllListeners("uncaughtException"); - -function test(forked) { - let context; - const mnemonic = "sweet candy treat"; - const gas = 3141592; - let hashToTrace = null; - let multipleCallsHashToTrace = null; - const expectedValueBeforeTrace = "1234"; - const val = "26"; - const targetPort = 21345; - const forkedTargetUrl = "ws://localhost:" + targetPort; - let forkedTransactionHash; - let mainContext; - - const contractRef = { - contractFiles: ["DebugContract"], - contractSubdirectory: "debug" - }; - - // steps: - - /* - setValue(26) - this sets .value to 26 and otherValue to 31 (26 + 5) - then setValue(1234) - this sets .value to 1234 and otherValue to 1265 (31 + 1234) - then trace the first tx - we want to make sure the data set by the traced transaction: - 26 and 31. - and that it didn't modify the original data - */ - - if (forked) { - before("init forkedServer", async function() { - this.timeout(10000); - - const contractRef = { - contractFiles: ["DebugContract"], - contractSubdirectory: "debug" - }; - - const forkedServer = Ganache.server({ mnemonic }); - await promisify(forkedServer.listen)(targetPort); - mainContext = await bootstrap(contractRef, { - provider: forkedServer.provider, - mnemonic - }); - mainContext.server = forkedServer; - }); - before("set up transaction that should be traced", async() => { - const { accounts, instance } = mainContext; - const options = { from: accounts[0], gas }; - - const result = await instance.methods.setValue(val).send(options); - - forkedTransactionHash = result.transactionHash; - }); - after("shutdown forkedServer", () => { - mainContext.server.close(); - }); - } - - before("set up web3 and contract", async function() { - this.timeout(10000); - // forked = false; - const options = forked - ? { - fork: forkedTargetUrl.replace("ws", "http"), - unlocked_accounts: mainContext.accounts - } - : { mnemonic }; - context = await bootstrap(contractRef, options); - }); - - describe("Trace a successful transaction", function() { - let options; - let originalStoredBlockNumber; - let latestStoredBlockNumber; - let latestBlockNumber; - before("set up transaction that should be traced", async() => { - const { accounts, instance } = context; - options = { from: accounts[0], gas }; - const tx = await instance.methods.setValue(val).send(options); - - // set hashToTrace to the tx we made, so we know preconditions are correctly set - hashToTrace = tx.transactionHash; - }); - - it("sets the blockNumber in storage", async() => { - const { instance, web3 } = context; - // check the value is what we expect it to be: 26 - originalStoredBlockNumber = await instance.methods.currentBlock().call(options); - const blockNumber = await web3.eth.getBlockNumber(); - assert.strictEqual(parseInt(originalStoredBlockNumber, 10), blockNumber); - }); - - it("sets the value to 26", async() => { - const { instance } = context; - // check the value is what we expect it to be: 26 - const value = await instance.methods.value().call(options); - assert.strictEqual(value, val); - }); - - it("changes state of contract to ensure trace doesn't overwrite data", async() => { - const { accounts, instance, web3 } = context; - options = { from: accounts[0], gas }; - await instance.methods.setValue(expectedValueBeforeTrace).send(options); - latestStoredBlockNumber = await instance.methods.currentBlock().call(options); - - latestBlockNumber = await web3.eth.getBlockNumber(); - - // check the value is what we expect it to be: 1234 - const value = await instance.methods.value().call(options); - assert.strictEqual(value, expectedValueBeforeTrace); - }); - - it("should trace a successful transaction without changing state", async function() { - // We want to trace the transaction that sets the value to 26 - const { accounts, instance, send, web3 } = context; - - const response = await send("debug_traceTransaction", hashToTrace, []); - - if (response.error) { - assert.fail(response.error); - } - - const structLogs = response.result.structLogs; - - // To at least assert SOMETHING, let's assert the last opcode - assert(structLogs.length > 0); - - for (const op of structLogs) { - if (op.stack.length > 0) { - // check formatting of stack - it was broken when updating to ethereumjs-vm v2.3.3 - assert.strictEqual(op.stack[0].length, 64); - assert.notStrictEqual(op.stack[0].substr(0, 2), "0x"); - break; - } - } - - const lastop = structLogs[structLogs.length - 1]; - - assert.strictEqual(lastop.op, "STOP"); - assert.strictEqual(lastop.gasCost, 0); - assert.strictEqual(lastop.pc, 235); - assert.strictEqual( - lastop.storage["0000000000000000000000000000000000000000000000000000000000000000"], - "000000000000000000000000000000000000000000000000000000000000001a" - ); - assert.strictEqual( - lastop.storage["0000000000000000000000000000000000000000000000000000000000000001"], - "000000000000000000000000000000000000000000000000000000000000001f" - ); - assert.strictEqual( - lastop.storage["0000000000000000000000000000000000000000000000000000000000000002"], - originalStoredBlockNumber.padStart(64, "0") - ); - console.log("--------------------------------------------------"); - const value = await instance.methods.value().call({ from: accounts[0], gas }); - assert.strictEqual(value, expectedValueBeforeTrace); - - const otherValue = await instance.methods.otherValue().call({ from: accounts[0], gas }); - assert.strictEqual(otherValue, "1265"); - - // stored block number should not have changed: - const storedBlockNumber = await instance.methods.currentBlock().call({ from: accounts[0], gas }); - assert.strictEqual(storedBlockNumber, latestStoredBlockNumber); - - // block number should not have incremented because of `debug_traceTransaction` - const currentBlockNumber = await web3.eth.getBlockNumber(); - assert.strictEqual(currentBlockNumber, latestBlockNumber); - }); - }); - - if (forked) { - describe("Trace a transaction on the main chain through the forked chain", function() { - before("Increment nonce on original chain", async() => { - // we need to make sure we trace the transaction at the correct block number - // with the right state root. By incrementing the nonce we can ensure - // we don't try to read off the main chain at after the fork. - const { send, accounts } = mainContext; - - // increment the account's nonce on the main chain by sending a transaction - await send("eth_sendTransaction", { - from: accounts[0], - to: accounts[1], - value: 100, - gas - }); - }); - - it("traces it", async() => { - const result = await context.send("debug_traceTransaction", forkedTransactionHash, []); - assert(result, "Result should be defined"); - }); - }); - } - - describe("Trace a successful transaction with multiple calls", function() { - let options; - before("set up transaction with multiple calls to the same contract to be traced", async() => { - const { accounts, instance } = context; - options = { from: accounts[0], gas }; - - // from previous tests, otherValue should be 26 + 1234 - const ov = instance.methods.otherValue(); - const c = ov.call(options); - const otherValue = await c; - assert.strictEqual(otherValue, "1265"); - - const tx = await instance.methods.callSetValueTwice().send(options); - multipleCallsHashToTrace = tx.transactionHash; - - // we add 1 + 2 to otherValue, so now it should be 1268 - const updatedValue = await instance.methods.otherValue().call(options); - assert.strictEqual(updatedValue, "1268"); - }); - - it("should trace a transaction with multiple calls to the same contract", function(done) { - const { web3 } = context; - const provider = web3.currentProvider; - const arrayOfStorageKeyValues = []; - - provider.send( - { - jsonrpc: "2.0", - method: "debug_traceTransaction", - params: [multipleCallsHashToTrace, []], - id: new Date().getTime() - }, - function(_, result) { - for (var i = 0; i < result.result.structLogs.length; i++) { - const op = result.result.structLogs[i]; - const nextOp = result.result.structLogs[i + 1]; - if (op.op === "SSTORE") { - // we want the nextOp because the storage changes doesn't take affect until after the SSTORE opcode - arrayOfStorageKeyValues.push(nextOp.storage); - } - } - - // ensure the call to setValue with 1 was successfully stored for value - assert.strictEqual( - arrayOfStorageKeyValues[0]["0000000000000000000000000000000000000000000000000000000000000000"], - "0000000000000000000000000000000000000000000000000000000000000001" - ); - // ensure the call to setValue with 1 was successfully stored for otherValue - assert.strictEqual( - arrayOfStorageKeyValues[1]["0000000000000000000000000000000000000000000000000000000000000001"], - "00000000000000000000000000000000000000000000000000000000000004f2" - ); - - // ensure the call to setValue with 2 was successfully stored for value - assert.strictEqual( - arrayOfStorageKeyValues[3]["0000000000000000000000000000000000000000000000000000000000000000"], - "0000000000000000000000000000000000000000000000000000000000000002" - ); - // ensure the call to setValue with 2 was successfully stored for otherValue - assert.strictEqual( - arrayOfStorageKeyValues[4]["0000000000000000000000000000000000000000000000000000000000000001"], - "00000000000000000000000000000000000000000000000000000000000004f4" - ); - - done(); - } - ); - }); - }); -} - -describe("Debug", function() { - describe("Direct", function() { - test(); - }); - - describe("Forked", function() { - test(true); - }); -}); diff --git a/test/local/debug/debugStorage.js b/test/local/debug/debugStorage.js deleted file mode 100644 index 7f497b6db8..0000000000 --- a/test/local/debug/debugStorage.js +++ /dev/null @@ -1,104 +0,0 @@ -const assert = require("assert"); -const bootstrap = require("../../helpers/contract/bootstrap"); - -// Thanks solc. At least this works! -// This removes solc's overzealous uncaughtException event handler. -process.removeAllListeners("uncaughtException"); - -describe("Debug Storage", function() { - let options = {}; - let context; - - const gas = 3141592; - - let hashToTrace = null; - const expectedValueBeforeTrace = "5"; - - before("set up web3 and contract", async() => { - this.timeout(10000); - const contractRef = { - contractFiles: ["DebugContractStorage", "DebugContract"], - contractSubdirectory: "debug" - }; - context = await bootstrap(contractRef); - }); - - before("set up transaction that should be traced", async() => { - const { accounts, instance } = context; - options = { - from: accounts[0], - gas - }; - - // check initial value and initial other value to ensure we know what we are starting with - const initialValue = await instance.methods.getValue().call(options); - const initialOtherValue = await instance.methods.getOtherValue().call(options); - assert.strictEqual(initialValue, expectedValueBeforeTrace); - assert.strictEqual(initialOtherValue, expectedValueBeforeTrace); - - const tx = await instance.methods.set().send(options); - hashToTrace = tx.transactionHash; - - // check value and other value to see if it updated - const updatedValue = await instance.methods.getValue().call(options); - const updatedOtherValue = await instance.methods.getOtherValue().call(options); - assert.strictEqual(updatedValue, "2"); - // otherValue should be the total of the initial value (5) plus 3 from calling set() - assert.strictEqual(updatedOtherValue, "8"); - }); - - it("should successfully trace a transaction with multiple calls to an instantiated contract", async() => { - const { web3 } = context; - const provider = web3.currentProvider; - - const arrayOfStorageKeyValues = []; - - provider.send( - { - jsonrpc: "2.0", - method: "debug_traceTransaction", - params: [hashToTrace, []], - id: new Date().getTime() - }, - function(_, result) { - for (var i = 0; i < result.result.structLogs.length; i++) { - const op = result.result.structLogs[i]; - const nextOp = result.result.structLogs[i + 1]; - if (op.op === "SSTORE") { - // we want the nextOp because the storage changes doesn't take affect until after the SSTORE opcode - arrayOfStorageKeyValues.push(nextOp.storage); - } - } - - // ensure the call to setValue with 1 was successfully stored for value - assert.strictEqual( - arrayOfStorageKeyValues[0]["0000000000000000000000000000000000000000000000000000000000000000"], - "0000000000000000000000000000000000000000000000000000000000000001" - ); - - // ensure the call to setValue with 1 was successfully stored for otherValue, making it 6 - assert.strictEqual( - arrayOfStorageKeyValues[1]["0000000000000000000000000000000000000000000000000000000000000001"], - "0000000000000000000000000000000000000000000000000000000000000006" - ); - - assert.strictEqual( - arrayOfStorageKeyValues[1]["0000000000000000000000000000000000000000000000000000000000000001"], - "0000000000000000000000000000000000000000000000000000000000000006" - ); - - // ensure the call to setValue with 2 was successfully stored for value - assert.strictEqual( - arrayOfStorageKeyValues[3]["0000000000000000000000000000000000000000000000000000000000000000"], - "0000000000000000000000000000000000000000000000000000000000000002" - ); - - // ensure the call to setValue with 2 was successfully stored for otherValue, making it 8 - assert.strictEqual( - arrayOfStorageKeyValues[4]["0000000000000000000000000000000000000000000000000000000000000001"], - "0000000000000000000000000000000000000000000000000000000000000008" - ); - } - ); - }); -}); diff --git a/test/local/enable_constantinople_hardfork.js b/test/local/enable_constantinople_hardfork.js deleted file mode 100644 index 33479dfb70..0000000000 --- a/test/local/enable_constantinople_hardfork.js +++ /dev/null @@ -1,65 +0,0 @@ -const assert = require("assert"); -const bootstrap = require("../helpers/contract/bootstrap"); - -describe("Constantinople Hardfork", function() { - const mnemonic = "candy maple cake sugar pudding cream honey rich smooth crumble sweet treat"; - - describe("Disallow Constantinople features", function() { - let context; - - before("Setting up web3 and contract", async function() { - this.timeout(10000); - - const contractRef = { - contractFiles: ["ConstantinopleContract"], - contractSubdirectory: "constantinople" - }; - - const ganacheProviderOptions = { - mnemonic, - gasLimit: 20000000, - hardfork: "byzantium" - }; - - context = await bootstrap(contractRef, ganacheProviderOptions); - }); - - it("should fail execution", async function() { - const { instance } = context; - - await assert.rejects( - instance.methods.test(2).call(), - /VM Exception while processing transaction: invalid opcode/, - "Call did not fail execution like it was supposed to" - ); - }); - }); - - describe("Allow Constantinople features", function() { - let context; - - before("Setting up web3 and contract", async function() { - this.timeout(10000); - - const contractRef = { - contractFiles: ["ConstantinopleContract"], - contractSubdirectory: "constantinople" - }; - - const ganacheProviderOptions = { - gasLimit: 20000000, - hardfork: "constantinople", - mnemonic - }; - - context = await bootstrap(contractRef, ganacheProviderOptions); - }); - - it("should succeed execution", async function() { - const { instance } = context; - - const result = await instance.methods.test(2).call(); - assert(result, "successful execution"); - }); - }); -}); diff --git a/test/local/ethereum.js b/test/local/ethereum.js deleted file mode 100644 index 67c4943848..0000000000 --- a/test/local/ethereum.js +++ /dev/null @@ -1,11 +0,0 @@ -const assert = require("assert"); -const initializeTestProvider = require("../helpers/web3/initializeTestProvider"); - -describe("Ethereum", function() { - it("should get ethereum version (eth_protocolVersion)", async function() { - const { web3 } = await initializeTestProvider(); - - const result = await web3.eth.getProtocolVersion(); - assert.strictEqual(result, "63", "Network Version should be 63"); - }); -}); diff --git a/test/local/ethers.js b/test/local/ethers.js deleted file mode 100644 index a8474339a9..0000000000 --- a/test/local/ethers.js +++ /dev/null @@ -1,66 +0,0 @@ -const assert = require("assert"); -const { BN } = require("ethereumjs-util"); -const ethers = require("ethers"); -const initializeTestProvider = require("../helpers/web3/initializeTestProvider"); - -describe("ethers", async() => { - let ethersProvider, wallet, gasPrice, value; - const secretKey = "46".repeat(32); - - before("Setting up ethers wallet provider", async function() { - this.timeout(10000); - const ganacheOptions = { - accounts: [ - { - secretKey: `0x${secretKey}`, - balance: `0x${new BN("1000000000000000000000").toString("hex")}` - } - ] - }; - - const { provider } = await initializeTestProvider(ganacheOptions); - - ethersProvider = new ethers.providers.Web3Provider(provider); - const privateKey = Buffer.from(secretKey, "hex"); - wallet = new ethers.Wallet(privateKey); - gasPrice = 20 * 10 ** 9; // 20000000000 - value = `0x${new BN(10).pow(new BN(18)).toString("hex")}`; - }); - - it("ether.js transaction hash matches ganache transaction hash for chainId 1", async() => { - // This tx mostly matches EIP-155 example except for the nonce - // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md - const transaction = { - nonce: 0, - to: `0x${"35".repeat(20)}`, - gasPrice, - gasLimit: 21000, - value: value, - data: "", - chainId: 1 // EIP 155 chainId - mainnet: 1, ropsten: 3 - }; - const signedTransaction = await wallet.sign(transaction); - const ethersTxHash = ethers.utils.keccak256(signedTransaction); - - const { hash } = await ethersProvider.sendTransaction(signedTransaction); - assert.deepStrictEqual(hash, ethersTxHash, "Transaction hash doesn't match etherjs signed transaction hash"); - }); - - it("ether.js transaction hash matches ganache transaction hash for auto chainId", async() => { - // This tx mostly matches EIP-155 example except for the nonce and chainId - // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md - const transaction = { - nonce: 1, - to: `0x${"35".repeat(20)}`, - gasPrice, - gasLimit: 21000, - value: value, - data: "" - }; - const signedTransaction = await wallet.sign(transaction); - const ethersTxHash = ethers.utils.keccak256(signedTransaction); - - const { hash } = await ethersProvider.sendTransaction(signedTransaction); - assert.deepStrictEqual(hash, ethersTxHash, "Transaction hash doesn't match etherjs signed transaction hash"); - }); -}); diff --git a/test/local/events.js b/test/local/events.js deleted file mode 100644 index a7e7df133f..0000000000 --- a/test/local/events.js +++ /dev/null @@ -1,369 +0,0 @@ -var Web3 = require("web3"); -var Web3WsProvider = require("web3-providers-ws"); -var Ganache = require(process.env.TEST_BUILD - ? "../../build/ganache.core." + process.env.TEST_BUILD + ".js" - : "../../index.js"); -var assert = require("assert"); -const compile = require("../helpers/contract/singleFileCompile"); - -var tests = function(web3, EventTest) { - var accounts; - var instance; - - describe("events", function() { - before(function(done) { - web3.eth.getAccounts(function(err, accs) { - if (err) { - return done(err); - } - accounts = accs; - done(); - }); - }); - - before(function(done) { - this.timeout(10000); - var { result } = compile("./test/contracts/events/", "EventTest"); - - if (result.errors != null) { - done(result.errors[0]); - return; - } - - var abi = result.contracts["EventTest.sol"].EventTest.abi; - EventTest = new web3.eth.Contract(abi); - EventTest._data = "0x" + result.contracts["EventTest.sol"].EventTest.evm.bytecode.object; - done(); - }); - - before(function() { - return EventTest.deploy({ data: EventTest._data }) - .send({ from: accounts[0], gas: 3141592 }) - .then((contract) => { - instance = contract; - - // TODO: ugly workaround - not sure why this is necessary. - if (!instance._requestManager.provider) { - instance._requestManager.setProvider(web3.eth._provider); - } - }); - }); - - it("should handle events properly via the data event handler", function(done) { - var expectedValue = "1"; - - var event = instance.events.ExampleEvent({ filter: { first: expectedValue } }); - - var listener = function(result) { - assert.strictEqual(result.returnValues.first, expectedValue); - done(); - }; - - event.once("data", listener); - event.once("error", (err) => done(err)); - - instance.methods.triggerEvent(1, 6).send({ from: accounts[0], gas: 3141592 }); - }); - - // NOTE! This test relies on the events triggered in the tests above. - it("grabs events in the past", function(done) { - var expectedValue = "2"; - - var event = instance.events.ExampleEvent({ filter: { first: expectedValue }, fromBlock: 0 }); - - var listener = function(result) { - assert.strictEqual(result.returnValues.first, expectedValue); - done(); - }; - - event.once("data", listener); - - instance.methods.triggerEvent(2, 6).send({ from: accounts[0], gas: 3141592 }); - }); - - // NOTE! This test relies on the events triggered in the tests above. - it("accepts an array of topics as a filter", function(done) { - var expectedValueA = 3; - var expectedValueB = 4; - - var event = instance.events.ExampleEvent({ filter: { first: [expectedValueA, expectedValueB] }, fromBlock: 0 }); - - var waitingFor = {}; - waitingFor[expectedValueA] = true; - waitingFor[expectedValueB] = true; - - var listener = function(result) { - assert(Object.prototype.hasOwnProperty.call(waitingFor, result.returnValues.first)); - delete waitingFor[result.returnValues.first]; - - if (Object.keys(waitingFor).length === 0) { - event.removeAllListeners(); - done(); - } - }; - - event.on("data", listener); - - event.once("error", (err) => { - event.removeAllListeners(); - done(err); - }); - - instance.methods - .triggerEvent(expectedValueA, 6) - .send({ from: accounts[0], gas: 3141592 }) - .then((result) => { - return instance.methods.triggerEvent(expectedValueB, 7).send({ from: accounts[0], gas: 3141592 }); - }); - }); - - it("only returns logs for the expected address", function(done) { - var expectedValue = "1"; - - EventTest.deploy({ data: EventTest._data }) - .send({ from: accounts[0], gas: 3141592 }) - .then((newInstance) => { - // TODO: ugly workaround - not sure why this is necessary. - if (!newInstance._requestManager.provider) { - newInstance._requestManager.setProvider(web3.eth._provider); - } - - var event = newInstance.events.ExampleEvent({ filter: { first: expectedValue }, fromBlock: 0 }); - - event.on("data", function(result) { - assert(result.returnValues.first === expectedValue); - // event.removeAllListeners() - done(); - }); - - instance.methods - .triggerEvent(5, 6) - .send({ from: accounts[0], gas: 3141592 }) - .then(() => { - newInstance.methods.triggerEvent(expectedValue, 6).send({ from: accounts[0], gas: 3141592 }); - }); - }); - }); - - // NOTE! This test relies on the events triggered in the tests above. - it("should return logs with correctly formatted logIndex and transactionIndex", function(done) { - var provider = web3.currentProvider; - - provider.send( - { - jsonrpc: "2.0", - method: "eth_getLogs", - params: [ - { - fromBlock: "0x0", - toBlock: "latest", - topics: [ - "0xc54307031d9aa93e0568c363be84a9400dce343fef6a2851d55662a6af1a29da", - "0x0000000000000000000000000000000000000000000000000000000000000001", - "0x0000000000000000000000000000000000000000000000000000000000000006" - ] - } - ], - id: new Date().getTime() - }, - function(err, result) { - if (err) { - return done(err); - } - const logIndex = result.result[0].logIndex; - const transactionIndex = result.result[0].transactionIndex; - assert.strictEqual(logIndex, "0x0"); - assert.strictEqual(transactionIndex, "0x0"); - done(); - } - ); - }); - - // NOTE! This test relies on the events triggered in the tests above. - it("should return logs using blockHash", function(done) { - var provider = web3.currentProvider; - - provider.send( - { - jsonrpc: "2.0", - method: "eth_getBlockByNumber", - params: [ - "latest", - false - ], - id: new Date().getTime() - }, - function(err, result) { - if (err) { - return done(err); - } - - const hash = result.result.hash; - - provider.send( - { - jsonrpc: "2.0", - method: "eth_getLogs", - params: [ - { - blockHash: hash, - topics: [ - "0xc54307031d9aa93e0568c363be84a9400dce343fef6a2851d55662a6af1a29da", - "0x0000000000000000000000000000000000000000000000000000000000000001", - "0x0000000000000000000000000000000000000000000000000000000000000006" - ] - } - ], - id: new Date().getTime() - }, - function(err, result) { - if (err) { - return done(err); - } - const logIndex = result.result[0].logIndex; - const transactionIndex = result.result[0].transactionIndex; - assert.strictEqual(logIndex, "0x0"); - assert.strictEqual(transactionIndex, "0x0"); - done(); - } - ); - } - ); - }); - - it("always returns a change for every new block subscription when instamining", function(done) { - var provider = web3.currentProvider; - - provider.send( - { - jsonrpc: "2.0", - method: "eth_subscribe", - params: ["newHeads"], - id: new Date().getTime() - }, - function(err, result) { - if (err) { - return done(err); - } - - const listener = function(err, result) { - if (result === undefined) { - // If there's only one argument, it's the result, not an error - result = err; - } else if (err) { - return done(err); - } - const firstChanges = result.params.result.hash; - assert.strictEqual(firstChanges.length, 66); // Ensure we have a hash - provider.removeAllListeners("data"); - done(); - }; - - // can't use `once` here because Web3WsProvider only has `on` :-( - provider.on("data", listener); - - web3.currentProvider.send( - { - jsonrpc: "2.0", - method: "evm_mine", - id: new Date().getTime() - }, - function(err) { - if (err) { - done(err); - } - } - ); - } - ); - }); - - // NOTE! This test relies on the events triggered in the tests above. - it("ensures topics are respected in past events, using `event.get()` (exclusive)", function(done) { - var unexpectedValue = 1337; - var event = instance.events.ExampleEvent({ filter: { first: unexpectedValue }, fromBlock: 0 }); - - // There should be no logs because we provided a different number. - var listener = function(result) { - assert.fail("Event should not have fired"); - }; - - event.once("data", listener); - - instance.methods - .triggerEvent(6, 6) - .send({ from: accounts[0], gas: 3141592 }) - .then(() => { - // have to finish somehow... - setTimeout(() => { - event.removeAllListeners(); - done(); - }, 250); - }); - }); - - it("will not fire if logs are requested when fromBlock doesn't exist", function(done) { - var event = instance.events.ExampleEvent({ fromBlock: 100000 }); - - // fromBlock doesn't exist, hence no logs - var listener = function(result) { - assert.fail("Event should not have fired"); - }; - - event.on("data", listener); - - instance.methods - .triggerEvent(8, 6) - .send({ from: accounts[0], gas: 3141592 }) - .then(() => { - // have to finish somehow... - setTimeout(() => { - event.removeAllListeners(); - done(); - }, 250); - }); - }); - }); -}; - -var logger = { - log: function(message) { - // console.log(message); - } -}; - -describe("Provider:", function() { - var web3 = new Web3(); - web3.setProvider( - Ganache.provider({ - logger: logger - }) - ); - tests(web3); -}); - -describe("Server:", function(done) { - var web3 = new Web3(); - var port = 12345; - var server; - - before("Initialize Ganache server", function(done) { - server = Ganache.server({ - logger: logger, - ws: true - }); - server.listen(port, function() { - web3.setProvider(new Web3WsProvider("ws://localhost:" + port)); - done(); - }); - }); - - tests(web3); - - after("Shutdown server", function(done) { - const provider = web3._provider; - web3.setProvider(); - provider.connection.close(); - server.close(done); - }); -}); diff --git a/test/local/forking/call.js b/test/local/forking/call.js deleted file mode 100644 index 67d2900ba2..0000000000 --- a/test/local/forking/call.js +++ /dev/null @@ -1,62 +0,0 @@ -const assert = require("assert"); -const bootstrap = require("../../helpers/contract/bootstrap"); -const initializeTestProvider = require("../../helpers/web3/initializeTestProvider"); - -describe("Forking eth_call", () => { - let forkedContext; - const logger = { - log: function(msg) {} - }; - - before("Set up forked provider with web3 instance and deploy a contract", async function() { - this.timeout(5000); - - const contractRef = { - contractFiles: ["Snapshot"], - contractSubdirectory: "forking" - }; - - const ganacheProviderOptions = { - logger, - seed: "main provider" - }; - - forkedContext = await bootstrap(contractRef, ganacheProviderOptions); - }); - - it("gets values at specified blocks on the original change", async function() { - const { - send, - accounts: [from], - abi, - web3: originalWeb3, - instance: { _address: contractAddress }, - provider: originalProvider - } = forkedContext; - - const originalContract = new originalWeb3.eth.Contract(abi, contractAddress); - - const txParams = { from }; - - await originalContract.methods.test().send(txParams); - const initialValue = await originalContract.methods.value().call(); - await send("evm_mine", null); - const { result: preForkBlockNumber } = await send("eth_blockNumber"); - - // Fork the "original" chain _now_ - const { web3 } = await initializeTestProvider({ - fork: originalProvider, - logger, - seed: "forked provider" - }); - - const instance = new web3.eth.Contract(abi, contractAddress); - - // get the value as it was before we forked - instance.defaultBlock = preForkBlockNumber; - // there is a bug possibly unrelated to this PR that prevents this from - // returning the wrong value (it crashes with a `pop of undefined` instead). - const finalValue = await instance.methods.value().call(); - assert.strictEqual(finalValue, initialValue); - }); -}); diff --git a/test/local/forking/caseSensitivity.js b/test/local/forking/caseSensitivity.js deleted file mode 100644 index aac90d2b25..0000000000 --- a/test/local/forking/caseSensitivity.js +++ /dev/null @@ -1,137 +0,0 @@ -const assert = require("assert"); -const bootstrap = require("../../helpers/contract/bootstrap"); -const generateSend = require("../../helpers/utils/rpc"); -const initializeTestProvider = require("../../helpers/web3/initializeTestProvider"); - -/** - * NOTE: Naming in these tests is a bit confusing. Here, the "main chain" - * is the main chain the tests interact with; and the "forked chain" is the - * chain that _was forked_. This is in contrast to general naming, where the - * main chain represents the main chain to be forked (like the Ethereum live - * network) and the fork chain being "the fork". - */ - -describe("Forking methods are Case Insensitive", () => { - let forkedContext; - let forkedAccounts; - let forkedBlockNumber; - let mainContext; - let mainAccounts; - let instance; - const logger = { - log: function(msg) {} - }; - - before("Set up forked provider with web3 instance and deploy a contract", async function() { - this.timeout(5000); - - const contractRef = { - contractFiles: ["Snapshot"], - contractSubdirectory: "forking" - }; - - const ganacheProviderOptions = { - logger, - seed: "main provider" - }; - - forkedContext = await bootstrap(contractRef, ganacheProviderOptions); - forkedAccounts = await forkedContext.web3.eth.getAccounts(); - forkedBlockNumber = await forkedContext.web3.eth.getBlockNumber(); - }); - - before("Set up main provider and web3 instance", async function() { - const { provider: forkedProvider } = forkedContext; - mainContext = await initializeTestProvider({ - fork: forkedProvider, - logger, - seed: "forked provider" - }); - const send = generateSend(mainContext.web3.currentProvider); - mainContext.send = async function() { - const result = await send(...arguments); - return result.result; - }; - mainAccounts = await mainContext.web3.eth.getAccounts(); - }); - - before("Make transaction to a forked account", async function() { - await mainContext.web3.eth.sendTransaction({ - from: mainAccounts[1], - to: forkedAccounts[1], - value: mainContext.web3.utils.toWei("1", "ether") - }); - }); - - before("Make a transaction to a forked contract that will modify storage", async function() { - const { instance: forkedInstance, abi } = forkedContext; - instance = new mainContext.web3.eth.Contract(abi, forkedInstance._address); - await instance.methods.test().send({ from: mainAccounts[0] }); - }); - - it("eth_getBalance", async function() { - const { web3, send } = mainContext; - - const addressLower = forkedAccounts[1].toLowerCase(); - const balanceBeforeForkLower = await send("eth_getBalance", addressLower, forkedBlockNumber); - const balanceNowLower = await send("eth_getBalance", addressLower, "latest"); - assert.strictEqual(balanceBeforeForkLower, web3.utils.toHex(web3.utils.toWei("100", "ether"))); - assert.strictEqual(balanceNowLower, web3.utils.toHex(web3.utils.toWei("101", "ether"))); - - const addressUpper = forkedAccounts[1].toUpperCase().replace(/^0X/, "0x"); - const balanceBeforeForkUpper = await send("eth_getBalance", addressUpper, forkedBlockNumber); - const balanceNowUpper = await send("eth_getBalance", addressUpper, "latest"); - assert.strictEqual(balanceBeforeForkUpper, web3.utils.toHex(web3.utils.toWei("100", "ether"))); - assert.strictEqual(balanceNowUpper, web3.utils.toHex(web3.utils.toWei("101", "ether"))); - - // ensure nothing got changed in these calls - const balanceNowLower2 = await send("eth_getBalance", addressLower, "latest"); - assert.strictEqual(balanceNowLower2, balanceNowLower); - }); - - it("eth_getCode", async function() { - const { send } = mainContext; - - const addressLower = forkedContext.instance._address.toLowerCase(); - const codeBeforeDeployLower = await send("eth_getCode", addressLower, "earliest"); - const codeBeforeForkLower = await send("eth_getCode", addressLower, forkedBlockNumber); - const codeNowLower = await send("eth_getCode", addressLower, "latest"); - assert.strictEqual(codeBeforeDeployLower, "0x"); - assert.strictEqual(codeBeforeForkLower.length > 2, true); - assert.strictEqual(codeNowLower.length > 2, true); - assert.strictEqual(codeBeforeForkLower, codeNowLower); - - const addressUpper = forkedContext.instance._address.toUpperCase().replace(/^0X/, "0x"); - const codeBeforeDeployUpper = await send("eth_getCode", addressUpper, "earliest"); - const codeBeforeForkUpper = await send("eth_getCode", addressUpper, forkedBlockNumber); - const codeNowUpper = await send("eth_getCode", addressUpper, "latest"); - assert.strictEqual(codeBeforeDeployUpper, "0x"); - assert.strictEqual(codeBeforeForkUpper.length > 2, true); - assert.strictEqual(codeNowUpper.length > 2, true); - assert.strictEqual(codeBeforeForkUpper, codeNowUpper); - - // ensure nothing got changed in these calls - const codeNowLower2 = await send("eth_getCode", addressLower, "latest"); - assert.strictEqual(codeNowLower2, codeNowLower); - }); - - it("eth_getStorageAt", async function() { - const { send } = mainContext; - - const addressLower = forkedContext.instance._address.toLowerCase(); - const valueBeforeForkLower = await send("eth_getStorageAt", addressLower, 0, forkedBlockNumber); - const valueNowLower = await send("eth_getStorageAt", addressLower, 0, "latest"); - assert.strictEqual(valueBeforeForkLower, "0x00"); - assert.strictEqual(valueNowLower, "0x01"); - - const addressUpper = forkedContext.instance._address.toUpperCase().replace(/^0X/, "0x"); - const valueBeforeForkUpper = await send("eth_getStorageAt", addressUpper, 0, forkedBlockNumber); - const valueNowUpper = await send("eth_getStorageAt", addressUpper, 0, "latest"); - assert.strictEqual(valueBeforeForkUpper, "0x00"); - assert.strictEqual(valueNowUpper, "0x01"); - - // ensure nothing got changed in these calls - const valueNowLower2 = await send("eth_getStorageAt", addressLower, 0, "latest"); - assert.strictEqual(valueNowLower2, valueNowLower); - }); -}); diff --git a/test/local/forking/debug.js b/test/local/forking/debug.js deleted file mode 100644 index b5feef0827..0000000000 --- a/test/local/forking/debug.js +++ /dev/null @@ -1,102 +0,0 @@ -const assert = require("assert"); -const bootstrap = require("../../helpers/contract/bootstrap"); -const generateSend = require("../../helpers/utils/rpc"); -const initializeTestProvider = require("../../helpers/web3/initializeTestProvider"); -const Common = require("ethereumjs-common").default; - -/** - * NOTE: Naming in these tests is a bit confusing. Here, the "main chain" - * is the main chain the tests interact with; and the "forked chain" is the - * chain that _was forked_. This is in contrast to general naming, where the - * main chain represents the main chain to be forked (like the Ethereum live - * network) and the fork chaing being "the fork". - */ - -describe("Forking Debugging", () => { - let forkedContext; - let mainContext; - let mainAccounts; - let common; - const logger = { - log: function(msg) {} - }; - - before("Set up forked provider with web3 instance and deploy a contract", async function() { - this.timeout(5000); - - const contractRef = { - contractFiles: ["Debug"], - contractSubdirectory: "forking" - }; - - const ganacheProviderOptions = { - logger, - seed: "main provider" - }; - - forkedContext = await bootstrap(contractRef, ganacheProviderOptions); - }); - - before("Set up main provider and web3 instance", async function() { - const { provider: forkedProvider } = forkedContext; - mainContext = await initializeTestProvider({ - fork: forkedProvider, - logger, - seed: "forked provider" - }); - - common = Common.forCustomChain("mainnet", { - name: "ganache" - }); - mainAccounts = await mainContext.web3.eth.getAccounts(); - }); - - it("successfully manages storage slot deletion", async() => { - const { instance: forkedInstance, abi } = forkedContext; - const { web3: mainWeb3 } = mainContext; - let value; - - const instance = new mainWeb3.eth.Contract(abi, forkedInstance._address); - - value = await instance.methods.value().call(); - assert.strictEqual(value, "1"); - - const tx = await instance.methods.test().send({ - from: mainAccounts[0] - }); - value = await instance.methods.value().call(); - assert.strictEqual(value, "2"); - - const send = generateSend(mainWeb3.currentProvider); - - const result = await send("debug_traceTransaction", tx.transactionHash, {}); - - const txStructLogs = result.result.structLogs; - const txMemory = txStructLogs[txStructLogs.length - 1].memory; - const txReturnValue = parseInt(txMemory[txMemory.length - 1], 16); - assert.strictEqual(txReturnValue, 2); - }); - - it("successfully manages storage slot creation gas consumption", async() => { - const { bytecode } = forkedContext; - const { web3: mainWeb3 } = mainContext; - - const deployedContract = await mainWeb3.eth.sendTransaction({ - from: mainAccounts[0], - data: bytecode, - gas: 3141592 - }); - - const send = generateSend(mainWeb3.currentProvider); - - const result = await send("debug_traceTransaction", deployedContract.transactionHash, {}); - - for (let i = 0; i < result.result.structLogs.length; i++) { - if (result.result.structLogs[i].op === "SSTORE") { - // ensure that every SSTORE in contract creation triggers slot creation - const gasCost = common.param("gasPrices", "sstoreInitGasEIP2200", "istanbul"); - assert.strictEqual(result.result.structLogs[i].gasCost, gasCost); - } - } - }); -}); diff --git a/test/local/forking/delete.js b/test/local/forking/delete.js deleted file mode 100644 index c33727d422..0000000000 --- a/test/local/forking/delete.js +++ /dev/null @@ -1,54 +0,0 @@ -const assert = require("assert"); -const bootstrap = require("../../helpers/contract/bootstrap"); -const initializeTestProvider = require("../../helpers/web3/initializeTestProvider"); - -/** - * NOTE: Naming in these tests is a bit confusing. Here, the "main chain" - * is the main chain the tests interact with; and the "forked chain" is the - * chain that _was forked_. This is in contrast to general naming, where the - * main chain represents the main chain to be forked (like the Ethereum live - * network) and the fork chaing being "the fork". - */ - -describe("Forking Deletion", () => { - let forkedContext; - let mainContext; - const logger = { - log: function(msg) {} - }; - - before("Set up forked provider with web3 instance and deploy a contract", async function() { - this.timeout(5000); - - const contractRef = { - contractFiles: ["StorageDelete"], - contractSubdirectory: "forking" - }; - - const ganacheProviderOptions = { - logger, - seed: "main provider" - }; - - forkedContext = await bootstrap(contractRef, ganacheProviderOptions); - }); - - before("Set up main provider and web3 instance", async function() { - const { provider: forkedProvider } = forkedContext; - mainContext = await initializeTestProvider({ - fork: forkedProvider, - logger, - seed: "forked provider" - }); - }); - - it("successfully manages storage slot deletion", async() => { - const { instance: forkedInstance, abi } = forkedContext; - const { web3: mainWeb3 } = mainContext; - - const instance = new mainWeb3.eth.Contract(abi, forkedInstance._address); - - assert.ok(await instance.methods.test().call()); - assert.ok(await instance.methods.test().call()); - }).timeout(5000); -}); diff --git a/test/local/forking/deployAfterFork.js b/test/local/forking/deployAfterFork.js deleted file mode 100644 index 3b34e96a3b..0000000000 --- a/test/local/forking/deployAfterFork.js +++ /dev/null @@ -1,141 +0,0 @@ -var Web3 = require("web3"); -var Web3WsProvider = require("web3-providers-ws"); -var assert = require("assert"); -var Ganache = require(process.env.TEST_BUILD - ? "../../../build/ganache.core." + process.env.TEST_BUILD + ".js" - : "../../../index.js"); -const compile = require("../../helpers/contract/singleFileCompile"); -var logger = { - log: function(msg) { - /* console.log(msg) */ - } -}; - -/** - * NOTE: Naming in these tests is a bit confusing. Here, the "main chain" - * is the main chain the tests interact with; and the "forked chain" is the - * chain that _was forked_. This is in contrast to general naming, where the - * main chain represents the main chain to be forked (like the Ethereum live - * network) and the fork chaing being "the fork". - */ - -describe("Contract Deployed on Main Chain After Fork", function() { - var contract; - var contractAddress; - var forkedServer; - var mainAccounts; - - var forkedWeb3 = new Web3(); - var mainWeb3 = new Web3(); - - var forkedTargetUrl = "ws://localhost:21345"; - - before("set up test data", function() { - this.timeout(10000); - const { result, source } = compile("./test/contracts/examples/", "Example"); - - // Note: Certain properties of the following contract data are hardcoded to - // maintain repeatable tests. If you significantly change the solidity code, - // make sure to update the resulting contract data with the correct values. - const example = result.contracts["Example.sol"].Example; - contract = { - solidity: source, - abi: example.abi, - binary: "0x" + example.evm.bytecode.object, - position_of_value: "0x0000000000000000000000000000000000000000000000000000000000000000", - expected_default_value: 5, - call_data: { - gas: "0x2fefd8", - gasPrice: "0x1", // This is important, as passing it has exposed errors in the past. - to: null, // set by test - data: "0x3fa4f245" - }, - transaction_data: { - from: null, // set by test - gas: "0x2fefd8", - to: null, // set by test - data: "0x552410770000000000000000000000000000000000000000000000000000000000000019" // sets value to 25 (base 10) - } - }; - }); - - before("Initialize Fallback Ganache server", function(done) { - this.timeout(10000); - forkedServer = Ganache.server({ - // Do not change seed. Determinism matters for these tests. - seed: "let's make this deterministic", - ws: true, - logger: logger - }); - - forkedServer.listen(21345, function(err) { - if (err) { - return done(err); - } - done(); - }); - }); - - before("set forkedWeb3 provider", function() { - forkedWeb3.setProvider(new Web3WsProvider(forkedTargetUrl)); - }); - - before("Set main web3 provider, forking from forked chain at this point", function() { - mainWeb3.setProvider( - Ganache.provider({ - fork: forkedTargetUrl.replace("ws", "http"), - logger, - verbose: true, - - // Do not change seed. Determinism matters for these tests. - seed: "a different seed" - }) - ); - }); - - before("Gather main accounts", async function() { - this.timeout(5000); - mainAccounts = await mainWeb3.eth.getAccounts(); - }); - - before("Deploy initial contract", async function() { - const receipt = await mainWeb3.eth.sendTransaction({ - from: mainAccounts[0], - data: contract.binary, - gas: 3141592, - value: mainWeb3.utils.toWei("1", "ether") - }); - - contractAddress = receipt.contractAddress; - - // Ensure there's *something* there. - const code = await mainWeb3.eth.getCode(contractAddress); - assert.notStrictEqual(code, null); - assert.notStrictEqual(code, "0x"); - assert.notStrictEqual(code, "0x0"); - }); - - it("should send 1 ether to the created contract, checked on the forked chain", async function() { - const balance = await mainWeb3.eth.getBalance(contractAddress); - - assert.strictEqual(balance, mainWeb3.utils.toWei("1", "ether")); - }); - - after("Shutdown server", function(done) { - forkedWeb3._provider.connection.close(); - forkedServer.close(function(serverCloseErr) { - forkedWeb3.setProvider(); - const mainProvider = mainWeb3._provider; - mainWeb3.setProvider(); - mainProvider.close(function(providerCloseErr) { - if (serverCloseErr) { - return done(serverCloseErr); - } - if (providerCloseErr) { - return done(providerCloseErr); - } - done(); - }); - }); - }); -}); diff --git a/test/local/forking/forking.js b/test/local/forking/forking.js deleted file mode 100644 index dbe24acb4a..0000000000 --- a/test/local/forking/forking.js +++ /dev/null @@ -1,735 +0,0 @@ -const Transaction = require("../../../lib/utils/transaction"); -var Web3 = require("web3"); -var Web3WsProvider = require("web3-providers-ws"); -var assert = require("assert"); -var Ganache = require(process.env.TEST_BUILD - ? "../../../build/ganache.core." + process.env.TEST_BUILD + ".js" - : "../../../index.js"); - -const compile = require("../../helpers/contract/singleFileCompile"); -var to = require("../../../lib/utils/to.js"); -var generateSend = require("../../helpers/utils/rpc"); - -var logger = { - log: function(msg) { - /* console.log(msg) */ - } -}; - -/** - * NOTE: Naming in these tests is a bit confusing. Here, the "main chain" - * is the main chain the tests interact with; and the "forked chain" is the - * chain that _was forked_. This is in contrast to general naming, where the - * main chain represents the main chain to be forked (like the Ethereum live - * network) and the fork chaing being "the fork". - */ - -describe("Forking", function() { - var contract; - var contractAddress; - var secondContractAddress; // used sparingly - var thirdContractAddress; // the same contract deployed to the fork ("mainWeb3") - var forkedServer; - var mainAccounts; - var forkedAccounts; - - var initialFallbackAccountState = {}; - - var forkedWeb3 = new Web3(); - var mainWeb3 = new Web3(); - - var forkedWeb3NetworkId = Date.now(); - var forkedWeb3Port = 21345; - var forkedTargetUrl = "ws://localhost:" + forkedWeb3Port; - var forkBlockNumber; - - var initialDeployTransactionHash; - var variableChangedBlockNumber; - - before("set up test data", function() { - this.timeout(10000); - const { result, source } = compile("./test/contracts/examples/", "Example"); - - // Note: Certain properties of the following contract data are hardcoded to - // maintain repeatable tests. If you significantly change the solidity code, - // make sure to update the resulting contract data with the correct values. - const example = result.contracts["Example.sol"].Example; - contract = { - solidity: source, - abi: example.abi, - binary: "0x" + example.evm.bytecode.object, - position_of_value: "0x0000000000000000000000000000000000000000000000000000000000000000", - expected_default_value: 5, - call_data: { - gas: "0x2fefd8", - gasPrice: "0x1", // This is important, as passing it has exposed errors in the past. - to: null, // set by test - data: "0x3fa4f245" - }, - transaction_data: { - from: null, // set by test - gas: "0x2fefd8", - to: null, // set by test - data: "0x552410770000000000000000000000000000000000000000000000000000000000000019" // sets value to 25 (base 10) - } - }; - }); - - before("Initialize Fallback Ganache server", async() => { - forkedServer = Ganache.server({ - // Do not change seed. Determinism matters for these tests. - seed: "let's make this deterministic", - ws: true, - logger: logger, - network_id: forkedWeb3NetworkId - }); - - await forkedServer.listen(forkedWeb3Port); - }); - - before("set forkedWeb3 provider", () => { - forkedWeb3.setProvider(new Web3WsProvider(forkedTargetUrl)); - }); - - before("Gather forked accounts", async() => { - forkedAccounts = await forkedWeb3.eth.getAccounts(); - }); - - before("Deploy initial contracts", async() => { - const receipt = await forkedWeb3.eth.sendTransaction({ - from: forkedAccounts[0], - data: contract.binary, - gas: 3141592 - }); - - // Save this for a later test. - initialDeployTransactionHash = receipt.transactionHash; - contractAddress = receipt.contractAddress; - - // Ensure there's *something* there. - const code = await forkedWeb3.eth.getCode(contractAddress); - assert.notStrictEqual(code, null); - assert.notStrictEqual(code, "0x"); - assert.notStrictEqual(code, "0x0"); - - // Deploy a second one, which we won't use often. - const receipt2 = await forkedWeb3.eth.sendTransaction({ - from: forkedAccounts[0], - data: contract.binary, - gas: 3141592 - }); - - secondContractAddress = receipt2.contractAddress; - }); - - before("Make a transaction on the forked chain that produces a log", async() => { - var forkedExample = new forkedWeb3.eth.Contract(contract.abi, contractAddress); - var event = forkedExample.events.ValueSet({}); - - const eventData = new Promise((resolve, reject) => { - event.once("data", function(logs) { - resolve(); - }); - }); - - await forkedExample.methods.setValue(7).send({ from: forkedAccounts[0] }); - await eventData; - }); - - before("Get initial balance and nonce", async() => { - const [balance, nonce] = await Promise.all([ - forkedWeb3.eth.getBalance(forkedAccounts[0]), - forkedWeb3.eth.getTransactionCount(forkedAccounts[0]) - ]); - initialFallbackAccountState = { - nonce: to.number(nonce), - balance - }; - }); - - before("Set main web3 provider, forking from forked chain at this point", async() => { - mainWeb3.setProvider( - Ganache.provider({ - fork: forkedTargetUrl.replace("ws", "http"), - logger, - // Do not change seed. Determinism matters for these tests. - seed: "a different seed" - }) - ); - - forkBlockNumber = await forkedWeb3.eth.getBlockNumber(); - }); - - before("Gather main accounts", async() => { - mainAccounts = await mainWeb3.eth.getAccounts(); - }); - before("Deploy a conttact to the main chain", async() => { - // Deploy a third one, which is used to verify the forked storage provider's duck punching - // works as expected on it's own data. - const receipt3 = await mainWeb3.eth.sendTransaction({ - from: mainAccounts[0], - data: contract.binary, - gas: 3141592 - }); - - thirdContractAddress = receipt3.contractAddress; - }); - - before("Make a transaction on the main chain using the it's own contract", async() => { - var mainExample = new mainWeb3.eth.Contract(contract.abi, thirdContractAddress); - var event = mainExample.events.ValueSet({}); - - const eventData = new Promise((resolve) => { - event.once("data", () => { - resolve(); - }); - }); - - const receipt = await mainExample.methods.setValue(7).send({ from: mainAccounts[0] }); - variableChangedBlockNumber = receipt.blockNumber; - await eventData; - }); - - it("should get the id of the forked chain", async() => { - const id = await mainWeb3.eth.net.getId(); - assert.strictEqual(id, forkedWeb3NetworkId); - }); - - describe("cache", () => { - function testCache(forkCacheSize, expectedCalls) { - return async() => { - async function checkIt(baseLine) { - const r = await send("eth_getStorageAt", ...params); - const testResult = Object.assign({}, r, { id: null }); - assert.deepStrictEqual(testResult, baseLine); - } - - const provider = Ganache.provider({ fork: forkedTargetUrl.replace("ws", "http"), forkCacheSize }); - const send = generateSend(provider); - const params = [contractAddress, contract.position_of_value]; - let callCount = 0; - const oldSend = forkedServer.provider.send; - try { - // patch the original server's send so we can listen in on calls made to it. - forkedServer.provider.send = (...args) => { - const payload = args[0]; - if (payload.method === "eth_getStorageAt" && payload.params[0] === contractAddress.toLowerCase()) { - callCount++; - } - return oldSend.apply(forkedServer.provider, args); - }; - - // cache something by requesting stuff from the original chain: - const result = await send("eth_getStorageAt", ...params); - const baseLine = Object.assign({}, result, { id: null }); - assert.strictEqual(parseInt(baseLine.result), 7, "return value is incorrect"); - - // then check that it is cached - await checkIt(baseLine); - await checkIt(baseLine); - - // put something else in the cache to give it a chance to be evicted - await send("eth_getStorageAt", forkedAccounts[0], "0x0"); - - await checkIt(baseLine); - await checkIt(baseLine); - - // after all those checks, we should have `expectedCalls` into the original chain - assert.strictEqual(callCount, expectedCalls, "cache didn't work"); - } finally { - forkedServer.provider.send = oldSend; - } - }; - } - - it("should evict from the cache on successive calls to the same data when cache is small", testCache(1230, 2)); - - it("should return from the cache on successive calls to the same data when cache is infinite", testCache(-1, 1)); - - it("should not return from the cache on successive calls to the same data when cache is off", testCache(0, 5)); - - it("should return from the cache on calls for same data when cache size is default", testCache(undefined, 1)); - }); - - it("should match nonce of accounts on original chain", async() => { - const provider = Ganache.provider({ fork: forkedTargetUrl, seed: forkedServer.ganacheProvider.options.seed }); - - const send = generateSend(provider); - const originalSend = generateSend(forkedServer.ganacheProvider); - - const accounts = await send("eth_accounts"); - assert.deepStrictEqual( - accounts.result, - forkedAccounts.map((a) => a.toLowerCase()), - "generated accounts don't match" - ); - - const results = await Promise.all( - accounts.result.map((account) => { - const originalCountProm = originalSend("eth_getTransactionCount", account); - const forkedCountProm = send("eth_getTransactionCount", account); - return Promise.all([forkedCountProm, originalCountProm]); - }) - ); - - results.map(([forkedCount, originalCount]) => { - assert.strictEqual(forkedCount.result, originalCount.result); - }); - }); - - it("should fetch a contract from the forked provider via the main provider", async() => { - const mainCode = await mainWeb3.eth.getCode(contractAddress); - // Ensure there's *something* there. - assert.notStrictEqual(mainCode, null); - assert.notStrictEqual(mainCode, "0x"); - assert.notStrictEqual(mainCode, "0x0"); - - // Now make sure it matches exactly. - const forkedCode = await forkedWeb3.eth.getCode(contractAddress); - assert.strictEqual(mainCode, forkedCode); - }); - - it("internal `fork.send` should handle batched transactions", (done) => { - // this is a weird test because we dont' actually use batched transactions in forking - // but just in case we start doing so later, for whatever reason, I'm making sure it works now - const tx1 = { id: 1, method: "eth_accounts", jsonrpc: "2.0", params: [] }; - const tx2 = { id: 2, method: "eth_getBalance", jsonrpc: "2.0", params: [forkedAccounts[0]] }; - const tx3 = { id: 3, method: "eth_chainId", jsonrpc: "2.0", params: [] }; - - // gross? yes. - mainWeb3.currentProvider.manager.state.blockchain.fork.send([tx1, tx2, tx3], (mainErr, mainResults) => { - forkedWeb3.currentProvider.send([tx1, tx2, tx3], (_, forkedResults) => { - assert.strictEqual(mainErr, null); - assert.strictEqual(mainResults[0].id, tx1.id); - assert.strictEqual(mainResults[1].id, tx2.id); - assert.strictEqual(mainResults[2].id, tx3.id); - assert.strictEqual(mainResults[0].result.length, 10); - assert.strictEqual(mainResults[1].result, forkedResults[1].result); - assert.strictEqual(mainResults[2].result, forkedResults[2].result); - done(); - }); - }); - }); - - it("should get the balance of an address in the forked provider via the main provider", async() => { - // Assert preconditions - const firstForkedAccount = forkedAccounts[0]; - assert(mainAccounts.indexOf(firstForkedAccount) < 0); - - // Now for the real test: Get the balance of a forked account through the main provider. - const balance = await mainWeb3.eth.getBalance(firstForkedAccount); - assert(balance > 999999); - }); - - it("should get storage values on the forked provider itself", async() => { - const result = await mainWeb3.eth.getStorageAt(thirdContractAddress, contract.position_of_value); - assert.strictEqual(mainWeb3.utils.hexToNumber(result), 7); - }); - - it("should get the correct storage values based on block", async() => { - const result = await mainWeb3.eth.getStorageAt( - thirdContractAddress, - contract.position_of_value, - variableChangedBlockNumber - 1 - ); - assert.strictEqual(mainWeb3.utils.hexToNumber(result), 5); - }); - - it("should get storage values on the forked provider via the main provider", async() => { - const result = await mainWeb3.eth.getStorageAt(contractAddress, contract.position_of_value); - assert.strictEqual(mainWeb3.utils.hexToNumber(result), 7); - }); - - it("should get storage values on the forked provider via the main provider at a block number", async() => { - const result = await mainWeb3.eth.getStorageAt(contractAddress, contract.position_of_value, 1); - assert.strictEqual(mainWeb3.utils.hexToNumber(result), 5); - }); - - it("should execute calls against a contract on the forked provider via the main provider", async() => { - var example = new mainWeb3.eth.Contract(contract.abi, contractAddress); - - const result = await example.methods.value().call({ from: mainAccounts[0] }); - assert.strictEqual(parseInt(result, 10), 7); - - // Make the call again to ensure caches updated and the call still works. - const result2 = await example.methods.value().call({ from: mainAccounts[0] }); - assert.strictEqual(parseInt(result2, 10), 7); - }); - - it("should make a transaction on the main provider while not transacting on the forked provider", async() => { - var example = new mainWeb3.eth.Contract(contract.abi, contractAddress); - - var forkedExample = new forkedWeb3.eth.Contract(contract.abi, contractAddress); - - // TODO: ugly workaround - not sure why this is necessary. - if (!forkedExample._requestManager.provider) { - forkedExample._requestManager.setProvider(forkedWeb3.eth._provider); - } - - await example.methods.setValue(25).send({ from: mainAccounts[0] }); - - // It insta-mines, so we can make a call directly after. - const result = await example.methods.value().call({ from: mainAccounts[0] }); - assert.strictEqual(parseInt(result, 10), 25); - - // Now call back to the forked to ensure it's value stayed 5 - const forkedResult = await forkedExample.methods.value().call({ from: forkedAccounts[0] }); - assert.strictEqual(parseInt(forkedResult, 10), 7); - }); - - it("should ignore continued transactions on the forked blockchain by pegging the forked block number", async() => { - // In this test, we're going to use the second contract address that we haven't - // used previously. This ensures the data hasn't been cached on the main web3 trie - // yet, and it will require it forked to the forked provider at a specific block. - // If that block handling is done improperly, this should fail. - - var example = new mainWeb3.eth.Contract(contract.abi, secondContractAddress); - - var forkedExample = new forkedWeb3.eth.Contract(contract.abi, secondContractAddress); - - // TODO: ugly workaround - not sure why this is necessary. - if (!forkedExample._requestManager.provider) { - forkedExample._requestManager.setProvider(forkedWeb3.eth._provider); - } - - // This transaction happens entirely on the forked chain after forking. - // It should be ignored by the main chain. - await forkedExample.methods.setValue(800).send({ from: forkedAccounts[0] }); - // Let's assert the value was set correctly. - const result = await forkedExample.methods.value().call({ from: forkedAccounts[0] }); - assert.strictEqual(parseInt(result, 10), 800); - - // Now lets check the value on the main chain. It shouldn't be 800. - const mainResult = await example.methods.value().call({ from: mainAccounts[0] }); - assert.strictEqual(parseInt(mainResult, 10), 5); - }); - - it("should maintain a block number that includes new blocks PLUS the existing chain", async() => { - // Note: The main provider should be at block 7 at this test. Reasoning: - // - The forked chain has an initial block, which is block 0. - // - The forked chain performed a transaction that produced a log, resulting in block 1. - // - The forked chain had two transactions initially, resulting blocks 2 and 3. - // - The main chain forked from there, creating its own initial block, block 4. - // - Then the main chain deploy a contract, putting it at block 5. - // - Then the main chain sent a transaction to that contract, block 6. - // - Then the main chain performed a transaction, putting it at block 7. - - const result = await mainWeb3.eth.getBlockNumber(); - assert.strictEqual(mainWeb3.utils.hexToNumber(result), 7); - - // Now lets get a block that exists on the forked chain. - const mainBlock = await mainWeb3.eth.getBlock(0); - // And compare it to the forked chain's block - const forkedBlock = await forkedWeb3.eth.getBlock(0); - // Block hashes should be the same. - assert.strictEqual(mainBlock.hash, forkedBlock.hash); - - // Now make sure we can get the block by hash as well. - const mainBlockByHash = await mainWeb3.eth.getBlock(mainBlock.hash); - assert.strictEqual(mainBlock.hash, mainBlockByHash.hash); - }); - - it("should have a genesis block whose parent is the last block from the forked provider", async() => { - const forkedBlock = await forkedWeb3.eth.getBlock(forkBlockNumber); - const parentHash = forkedBlock.hash; - const mainGenesisNumber = mainWeb3.utils.hexToNumber(forkBlockNumber) + 1; - const mainGenesis = await mainWeb3.eth.getBlock(mainGenesisNumber); - assert.strictEqual(mainGenesis.parentHash, parentHash); - }); - - // Note: This test also puts a new contract on the forked chain, which is a good test. - it( - "should represent the block number correctly in the Oracle contract (oracle.blockhash0)," + - " providing forked block hash and number", - async() => { - const { result: solcResult } = compile("./test/contracts/misc/", "Oracle"); - const oracleOutput = solcResult.contracts["Oracle.sol"].Oracle; - - const contract = new mainWeb3.eth.Contract(oracleOutput.abi); - const deployTxn = contract.deploy({ data: oracleOutput.evm.bytecode.object }); - const oracle = await deployTxn.send({ from: mainAccounts[0], gas: 3141592 }); - - const block = await mainWeb3.eth.getBlock(0); - const blockhash = await oracle.methods.blockhash0().call(); - assert.strictEqual(blockhash, block.hash); - - const expectedNumber = await mainWeb3.eth.getBlockNumber(); - - const number = await oracle.methods.currentBlock().call(); - assert.strictEqual(to.number(number), expectedNumber); - - await oracle.methods.setCurrentBlock().send({ from: mainAccounts[0], gas: 3141592 }); - const val = await oracle.methods.lastBlock().call({ from: mainAccounts[0] }); - assert.strictEqual(to.number(val), expectedNumber + 1); - } - ).timeout(10000); - - // TODO - it("should be able to get logs across the fork boundary", async() => { - const example = new mainWeb3.eth.Contract(contract.abi, contractAddress); - const event = example.events.ValueSet({ fromBlock: 0, toBlock: "latest" }); - let callcount = 0; - const eventData = new Promise((resolve, reject) => { - event.on("data", function(log) { - callcount++; - if (callcount === 2) { - event.removeAllListeners(); - resolve(); - } - }); - }); - await eventData; - }).timeout(30000); - - it("should return the correct nonce based on block number", async() => { - // Note for the first two requests, we choose the block numbers 1 before and after the fork to - // ensure we're pulling data off the correct provider in both cases. - const [nonceBeforeFork, nonceAtFork, nonceLatestMain, nonceLatestFallback] = await Promise.all([ - mainWeb3.eth.getTransactionCount(forkedAccounts[0], forkBlockNumber - 1), - mainWeb3.eth.getTransactionCount(forkedAccounts[0], forkBlockNumber + 1), - mainWeb3.eth.getTransactionCount(forkedAccounts[0], "latest"), - forkedWeb3.eth.getTransactionCount(forkedAccounts[0], "latest") - ]); - - // First ensure our nonces for the block before the fork - // Note that we're asking for the block *before* the forked block, - // which automatically means we sacrifice a transaction (i.e., one nonce value) - assert.strictEqual(nonceBeforeFork, initialFallbackAccountState.nonce - 1); - - // Now check at the fork. We should expect our initial state. - assert.strictEqual(nonceAtFork, initialFallbackAccountState.nonce); - - // Make sure the main web3 provider didn't alter the state of the forked account. - // This means the nonce should stay the same. - assert.strictEqual(nonceLatestMain, initialFallbackAccountState.nonce); - - // And since we made one additional transaction with this account on the forked - // provider AFTER the fork, it's nonce should be one ahead, and the main provider's - // nonce for that address shouldn't acknowledge it. - assert.strictEqual(nonceLatestFallback, nonceLatestMain + 1); - }); - - it("should return the correct balance based on block number", async() => { - // Note for the first two requests, we choose the block numbers 1 before and after the fork to - // ensure we're pulling data off the correct provider in both cases. - const [balanceBeforeFork, balanceAfterFork, balanceLatestMain, balanceLatestFallback] = [ - ...(await Promise.all([ - mainWeb3.eth.getBalance(forkedAccounts[0], forkBlockNumber - 1), - mainWeb3.eth.getBalance(forkedAccounts[0], forkBlockNumber + 1), - mainWeb3.eth.getBalance(forkedAccounts[0], "latest"), - forkedWeb3.eth.getBalance(forkedAccounts[0], "latest") - ])) - ].map(function(el) { - return mainWeb3.utils.toBN(el); - }); - - // First ensure our balances for the block before the fork - // We do this by simply ensuring the balance has decreased since exact values - // are hard to assert in this case. - assert(balanceBeforeFork.gt(balanceAfterFork)); - - // Make sure it's not substantially larger. it should only be larger by a small - // amount (<2%). This assertion was added since forked balances were previously - // incorrectly being converted between decimal and hex - assert(balanceBeforeFork.muln(0.95).lt(balanceAfterFork)); - - // Since the forked provider had once extra transaction for this account, - // it should have a lower balance, and the main provider shouldn't acknowledge - // that transaction. - assert(balanceLatestMain.gt(balanceLatestFallback)); - - // Make sure it's not substantially larger. it should only be larger by a small - // amount (<2%). This assertion was added since forked balances were previously - // incorrectly being converted between decimal and hex - assert(balanceLatestMain.muln(0.95).lt(balanceLatestFallback)); - }); - - it("should return the correct code based on block number", async() => { - // This one is simpler than the previous two. Either the code exists or doesn't. - const [codeEarliest, codeAfterFork, codeLatest] = [ - ...(await Promise.all([ - mainWeb3.eth.getCode(contractAddress, "earliest"), - mainWeb3.eth.getCode(contractAddress, forkBlockNumber + 1), - mainWeb3.eth.getCode(contractAddress, "latest") - ])) - ]; - - // There should be no code initially. - assert.strictEqual(codeEarliest, "0x"); - - // Arbitrary length check since we can't assert the exact value - assert(codeAfterFork.length > 20); - assert(codeLatest.length > 20); - - // These should be the same since code can't change. - assert.strictEqual(codeAfterFork, codeLatest); - }); - - it("should return transactions for blocks requested before the fork", async() => { - const receipt = await forkedWeb3.eth.getTransactionReceipt(initialDeployTransactionHash); - const referenceBlock = await forkedWeb3.eth.getBlock(receipt.blockNumber, true); - const forkedBlock = await mainWeb3.eth.getBlock(receipt.blockNumber, true); - assert.strictEqual(forkedBlock.transactions.length, referenceBlock.transactions.length); - assert.deepStrictEqual(forkedBlock.transactions, referenceBlock.transactions); - }); - - it("should return a transaction for transactions made before the fork", async() => { - const referenceTransaction = await forkedWeb3.eth.getTransaction(initialDeployTransactionHash); - const forkedTransaction = await mainWeb3.eth.getTransaction(initialDeployTransactionHash); - assert.deepStrictEqual(referenceTransaction, forkedTransaction); - }); - - it("should return a transaction receipt for transactions made before the fork", async() => { - const referenceReceipt = await forkedWeb3.eth.getTransactionReceipt(initialDeployTransactionHash); - assert.deepStrictEqual(referenceReceipt.transactionHash, initialDeployTransactionHash); - - const forkedReceipt = await mainWeb3.eth.getTransactionReceipt(initialDeployTransactionHash); - assert.deepStrictEqual(forkedReceipt.transactionHash, initialDeployTransactionHash); - assert.deepStrictEqual(referenceReceipt, forkedReceipt); - }); - - it("should return the same network version as the chain it forked from", async() => { - const forkedNetwork = await forkedWeb3.eth.net.getId(); - const mainNetwork = await mainWeb3.eth.net.getId(); - assert.strictEqual(mainNetwork, forkedNetwork); - }); - - it("should be able to delete data", async() => { - const from = mainAccounts[0]; - const example = new mainWeb3.eth.Contract(contract.abi, contractAddress); - const example2 = new mainWeb3.eth.Contract(contract.abi, secondContractAddress); - - const example2value = await example2.methods.value().call(); - assert.strictEqual(example2value, "5"); - - // delete the data from our fork - await example.methods.setValue(0).send({ from }); - const result = await example.methods.value().call(); - assert.strictEqual(result, "0"); - - // Check this hasn't clobbered data in the same slot in other contracts - const example2valueAfter = await example2.methods.value().call(); - assert.strictEqual(example2valueAfter, "5"); - - await example.methods.setValue(7).send({ from }); - const result2 = await example.methods.value().call(); - assert.strictEqual(result2, "7"); - }); - - it("should be able to selfdestruct a contract", async() => { - const from = mainAccounts[0]; - const example = new mainWeb3.eth.Contract(contract.abi, contractAddress); - - // delete the contract from our fork - await example.methods.destruct().send({ from }); - const code = await mainWeb3.eth.getCode(contractAddress); - assert.strictEqual(code, "0x"); - }); - - it("should be able to send a signed transaction", async() => { - const transaction = new Transaction({ - value: "0x10000000", - gasLimit: "0x33450", - from: mainAccounts[8], - to: mainAccounts[7], - nonce: "0x0" - }); - - const secretKey = mainWeb3.currentProvider.manager.state.accounts[mainAccounts[8].toLowerCase()].secretKey; - transaction.sign(secretKey); - - const result = await mainWeb3.eth.sendSignedTransaction(transaction.serialize()); - assert.strictEqual(result.status, true); - }); - - describe("Can debug a transaction", function() { - let send; - before("generate send", function() { - send = generateSend(mainWeb3.currentProvider); - }); - - // this test does NOT validate the state of the debugged transaction. It only checks that - // the debug_traceTransaction is callable on a forked Chain. We don't yet have tests - // for forked debug_traceTransaction, but when we do, they'll be in debug.js (or similar), not here. - it("can debug the transaction", async function() { - const receipt = await mainWeb3.eth.sendTransaction({ from: mainAccounts[0], to: mainAccounts[1], value: 1 }); - await assert.doesNotReject(send("debug_traceTransaction", receipt.transactionHash, [])); - }); - }); - - describe("fork_block_number", function() { - const initialValue = "123"; - let forkedExample; - let forkBlockNumber; - let web3; - before("Set up the initial chain with the values we want to test", async function() { - forkedExample = new forkedWeb3.eth.Contract(contract.abi, contractAddress); - await forkedExample.methods.setValue(initialValue).send({ from: forkedAccounts[0] }); - forkBlockNumber = await forkedWeb3.eth.getBlockNumber(); - await forkedExample.methods.setValue("999").send({ from: forkedAccounts[0] }); - }); - - before("create provider", function() { - const provider = Ganache.provider({ - fork: forkedTargetUrl.replace("ws", "http"), - fork_block_number: forkBlockNumber - }); - web3 = new Web3(provider); - }); - - it("should create a provider who's initial block is immediately after the fork_block_number", async() => { - const blockNumber = await web3.eth.getBlockNumber(); - // Because we (currently) mine a "genesis" block when forking, the current block immediately after - // initialization is 1 higher than the fork_block_number. This may change in the future by: - // https://github.com/trufflesuite/ganache-core/issues/341 - assert(blockNumber - 1, forkBlockNumber, "Initial block number on forked chain is not as expected"); - }); - - it("should return original chain data from before the fork", async() => { - const example = new web3.eth.Contract(contract.abi, contractAddress); - const result = await example.methods.value().call({ from: mainAccounts[0] }); - - assert(result, initialValue, "Value return on forked chain is not as expected"); - }); - }); - - describe("Intra block state", function() { - it("should be aware of the vm cache", async() => { - const { result } = compile("./test/contracts/forking/", "IntraBlockCache"); - const contract = new mainWeb3.eth.Contract(result.contracts["IntraBlockCache.sol"].IntraBlockCache.abi); - const accounts = await mainWeb3.eth.getAccounts(); - const ibc = await contract - .deploy({ - data: result.contracts["IntraBlockCache.sol"].IntraBlockCache.evm.bytecode.object - }) - .send({ - from: accounts[0], - gas: 190941 - }); - return assert.doesNotReject( - ibc.methods.deploy().send({ from: accounts[0] }), - undefined, - "Should reference state in the VM's cache" - ); - }); - }); - - after("Shutdown server", (done) => { - forkedWeb3._provider.connection.close(); - forkedServer.close(function(serverCloseErr) { - forkedWeb3.setProvider(); - const mainProvider = mainWeb3.currentProvider; - mainWeb3.setProvider(); - mainProvider && - mainProvider.close(function(providerCloseErr) { - if (serverCloseErr) { - return done(serverCloseErr); - } - if (providerCloseErr) { - return done(providerCloseErr); - } - done(); - }); - }); - }); -}); diff --git a/test/local/forking/forkingAsProvider.js b/test/local/forking/forkingAsProvider.js deleted file mode 100644 index 07862e2408..0000000000 --- a/test/local/forking/forkingAsProvider.js +++ /dev/null @@ -1,60 +0,0 @@ -const assert = require("assert"); -const bootstrap = require("../../helpers/contract/bootstrap"); -const initializeTestProvider = require("../../helpers/web3/initializeTestProvider"); - -/** - * NOTE: Naming in these tests is a bit confusing. Here, the "main chain" - * is the main chain the tests interact with; and the "forked chain" is the - * chain that _was forked_. This is in contrast to general naming, where the - * main chain represents the main chain to be forked (like the Ethereum live - * network) and the fork chaing being "the fork". - */ - -describe("Forking using a Provider", () => { - let forkedContext; - let mainContext; - const logger = { - log: function(msg) {} - }; - - before("Set up forked provider with web3 instance and deploy a contract", async function() { - this.timeout(5000); - - const contractRef = { - contractFiles: ["Example"], - contractSubdirectory: "examples" - }; - - const ganacheProviderOptions = { - logger, - seed: "main provider" - }; - - forkedContext = await bootstrap(contractRef, ganacheProviderOptions); - }); - - before("Set up main provider and web3 instance", async function() { - const { provider: forkedProvider } = forkedContext; - mainContext = await initializeTestProvider({ - fork: forkedProvider, - logger, - seed: "forked provider" - }); - }); - - // NOTE: This is the only real test in this file. Since we have another forking test file filled - // with good tests, this one simply ensures the forked feature still works by testing that we can - // grab data from the forked chain when a provider instance is passed (instead of a URL). If this - // one passes, it should follow that the rest of the forking features should work as normal. - it("gets code correctly via the main chain (i.e., internally requests it from forked chain)", async() => { - const { instance } = forkedContext; - const { web3: mainWeb3 } = mainContext; - - const code = await mainWeb3.eth.getCode(instance._address); - - // Ensure that a contract is at the address - assert.notStrictEqual(code, null); - assert.notStrictEqual(code, "0x"); - assert.notStrictEqual(code, "0x0"); - }); -}); diff --git a/test/local/forking/snapshot.js b/test/local/forking/snapshot.js deleted file mode 100644 index 78c3cdfb42..0000000000 --- a/test/local/forking/snapshot.js +++ /dev/null @@ -1,126 +0,0 @@ -const assert = require("assert"); -const bootstrap = require("../../helpers/contract/bootstrap"); -const initializeTestProvider = require("../../helpers/web3/initializeTestProvider"); - -/** - * NOTE: Naming in these tests is a bit confusing. Here, the "main chain" - * is the main chain the tests interact with; and the "forked chain" is the - * chain that _was forked_. This is in contrast to general naming, where the - * main chain represents the main chain to be forked (like the Ethereum live - * network) and the fork chaing being "the fork". - */ - -async function takeSnapshot(web3) { - return new Promise((resolve, reject) => { - web3.currentProvider.send( - { - jsonrpc: "2.0", - method: "evm_snapshot", - id: new Date().getTime() - }, - (err, result) => { - if (err) { - return reject(err); - } - return resolve(result.result); - } - ); - }); -} - -async function revertToSnapShot(web3, stateId) { - await new Promise((resolve, reject) => { - web3.currentProvider.send( - { - jsonrpc: "2.0", - method: "evm_revert", - params: [stateId], - id: new Date().getTime() - }, - (err, result) => { - if (err) { - return reject(err); - } - return resolve(result); - } - ); - }); -} - -describe("Forking Snapshots", () => { - let forkedContext; - let mainContext; - const logger = { - log: function(msg) {} - }; - - before("Set up forked provider with web3 instance and deploy a contract", async function() { - this.timeout(5000); - - const contractRef = { - contractFiles: ["Snapshot"], - contractSubdirectory: "forking" - }; - - const ganacheProviderOptions = { - logger, - seed: "main provider" - }; - - forkedContext = await bootstrap(contractRef, ganacheProviderOptions); - }); - - before("Set up main provider and web3 instance", async function() { - const { provider: forkedProvider } = forkedContext; - mainContext = await initializeTestProvider({ - fork: forkedProvider, - logger, - seed: "forked provider" - }); - }); - - it("successfully handles snapshot/revert scenarios", async() => { - const { instance: forkedInstance, abi } = forkedContext; - const { web3: mainWeb3 } = mainContext; - - const accounts = await mainWeb3.eth.getAccounts(); - const instance = new mainWeb3.eth.Contract(abi, forkedInstance._address); - const txParams = { - from: accounts[0] - }; - - let value; - - value = await instance.methods.value().call(); - assert.strictEqual(value, "0"); - - await instance.methods.test().send(txParams); - - value = await instance.methods.value().call(); - assert.strictEqual(value, "1"); - - const beforeSnapshotNonce = await mainWeb3.eth.getTransactionCount(accounts[0]); - const snapshotId = await takeSnapshot(mainWeb3); - - await instance.methods.test().send(txParams); - - value = await instance.methods.value().call(); - assert.strictEqual(value, "2"); - - const beforeRevertNonce = await mainWeb3.eth.getTransactionCount(accounts[0]); - assert.strictEqual(beforeRevertNonce, beforeSnapshotNonce + 1); - - await revertToSnapShot(mainWeb3, snapshotId); - - const afterRevertNonce = await mainWeb3.eth.getTransactionCount(accounts[0]); - assert.strictEqual(afterRevertNonce, beforeSnapshotNonce); - - value = await instance.methods.value().call(); - assert.strictEqual(value, "1"); - - await instance.methods.test().send(txParams); - - value = await instance.methods.value().call(); - assert.strictEqual(value, "2"); - }).timeout(5000); -}); diff --git a/test/local/gas/customGasLimit.js b/test/local/gas/customGasLimit.js deleted file mode 100644 index 43a7bb844f..0000000000 --- a/test/local/gas/customGasLimit.js +++ /dev/null @@ -1,14 +0,0 @@ -const assert = require("assert"); -const initializeTestProvider = require("../../helpers/web3/initializeTestProvider"); - -describe("Gas", function() { - describe("Custom Gas Limit", function() { - it("The block should show the correct custom Gas Limit", async function() { - const ganacheProviderOptions = { gasLimit: 5000000 }; - const { web3 } = await initializeTestProvider(ganacheProviderOptions); - const { gasLimit } = await web3.eth.getBlock(0); - - assert.deepStrictEqual(gasLimit, 5000000); - }); - }); -}); diff --git a/test/local/gas/customGasPrice.js b/test/local/gas/customGasPrice.js deleted file mode 100644 index 326d9cef43..0000000000 --- a/test/local/gas/customGasPrice.js +++ /dev/null @@ -1,33 +0,0 @@ -const assert = require("assert"); -const initializeTestProvider = require("../../helpers/web3/initializeTestProvider"); - -describe("Gas", function() { - describe("Custom Gas Price", function() { - it("should return gas price of 15 when specified as a decimal", async function() { - const ganacheProviderOptions = { - gasPrice: 15 - }; - const { web3 } = await initializeTestProvider(ganacheProviderOptions); - const result = await web3.eth.getGasPrice(); - assert.strictEqual(parseInt(result), 15); - }); - - it("should return gas price of 15 when specified as hex (string)", async function() { - const ganacheProviderOptions = { - gasPrice: "0xf" - }; - const { web3 } = await initializeTestProvider(ganacheProviderOptions); - const result = await web3.eth.getGasPrice(); - assert.strictEqual(parseInt(result), 15); - }); - - it("should return gas price of 15 when specified as decimal (string)", async function() { - const ganacheProviderOptions = { - gasPrice: "15" - }; - const { web3 } = await initializeTestProvider(ganacheProviderOptions); - const result = await web3.eth.getGasPrice(); - assert.strictEqual(parseInt(result), 15); - }); - }); -}); diff --git a/test/local/gas/gas.js b/test/local/gas/gas.js deleted file mode 100644 index e5033ac5db..0000000000 --- a/test/local/gas/gas.js +++ /dev/null @@ -1,945 +0,0 @@ -const memdown = require("memdown"); -const assert = require("assert"); -const bootstrap = require("../../helpers/contract/bootstrap"); -const confirmGasPrice = require("./lib/confirmGasPrice"); -const initializeTestProvider = require("../../helpers/web3/initializeTestProvider"); -const randomInteger = require("../../helpers/utils/generateRandomInteger"); -const testTransactionEstimate = require("./lib/transactionEstimate"); -const toBytesHexString = require("../../helpers/utils/toBytesHexString"); -const { deploy } = require("../../helpers/contract/compileAndDeploy"); -const { BN } = require("ethereumjs-util"); -const compile = require("../../helpers/contract/singleFileCompile"); -const createSignedTx = require("../../helpers/utils/create-signed-tx"); -const SEED_RANGE = 1000000; -const RSCLEAR_REFUND = 15000; -const RSCLEAR_REFUND_FOR_RESETTING_DIRTY_SLOT_TO_ZERO_ISTANBUL = 19200; -const RSCLEAR_REFUND_FOR_RESETTING_DIRTY_SLOT_TO_ZERO = 19800; -const RSELFDESTRUCT_REFUND = 24000; -const HARDFORKS = ["byzantium", "constantinople", "petersburg", "istanbul", "muirGlacier"]; - -describe("Gas", function() { - HARDFORKS.forEach((hardfork) => { - describe(`Hardfork: ${hardfork.toUpperCase()}`, function() { - let context; - const seed = randomInteger(SEED_RANGE); - - before("Setting up web3 and contract", async function() { - this.timeout(10000); - - const contractRef = { - contractFiles: ["EstimateGas"], - contractSubdirectory: "gas" - }; - - const ganacheProviderOptions = { - seed, - hardfork - }; - - context = await bootstrap(contractRef, ganacheProviderOptions, hardfork); - }); - - describe("EIP150 Gas Estimation: ", function() { - const privateKey = Buffer.from("4646464646464646464646464646464646464646464646464646464646464646", "hex"); - let ContractFactory; - let TestDepth; - let Donation; - let Fib; - let SendContract; - let Create2; - let NonZero; - before("Setting up EIP150 contracts", async function() { - this.timeout(10000); - - const subDirectory = { contractSubdirectory: "gas" }; - const create2 = Object.assign({ contractFiles: ["CreateTwo", "GasLeft"] }, subDirectory); - const factory = Object.assign({ contractFiles: ["ContractFactory"] }, subDirectory); - const testDepth = Object.assign({ contractFiles: ["TestDepth"] }, subDirectory); - const donation = Object.assign({ contractFiles: ["Donation"] }, subDirectory); - const fib = Object.assign({ contractFiles: ["Fib"] }, subDirectory); - const sendContract = Object.assign({ contractFiles: ["SendContract"] }, subDirectory); - const nonZero = Object.assign({ contractFiles: ["NonZero"] }, subDirectory); - - const ganacheProviderOptions = { - seed, - hardfork - }; - - ContractFactory = await bootstrap(factory, ganacheProviderOptions, hardfork); - // memdown makes the test that uses TestDepth about 20% faster, so we use it here because CI makes us sad. - TestDepth = await bootstrap(testDepth, Object.assign({ db: memdown() }, ganacheProviderOptions), hardfork); - Donation = await bootstrap(donation, ganacheProviderOptions, hardfork); - Fib = await bootstrap(fib, ganacheProviderOptions, hardfork); - NonZero = await bootstrap(nonZero, ganacheProviderOptions, hardfork); - SendContract = await bootstrap( - sendContract, - Object.assign( - { - accounts: [ - { - secretKey: "0x" + privateKey.toString("hex"), - balance: "0x" + new BN("1000000000000000000000").toString("hex") - } - ] - }, - ganacheProviderOptions - ), - hardfork - ); - if (hardfork !== "byzantium") { - Create2 = await bootstrap(create2, ganacheProviderOptions, hardfork); - } - }); - - it("Should not timeout when running a long test", async() => { - try { - await context.instance.methods.runsOutOfGas().send({ from: context.accounts[0] }); - assert.fail(); - } catch (e) { - assert(e.message.includes("out of gas")); - } - }).timeout(5000); - - it("Should estimate gas perfectly with EIP150 - recursive CALL", async() => { - const { accounts, instance, send } = Fib; - const txParams = { - from: accounts[0], - to: instance._address, - value: 10 - }; - const { result: estimateHex } = await send("eth_estimateGas", txParams); - const estimate = parseInt(estimateHex); - const tx = Object.assign({ gas: `0x${(estimate - 1).toString(16)}` }, txParams); - await assert.rejects( - () => send("eth_sendTransaction", tx), - { - message: "VM Exception while processing transaction: out of gas" - }, - `SANITY CHECK: Gas estimate: ${estimate - 1} is too high.` - ); - tx.gas = estimateHex; - await assert.doesNotReject( - () => send("eth_sendTransaction", tx), - undefined, - `SANITY CHECK. Still not enough gas? ${estimate} Our estimate is still too low` - ); - }); - - it("Should estimate gas perfectly with EIP150 - CREATE", async() => { - const { accounts, instance, send } = ContractFactory; - const txParams = { - from: accounts[0], - to: instance._address, - data: instance.methods.createInstance().encodeABI() - }; - const { result: estimateHex } = await send("eth_estimateGas", txParams); - const estimate = new BN(estimateHex.substring(2), "hex"); - txParams.gas = "0x" + estimate.subn(1).toString("hex"); - await assert.rejects(() => send("eth_sendTransaction", txParams), { - message: "VM Exception while processing transaction: revert" - }); - - txParams.gas = estimateHex; - await assert.doesNotReject( - () => send("eth_sendTransaction", txParams), - undefined, - `SANITY CHECK. Still not enough gas? ${estimate} Our estimate is still too low` - ); - }); - - it("Should estimate gas perfectly with EIP150 - CALL INSIDE CREATE", async() => { - const { accounts, instance } = Donation; - // Pre-condition - const address = accounts[0]; - const donateTx = { from: address, value: 50 }; - donateTx.gas = await instance.methods.donate().estimateGas(donateTx); - await instance.methods.donate().send(donateTx); - const tx = { from: address }; - const est = await instance.methods.moveFund(address, 5).estimateGas(tx); - tx.gas = est - 1; - await assert.rejects(() => instance.methods.moveFund(address, 5).send(tx), { - message: "VM Exception while processing transaction: out of gas" - }); - tx.gas = est; - await assert.doesNotReject( - () => instance.methods.moveFund(address, 5).send(tx), - undefined, - `SANITY CHECK. Still not enough gas? ${est} Our estimate is still too low` - ); - }).timeout(1000000); - - if (hardfork !== "byzantium") { - it("Should estimate gas perfectly with EIP150 - DELEGATECALL", async() => { - const { accounts, instance } = TestDepth; - const depth = 3; - const promises = Array(depth) - .fill(0) - .map((_, i) => { - const depth = i + 1; - return instance.methods - .depth(depth) - .estimateGas() - .then(async(est) => { - return Promise.all([ - assert.doesNotReject( - instance.methods.depth(depth).send({ - from: accounts[5], - gas: est - }), - undefined, - `SANITY CHECK. Still not enough gas? ${est} Our estimate is still too low` - ), - assert.rejects( - instance.methods.depth(depth).send({ - from: accounts[5], - gas: est - 1 - }), - { - name: "RuntimeError" - } - ) - ]); - }); - }); - await Promise.all(promises); - }).timeout(3000); - - it("Should estimate gas perfectly with EIP150 - CREATE2", async() => { - const { accounts, instance, web3 } = Create2; - const { result: newContract } = compile("./test/contracts/gas/", "GasLeft"); - const bytecode = newContract.contracts["GasLeft.sol"].GasLeft.evm.bytecode.object; - const byteCode = `0x${bytecode}${web3.eth.abi.encodeParameter("address", accounts[0]).slice(2)}`; - const salt = `0x${"0".repeat(63)}1`; - const futureAddress = `0x${web3.utils - .sha3( - `0x${["ff", instance._address, salt, web3.utils.sha3(byteCode)] - .map((val) => val.replace(/0x/, "")) - .join("")}` - ) - .slice(-40)}`.toLowerCase(); - const codeCheck = await web3.eth.getCode(futureAddress); - assert(codeCheck.slice(2).length === 0, "contract should not be deployed on chain!"); - - const nonce = await web3.eth.getTransactionCount(accounts[0]); - const result = await instance.methods.deploy(byteCode, salt).send({ - from: accounts[0], - gas: 4500000, - gasPrice: 10000000000, - nonce - }); - - const addr = result.events.RelayAddress.returnValues.addr; - assert(addr, futureAddress, "future contract address not the same as computed value"); - const codeCheck2 = await web3.eth.getCode(futureAddress); - assert(codeCheck2.slice(2).length > 0, "contract should be deployed on chain!"); - }).timeout(1000000); - - // TODO: Make this actually test SVT - it("Should estimate gas perfectly with EIP150 - Simple Value Transfer", async() => { - const { accounts, instance, send, web3 } = SendContract; - const toBN = (hex) => new BN(hex.substring(2), "hex"); - const toBNStr = (hex, base = 10) => toBN(hex).toString(base); - const sign = createSignedTx(privateKey); - const gasPrice = "0x77359400"; - const amountToTransfer = "0xfffffff1ff000000"; - - // Get initial Balance after contract deploy - const { result: balance } = await send("eth_getBalance", accounts[0]); - - // Initial seeding of capital to contract, we check the balance after. - const tx = { - gasPrice, - value: amountToTransfer, // ~18 ether - from: accounts[0], - to: instance._address - }; - ({ result: tx.gasLimit } = await send("eth_estimateGas", tx)); - const { result: hash } = await send("eth_sendTransaction", tx); - const { - result: { gasUsed: initialGasUsed } - } = await send("eth_getTransactionReceipt", hash); - - // Assert that the contract has the correct balance ~18 ether - const getBalance = await instance.methods.getBalance().call({ from: accounts[0] }); - assert.strictEqual(toBNStr(amountToTransfer), getBalance, "balance is not ~18 ether"); - - // It's not neccessary to sign but currently it's the only test that demonstrates sending signed - // transactions that call a contract method. - // Calling `encodeABI()` on the desired contract method will return the - // the necessary bytecode to call the contract method with the given parameters - // NOTE: errors from encodeABI are likely do to incorrect types for the method arguments - // Double check the contracts method signature and your arguments - // Ex: transfer(Address[], uint)) => transfer(Array, hex string of int) - const txParams = { - gasPrice, - nonce: "0x2", - to: instance._address, - value: "0x0", - data: instance.methods.transfer(accounts, amountToTransfer).encodeABI() - }; - ({ result: txParams.gasLimit } = await send("eth_estimateGas", sign(txParams).serialize())); - const { gasUsed: signedGasUsed } = await web3.eth.sendSignedTransaction(sign(txParams).serialize()); - const { result: newBalance } = await send("eth_getBalance", accounts[0]); - // Gasprice * ( sum of gas used ) - const gas = toBN(gasPrice).mul(toBN(initialGasUsed).addn(signedGasUsed)); - // Our current balance, plus the wei spent on gas === original gas - const currentBalancePlusGas = toBN(newBalance) - .add(gas) - .toString(); - assert.strictEqual(toBNStr(balance), currentBalancePlusGas, "balance + gas used !== to start balance"); - - // Assert that signed tx successfully drains contract to address - const newContractBalance = await instance.methods.getBalance().call({ from: accounts[0] }); - assert.strictEqual(newContractBalance, "0", "balance is not 0"); - }).timeout(10000); - } - - it("should correctly handle non-zero value child messages", async() => { - const { - accounts: [from], - instance: { _address: to, methods }, - send - } = NonZero; - const fns = [methods.doSend, methods.doTransfer, methods.doCall]; - for (let i = 0, l = fns; i < l.length; i++) { - const tx = { - from, - to, - value: "1000000000000000000", - data: fns[i]().encodeABI() - }; - const { result: gasLimit } = await send("eth_estimateGas", tx); - tx.gasLimit = "0x" + (parseInt(gasLimit) - 1).toString(16); - await assert.rejects(() => send("eth_sendTransaction", tx), { - message: "VM Exception while processing transaction: out of gas" - }); - tx.gasLimit = gasLimit; - await assert.doesNotReject( - () => send("eth_sendTransaction", tx), - undefined, - `SANITY CHECK. Still not enough gas? ${gasLimit} Our estimate is still too low` - ); - } - }); - }); - - describe("Refunds", function() { - it( - "accounts for Rsclear Refund in gasEstimate when a dirty storage slot is reset and it's original " + - "value is 0", - async function() { - const { accounts, instance, provider } = context; - const from = accounts[0]; - const options = { from, gas: 5000000 }; - - // prime storage by making sure it is set to 0 - await instance.methods.reset().send(options); - - // update storage and then reset it back to 0 - const method = instance.methods.triggerRsclearRefund(); - - const estimate = await method.estimateGas(options); - - const receipt = await method.send({ from, gas: estimate }); - - switch (provider.options.hardfork) { - case "byzantium": - case "petersburg": - assert.strictEqual(receipt.gasUsed, estimate - RSCLEAR_REFUND); - break; - case "muirGlacier": - case "istanbul": - // EIP-2200 - assert(receipt.gasUsed <= estimate - RSCLEAR_REFUND_FOR_RESETTING_DIRTY_SLOT_TO_ZERO_ISTANBUL + 2300); - break; - case "constantinople": - // since storage was initially primed to 0 and we call triggerRsclearRefund(), which then - // resets storage back to 0, 19800 gas is added to the refund counter per Constantinople EIP 1283 - assert.strictEqual(receipt.gasUsed, estimate - RSCLEAR_REFUND_FOR_RESETTING_DIRTY_SLOT_TO_ZERO); - break; - default: - throw new Error("Invalid hardfork option: " + provider.options.hardfork); - } - assert.strictEqual(receipt.gasUsed, receipt.cumulativeGasUsed); - } - ); - - it( - "accounts for Rsclear Refund in gasEstimate when a dirty storage slot is reset and it's " + - "original value is not 0", - async function() { - const { accounts, instance, provider } = context; - const from = accounts[0]; - const rsclearRefundForResettingDirtySlotToNonZeroValue = 4800; - const rsclearRefundForResettingDirtySlotToNonZeroValueIstanbul = 4200; - const options = { from, gas: 5000000 }; - - await instance.methods.reset().send(options); // prime storage by making sure y is set to 1 - - // update storage and then reset it back to 1 - const method = instance.methods.triggerRsclearRefundForY(); - - const gasEstimate = await method.estimateGas(options); - - const receipt = await method.send({ from, gas: gasEstimate }); - - switch (provider.options.hardfork) { - case "byzantium": - case "petersburg": - // since we are resetting to a non-zero value, there is no gas added to the refund counter here - assert.strictEqual(receipt.gasUsed, gasEstimate); - break; - case "muirGlacier": - case "istanbul": // EIP-2200 - assert( - receipt.gasUsed <= gasEstimate - rsclearRefundForResettingDirtySlotToNonZeroValueIstanbul + 2300 - ); - break; - case "constantinople": - // since storage was initially primed to 1 and we call triggerRsclearRefundForY(), which then - // resets storage back to 1, 4800 gas is added to the refund counter per Constantinople EIP 1283 - assert.strictEqual(receipt.gasUsed, gasEstimate - rsclearRefundForResettingDirtySlotToNonZeroValue); - break; - default: - throw new Error("Invalid hardfork option: " + provider.options.hardfork); - } - assert.strictEqual(receipt.gasUsed, receipt.cumulativeGasUsed); - } - ); - - it( - "accounts for Rsclear Refund in gasEstimate when a fresh storage slot's original " + - "value is not 0 and new value is 0", - async function() { - const { accounts, instance, provider } = context; - const from = accounts[0]; - const options = { from, gas: 5000000 }; - - // prime storage by making sure storage is set to 1 - await instance.methods.initialSettingOfX().send(options); - - // update storage to be 0 - const method = instance.methods.reset(); - - const gasEstimate = await method.estimateGas(options); - - const receipt = await method.send({ from, gas: gasEstimate }); - - switch (provider.options.hardfork) { - case "byzantium": - case "petersburg": - case "constantinople": - assert.strictEqual(receipt.gasUsed, gasEstimate - RSCLEAR_REFUND); - break; - case "muirGlacier": - case "istanbul": // EIP-2200 - assert(receipt.gasUsed <= gasEstimate - RSCLEAR_REFUND + 2300); - break; - default: - throw new Error("Invalid hardfork option: " + provider.options.hardfork); - } - assert.strictEqual(receipt.gasUsed, receipt.cumulativeGasUsed); - } - ); - - it( - "accounts for Rsclear Refund in gasEstimate when a dirty storage slot's original value " + - "is not 0 and new value is 0", - async function() { - const { accounts, instance, provider } = context; - const from = accounts[0]; - const options = { from, gas: 5000000 }; - - // prime storage by making sure storage is set to 1 - await instance.methods.initialSettingOfX().send(options); - - // update storage and then reset it to 0 - const method = instance.methods.triggerRsclearRefund(); - - const gasEstimate = await method.estimateGas(options); - - const receipt = await method.send({ from, gas: gasEstimate }); - - switch (provider.options.hardfork) { - case "byzantium": - case "petersburg": - case "constantinople": - assert.strictEqual(receipt.gasUsed, gasEstimate - RSCLEAR_REFUND); - break; - case "muirGlacier": - case "istanbul": // EIP-2200 - assert(receipt.gasUsed <= gasEstimate - RSCLEAR_REFUND + 2300); - break; - default: - throw new Error("Invalid hardfork option: " + provider.options.hardfork); - } - assert.strictEqual(receipt.gasUsed, receipt.cumulativeGasUsed); - } - ); - - it( - "accounts for Rsclear Refund in gasEstimate when a dirty storage slot's original value " + - "is not 0 and current value is 0", - async function() { - const { accounts, instance, provider } = context; - const from = accounts[0]; - const options = { from, gas: 5000000 }; - - // prime storage by making sure storage is set to 1 - await instance.methods.initialSettingOfX().send(options); - - // updates current value to 0 and new value to be the remaining amount of gas - const method = instance.methods.triggerRsclearRefundForX(); - - const gasEstimate = await method.estimateGas(options); - - const receipt = await method.send({ from, gas: gasEstimate }); - - switch (provider.options.hardfork) { - case "byzantium": - case "petersburg": - assert.strictEqual(receipt.gasUsed, gasEstimate - RSCLEAR_REFUND); - break; - case "muirGlacier": - case "istanbul": - assert(receipt.gasUsed <= gasEstimate + 2300); - break; - case "constantinople": - // since storage was initially primed to 1 and we call triggerRsclearRefundForX(), which then - // resets storage's current value to 0 and 15000 gas is added to the refund counter, and then - // it replaces x with gasleft, which removes 150000 gas from the refund counter per Constantinople - // EIP 1283 leaving us with a rsclear refund of 0 - assert.strictEqual(receipt.gasUsed, gasEstimate); - break; - default: - throw new Error("Invalid hardfork option: " + provider.options.hardfork); - } - assert.strictEqual(receipt.gasUsed, receipt.cumulativeGasUsed); - } - ); - - it("accounts for Rselfdestruct Refund in gasEstimate", async function() { - const { abi, accounts, bytecode, web3 } = context; - const from = accounts[0]; - const options = { from, gas: 5000000 }; - - const deploymentOptions = { gas: 3141592 }; - const { instance } = await deploy(abi, bytecode, web3, deploymentOptions); - await instance.methods.reset().send(options); // prime storage by making sure it is set to 0 - - const method = instance.methods.triggerRselfdestructRefund(); - - const gasEstimate = await method.estimateGas(options); - - const receipt = await method.send({ from, gas: gasEstimate }); - - assert.strictEqual(receipt.gasUsed, gasEstimate - RSELFDESTRUCT_REFUND); - assert.strictEqual(receipt.gasUsed, receipt.cumulativeGasUsed); - }); - - it("accounts for Rsclear and Rselfdestruct Refunds in gasEstimate", async function() { - const { abi, accounts, bytecode, provider, web3 } = context; - const from = accounts[0]; - - const deploymentOptions = { gas: 3141592 }; - const { instance } = await deploy(abi, bytecode, web3, deploymentOptions); - await instance.methods.reset().send({ from, gas: 5000000 }); // prime storage by making sure it is set to 0 - - const method = instance.methods.triggerAllRefunds(); - - const gasEstimate = await method.estimateGas({ from }); - - const receipt = await method.send({ from, gas: gasEstimate }); - - switch (provider.options.hardfork) { - case "byzantium": - case "petersburg": - assert.strictEqual(receipt.gasUsed, gasEstimate - RSELFDESTRUCT_REFUND - RSCLEAR_REFUND); - break; - case "muirGlacier": - case "istanbul": // EIP-2200 - assert.strictEqual( - receipt.gasUsed, - gasEstimate - RSELFDESTRUCT_REFUND - RSCLEAR_REFUND_FOR_RESETTING_DIRTY_SLOT_TO_ZERO_ISTANBUL - ); - break; - case "constantinople": - // since storage was initially primed to 0 and we call triggerAllRefunds(), which then - // resets storage back to 0, 19800 gas is added to the refund counter per Constantinople EIP 1283 - assert.strictEqual( - receipt.gasUsed, - gasEstimate - RSELFDESTRUCT_REFUND - RSCLEAR_REFUND_FOR_RESETTING_DIRTY_SLOT_TO_ZERO - ); - break; - default: - throw new Error("Invalid hardfork option: " + provider.options.hardfork); - } - assert.strictEqual(receipt.gasUsed, receipt.cumulativeGasUsed); - }); - - it("account Rsclear/Rselfdestruct/Refunds in gasEstimate w/many transactions in a block", async function() { - const { abi, bytecode, provider } = context; - const options = { - seed, - hardfork - }; - const { send, accounts, web3 } = await initializeTestProvider(options); - - const transactions = [ - { - value: "0x10000000", - gasLimit: "0x33450", - from: accounts[2], - to: accounts[1], - nonce: "0x0" - }, - { - value: "0x10000000", - gasLimit: "0x33450", - from: accounts[2], - to: accounts[1], - nonce: "0x1" - }, - { - value: "0x10000000", - gasLimit: "0x33450", - from: accounts[1], // < - to: accounts[2], // <^ reversed tx order - nonce: "0x0" - } - ]; - - // Precondition - const initialBlockNumber = await web3.eth.getBlockNumber(); - assert.deepStrictEqual(initialBlockNumber, 0, "Current Block Should be 0"); - - const deploymentOptions = { gas: 3141592 }; - const { instance } = await deploy(abi, bytecode, web3, deploymentOptions); - - // prime storage by making sure it is set to 0 - await instance.methods.reset().send({ from: accounts[0], gas: 5000000 }); - await send("miner_stop"); - const hashes = await Promise.all( - transactions.map((transaction) => { - const promiEvent = web3.eth.sendTransaction(transaction); - - return new Promise((resolve) => { - promiEvent.once("transactionHash", async(hash) => { - // Ensure there's no receipt since the transaction hasn't yet been processed. Ensure IntervalMining - const receipt = await web3.eth.getTransactionReceipt(hash); - assert.strictEqual(receipt, null, "No receipt since the transaction hasn't yet been processed."); - resolve(hash); - }); - }); - }) - ); - - const currentBlockNumber = await web3.eth.getBlockNumber(); - assert.deepStrictEqual(currentBlockNumber, 2, "Current Block Should be 2"); - - const method = instance.methods.triggerAllRefunds(); - const gasEstimate = await method.estimateGas({ from: accounts[0] }); - const prom = method.send({ from: accounts[0], gas: gasEstimate }); - await new Promise((resolve) => { - prom.once("transactionHash", resolve); - }); - await send("evm_mine"); - // web3 doesn't subscribe fast enough to newHeads after issuing the previous send - // we we mine another block to give it an additional newHeads notification. /shrug - await send("evm_mine"); - const rec = await prom; - const { gasUsed } = rec; - - let transactionCostMinusRefund = gasEstimate - RSELFDESTRUCT_REFUND - RSCLEAR_REFUND; - switch (provider.options.hardfork) { - case "byzantium": - case "petersburg": - assert.strictEqual(gasUsed, transactionCostMinusRefund); - break; - case "muirGlacier": - case "istanbul": - // EIP-2200 - transactionCostMinusRefund = - gasEstimate - RSELFDESTRUCT_REFUND - RSCLEAR_REFUND_FOR_RESETTING_DIRTY_SLOT_TO_ZERO_ISTANBUL; - assert.strictEqual(gasUsed, transactionCostMinusRefund); - break; - case "constantinople": - // since storage was initially primed to 0 and we call triggerAllRefunds(), which then - // resets storage back to 0, 19800 gas is added to the refund counter per Constantinople EIP 1283 - transactionCostMinusRefund = - gasEstimate - RSELFDESTRUCT_REFUND - RSCLEAR_REFUND_FOR_RESETTING_DIRTY_SLOT_TO_ZERO; - assert.strictEqual(gasUsed, transactionCostMinusRefund); - break; - default: - throw new Error("Invalid hardfork option: " + provider.options.hardfork); - } - - const receipt = await Promise.all(hashes.map((hash) => web3.eth.getTransactionReceipt(hash))); - assert.deepStrictEqual(receipt[0].gasUsed, receipt[1].gasUsed, "Tx1 and Tx2 should cost the same gas."); - assert.deepStrictEqual( - receipt[1].gasUsed, - receipt[2].gasUsed, - "Tx2 and Tx3 should cost the same gas. -> Tx1 gas === Tx3 gas Transitive" - ); - assert.deepStrictEqual( - receipt[1].transactionIndex > receipt[2].transactionIndex, - true, - "(Tx3 has a lower nonce) -> (Tx3 index is < Tx2 index)" - ); - const currentBlock = await web3.eth.getBlock(receipt[0].blockNumber); - - // ( Tx3 has a lower nonce -> Tx3 index is < Tx2 index ) -> cumulative gas Tx2 > Tx3 > Tx1 - const isAccumulating = - receipt[1].cumulativeGasUsed > receipt[2].cumulativeGasUsed && - receipt[2].cumulativeGasUsed > receipt[0].cumulativeGasUsed; - - assert.deepStrictEqual( - isAccumulating, - true, - "Cumulative gas should be accumulating for any transactions in the same block." - ); - - assert.deepStrictEqual( - receipt[0].gasUsed, - receipt[0].cumulativeGasUsed, - "Gas and cumulative gas should be equal for the FIRST Tx." - ); - - assert.notDeepStrictEqual( - receipt[1].gasUsed, - receipt[1].cumulativeGasUsed, - "Gas and cumulative gas should NOT be equal for the Second Tx." - ); - - assert.notDeepStrictEqual( - receipt[2].gasUsed, - receipt[2].cumulativeGasUsed, - "Gas and cumulative gas should NOT be equal for the Third Tx." - ); - - const totalGas = receipt[0].gasUsed + receipt[1].gasUsed + receipt[2].gasUsed; - assert.deepStrictEqual( - totalGas + transactionCostMinusRefund, - receipt[1].cumulativeGasUsed, - "Total Gas should equal the final tx.cumulativeGas" - ); - - assert.deepStrictEqual( - totalGas + transactionCostMinusRefund, - currentBlock.gasUsed, - "Total Gas should be equal to the currentBlock.gasUsed" - ); - }); - - it("clears mapping storage slots", async function() { - const { accounts, instance } = context; - const from = accounts[0]; - const options = { from }; - - await instance.methods.reset().send({ from, gas: 5000000 }); - - const uintsa = await instance.methods.uints(1).call(); - assert.strictEqual(uintsa, "0", "initial value is not correct"); - - const receipta = await instance.methods.store(1).send(options); - assert.strictEqual(receipta.status, true, "storing value did not work"); - - const uintsb = await instance.methods.uints(1).call(); - assert.strictEqual(uintsb, "1", "set value is incorrect"); - - const receiptb = await instance.methods.clear().send(options); - assert.strictEqual(receiptb.status, true, "clearing value did not work"); - - const uintsc = await instance.methods.uints(1).call(); - assert.strictEqual(uintsc, "0", "cleared value is not correct"); - }); - }).timeout(4000); - - describe("Estimation", function() { - it("matches estimate for deployment", async function() { - const { accounts, bytecode, contract, receipt } = context; - const gasEstimate = await contract.deploy({ data: bytecode }).estimateGas({ - from: accounts[1] - }); - - assert.deepStrictEqual(receipt.gasUsed, gasEstimate); - assert.deepStrictEqual(receipt.cumulativeGasUsed, gasEstimate); - }).timeout(4000); - - it("matches usage for complex function call (add)", async function() { - const { accounts, instance } = context; - await testTransactionEstimate( - instance.methods.add, - [toBytesHexString("Tim"), toBytesHexString("A great guy"), 10], - { from: accounts[0], gas: 3141592 }, - instance - ); - }).timeout(10000); - - it("matches usage for complex function call (transfer)", async function() { - const { accounts, instance } = context; - await testTransactionEstimate( - instance.methods.transfer, - ["0x0123456789012345678901234567890123456789", 5, toBytesHexString("Tim")], - { from: accounts[0], gas: 3141592 }, - instance - ); - }).timeout(10000); - - it("matches usage for simple account to account transfer", async function() { - const { accounts, web3 } = context; - const transferAmount = web3.utils.toBN(web3.utils.toWei("5", "finney")); - const transactionData = { - from: accounts[0], - to: accounts[1], - value: transferAmount - }; - - const web3Transactions = [ - await web3.eth.estimateGas(transactionData), - await web3.eth.sendTransaction(transactionData) - ]; - const [gasEstimate, receipt] = await Promise.all(web3Transactions); - - assert.strictEqual(receipt.gasUsed, gasEstimate); - }); - }); - - describe("Expenditure", function() { - it("should calculate gas expenses correctly in consideration of the default gasPrice", async function() { - const { accounts, web3 } = context; - const transferAmount = "500"; - const gasPrice = await web3.eth.getGasPrice(); - await confirmGasPrice(gasPrice, false, web3, accounts, transferAmount); - }); - - it("should calculate gas expenses correctly in consideration of the requested gasPrice", async function() { - const transferAmount = "500"; - const gasPrice = "0x10000"; - const { accounts, web3 } = context; - await confirmGasPrice(gasPrice, true, web3, accounts, transferAmount); - }); - - it("should calculate gas expenses correctly with a user-defined default gasPrice", async function() { - const transferAmount = "500"; - const gasPrice = "0x2000"; - const options = { seed, gasPrice }; - const { accounts, web3 } = await initializeTestProvider(options); - await confirmGasPrice(gasPrice, false, web3, accounts, transferAmount); - }); - - it("should calculate cumalativeGas and gasUsed correctly for many transactions in a block", async function() { - const options = { - blockTime: 0.5, // seconds - seed - }; - const { send, accounts, web3 } = await initializeTestProvider(options); - await send("miner_stop"); - - const transactions = [ - { - value: "0x10000000", - gasLimit: "0x33450", - from: accounts[0], - to: accounts[1], - nonce: "0x0" - }, - { - value: "0x10000000", - gasLimit: "0x33450", - from: accounts[0], - to: accounts[1], - nonce: "0x1" - }, - { - value: "0x10000000", - gasLimit: "0x33450", - from: accounts[1], // < - to: accounts[0], // <^ reversed tx order - nonce: "0x0" - } - ]; - - // Precondition - const initialBlockNumber = await web3.eth.getBlockNumber(); - assert.deepStrictEqual(initialBlockNumber, 0, "Current Block Should be 0"); - - const hashes = await Promise.all( - transactions.map((transaction) => { - const promiEvent = web3.eth.sendTransaction(transaction); - - return new Promise((resolve) => { - promiEvent.once("transactionHash", async(hash) => { - // Ensure there's no receipt since the transaction hasn't yet been processed. Ensure IntervalMining - const receipt = await web3.eth.getTransactionReceipt(hash); - assert.strictEqual(receipt, null, "No receipt since the transaction hasn't yet been processed."); - - resolve(hash); - }); - }); - }) - ); - - await send("evm_mine"); - - const currentBlockNumber = await web3.eth.getBlockNumber(); - assert.deepStrictEqual(currentBlockNumber, 1, "Current Block Should be 1"); - - const [currentBlock, receipt] = await Promise.all([ - web3.eth.getBlock(currentBlockNumber), - Promise.all(hashes.map((hash) => web3.eth.getTransactionReceipt(hash))) - ]); - - assert.deepStrictEqual(receipt[0].gasUsed, receipt[1].gasUsed, "Tx1 and Tx2 should cost the same gas."); - assert.deepStrictEqual( - receipt[1].gasUsed, - receipt[2].gasUsed, - "Tx2 and Tx3 should cost the same gas. -> Tx1 gas === Tx3 gas Transitive" - ); - assert.deepStrictEqual( - receipt[1].transactionIndex > receipt[2].transactionIndex, - true, - "(Tx3 has a lower nonce) -> (Tx3 index is < Tx2 index)" - ); - - // ( Tx3 has a lower nonce -> Tx3 index is < Tx2 index ) -> cumulative gas Tx2 > Tx3 > Tx1 - const isAccumulating = - receipt[1].cumulativeGasUsed > receipt[2].cumulativeGasUsed && - receipt[2].cumulativeGasUsed > receipt[0].cumulativeGasUsed; - assert.deepStrictEqual( - isAccumulating, - true, - "Cumulative gas should be accumulating for any transactions in the same block." - ); - assert.deepStrictEqual( - receipt[0].gasUsed, - receipt[0].cumulativeGasUsed, - "Gas and cumulative gas should be equal for the FIRST Tx." - ); - assert.notDeepStrictEqual( - receipt[1].gasUsed, - receipt[1].cumulativeGasUsed, - "Gas and cumulative gas should NOT be equal for the Second Tx." - ); - assert.notDeepStrictEqual( - receipt[2].gasUsed, - receipt[2].cumulativeGasUsed, - "Gas and cumulative gas should NOT be equal for the Third Tx." - ); - - const totalGas = receipt[0].gasUsed + receipt[1].gasUsed + receipt[2].gasUsed; - assert.deepStrictEqual( - totalGas, - receipt[1].cumulativeGasUsed, - "Total Gas should be equal the final tx.cumulativeGas" - ); - assert.deepStrictEqual( - totalGas, - currentBlock.gasUsed, - "Total Gas should be equal to the currentBlock.gasUsed" - ); - }).timeout(4000); - }); - }); - }); -}); diff --git a/test/local/gas/gasLimit.js b/test/local/gas/gasLimit.js deleted file mode 100644 index 1e743b7ba9..0000000000 --- a/test/local/gas/gasLimit.js +++ /dev/null @@ -1,24 +0,0 @@ -const assert = require("assert"); -const to = require("../../../lib/utils/to.js"); -const initializeTestProvider = require("../../helpers/web3/initializeTestProvider"); -const randomInteger = require("../../helpers/utils/generateRandomInteger"); -const SEED_RANGE = 1000000; - -describe("Gas", function() { - describe("options:gasLimit", function() { - let context; - before("Setting up web3", async function() { - this.timeout(10000); - const seed = randomInteger(SEED_RANGE); - const ganacheProviderOptions = { seed }; - context = await initializeTestProvider(ganacheProviderOptions); - }); - - it("should respect the assigned gasLimit", async function() { - const { provider, web3 } = context; - const assignedGasLimit = provider.engine.manager.state.blockchain.blockGasLimit; - const { gasLimit } = await web3.eth.getBlock("latest"); - assert.deepStrictEqual(gasLimit, to.number(assignedGasLimit)); - }); - }); -}); diff --git a/test/local/gas/gasPrice.js b/test/local/gas/gasPrice.js deleted file mode 100644 index 37f80ecc79..0000000000 --- a/test/local/gas/gasPrice.js +++ /dev/null @@ -1,51 +0,0 @@ -const assert = require("assert"); -const bootstrap = require("../../helpers/contract/bootstrap"); -const { hex } = require("../../../lib/utils/to"); -const randomInteger = require("../../helpers/utils/generateRandomInteger"); -const SEED_RANGE = 1000000; - -describe("Gas", function() { - describe("options:gasPrice", function() { - const contractRef = { - contractFiles: ["Example"], - contractSubdirectory: "examples" - }; - - describe("default gasPrice", async function() { - this.timeout(10000); - it("should respect the default gasPrice", async function() { - const ganacheProviderOptions = {}; - const context = await bootstrap(contractRef, ganacheProviderOptions); - const { accounts, instance, provider, web3 } = context; - - const assignedGasPrice = provider.engine.manager.state.gasPriceVal; - - const { transactionHash } = await instance.methods.setValue("0x10").send({ from: accounts[0], gas: 3141592 }); - const { gasPrice } = await web3.eth.getTransaction(transactionHash); - - assert.deepStrictEqual(hex(gasPrice), hex(assignedGasPrice)); - }); - }); - - describe("zero gasPrice", async function() { - this.timeout(10000); - it("should be possible to set a zero gas price", async function() { - const seed = randomInteger(SEED_RANGE); - const ganacheProviderOptions = { - seed, - gasPrice: 0 - }; - const context = await bootstrap(contractRef, ganacheProviderOptions); - - const { accounts, instance, provider, web3 } = context; - - const assignedGasPrice = provider.engine.manager.state.gasPriceVal; - assert.deepStrictEqual(hex(assignedGasPrice), "0x0"); - - const { transactionHash } = await instance.methods.setValue("0x10").send({ from: accounts[0], gas: 3141592 }); - const { gasPrice } = await web3.eth.getTransaction(transactionHash); - assert.deepStrictEqual(hex(gasPrice), hex(assignedGasPrice)); - }); - }); - }); -}); diff --git a/test/local/gas/lib/confirmGasPrice.js b/test/local/gas/lib/confirmGasPrice.js deleted file mode 100644 index 3b16d98394..0000000000 --- a/test/local/gas/lib/confirmGasPrice.js +++ /dev/null @@ -1,61 +0,0 @@ -const assert = require("assert"); -const to = require("../../../../lib/utils/to.js"); -const numberToBN = require("number-to-bn"); - -const confirmGasPrice = async( - expectedGasPrice, - setGasPriceOnTransaction = false, - web3, - accounts, - transferAmount = "5" -) => { - // Convert transferAmount into a big number - const _transferAmount = web3.utils.toWei(numberToBN(transferAmount), "finney"); - - // Convert expected gas price into a big number - const expectedGasPriceBN = numberToBN(expectedGasPrice); - - // Get balance of account - const balance = await web3.eth.getBalance(accounts[0]); - - // Convert balance into a big number - const initialBalance = numberToBN(balance); - - const params = { - from: accounts[0], - to: accounts[1], - value: _transferAmount - }; - - // Check to set the gas price - if (setGasPriceOnTransaction) { - params.gasPrice = expectedGasPriceBN; - } - // Transfer funds between two accounts - const receipt = await web3.eth.sendTransaction(params); - - // Convert gasUsed to a big number - const gasUsed = numberToBN(receipt.gasUsed); - - // Get balance of sender account - const finalBalance = await web3.eth.getBalance(accounts[0]); - - // Subtract the initial balance from the final balance - const deltaBalance = initialBalance.sub(numberToBN(finalBalance)); - - // the amount we paid in excess of our transferAmount is what we spent on gas - const gasExpense = deltaBalance.sub(_transferAmount); - - assert(!gasExpense.eq(numberToBN("0")), "Calculated gas expense must be nonzero."); - - // gas expense is just gasPrice * gasUsed, so just solve accordingly - const actualGasPriceBN = gasExpense.div(gasUsed); - - assert( - expectedGasPriceBN.eq(actualGasPriceBN), - `Gas price used by EVM (${to.hex(actualGasPriceBN)}) was different from` + - ` expected gas price (${to.hex(expectedGasPriceBN)})` - ); -}; - -module.exports = confirmGasPrice; diff --git a/test/local/gas/lib/transactionEstimate.js b/test/local/gas/lib/transactionEstimate.js deleted file mode 100644 index a645293b27..0000000000 --- a/test/local/gas/lib/transactionEstimate.js +++ /dev/null @@ -1,14 +0,0 @@ -const assert = require("assert"); - -const testTransactionEstimate = async(contractFn, args, options, instance) => { - await instance.methods.reset().send({ from: options.from, gas: 5000000 }); - const method = contractFn(...args); - const gasEstimate = await method.estimateGas(options); - const receipt = await method.send(options); - - assert.strictEqual(receipt.status, true, "Transaction must succeed"); - assert.strictEqual(receipt.gasUsed, gasEstimate, "gasUsed"); - assert.strictEqual(receipt.cumulativeGasUsed, gasEstimate, "estimate"); -}; - -module.exports = testTransactionEstimate; diff --git a/test/local/hex.js b/test/local/hex.js deleted file mode 100644 index c5ceca9efa..0000000000 --- a/test/local/hex.js +++ /dev/null @@ -1,40 +0,0 @@ -const assert = require("assert"); -const to = require("../../lib/utils/to.js"); - -describe("to.rpcQuantityHexString", function() { - it("should print '0x0' for input '0x'", function() { - assert.strictEqual(to.rpcQuantityHexString("0x"), "0x0"); - }); - - it("should print '0x0' for input 0", function() { - assert.strictEqual(to.rpcQuantityHexString(0), "0x0"); - }); - - it("should print '0x0' for input '0'", function() { - assert.strictEqual(to.rpcQuantityHexString("0"), "0x0"); - }); - - it("should print '0x0' for input '000'", function() { - assert.strictEqual(to.rpcQuantityHexString("000"), "0x0"); - }); - - it("should print '0x0' for input '0x000'", function() { - assert.strictEqual(to.rpcQuantityHexString("0x000"), "0x0"); - }); - - it("should print '0x20' for input '0x0020'", function() { - assert.strictEqual(to.rpcQuantityHexString("0x0020"), "0x20"); - }); -}); - -describe("to.rpcDataHexString", function() { - it("should differentiate between a list of 0 items and a list of one 0", function() { - assert.notStrictEqual(to.rpcDataHexString(Buffer.from("", "hex")), to.rpcDataHexString(Buffer.from("00", "hex"))); - }); -}); - -describe("to.hex", function() { - it("should print '0x' for input '' (blank)", function() { - assert.strictEqual(to.hex(Buffer.from("")), "0x"); - }); -}); diff --git a/test/local/interval-mining.js b/test/local/interval-mining.js deleted file mode 100644 index 64c82f5ce6..0000000000 --- a/test/local/interval-mining.js +++ /dev/null @@ -1,222 +0,0 @@ -const { BN } = require("ethereumjs-util"); -const assert = require("assert"); -const sleep = require("../helpers/utils/sleep"); -const initializeTestProvider = require("../helpers/web3/initializeTestProvider"); -const { compile } = require("../helpers/contract/compileAndDeploy"); -const generateRandomInteger = require("../helpers/utils/generateRandomInteger"); -const seed = generateRandomInteger(1000000); - -describe("Interval Mining", function() { - let firstAddress; - - describe("Interval mining", function() { - let context; - before("Setting up provider and web3 instance", async function() { - context = await initializeTestProvider({ - blockTime: 0.5, // seconds - seed - }); - firstAddress = context.accounts[0]; - }); - - it("should mine a block on the interval", async function() { - this.timeout(5000); - const { web3 } = context; - - // Get the first block (pre-condition) - const blockNumber = await web3.eth.getBlockNumber(); - assert.strictEqual(blockNumber, 0); - - // Wait 1.25 seconds (two and a half mining intervals) then get the next block. - // It should be block number 2 (the third block). We wait more than one iteration - // to ensure the timeout gets reset. - await sleep(1250); - - const latestNumber = await web3.eth.getBlockNumber(); - assert.strictEqual(latestNumber, 2); - }); - }); - - describe("Instamine vs Interval", function() { - let context; - before("Setting up provider and web3 instance", async function() { - context = await initializeTestProvider({ - blockTime: 0.25, // seconds - seed - }); - }); - - it("shouldn't instamine when mining on an interval", function(done) { - this.timeout(5000); - const { web3 } = context; - - // Get the first block (pre-condition) - web3.eth.getBlockNumber(function(err, number) { - if (err) { - return done(err); - } - assert.strictEqual(number, 0); - - // Queue a transaction - web3.eth.sendTransaction( - { - from: firstAddress, - to: "0x1234567890123456789012345678901234567890", - value: web3.utils.toWei(new BN(1), "ether"), - gas: 90000 - }, - function(err, tx) { - if (err) { - return done(err); - } - - // Ensure there's no receipt since the transaction hasn't yet been processed. - web3.eth.getTransactionReceipt(tx, function(err, receipt) { - if (err) { - return done(err); - } - - assert.strictEqual(receipt, null); - - // Wait .75 seconds (one and a half mining intervals) then get the receipt. It should be processed. - - setTimeout(function() { - // Get the first block (pre-condition) - web3.eth.getTransactionReceipt(tx, function(err, newReceipt) { - if (err) { - return done(err); - } - - assert.notStrictEqual(newReceipt, null); - done(); - }); - }, 750); - }); - } - ); - }); - }); - }); - - describe("miner_start and restart", function() { - let context; - before("Setting up provider and web3 instance", async function() { - context = await initializeTestProvider({ - blockTime: 0.5, // seconds - seed - }); - }); - - it("miner_stop should stop interval mining, and miner_start should start it again", function(done) { - this.timeout(5000); - const { provider, web3 } = context; - - // Stop mining - provider.send( - { - jsonrpc: "2.0", - method: "miner_stop", - id: new Date().getTime() - }, - function(err, result) { - if (err) { - return done(err); - } - if (result.error) { - return done(result.error.message); - } - - // Get the first block (pre-condition) - web3.eth.getBlockNumber(function(err, initialNumber) { - if (err) { - return done(err); - } - - // Wait .75 seconds (one and a half mining intervals) and ensure - // the block number hasn't increased. - setTimeout(function() { - web3.eth.getBlockNumber(function(err, stoppedNumber) { - if (err) { - return done(err); - } - assert.strictEqual(stoppedNumber, initialNumber); - - // Start mining again - provider.send( - { - jsonrpc: "2.0", - method: "miner_start", - params: [1], - id: new Date().getTime() - }, - function(err, result) { - if (err) { - return done(err); - } - if (result.error) { - return done(result.error.message); - } - - // Wait .75 seconds (one and a half mining intervals) and ensure - // the block number has increased by one. - setTimeout(function() { - web3.eth.getBlockNumber(function(err, lastNumber) { - if (err) { - return done(err); - } - - assert(lastNumber > stoppedNumber); - done(); - }); - }, 750); - } - ); - }); - }, 750); - }); - } - ); - }); - }); - - describe("Logging errors", function() { - let context; - let logData = ""; - - before("Setting up provider and web3 instance", async function() { - const logger = { - log: function(message) { - logData += message + "\n"; - } - }; - - context = await initializeTestProvider({ - blockTime: 0.5, // seconds - seed, - logger - }); - }); - - before("Compile contract", async function() { - this.timeout(10000); - const contractFilename = "Example2"; - const subcontractFiles = []; - const contractSubdirectory = "examples"; - const { bytecode } = await compile(contractFilename, subcontractFiles, contractSubdirectory); - - context.bytecode = bytecode; - }); - - it("should log runtime errors to the log", async function() { - this.timeout(5000); - const { bytecode, web3 } = context; - - await assert.rejects( - web3.eth.sendTransaction({ from: firstAddress, data: bytecode, gas: 3141592 }), - /The contract code couldn't be stored, please check your gas limit/ - ); - - assert(logData.indexOf("Runtime Error: revert") >= 0); - }); - }); -}); diff --git a/test/local/library.js b/test/local/library.js deleted file mode 100644 index 3190e60211..0000000000 --- a/test/local/library.js +++ /dev/null @@ -1,98 +0,0 @@ -const Web3 = require("web3"); -const Ganache = require(process.env.TEST_BUILD - ? "../../build/ganache.core." + process.env.TEST_BUILD + ".js" - : "../../index.js"); -const fs = require("fs"); -const path = require("path"); -const solc = require("solc"); -const linker = require("solc/linker"); -const assert = require("assert"); - -// Thanks solc. At least this works! -// This removes solc's overzealous uncaughtException event handler. -process.removeAllListeners("uncaughtException"); - -describe("Libraries", function() { - let libraryData; - let libraryAbi; - let libraryAddress; - let contractAbi; - let contractInstance; - let contractBytecode; - - const provider = Ganache.provider(); - const web3 = new Web3(provider); - let accounts = []; - - before("get accounts", async() => { - accounts = await web3.eth.getAccounts(); - }); - - before("compile sources - library & contract", async() => { - this.timeout(10000); - const librarySource = fs.readFileSync(path.join(__dirname, "Library.sol"), "utf8"); - const contractSource = fs.readFileSync(path.join(__dirname, "CallLibrary.sol"), "utf8"); - const input = { - language: "Solidity", - sources: { - "Library.sol": { content: librarySource }, - "CallLibrary.sol": { content: contractSource } - }, - settings: { - outputSelection: { - "*": { - "*": ["*"] - } - } - } - }; - const result = JSON.parse(solc.compile(JSON.stringify(input))); - - const lib = result.contracts["Library.sol"].Library; - libraryData = "0x" + lib.evm.bytecode.object; - libraryAbi = lib.abi; - - const callLib = result.contracts["CallLibrary.sol"].CallLibrary; - contractBytecode = callLib.evm.bytecode.object; - contractAbi = callLib.abi; - }); - - before("deploy library", async() => { - const Library = new web3.eth.Contract(libraryAbi); - const promiEvent = Library.deploy({ data: libraryData }).send({ - from: accounts[0], - gas: 3141592 - }); - - promiEvent.on("receipt", function(receipt) { - libraryAddress = receipt.contractAddress; - }); - - await promiEvent; - }); - - before("deploy contract", async() => { - contractBytecode = linker.linkBytecode(contractBytecode, { "Library.sol:Library": libraryAddress }); - const contractData = "0x" + contractBytecode; - - const CallLibraryContract = new web3.eth.Contract(contractAbi); - const promiEvent = CallLibraryContract.deploy({ data: contractData }).send({ - from: accounts[0], - gas: 3141592 - }); - - contractInstance = await promiEvent; - }); - - after("cleanup", function() { - web3.setProvider(null); - provider.close(() => {}); - }); - - describe("msg.sender for external library function calls", async() => { - it("should return true - msg.sender is the externally owned account", async() => { - const result = await contractInstance.methods.callExternalLibraryFunction().call(); - assert.strictEqual(true, result); - }); - }); -}); diff --git a/test/local/mining.js b/test/local/mining.js deleted file mode 100644 index 739b8be991..0000000000 --- a/test/local/mining.js +++ /dev/null @@ -1,423 +0,0 @@ -const { BN } = require("ethereumjs-util"); -var Web3 = require("web3"); -var Ganache = require(process.env.TEST_BUILD - ? "../../build/ganache.core." + process.env.TEST_BUILD + ".js" - : "../../index.js"); -var assert = require("assert"); -var to = require("../../lib/utils/to.js"); -var solc = require("solc"); -var pify = require("pify"); - -describe("Mining", function() { - var web3 = new Web3( - Ganache.provider({ - vmErrorsOnRPCResponse: true - // logger: console, - }) - ); - var accounts; - var snapshotId; - var badBytecode; - var goodBytecode; - - function compileSolidity(source) { - const result = JSON.parse( - solc.compile( - JSON.stringify({ - language: "Solidity", - sources: { - "Contract.sol": { - content: source - } - }, - settings: { - outputSelection: { - "*": { - "*": ["*"] - } - } - } - }) - ) - ); - - return Promise.resolve({ - code: "0x" + result.contracts["Contract.sol"].Example.evm.bytecode.object - }); - } - - before("compile solidity code that causes runtime errors", async function() { - this.timeout(10000); - const result = await compileSolidity( - "pragma solidity ^0.6.0; contract Example { constructor() public {require(false);} }" - ); - badBytecode = result.code; - }); - - before("compile solidity code that causes an event", async function() { - this.timeout(10000); - const result = await compileSolidity( - "pragma solidity ^0.6.0; contract Example { event Event(); constructor() public { emit Event(); } }" - ); - goodBytecode = result.code; - }); - - beforeEach("checkpoint, so that we can revert later", async function() { - const res = await pify(web3.currentProvider.send)({ - jsonrpc: "2.0", - method: "evm_snapshot", - id: new Date().getTime() - }); - - snapshotId = res.result; - }); - - afterEach("revert back to checkpoint", async function() { - await pify(web3.currentProvider.send)({ - jsonrpc: "2.0", - method: "evm_revert", - params: [snapshotId], - id: new Date().getTime() - }); - }); - - // Everything's a Promise to add in readibility. - async function getBlockNumber() { - return to.number(await web3.eth.getBlockNumber()); - } - - async function startMining() { - await pify(web3.currentProvider.send)({ - jsonrpc: "2.0", - method: "miner_start", - params: [1], - id: new Date().getTime() - }); - } - - async function stopMining() { - await pify(web3.currentProvider.send)({ - jsonrpc: "2.0", - method: "miner_stop", - id: new Date().getTime() - }); - } - - async function checkMining() { - const response = await pify(web3.currentProvider.send)({ - jsonrpc: "2.0", - method: "eth_mining", - id: new Date().getTime() - }); - - return response.result; - } - - async function mineSingleBlock() { - const result = await pify(web3.currentProvider.send)({ - jsonrpc: "2.0", - method: "evm_mine", - id: new Date().getTime() - }); - assert.deepStrictEqual(result.result, "0x0"); - } - - async function queueTransaction(from, to, gasLimit, value, data) { - const response = await pify(web3.currentProvider.send)({ - jsonrpc: "2.0", - method: "eth_sendTransaction", - id: new Date().getTime(), - params: [ - { - from: from, - to: to, - gas: gasLimit, - value: value, - data: data - } - ] - }); - if (response.error) { - throw new Error(response.error.message); - } - return response.result; - } - - async function getCode(address) { - return web3.eth.getCode(address); - } - - before(async function() { - accounts = await web3.eth.getAccounts(); - }); - - it("should mine a single block with two queued transactions", async function() { - await stopMining(); - const blockNumber = await getBlockNumber(); - - const tx1 = await queueTransaction(accounts[0], accounts[1], 90000, web3.utils.toWei(new BN(2), "ether")); - const receipt1 = await web3.eth.getTransactionReceipt(tx1); - assert.strictEqual(receipt1, null); - - const tx2 = await queueTransaction(accounts[0], accounts[1], 90000, web3.utils.toWei(new BN(3), "ether")); - const receipt2 = await web3.eth.getTransactionReceipt(tx2); - assert.strictEqual(receipt2, null); - - await startMining(); - - const receipts = await Promise.all([web3.eth.getTransactionReceipt(tx1), web3.eth.getTransactionReceipt(tx2)]); - - assert.strictEqual(receipts.length, 2); - - assert.notStrictEqual(receipts[0], null); - assert.strictEqual(receipts[0].transactionHash, tx1); - assert.notStrictEqual(receipts[1], null); - assert.strictEqual(receipts[1].transactionHash, tx2); - assert.strictEqual( - receipts[0].blockNumber, - receipts[1].blockNumber, - "Transactions should be mined in the same block." - ); - - const number = await getBlockNumber(); - assert.strictEqual(number, blockNumber + 1); - }); - - it("should mine two blocks when two queued transactions won't fit into a single block", async function() { - // This is a very similar test to the above, except the gas limits are much higher - // per transaction. This means the Ganache will react differently and process - // each transaction it its own block. - - await stopMining(); - const blockNumber = await getBlockNumber(); - - const tx1 = await queueTransaction(accounts[0], accounts[1], 4000000, web3.utils.toWei(new BN(2), "ether")); - const receipt1 = await web3.eth.getTransactionReceipt(tx1); - assert.strictEqual(receipt1, null); - - const tx2 = await queueTransaction(accounts[0], accounts[1], 4000000, web3.utils.toWei(new BN(3), "ether")); - const receipt2 = await web3.eth.getTransactionReceipt(tx2); - assert.strictEqual(receipt2, null); - - await startMining(); - - const receipts = await Promise.all([web3.eth.getTransactionReceipt(tx1), web3.eth.getTransactionReceipt(tx2)]); - - assert.strictEqual(receipts.length, 2); - - assert.notStrictEqual(receipts[0], null); - assert.strictEqual(receipts[0].transactionHash, tx1); - - assert.notStrictEqual(receipts[1], null); - assert.strictEqual(receipts[1].transactionHash, tx2); - - assert.notStrictEqual( - receipts[0].blockNumber, - receipts[1].blockNumber, - "Transactions should not be mined in the same block." - ); - - const number = await getBlockNumber(); - assert.strictEqual(number, blockNumber + 2); - }); - - it( - "should mine one block when requested, and only one transaction, when two queued transactions" + - " together are larger than a single block", - async function() { - // This is a very similar test to the above, except we don't start mining again, - // we only mine one block by request. - - await stopMining(); - const blockNumber = await getBlockNumber(); - const tx1 = await queueTransaction(accounts[0], accounts[1], 4000000, web3.utils.toWei(new BN(2), "ether")); - const receipt1 = await web3.eth.getTransactionReceipt(tx1); - assert.strictEqual(receipt1, null); - - const tx2 = await queueTransaction(accounts[0], accounts[1], 4000000, web3.utils.toWei(new BN(3), "ether")); - const receipt2 = await web3.eth.getTransactionReceipt(tx2); - assert.strictEqual(receipt2, null); - - await mineSingleBlock(); - - const receipts = await Promise.all([web3.eth.getTransactionReceipt(tx1), web3.eth.getTransactionReceipt(tx2)]); - - assert.strictEqual(receipts.length, 2); - - assert.notStrictEqual(receipts[0], null); - assert.strictEqual(receipts[0].transactionHash, tx1); - - assert.strictEqual(receipts[1], null); - - const number = await getBlockNumber(); - assert.strictEqual(number, blockNumber + 1); - } - ); - - it("should error if queued transaction exceeds the block gas limit", async function() { - try { - await stopMining(); - await queueTransaction(accounts[0], accounts[1], 10000000, web3.utils.toWei(new BN(2), "ether")); - assert.fail("Transaction was processed without erroring; gas limit should have been too high"); - } catch (err) { - // We caught an error like we expected. Ensure it's the right error, or rethrow. - if (err.message.toLowerCase().indexOf("exceeds block gas limit") < 0) { - assert.fail("Did not receive expected error; instead received: " + err); - } - } - }); - - it("should error via instamining when queued transaction throws a runtime errors", async function() { - try { - await startMining(); - await queueTransaction(accounts[0], null, 3141592, 0, badBytecode); - // This transaction should be processed immediately. - assert.fail("Execution should never get here as we expected `eth_sendTransaction` to throw an error"); - } catch (err) { - if (err.message.indexOf("VM Exception while processing transaction") !== 0) { - assert.fail("Received error we didn't expect: " + err); - } - } - }); - - it("should error via evm_mine when queued transaction throws a runtime errors", async function() { - try { - await stopMining(); - await queueTransaction(accounts[0], null, 3141592, 0, badBytecode); - await mineSingleBlock(); - assert.fail("Execution should never get here as we expected `evm_mine` to throw an error"); - } catch (err) { - if (err.message.indexOf("VM Exception while processing transaction") !== 0) { - assert.fail("Received error we didn't expect: " + err); - } - } - }); - - it("should error via evm_mine when multiple queued transactions throw runtime errors in a single block", async() => { - // Note: The two transactions queued in this test do not exceed the block gas limit - // and thus should fit within a single block. - - try { - await stopMining(); - await queueTransaction(accounts[0], null, 1000000, 0, badBytecode); - await queueTransaction(accounts[0], null, 1000000, 0, badBytecode); - await mineSingleBlock(); - assert.fail("Execution should never get here as we expected `evm_mine` to throw an error"); - } catch (err) { - if (err.message.indexOf("Multiple VM Exceptions while processing transactions") !== 0) { - assert.fail("Received error we didn't expect: " + err); - } - // We got the error we wanted. Test passed! - } - }); - - it("should error via miner_start when queued transactions throw runtime errors in multiple blocks", async() => { - // Note: The two transactions queued in this test together DO exceed the block gas limit - // and thus will fit in two blocks, one block each. - - try { - await stopMining(); - await queueTransaction(accounts[0], null, 3141592, 0, badBytecode); - await queueTransaction(accounts[0], null, 3141592, 0, badBytecode); - await startMining(); - assert.fail("Execution should never get here as we expected `miner_start` to throw an error"); - } catch (err) { - if (err.message.indexOf("Multiple VM Exceptions while processing transactions") !== 0) { - assert.fail("Received error we didn't expect: " + err); - } - // We got the error we wanted. Test passed! - } - }); - - it("even if we receive a runtime error, logs for successful transactions need to be processed", async function() { - // Note: The two transactions queued in this test should exist within the same block. - let tx2; - - try { - await stopMining(); - - await queueTransaction(accounts[0], null, 1000000, 0, badBytecode); - tx2 = await queueTransaction(accounts[0], null, 1000000, 0, goodBytecode); - - await startMining(); - assert.fail("Execution should never get here as we expected `miner_start` to throw an error"); - } catch (err) { - if (err.message.indexOf("VM Exception while processing transaction") !== 0) { - assert.fail("Received error we didn't expect: " + err); - } - - // We got the error we wanted. Now check to see if the transaction was processed correctly. - const receiptTx2 = await web3.eth.getTransactionReceipt(tx2); - - // We should have a receipt for the second transaction - it should have been processed. - assert.notStrictEqual(receiptTx2, null); - assert.notStrictEqual(receiptTx2, {}); - - // It also should have logs. - assert.notStrictEqual(receiptTx2.logs.length, 0); - - // Now check that there's code at the address, which means it deployed successfully. - const code = await getCode(receiptTx2.contractAddress); - - // Convert hex to a big number and ensure it's not zero. - assert(web3.utils.toBN(code).eq(0) === false); - } - }); - - it("should return the correct value for eth_mining when miner started and stopped", async function() { - await stopMining(); - let isMining = await checkMining(); - assert(!isMining); - await startMining(); - isMining = await checkMining(); - assert(isMining); - }); - - describe("stopping", () => { - function setUp(close, done) { - const blockTime = 0.1; - const provider = Ganache.provider({ blockTime }); - let closed = false; - let closing = false; - let timer; - - // duck punch provider.send so we can detect when it is called - const send = provider.send; - provider.send = function(payload) { - if (payload.method === "evm_mine") { - if (closed) { - clearTimeout(timer); - assert.fail("evm_mine after provider closed"); - } else if (!closing) { - closing = true; - close(provider, () => { - closed = true; - - // give the miner a chance to mine a block before calling done: - timer = setTimeout(done, blockTime * 2 * 1000); - }); - } - } - send.apply(provider, arguments); - }; - } - - it("should stop mining when the provider is stopped during an evm_mine (same REPL)", (done) => { - setUp(function(provider, callback) { - provider.close(callback); - }, done); - }); - - it("should stop mining when the provider is stopped during evm_mine (next tick)", (done) => { - setUp(function(provider, callback) { - process.nextTick(() => provider.close(callback)); - }, done); - }); - - it("should stop mining when the provider is stopped during evm_mine (setImmediate)", (done) => { - setUp(function(provider, callback) { - setImmediate(() => provider.close(callback)); - }, done); - }); - }); -}); diff --git a/test/local/options/account_keys_path.js b/test/local/options/account_keys_path.js deleted file mode 100644 index 109455ff52..0000000000 --- a/test/local/options/account_keys_path.js +++ /dev/null @@ -1,28 +0,0 @@ -const fs = require("fs"); -const path = require("path"); -const assert = require("assert"); -const Ganache = require(process.env.TEST_BUILD - ? "../../../build/ganache.core." + process.env.TEST_BUILD + ".js" - : "../../../index.js"); - -describe("options:account_keys_path", function() { - const fileName = path.join(__dirname, "/test-file.json"); - - function cleanUp() { - try { - fs.unlinkSync(fileName); - } catch (e) { - // ignore error - } - } - before("clean up", () => { - cleanUp(); - }); - it("should create the file", async function() { - Ganache.provider({ account_keys_path: fileName }); - assert.strictEqual(fs.existsSync(fileName), true, "The account_keys file doesn't exist."); - }); - after("clean up", () => { - cleanUp(); - }); -}); diff --git a/test/local/options/keepAliveTimeout.js b/test/local/options/keepAliveTimeout.js deleted file mode 100644 index b2348bd640..0000000000 --- a/test/local/options/keepAliveTimeout.js +++ /dev/null @@ -1,15 +0,0 @@ -const testTimeout = require("./lib/testTimeout"); - -describe("options:keepAliveTimeout", function() { - it("should timeout", async function() { - await testTimeout(2000, 1000, "timeout should have destroyed socket"); - }) - .timeout(2500) - .slow(1500); - - it("shouldn't timeout", async function() { - await testTimeout(1000, 2000, "timeout should not have destroyed socket"); - }) - .timeout(2500) - .slow(3000); -}); diff --git a/test/local/options/lib/testTimeout.js b/test/local/options/lib/testTimeout.js deleted file mode 100644 index a06d3fb41f..0000000000 --- a/test/local/options/lib/testTimeout.js +++ /dev/null @@ -1,48 +0,0 @@ -const assert = require("assert"); -const portfinder = require("portfinder"); -const request = require("request"); -const Ganache = require(process.env.TEST_BUILD - ? "../../../../build/ganache.core." + process.env.TEST_BUILD + ".js" - : "../../../../index.js"); - -const sleep = require("../../../helpers/utils/sleep"); - -const testTimeout = async(keepAliveTimeout, sleepTime, errorMessage) => { - const host = "127.0.0.1"; - const server = Ganache.server({ - keepAliveTimeout - }); - - try { - let socket; - const port = await portfinder.getPortPromise(); - const req = request.post({ - url: `http://${host}:${port}`, - json: { - jsonrpc: "2.0", - method: "eth_mining", - params: [], - id: 71 - }, - forever: true - }); - - server.listen(port); - req.on("socket", (s) => { - socket = s; - }); - - await sleep(sleepTime); - assert(socket.connecting === false, "socket should have connected by now"); - assert.deepStrictEqual(socket.destroyed, keepAliveTimeout < sleepTime, errorMessage); - - req.destroy(); - } catch (e) { - // tests crashed. - assert.fail(e); - } finally { - server.close(); - } -}; - -module.exports = testTimeout; diff --git a/test/local/persistence.js b/test/local/persistence.js deleted file mode 100644 index e408683b45..0000000000 --- a/test/local/persistence.js +++ /dev/null @@ -1,222 +0,0 @@ -const Ganache = require(process.env.TEST_BUILD - ? "../../build/ganache.core." + process.env.TEST_BUILD + ".js" - : "../../index.js"); -const temp = require("temp").track(); -const memdown = require("memdown"); -const { join } = require("path"); -const assert = require("assert"); -const Web3 = require("web3"); -const generateSend = require("../helpers/utils/rpc"); -const compile = require("../helpers/contract/singleFileCompile"); - -const { result } = compile("./test/contracts/examples/", "Example"); - -// Note: Certain properties of the following contract data are hardcoded to -// maintain repeatable tests. If you significantly change the solidity code, -// make sure to update the resulting contract data with the correct values. - -const runTests = function(providerInit) { - describe("Persistence ", function() { - const web3 = new Web3(); - let accounts; - let tx; - let provider; - - before("init provider", function() { - providerInit(function(p) { - provider = p; - web3.setProvider(p); - }); - }); - - before("Gather accounts", async function() { - accounts = await web3.eth.getAccounts(); - }); - - before("send transaction", async function() { - tx = await web3.eth.sendTransaction({ - from: accounts[0], - gas: "0x2fefd8", - data: result.contracts["Example.sol"].Example.evm.bytecode.object - }); - }); - - it("should have block height 1", async function() { - const res = await web3.eth.getBlockNumber(); - assert(res === 1); - // Close the first provider now that we've gotten where we need to be. - // Note: we specifically close the provider so we can read from the same db. - provider.close(() => null); // pass dummy fn to satisfy callback expectation - }).timeout(5000); - - it("should reopen the provider", function() { - providerInit(function(p) { - provider = p; - web3.setProvider(provider); - }); - }).slow(200); - - it("should still be on block height 1", async function() { - const result = await web3.eth.getBlockNumber(); - assert(result === 1); - }).timeout(5000); - - it("should still have block data for first block", async function() { - await web3.eth.getBlock(1); - }); - - it("should have a receipt for the previous transaction", async function() { - const receipt = await web3.eth.getTransactionReceipt(tx.transactionHash); - assert.notStrictEqual(receipt, null, "Receipt shouldn't be null!"); - assert.strictEqual(receipt.transactionHash, tx.transactionHash); - }); - - it("should maintain the balance of the original accounts", async function() { - const balance = await web3.eth.getBalance(accounts[0]); - assert(balance > 98); - }); - }); -}; - -const runRegressionTests = function(regressionProviderInit, memdbProviderInit) { - describe("Verify previous db compatibility", function() { - const web3 = new Web3(); - const memdbWeb3 = new Web3(); - const str = JSON.stringify; - const memdbBlocks = []; - const blocks = []; - const blockHeight = 2; - let accounts; - let memdbSend; - - before("init provider", function() { - regressionProviderInit(function(p) { - web3.setProvider(p); - }); - memdbProviderInit(function(p) { - memdbWeb3.setProvider(p); - memdbSend = generateSend(p); - }); - }); - - before("Gather accounts", async function() { - accounts = await web3.eth.getAccounts(); - }); - - it("should have identical accounts (same mnemonic)", async function() { - const memAccounts = await memdbWeb3.eth.getAccounts(); - assert.strictEqual(str(accounts), str(memAccounts), "accounts should be equal on both chains"); - }); - - it(`should be on block height ${blockHeight} (db store)`, async function() { - const result = await web3.eth.getBlockNumber(); - assert(result === blockHeight); - }); - - it("should be on block height 0 (mem store)", async function() { - const result = await memdbWeb3.eth.getBlockNumber(); - assert(result === 0); - }); - - it("should issue/accept two tx's (mem store)", async function() { - // Don't change the details of this tx - it's needed to deterministically match a manually created - // DB with prior versions of ganache-core - let { timestamp } = await memdbWeb3.eth.getBlock(0); - assert(timestamp); - const txOptions = { - from: accounts[0], - to: accounts[1], - value: 1 - }; - const receipt = await memdbSend("eth_sendTransaction", txOptions); - await memdbSend("evm_mine", ++timestamp); - const receipt2 = await memdbSend("eth_sendTransaction", txOptions); - await memdbSend("evm_mine", ++timestamp); - assert(receipt.result, "Should return a tx hash"); - assert(receipt2.result, "Should return a tx hash"); - }); - - it("should be on block height 2 (mem store)", async function() { - const result = await memdbWeb3.eth.getBlockNumber(); - assert(result === 2); - }); - - it.skip("should produce identical blocks (persistence db - memdb)", async function() { - blocks.push(await web3.eth.getBlock(0, true)); - blocks.push(await web3.eth.getBlock(1, true)); - blocks.push(await web3.eth.getBlock(2, true)); - memdbBlocks.push(await memdbWeb3.eth.getBlock(0, true)); - memdbBlocks.push(await memdbWeb3.eth.getBlock(1, true)); - memdbBlocks.push(await memdbWeb3.eth.getBlock(2, true)); - for (let i = 0; i < blocks.length; i++) { - assert.strictEqual(str(blocks[i]), str(memdbBlocks[i])); - } - }); - - it.skip("should produce identical transactions (persistence db - memdb)", async function() { - // Start at block 1 to skip genesis block - for (let i = 1; i < blocks.length; i++) { - const block = await memdbWeb3.eth.getBlock(i, false); - for (let j = 0; j < block.transactions.length; j++) { - const tx = await web3.eth.getTransaction(block.transactions[j]); - const memDbTx = await memdbWeb3.eth.getTransaction(block.transactions[j]); - assert(tx && memDbTx); - assert.strictEqual(str(tx), str(memDbTx)); - } - } - }); - }); -}; - -var mnemonic = "debris electric learn dove warrior grow pistol carry either curve radio hidden"; - -const providerInitGen = function(opts) { - return function(cb) { - const provider = Ganache.provider(opts); - cb(provider); - }; -}; - -describe("Default DB", function() { - const dbPath = temp.mkdirSync("testrpc-db-"); - - // initialize a persistent provider - const providerInit = providerInitGen({ - db_path: dbPath, - mnemonic - }); - - runTests(providerInit); -}); - -describe("Custom DB", function() { - const db = memdown(); - - // initialize a custom persistence provider - const providerInit = providerInitGen({ - db, - mnemonic - }); - - runTests(providerInit); -}); - -describe("Regression test DB", function() { - // Don't change these options, we need these to match the saved chain in ./test/testdb - const db = memdown(); - const dbPath = join(__dirname, "/testdb"); - const mnemonic = "candy maple cake sugar pudding cream honey rich smooth crumble sweet treat"; - const time = new Date("2009-01-03T18:15:05+00:00"); - const networkId = "1337"; - const blockTime = 1000; // An abundantly sufficient block time used with evm_mine for deterministic results - - // initialize a custom persistence provider - const options = { mnemonic, network_id: networkId, time, blockTime }; - const dbOptions = Object.assign({}, options, { db_path: dbPath }); - const memdbOptions = Object.assign({}, options, { db }); - - const dbProviderInit = providerInitGen(dbOptions); - const memdbProviderInit = providerInitGen(memdbOptions); - - runRegressionTests(dbProviderInit, memdbProviderInit); -}); diff --git a/test/local/public-exports.js b/test/local/public-exports.js deleted file mode 100644 index a8da0dc5a5..0000000000 --- a/test/local/public-exports.js +++ /dev/null @@ -1,10 +0,0 @@ -const Ganache = require(process.env.TEST_BUILD - ? "../../build/ganache.core." + process.env.TEST_BUILD + ".js" - : "../../index.js"); -const assert = require("assert"); - -describe("BuildType", function() { - it("Tests that we are using the right version", () => { - assert(process.env.TEST_BUILD ? Ganache._webpacked === true : Ganache._webpacked === false); - }); -}); diff --git a/test/local/runTimeErrors.js b/test/local/runTimeErrors.js deleted file mode 100644 index 025e921352..0000000000 --- a/test/local/runTimeErrors.js +++ /dev/null @@ -1,237 +0,0 @@ -const assert = require("assert"); -const to = require("../../lib/utils/to"); -const bootstrap = require("../helpers/contract/bootstrap"); - -const providerOptions = [{ vmErrorsOnRPCResponse: true }, { vmErrorsOnRPCResponse: false }]; - -// Run all test with options -providerOptions.forEach((ganacheProviderOptions) => { - tests(ganacheProviderOptions); -}); - -function tests(ganacheProviderOptions) { - describe("Runtime Errors with vmErrorsOnRPCResponse", function() { - let context; - - before("Compile contract and setup provider", async function() { - this.timeout(10000); - const contractRef = { - contractFiles: ["RuntimeError"], - contractSubdirectory: "runtime" - }; - - context = await bootstrap(contractRef, ganacheProviderOptions); - const _send = context.provider.send.bind(context.provider.send); - let jsonRpcId = 1; - // we want to ignore the callback `err` so we are creating our own promisified send here - context.send = (method, ...params) => - new Promise((resolve) => { - _send( - { - id: jsonRpcId++, - jsonrpc: "2.0", - method, - params: [...params] - }, - (err, response) => resolve({ err, response }) - ); - }); - }); - - it("Should fail to estimate gas when the transaction is invalid", async() => { - const { accounts, instance, send } = context; - const txParams = { - from: accounts[0], - // this errors: - to: instance.options.address - }; - const result = await send("eth_estimateGas", txParams); - assert.deepStrictEqual(result.response.error.code, -32000, "Gas estimation error code is not as expected"); - assert.deepStrictEqual( - result.response.error.message, - "VM Exception while processing transaction: revert", - "Gas estimation error message is not as expected" - ); - }); - - it("should output the transaction hash even if a (out of gas) runtime error occurred", async function() { - const { accounts, bytecode, provider, send } = context; - - const { response } = await send("eth_sendTransaction", { - from: accounts[0], - data: bytecode - }); - - if (provider.options.vmErrorsOnRPCResponse) { - // null & undefined are equivalent for equality tests, but I'm being - // pedantic here for readability's sake - assert(response.error !== null); - assert(response.error !== undefined); - } else { - assert(response.error === undefined); - } - - // null & undefined are equivalent for equality tests, but I'm being - // pedantic here for readability's sake - assert(response.result !== null); - assert(response.result !== undefined); - - assert.strictEqual(response.result.length, 66); // transaction hash - }); - - it("should output the transaction hash even if a runtime error occurs (revert)", async function() { - const { accounts, instance, provider, send } = context; - - const { response } = await send("eth_sendTransaction", { - from: accounts[0], - to: instance.options.address, - // calls error() - data: "0xc79f8b62", - gas: to.hex(3141592) - }); - - if (provider.options.vmErrorsOnRPCResponse) { - // null & undefined are equivalent for equality tests, but I'm being - // pedantic here for readability's sake - assert(response.error !== null); - assert(response.error !== undefined); - - assert( - /revert/.test(response.error.message), - `Expected error message (${response.error.message}) to contain 'revert'` - ); - } else { - assert(response.error === undefined); - } - - // null & undefined are equivalent for equality tests, but I'm being - // pedantic here for readability's sake - assert(response.result !== null); - assert(response.result !== undefined); - - assert.strictEqual(response.result.length, 66); // transaction hash - }); - - it("should have correct return value when calling a method that reverts without message", async function() { - const { accounts, instance, provider, send } = context; - - const { response } = await send("eth_call", { - from: accounts[0], - to: instance.options.address, - // calls error() - data: "0xc79f8b62", - gas: to.hex(3141592) - }); - - if (provider.options.vmErrorsOnRPCResponse) { - // null & undefined are equivalent for equality tests, but I'm being - // pedantic here for readability's sake - assert(response.error !== null); - assert(response.error !== undefined); - assert(response.result === undefined || response.result === null); - - assert( - /revert/.test(response.error.message), - `Expected error message (${response.error.message}) to contain 'revert'` - ); - } else { - assert(response.error === undefined); - assert(response.result === "0x"); - } - }); - - it("should have correct return value when calling a method that reverts without message", async function() { - const { accounts, instance, provider, send } = context; - - const { response } = await send("eth_call", { - from: accounts[0], - to: instance.options.address, - // calls error() - data: "0xc79f8b62", - gas: to.hex(3141592) - }); - - if (provider.options.vmErrorsOnRPCResponse) { - // null & undefined are equivalent for equality tests, but I'm being - // pedantic here for readability's sake - assert(response.error !== null); - assert(response.error !== undefined); - assert(response.result === undefined || response.result === null); - - assert( - /revert/.test(response.error.message), - `Expected error message (${response.error.message}) to contain 'revert'` - ); - } else { - assert(response.error === undefined); - assert(response.result === "0x"); - } - }); - - it("should have correct return value when calling a method that reverts with message", async function() { - const { accounts, instance, provider, send } = context; - - const { response } = await send("eth_call", { - from: accounts[0], - to: instance.options.address, - // calls error() - data: "0xcd4aed30", - gas: to.hex(3141592) - }); - - if (provider.options.vmErrorsOnRPCResponse) { - // null & undefined are equivalent for equality tests, but I'm being - // pedantic here for readability's sake - assert(response.error !== null); - assert(response.error !== undefined); - assert(response.result === undefined || response.result === null); - - // RuntimeError.sol reverts with revert("Message") - assert( - /Message/.test(response.error.message), - `Expected error message (${response.error.message}) to contain revert reason "Message"` - ); - assert( - /revert/.test(response.error.message), - `Expected error message (${response.error.message}) to contain 'revert'` - ); - } else { - assert(response.error === undefined); - assert( - response.result === - "0x08c379a000000000000000000000000000000000000000000000000000000000000000" + - "2000000000000000000000000000000000000000000000000000000000000000074d6573" + - "7361676500000000000000000000000000000000000000000000000000" - ); - } - }); - - if (ganacheProviderOptions.vmErrorsOnRPCResponse === true) { - it("should output instruction index on runtime errors", async function() { - const { accounts, instance, send } = context; - - const { err, response } = await send("eth_sendTransaction", { - from: accounts[0], - to: instance.options.address, - // calls error() - data: "0xc79f8b62", - gas: to.hex(3141592) - }); - - if (err) { - assert(err); - } - const txHash = response.result; - - assert(response.error); - assert(response.error.data[txHash]); - // magic number, will change if compiler changes. - assert.strictEqual(to.number(response.error.data[txHash].program_counter), 136); - }); - } - - after("shutdown", function(done) { - context.provider.close(done); - }); - }); -} diff --git a/test/local/server.js b/test/local/server.js deleted file mode 100644 index 07a4621f56..0000000000 --- a/test/local/server.js +++ /dev/null @@ -1,18 +0,0 @@ -const assert = require("assert"); -const pify = require("pify"); -// this should not be a reference the built/lib Ganache as we intentially are checking -// that it is returning an instance of an object in the test below. -const Ganache = require("../../index.js"); -const StateManager = require("../../lib/statemanager.js"); - -describe("server", () => { - it("should return instance of StateManager on start", async() => { - const server = Ganache.server(); - try { - const stateManager = await pify(server.listen)(8945); - assert(stateManager instanceof StateManager, "server.listen must return instance of StateManager"); - } finally { - await pify(server.close)(); - } - }); -}); diff --git a/test/local/snapshotting.js b/test/local/snapshotting.js deleted file mode 100644 index 0e306748de..0000000000 --- a/test/local/snapshotting.js +++ /dev/null @@ -1,133 +0,0 @@ -const { BN } = require("ethereumjs-util"); -const assert = require("assert"); -const bootstrap = require("../helpers/contract/bootstrap"); - -describe("Checkpointing / Reverting", function() { - let context; - let startingBalance; - let snapshotId; - - before("Set up provider with web3 instance and deploy a contract", async function() { - this.timeout(10000); - const contractRef = { - contractFiles: ["snapshot"], - contractSubdirectory: "snapshotting" - }; - - context = await bootstrap(contractRef); - }); - - before("send a transaction then make a checkpoint", async function() { - const { accounts, send, web3 } = context; - - await web3.eth.sendTransaction({ - from: accounts[0], - to: accounts[1], - value: web3.utils.toWei(new BN(1), "ether"), - gas: 90000 - }); - - // Since transactions happen immediately, we can assert the balance. - let balance = await web3.eth.getBalance(accounts[0]); - balance = parseFloat(web3.utils.fromWei(balance, "ether")); - - // Assert the starting balance is where we think it is, including tx costs. - assert(balance > 98.9 && balance < 99); - startingBalance = balance; - - // Now checkpoint. - snapshotId = await send("evm_snapshot"); - }); - - it("rolls back successfully", async() => { - const { accounts, send, web3 } = context; - - // Send another transaction, check the balance, then roll it back to the old one and check the balance again. - const { transactionHash } = await web3.eth.sendTransaction({ - from: accounts[0], - to: accounts[1], - value: web3.utils.toWei(new BN(1), "ether"), - gas: 90000 - }); - - let balance = await web3.eth.getBalance(accounts[0]); - balance = parseFloat(web3.utils.fromWei(balance, "ether")); - - // Assert the starting balance is where we think it is, including tx costs. - assert(balance > 97.9 && balance < 98); - - const status = await send("evm_revert", snapshotId.result); - - assert(status, "Snapshot should have returned true"); - - let revertedBalance = await web3.eth.getBalance(accounts[0]); - revertedBalance = parseFloat(web3.utils.fromWei(revertedBalance, "ether")); - - assert(revertedBalance === startingBalance, "Should have reverted back to the starting balance"); - - const oldReceipt = await web3.eth.getTransactionReceipt(transactionHash); - assert.strictEqual(oldReceipt, null, "Receipt should be null as it should have been removed"); - }); - - it("returns false when reverting a snapshot that doesn't exist", async() => { - const { send } = context; - - const snapShotId1 = await send("evm_snapshot"); - const snapShotId2 = await send("evm_snapshot"); - const response1 = await send("evm_revert", snapShotId1.result); - assert.strictEqual(response1.result, true, "Reverting a snapshot that exists does not work"); - const response2 = await send("evm_revert", snapShotId2.result); - assert.strictEqual(response2.result, false, "Reverting a snapshot that no longer exists does not work"); - const response3 = await send("evm_revert", snapShotId1.result); - assert.strictEqual(response3.result, false, "Reverting a snapshot that hasn't already been reverted does not work"); - const response4 = await send("evm_revert", 999); - assert.strictEqual(response4.result, false, "Reverting a snapshot that has never existed does not work"); - }); - - it("checkpoints and reverts without persisting contract storage", async() => { - const { accounts, instance, send } = context; - - const snapShotId = await send("evm_snapshot"); - const n1 = await instance.methods.n().call(); - assert.strictEqual(n1, "42", "Initial n is not 42"); - - await instance.methods.inc().send({ from: accounts[0] }); - const n2 = await instance.methods.n().call(); - assert.strictEqual(n2, "43", "n is not 43 after first call to `inc`"); - - await send("evm_revert", snapShotId.result); - const n3 = await instance.methods.n().call(); - assert.strictEqual(n3, "42", "n is not 42 after reverting snapshot"); - - // this is the real test. what happened was that the vm's contract storage - // trie cache wasn't cleared when the vm's stateManager cache was cleared. - await instance.methods.inc().send({ from: accounts[0] }); - const n4 = await instance.methods.n().call(); - assert.strictEqual(n4, "43", "n is not 43 after calling `inc` again"); - }); - - it("evm_revert rejects invalid subscriptionId types without crashing", async() => { - const { send } = context; - const ids = [{ foo: "bar" }, true, false]; - await Promise.all( - ids.map((id) => assert.rejects(send("evm_revert", id), /invalid type/, "evm_revert did not reject as expected")) - ); - }); - - it("evm_revert rejects null/undefined subscriptionId values", async() => { - const { send } = context; - const ids = [null, undefined]; - await Promise.all( - ids.map((id) => - assert.rejects(send("evm_revert", id), /invalid snapshotId/, "evm_revert did not reject as expected") - ) - ); - }); - - it("evm_revert returns false for out-of-range subscriptionId values", async() => { - const { send } = context; - const ids = [-1, Infinity, -Infinity, Buffer.from([0]), 0.5]; - const promises = ids.map((id) => send("evm_revert", id).then((r) => assert(r, false))); - await Promise.all(promises); - }); -}); diff --git a/test/local/stability.js b/test/local/stability.js deleted file mode 100644 index 6613b3d2bb..0000000000 --- a/test/local/stability.js +++ /dev/null @@ -1,131 +0,0 @@ -const { BN } = require("ethereumjs-util"); -const assert = require("assert"); -const utils = require("ethereumjs-util"); -const initializeTestProvider = require("../helpers/web3/initializeTestProvider"); - -describe("stability", function() { - let context; - - before("Initialize the provider", async function() { - context = await initializeTestProvider(); - }); - - it("should be able to handle multiple transactions at once and manage nonces accordingly", async function() { - const { accounts, web3 } = context; - - const txParams = { - from: accounts[0], - to: accounts[1], - value: web3.utils.toWei(new BN(1), "ether") - }; - const expected = 5; - const concurrentTransactions = Array(expected).fill(() => web3.eth.sendTransaction(txParams)); - - await Promise.all(concurrentTransactions.map((txFn) => assert.doesNotReject(txFn))); - }); - - it("should be able to handle batch transactions", function(done) { - const { accounts, provider, web3 } = context; - const expected = 5; - const requests = []; - - for (let i = 0; i < expected; i++) { - const req = web3.eth.sendTransaction.request({ - from: accounts[0], - to: accounts[1], - value: `0x${new BN(10).pow(new BN(18)).toString("hex")}` // 1 ETH - }); - - Object.assign(req, { - jsonrpc: "2.0", - id: 100 + i - }); - - requests.push(req); - } - - provider.sendAsync(requests, function(err, result) { - assert(err === undefined || err === null); - assert(Array.isArray(result)); - assert.deepStrictEqual(result.length, expected); - done(); - }); - }); - - it("should not crash when receiving transactions which don't pass FakeTransaction validation", async function() { - const { accounts, send } = context; - - const method = "eth_sendTransaction"; - const params = { - from: accounts[0], - to: "0x123", // bad address - value: `0x${new BN(10).pow(new BN(18)).toString("hex")}` // 1 ETH - }; - - await assert.rejects(() => send(method, params), /The field to must have byte length of 20/); - }); - - it("should not crash when receiving a request with too many arguments", async function() { - const { send } = context; - - const method = "evm_mine"; - const err = await send(method, "0x1", "0x2", "0x3", "0x4", "0x5", "0x6", "0x7", "0x8", "0x9", "0xA").catch( - (e) => e - ); - assert(err.message.indexOf("Incorrect number of arguments.") !== -1); - }); - - // TODO: remove `.skip` when working on and/or submitting fix for issue trufflesuite/ganache-cli#453 - describe.skip("race conditions", function(done) { - let context; - - before("Initialize the provider", async function() { - context = await initializeTestProvider(); - }); - - it("should not cause 'get' of undefined", function(done) { - const { accounts, provider } = context; - process.prependOnceListener("uncaughtException", function(err) { - done(err); - }); - - const blockchain = provider.manager.state.blockchain; - // processCall or processBlock - blockchain.vm.stateManager.checkpoint(); - // getCode (or any function that calls trie.get) - blockchain.stateTrie.get(utils.toBuffer(accounts[0]), function() {}); - blockchain.vm.stateManager.revert(function() { - done(); - }); // processCall or processBlock - }); - - it("should not cause 'pop' of undefined", function(done) { - const { provider, web3 } = context; - process.prependOnceListener("uncaughtException", function(err) { - done(err); - }); - - const blockchain = provider.manager.state.blockchain; - blockchain.vm.stateManager.checkpoint(); // processCall #1 - // processNextBlock triggered by interval mining which at some point calls - // vm.stateManager.commit() and blockchain.putBlock() - blockchain.processNextBlock(function(err, tx, results) { - if (err) { - return done(err); - } - blockchain.vm.stateManager.revert(function() { - // processCall #1 finishes - blockchain.latestBlock(function(err, latestBlock) { - if (err) { - return done(err); - } - blockchain.stateTrie.root = latestBlock.header.stateRoot; // getCode #1 (or any function with this logic) - web3.eth.call({}, function() { - done(); - }); // processCall #2 - }); - }); - }); - }); - }); -}); diff --git a/test/local/subscriptions.js b/test/local/subscriptions.js deleted file mode 100644 index 21ef939e24..0000000000 --- a/test/local/subscriptions.js +++ /dev/null @@ -1,103 +0,0 @@ -const Ganache = require(process.env.TEST_BUILD - ? "../../build/ganache.core." + process.env.TEST_BUILD + ".js" - : "../../index.js"); -const generateSend = require("../helpers/utils/rpc"); -const promisify = require("pify"); -const assert = require("assert"); -const PORT = 8545; -const HOST = "127.0.0.1"; -const HTTPADDRESS = `http://${HOST}:${PORT}`; - -const testHttp = function(web3) { - let web3send; - let accounts; - - before("get personal accounts", async function() { - accounts = await web3.eth.getAccounts(); - }); - - before("setup provider send fn", function() { - web3send = generateSend(web3.currentProvider); - }); - - describe("subscriptions", function() { - it("should gracefully handle http subscription attempts", async function() { - // Attempt to subscribe http connection to 'pendingTransactions' - const { error } = await web3send("eth_subscribe", "pendingTransactions"); - assert(error, "http subscription should respond with an error"); - assert.strictEqual(error.code, -32000, "Error code should equal -32000"); - assert.strictEqual(error.message, "notifications not supported", "notifications should not be supported"); - - // Issue a sendTransaction - ganache should not attempt to issue a message to http subscriptions - const { result } = await web3send("eth_sendTransaction", { from: accounts[0], value: "0x1" }); - // Get receipt -- ensure ganache is still running/accepting calls - const receipt = await web3send("eth_getTransactionReceipt", result); - // Receipt indicates that ganache has NOT crashed and continues to handle RPC requests - assert(!receipt.error, "Should not respond with an error."); - assert(receipt.result, "Should respond with a receipt."); - }); - }); -}; - -const testWebSocket = function(web3) { - let web3send; - - before("setup provider send fn", function() { - web3send = generateSend(web3.currentProvider); - }); - - describe("subscriptions", function() { - it("should handle eth_subscribe/eth_unsubscribe", async function() { - // Attempt to subscribe to 'newHeads' - const receipt = await web3send("eth_subscribe", "newHeads"); - assert(receipt.result, "ID must be returned (eth_subscribe successful)"); - const result = await web3send("eth_unsubscribe", receipt.result); - assert(result.result, "Result must be true (eth_unsubscribe successful)"); - }); - }); -}; - -describe("WebSockets Server:", function() { - const Web3 = require("web3"); - const web3 = new Web3(); - let server; - - before("Initialize Ganache server", async function() { - server = Ganache.server({ - seed: "1337" - }); - await promisify(server.listen)(PORT + 1); - const provider = new Web3.providers.WebsocketProvider("ws://localhost:" + (PORT + 1)); - web3.setProvider(provider); - }); - - testWebSocket(web3); - - after("Shutdown server", async function() { - const provider = web3._provider; - web3.setProvider(); - provider.connection.close(); - await promisify(server.close)(); - }); -}); - -describe("HTTP Server should not handle subscriptions:", function() { - const Web3 = require("web3"); - const web3 = new Web3(); - let server; - - before("Initialize Ganache server", async function() { - server = Ganache.server({ - seed: "1337" - }); - - await promisify(server.listen)(PORT); - web3.setProvider(new Web3.providers.HttpProvider(HTTPADDRESS)); - }); - - testHttp(web3); - - after("Shutdown server", async function() { - await promisify(server.close)(); - }); -}); diff --git a/test/local/swarm.js b/test/local/swarm.js deleted file mode 100644 index e7097bd773..0000000000 --- a/test/local/swarm.js +++ /dev/null @@ -1,17 +0,0 @@ -const assert = require("assert"); -const initializeTestProvider = require("../helpers/web3/initializeTestProvider"); - -describe("Swarm", function() { - const context = initializeTestProvider(); - it.skip("should get swarm info (bzz_info)", async function() { - const { web3 } = context; - const result = await web3.bzz.getInfo(); - assert.isArray(result, "Stub returns empty array"); - }); - - it.skip("should get swarm hive (bzz_hive)", async function() { - const { web3 } = context; - const result = await web3.bzz.getHive(); - assert.isArray(result, "Stub returns empty array"); - }); -}); diff --git a/test/local/testdb/!blockHashes!0x4b6de53cdbc759a655d98b3b60dc2c5f1b1b0f82a762c869bfa1b15acf8597e6 b/test/local/testdb/!blockHashes!0x4b6de53cdbc759a655d98b3b60dc2c5f1b1b0f82a762c869bfa1b15acf8597e6 deleted file mode 100644 index 56a6051ca2..0000000000 --- a/test/local/testdb/!blockHashes!0x4b6de53cdbc759a655d98b3b60dc2c5f1b1b0f82a762c869bfa1b15acf8597e6 +++ /dev/null @@ -1 +0,0 @@ -1 \ No newline at end of file diff --git a/test/local/testdb/!blockHashes!0xa339ce48c9eb2335ba87f9498478a5db4cc80792cd0a8b64821512a50802d4cd b/test/local/testdb/!blockHashes!0xa339ce48c9eb2335ba87f9498478a5db4cc80792cd0a8b64821512a50802d4cd deleted file mode 100644 index c227083464..0000000000 --- a/test/local/testdb/!blockHashes!0xa339ce48c9eb2335ba87f9498478a5db4cc80792cd0a8b64821512a50802d4cd +++ /dev/null @@ -1 +0,0 @@ -0 \ No newline at end of file diff --git a/test/local/testdb/!blockHashes!0xc02e0ee40051c3d4a5a9be310e7d4e128a396226ed4ce10a101b31262a88868c b/test/local/testdb/!blockHashes!0xc02e0ee40051c3d4a5a9be310e7d4e128a396226ed4ce10a101b31262a88868c deleted file mode 100644 index d8263ee986..0000000000 --- a/test/local/testdb/!blockHashes!0xc02e0ee40051c3d4a5a9be310e7d4e128a396226ed4ce10a101b31262a88868c +++ /dev/null @@ -1 +0,0 @@ -2 \ No newline at end of file diff --git a/test/local/testdb/!blockLogs!0 b/test/local/testdb/!blockLogs!0 deleted file mode 100644 index 0637a088a0..0000000000 --- a/test/local/testdb/!blockLogs!0 +++ /dev/null @@ -1 +0,0 @@ -[] \ No newline at end of file diff --git a/test/local/testdb/!blockLogs!1 b/test/local/testdb/!blockLogs!1 deleted file mode 100644 index 0637a088a0..0000000000 --- a/test/local/testdb/!blockLogs!1 +++ /dev/null @@ -1 +0,0 @@ -[] \ No newline at end of file diff --git a/test/local/testdb/!blockLogs!2 b/test/local/testdb/!blockLogs!2 deleted file mode 100644 index 0637a088a0..0000000000 --- a/test/local/testdb/!blockLogs!2 +++ /dev/null @@ -1 +0,0 @@ -[] \ No newline at end of file diff --git a/test/local/testdb/!blockLogs!length b/test/local/testdb/!blockLogs!length deleted file mode 100644 index e440e5c842..0000000000 --- a/test/local/testdb/!blockLogs!length +++ /dev/null @@ -1 +0,0 @@ -3 \ No newline at end of file diff --git a/test/local/testdb/!blocks!0 b/test/local/testdb/!blocks!0 deleted file mode 100644 index 7cbcbd8800..0000000000 --- a/test/local/testdb/!blocks!0 +++ /dev/null @@ -1 +0,0 @@ -{"header":{"parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000","uncleHash":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","coinbase":"0x0000000000000000000000000000000000000000","stateRoot":"0xa2616c2f24b3c29c29fedec4acc5c3d803aa7cb7818017520686266074832155","transactionsTrie":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","receiptTrie":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","bloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x","number":"0x","gasLimit":"0x6691b7","gasUsed":"0x","timestamp":"0x495fab29","extraData":"0x","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x"},"transactions":[],"uncleHeaders":[]} \ No newline at end of file diff --git a/test/local/testdb/!blocks!1 b/test/local/testdb/!blocks!1 deleted file mode 100644 index 51136cc7a0..0000000000 --- a/test/local/testdb/!blocks!1 +++ /dev/null @@ -1 +0,0 @@ -{"header":{"parentHash":"0xa339ce48c9eb2335ba87f9498478a5db4cc80792cd0a8b64821512a50802d4cd","uncleHash":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","coinbase":"0x0000000000000000000000000000000000000000","stateRoot":"0x21cf3512b27750f536562dc8ac85d44e521490c8475507cc62da7583cb9723e1","transactionsTrie":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","receiptTrie":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","bloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x","number":"0x01","gasLimit":"0x6691b7","gasUsed":"0x5208","timestamp":"0x495fab2a","extraData":"0x","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x"},"transactions":[{"nonce":"0x","gasPrice":"0x04a817c800","gasLimit":"0x015f90","to":"0xf17f52151ebef6c7334fad080c5704d77216b732","value":"0x01","data":"0x","v":"0x1c","r":"0x","s":"0x","from":"0x627306090abab3a6e1400e9345bc60c78a8bef57","hash":"0x59214a31f0a1aec159a239a9bd6a62fa371881f6e72690c178b22cda8e552ffb"}],"uncleHeaders":[]} \ No newline at end of file diff --git a/test/local/testdb/!blocks!2 b/test/local/testdb/!blocks!2 deleted file mode 100644 index 8e880a973f..0000000000 --- a/test/local/testdb/!blocks!2 +++ /dev/null @@ -1 +0,0 @@ -{"header":{"parentHash":"0x4b6de53cdbc759a655d98b3b60dc2c5f1b1b0f82a762c869bfa1b15acf8597e6","uncleHash":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","coinbase":"0x0000000000000000000000000000000000000000","stateRoot":"0x06de23a6d2ecbd78afda8c7fc42b84d07ef56de983d48b4e631291c607a71e5e","transactionsTrie":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","receiptTrie":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","bloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x","number":"0x02","gasLimit":"0x6691b7","gasUsed":"0x5208","timestamp":"0x495fab2b","extraData":"0x","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x"},"transactions":[{"nonce":"0x01","gasPrice":"0x04a817c800","gasLimit":"0x015f90","to":"0xf17f52151ebef6c7334fad080c5704d77216b732","value":"0x01","data":"0x","v":"0x1c","r":"0x","s":"0x","from":"0x627306090abab3a6e1400e9345bc60c78a8bef57","hash":"0x1b677be476665ed6c357c9318ea4882fe25e7d5fee8dfe6ac2ed4049c34a1883"}],"uncleHeaders":[]} \ No newline at end of file diff --git a/test/local/testdb/!blocks!length b/test/local/testdb/!blocks!length deleted file mode 100644 index e440e5c842..0000000000 --- a/test/local/testdb/!blocks!length +++ /dev/null @@ -1 +0,0 @@ -3 \ No newline at end of file diff --git a/test/local/testdb/!transactionReceipts!0x1b677be476665ed6c357c9318ea4882fe25e7d5fee8dfe6ac2ed4049c34a1883 b/test/local/testdb/!transactionReceipts!0x1b677be476665ed6c357c9318ea4882fe25e7d5fee8dfe6ac2ed4049c34a1883 deleted file mode 100644 index 075561f27c..0000000000 --- a/test/local/testdb/!transactionReceipts!0x1b677be476665ed6c357c9318ea4882fe25e7d5fee8dfe6ac2ed4049c34a1883 +++ /dev/null @@ -1 +0,0 @@ -{"transactionHash":"0x1b677be476665ed6c357c9318ea4882fe25e7d5fee8dfe6ac2ed4049c34a1883","transactionIndex":"0x0","blockHash":"0xc02e0ee40051c3d4a5a9be310e7d4e128a396226ed4ce10a101b31262a88868c","blockNumber":"0x2","gasUsed":"0x5208","cumulativeGasUsed":"0x5208","contractAddress":null,"logs":[],"status":"0x1","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"} \ No newline at end of file diff --git a/test/local/testdb/!transactionReceipts!0x59214a31f0a1aec159a239a9bd6a62fa371881f6e72690c178b22cda8e552ffb b/test/local/testdb/!transactionReceipts!0x59214a31f0a1aec159a239a9bd6a62fa371881f6e72690c178b22cda8e552ffb deleted file mode 100644 index ea1cb1659a..0000000000 --- a/test/local/testdb/!transactionReceipts!0x59214a31f0a1aec159a239a9bd6a62fa371881f6e72690c178b22cda8e552ffb +++ /dev/null @@ -1 +0,0 @@ -{"transactionHash":"0x59214a31f0a1aec159a239a9bd6a62fa371881f6e72690c178b22cda8e552ffb","transactionIndex":"0x0","blockHash":"0x4b6de53cdbc759a655d98b3b60dc2c5f1b1b0f82a762c869bfa1b15acf8597e6","blockNumber":"0x1","gasUsed":"0x5208","cumulativeGasUsed":"0x5208","contractAddress":null,"logs":[],"status":"0x1","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"} \ No newline at end of file diff --git a/test/local/testdb/!transactions!0x1b677be476665ed6c357c9318ea4882fe25e7d5fee8dfe6ac2ed4049c34a1883 b/test/local/testdb/!transactions!0x1b677be476665ed6c357c9318ea4882fe25e7d5fee8dfe6ac2ed4049c34a1883 deleted file mode 100644 index ed676c496b..0000000000 --- a/test/local/testdb/!transactions!0x1b677be476665ed6c357c9318ea4882fe25e7d5fee8dfe6ac2ed4049c34a1883 +++ /dev/null @@ -1 +0,0 @@ -{"nonce":"0x01","gasPrice":"0x04a817c800","gasLimit":"0x015f90","to":"0xf17f52151ebef6c7334fad080c5704d77216b732","value":"0x01","data":"0x","v":"0x1c","r":"0x","s":"0x","from":"0x627306090abab3a6e1400e9345bc60c78a8bef57","hash":"0x1b677be476665ed6c357c9318ea4882fe25e7d5fee8dfe6ac2ed4049c34a1883"} \ No newline at end of file diff --git a/test/local/testdb/!transactions!0x59214a31f0a1aec159a239a9bd6a62fa371881f6e72690c178b22cda8e552ffb b/test/local/testdb/!transactions!0x59214a31f0a1aec159a239a9bd6a62fa371881f6e72690c178b22cda8e552ffb deleted file mode 100644 index bf025ca196..0000000000 --- a/test/local/testdb/!transactions!0x59214a31f0a1aec159a239a9bd6a62fa371881f6e72690c178b22cda8e552ffb +++ /dev/null @@ -1 +0,0 @@ -{"nonce":"0x","gasPrice":"0x04a817c800","gasLimit":"0x015f90","to":"0xf17f52151ebef6c7334fad080c5704d77216b732","value":"0x01","data":"0x","v":"0x1c","r":"0x","s":"0x","from":"0x627306090abab3a6e1400e9345bc60c78a8bef57","hash":"0x59214a31f0a1aec159a239a9bd6a62fa371881f6e72690c178b22cda8e552ffb"} \ No newline at end of file diff --git a/test/local/testdb/!trie_db!0x0279439031111a3c85d5a32fe51d5677504f6867f7c6f31f55748f10544b64e3 b/test/local/testdb/!trie_db!0x0279439031111a3c85d5a32fe51d5677504f6867f7c6f31f55748f10544b64e3 deleted file mode 100644 index 77fe2f8344..0000000000 --- a/test/local/testdb/!trie_db!0x0279439031111a3c85d5a32fe51d5677504f6867f7c6f31f55748f10544b64e3 +++ /dev/null @@ -1 +0,0 @@ -"0xf8518080a07ad6cee5cd3acb548b1af064970c97eb75bbb1508b7a4570eda4b87ef76d7988a0a6221bba43ca4638c5b4ffdc6cccea1f3b4b170d53b9b92f92bde5573b585bfc80808080808080808080808080" \ No newline at end of file diff --git a/test/local/testdb/!trie_db!0x03a0f12083f4cdb767e0f47613296e562cafa8a25a91deb88a47433ccc4e138e b/test/local/testdb/!trie_db!0x03a0f12083f4cdb767e0f47613296e562cafa8a25a91deb88a47433ccc4e138e deleted file mode 100644 index 8fc825df52..0000000000 --- a/test/local/testdb/!trie_db!0x03a0f12083f4cdb767e0f47613296e562cafa8a25a91deb88a47433ccc4e138e +++ /dev/null @@ -1 +0,0 @@ -"0xf86694317f52151ebef6c7334fad080c5704d77216b732b84ff84d8089056bc75e2d63100002a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" \ No newline at end of file diff --git a/test/local/testdb/!trie_db!0x06de23a6d2ecbd78afda8c7fc42b84d07ef56de983d48b4e631291c607a71e5e b/test/local/testdb/!trie_db!0x06de23a6d2ecbd78afda8c7fc42b84d07ef56de983d48b4e631291c607a71e5e deleted file mode 100644 index d69f50a58f..0000000000 --- a/test/local/testdb/!trie_db!0x06de23a6d2ecbd78afda8c7fc42b84d07ef56de983d48b4e631291c607a71e5e +++ /dev/null @@ -1 +0,0 @@ -"0xf8f1a03b6a18a4c0691fbd7c2090c6cf01c59e1150fdc7c456a31b4c42fe4a64064aca80a0fa71baff75cc869d687859edac6345402a902e385a2829b9bded7697113e98368080a0a692717e52ff2d01d314834d9a639e60fad01fac4634d78c7203c6916e3f17fca03781707048c5d3acf2d0a78e1c8990e0f19a3fe5909007a6aefe8a9b0402689980a0a9c678e4e10500df2d3c913e87ce2f1b38b12409f5c65078b201783d6280e764808080a069b7a8125fdd1dac0b872648905e0e1410b8859b9fdfc1991e3860eecbcaa2da8080a003a0f12083f4cdb767e0f47613296e562cafa8a25a91deb88a47433ccc4e138e80" \ No newline at end of file diff --git a/test/local/testdb/!trie_db!0x0c94b6c2b893b998e5e67d36ee03f64eda5cbb14fb58cbeb3362709f510b2c0e b/test/local/testdb/!trie_db!0x0c94b6c2b893b998e5e67d36ee03f64eda5cbb14fb58cbeb3362709f510b2c0e deleted file mode 100644 index eab3b3ac3f..0000000000 --- a/test/local/testdb/!trie_db!0x0c94b6c2b893b998e5e67d36ee03f64eda5cbb14fb58cbeb3362709f510b2c0e +++ /dev/null @@ -1 +0,0 @@ -"0xf90111a0304356a9ff9f36e260c1ef38658725d9067d9e85450d043946d439cae014547ba05911f24d96912350de50f297c2d34d5d10e136757bf4cfff5fa41bfca219554aa05911f24d96912350de50f297c2d34d5d10e136757bf4cfff5fa41bfca219554aa05911f24d96912350de50f297c2d34d5d10e136757bf4cfff5fa41bfca219554aa05911f24d96912350de50f297c2d34d5d10e136757bf4cfff5fa41bfca219554aa05911f24d96912350de50f297c2d34d5d10e136757bf4cfff5fa41bfca219554aa05911f24d96912350de50f297c2d34d5d10e136757bf4cfff5fa41bfca219554aa05911f24d96912350de50f297c2d34d5d10e136757bf4cfff5fa41bfca219554a808080808080808080" \ No newline at end of file diff --git a/test/local/testdb/!trie_db!0x17e16fa1786714b7d5f86765fdfd3f12de1e1379c37b9753c5dbe62082ef8960 b/test/local/testdb/!trie_db!0x17e16fa1786714b7d5f86765fdfd3f12de1e1379c37b9753c5dbe62082ef8960 deleted file mode 100644 index de84772998..0000000000 --- a/test/local/testdb/!trie_db!0x17e16fa1786714b7d5f86765fdfd3f12de1e1379c37b9753c5dbe62082ef8960 +++ /dev/null @@ -1 +0,0 @@ -"0xf8b1a04172dc20058c99d8b96489c11e5400d677f12fb8da2a5650efa9b9f6383856008080808080a02f7ed65fe19e553cc2c9e20f9afcff45ff959c6bacb6fb2276bac7bee70ed2ce80a0a9c678e4e10500df2d3c913e87ce2f1b38b12409f5c65078b201783d6280e764808080a069b7a8125fdd1dac0b872648905e0e1410b8859b9fdfc1991e3860eecbcaa2da8080a0ed8c90ad5952c76a592f31c6b7b65d87b1089d447cde9cd46fad00d658f2e7c880" \ No newline at end of file diff --git a/test/local/testdb/!trie_db!0x1883479dfb5eded4883af730a86e532f7b442c08f6d7ed7d9a79601c93591386 b/test/local/testdb/!trie_db!0x1883479dfb5eded4883af730a86e532f7b442c08f6d7ed7d9a79601c93591386 deleted file mode 100644 index 1bebf1f962..0000000000 --- a/test/local/testdb/!trie_db!0x1883479dfb5eded4883af730a86e532f7b442c08f6d7ed7d9a79601c93591386 +++ /dev/null @@ -1 +0,0 @@ -"0xf86694317f52151ebef6c7334fad080c5704d77216b732b84ff84d8089056bc75e2d63100001a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" \ No newline at end of file diff --git a/test/local/testdb/!trie_db!0x21cf3512b27750f536562dc8ac85d44e521490c8475507cc62da7583cb9723e1 b/test/local/testdb/!trie_db!0x21cf3512b27750f536562dc8ac85d44e521490c8475507cc62da7583cb9723e1 deleted file mode 100644 index bde9d8d9b7..0000000000 --- a/test/local/testdb/!trie_db!0x21cf3512b27750f536562dc8ac85d44e521490c8475507cc62da7583cb9723e1 +++ /dev/null @@ -1 +0,0 @@ -"0xf8f1a029611bb48aac94d8020c92c251e958743b1c30db7bb63e7bbf247b8f78aeb1d780a0fa71baff75cc869d687859edac6345402a902e385a2829b9bded7697113e98368080a0a692717e52ff2d01d314834d9a639e60fad01fac4634d78c7203c6916e3f17fca00279439031111a3c85d5a32fe51d5677504f6867f7c6f31f55748f10544b64e380a0a9c678e4e10500df2d3c913e87ce2f1b38b12409f5c65078b201783d6280e764808080a069b7a8125fdd1dac0b872648905e0e1410b8859b9fdfc1991e3860eecbcaa2da8080a01883479dfb5eded4883af730a86e532f7b442c08f6d7ed7d9a79601c9359138680" \ No newline at end of file diff --git a/test/local/testdb/!trie_db!0x29611bb48aac94d8020c92c251e958743b1c30db7bb63e7bbf247b8f78aeb1d7 b/test/local/testdb/!trie_db!0x29611bb48aac94d8020c92c251e958743b1c30db7bb63e7bbf247b8f78aeb1d7 deleted file mode 100644 index 6d304604f1..0000000000 --- a/test/local/testdb/!trie_db!0x29611bb48aac94d8020c92c251e958743b1c30db7bb63e7bbf247b8f78aeb1d7 +++ /dev/null @@ -1 +0,0 @@ -"0xf871a0b01c8885a7276794acae6562c3d06a54045f4f69e8e1c0cacb05e11a7d2a23af808080808080808080808080a07559bb4901892880aec39ee574a87e32492071f22d0a42e184892f6b225df9b380a0de941f06d6f99270e651ece36725c555afc7fdfc4855bd86add2cdcdec4a4de380" \ No newline at end of file diff --git a/test/local/testdb/!trie_db!0x2f7ed65fe19e553cc2c9e20f9afcff45ff959c6bacb6fb2276bac7bee70ed2ce b/test/local/testdb/!trie_db!0x2f7ed65fe19e553cc2c9e20f9afcff45ff959c6bacb6fb2276bac7bee70ed2ce deleted file mode 100644 index d36f63f660..0000000000 --- a/test/local/testdb/!trie_db!0x2f7ed65fe19e553cc2c9e20f9afcff45ff959c6bacb6fb2276bac7bee70ed2ce +++ /dev/null @@ -1 +0,0 @@ -"0xf86694327306090abab3a6e1400e9345bc60c78a8bef57b84ff84d8089056bc75e2d63100000a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" \ No newline at end of file diff --git a/test/local/testdb/!trie_db!0x304356a9ff9f36e260c1ef38658725d9067d9e85450d043946d439cae014547b b/test/local/testdb/!trie_db!0x304356a9ff9f36e260c1ef38658725d9067d9e85450d043946d439cae014547b deleted file mode 100644 index cba87f22af..0000000000 --- a/test/local/testdb/!trie_db!0x304356a9ff9f36e260c1ef38658725d9067d9e85450d043946d439cae014547b +++ /dev/null @@ -1 +0,0 @@ -"0xf85120b84ef84c80885347442fa9f48000a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" \ No newline at end of file diff --git a/test/local/testdb/!trie_db!0x33bc209afd8d36f97af3e3342734e6bd259a6bc6d9f3a527e2b06a597ba27cb0 b/test/local/testdb/!trie_db!0x33bc209afd8d36f97af3e3342734e6bd259a6bc6d9f3a527e2b06a597ba27cb0 deleted file mode 100644 index a1a9fcb67e..0000000000 --- a/test/local/testdb/!trie_db!0x33bc209afd8d36f97af3e3342734e6bd259a6bc6d9f3a527e2b06a597ba27cb0 +++ /dev/null @@ -1 +0,0 @@ -"0xf8d1a081f3d60fe1f6e52b48917d15245c68de1b6642ace21159e538d0a96bf125446080a0fa71baff75cc869d687859edac6345402a902e385a2829b9bded7697113e9836808080a02f7ed65fe19e553cc2c9e20f9afcff45ff959c6bacb6fb2276bac7bee70ed2ce80a0a9c678e4e10500df2d3c913e87ce2f1b38b12409f5c65078b201783d6280e764808080a069b7a8125fdd1dac0b872648905e0e1410b8859b9fdfc1991e3860eecbcaa2da8080a0ed8c90ad5952c76a592f31c6b7b65d87b1089d447cde9cd46fad00d658f2e7c880" \ No newline at end of file diff --git a/test/local/testdb/!trie_db!0x372afacecb5d397370430cfa7481601253172b63d77e80278ab3d4895b20001b b/test/local/testdb/!trie_db!0x372afacecb5d397370430cfa7481601253172b63d77e80278ab3d4895b20001b deleted file mode 100644 index 7941daad10..0000000000 --- a/test/local/testdb/!trie_db!0x372afacecb5d397370430cfa7481601253172b63d77e80278ab3d4895b20001b +++ /dev/null @@ -1 +0,0 @@ -"0xf8f180a05911f24d96912350de50f297c2d34d5d10e136757bf4cfff5fa41bfca219554aa05911f24d96912350de50f297c2d34d5d10e136757bf4cfff5fa41bfca219554aa05911f24d96912350de50f297c2d34d5d10e136757bf4cfff5fa41bfca219554aa05911f24d96912350de50f297c2d34d5d10e136757bf4cfff5fa41bfca219554aa05911f24d96912350de50f297c2d34d5d10e136757bf4cfff5fa41bfca219554aa05911f24d96912350de50f297c2d34d5d10e136757bf4cfff5fa41bfca219554aa05911f24d96912350de50f297c2d34d5d10e136757bf4cfff5fa41bfca219554a808080808080808080" \ No newline at end of file diff --git a/test/local/testdb/!trie_db!0x3781707048c5d3acf2d0a78e1c8990e0f19a3fe5909007a6aefe8a9b04026899 b/test/local/testdb/!trie_db!0x3781707048c5d3acf2d0a78e1c8990e0f19a3fe5909007a6aefe8a9b04026899 deleted file mode 100644 index 9d97a84d5a..0000000000 --- a/test/local/testdb/!trie_db!0x3781707048c5d3acf2d0a78e1c8990e0f19a3fe5909007a6aefe8a9b04026899 +++ /dev/null @@ -1 +0,0 @@ -"0xf8518080a057de8e0787e5eb6e29bb1f509cf31f345ea3265c2faf00b6dcb883876eb89cc1a0a6221bba43ca4638c5b4ffdc6cccea1f3b4b170d53b9b92f92bde5573b585bfc80808080808080808080808080" \ No newline at end of file diff --git a/test/local/testdb/!trie_db!0x3a6bcf8171ae73adaff761bc4dc46d54e7067320d7093781ce3b944ad84c8f3a b/test/local/testdb/!trie_db!0x3a6bcf8171ae73adaff761bc4dc46d54e7067320d7093781ce3b944ad84c8f3a deleted file mode 100644 index e6e6f37a22..0000000000 --- a/test/local/testdb/!trie_db!0x3a6bcf8171ae73adaff761bc4dc46d54e7067320d7093781ce3b944ad84c8f3a +++ /dev/null @@ -1 +0,0 @@ -"0xf59310000000000000000000000000000000000000a00c94b6c2b893b998e5e67d36ee03f64eda5cbb14fb58cbeb3362709f510b2c0e" \ No newline at end of file diff --git a/test/local/testdb/!trie_db!0x3b6a18a4c0691fbd7c2090c6cf01c59e1150fdc7c456a31b4c42fe4a64064aca b/test/local/testdb/!trie_db!0x3b6a18a4c0691fbd7c2090c6cf01c59e1150fdc7c456a31b4c42fe4a64064aca deleted file mode 100644 index e0f952edc8..0000000000 --- a/test/local/testdb/!trie_db!0x3b6a18a4c0691fbd7c2090c6cf01c59e1150fdc7c456a31b4c42fe4a64064aca +++ /dev/null @@ -1 +0,0 @@ -"0xf871a03a6bcf8171ae73adaff761bc4dc46d54e7067320d7093781ce3b944ad84c8f3a808080808080808080808080a07559bb4901892880aec39ee574a87e32492071f22d0a42e184892f6b225df9b380a0de941f06d6f99270e651ece36725c555afc7fdfc4855bd86add2cdcdec4a4de380" \ No newline at end of file diff --git a/test/local/testdb/!trie_db!0x4172dc20058c99d8b96489c11e5400d677f12fb8da2a5650efa9b9f638385600 b/test/local/testdb/!trie_db!0x4172dc20058c99d8b96489c11e5400d677f12fb8da2a5650efa9b9f638385600 deleted file mode 100644 index f444559a37..0000000000 --- a/test/local/testdb/!trie_db!0x4172dc20058c99d8b96489c11e5400d677f12fb8da2a5650efa9b9f638385600 +++ /dev/null @@ -1 +0,0 @@ -"0xf851a062ab8b4e7f7569eb032e637fcdcfd5b561442347326a3ffca40ae225d0029d4c808080808080808080808080a07559bb4901892880aec39ee574a87e32492071f22d0a42e184892f6b225df9b3808080" \ No newline at end of file diff --git a/test/local/testdb/!trie_db!0x47fcc5864e5a790d538706379b31fbaf2ef6c71d2d8085ade2d58b55ab4ea2ac b/test/local/testdb/!trie_db!0x47fcc5864e5a790d538706379b31fbaf2ef6c71d2d8085ade2d58b55ab4ea2ac deleted file mode 100644 index 053fef529e..0000000000 --- a/test/local/testdb/!trie_db!0x47fcc5864e5a790d538706379b31fbaf2ef6c71d2d8085ade2d58b55ab4ea2ac +++ /dev/null @@ -1 +0,0 @@ -"0xf8d1a04172dc20058c99d8b96489c11e5400d677f12fb8da2a5650efa9b9f63838560080a0bf2795b671509d37d9d2b1bab1ab2419a5e09b9aede2d9e29b8d45535217abf1808080a02f7ed65fe19e553cc2c9e20f9afcff45ff959c6bacb6fb2276bac7bee70ed2ce80a0a9c678e4e10500df2d3c913e87ce2f1b38b12409f5c65078b201783d6280e764808080a069b7a8125fdd1dac0b872648905e0e1410b8859b9fdfc1991e3860eecbcaa2da8080a0ed8c90ad5952c76a592f31c6b7b65d87b1089d447cde9cd46fad00d658f2e7c880" \ No newline at end of file diff --git a/test/local/testdb/!trie_db!0x4a00bea2712cfd525de12555898ddcb997e6b8e624e09c5a3f4b200226c65383 b/test/local/testdb/!trie_db!0x4a00bea2712cfd525de12555898ddcb997e6b8e624e09c5a3f4b200226c65383 deleted file mode 100644 index a96936de0e..0000000000 --- a/test/local/testdb/!trie_db!0x4a00bea2712cfd525de12555898ddcb997e6b8e624e09c5a3f4b200226c65383 +++ /dev/null @@ -1 +0,0 @@ -"0xf8d1a081f3d60fe1f6e52b48917d15245c68de1b6642ace21159e538d0a96bf125446080a0fa71baff75cc869d687859edac6345402a902e385a2829b9bded7697113e9836808080a0d9268c1bf3f59a12714b735a83b70e89208877a149724ace5df5eb9ad355259e80a0a9c678e4e10500df2d3c913e87ce2f1b38b12409f5c65078b201783d6280e764808080a069b7a8125fdd1dac0b872648905e0e1410b8859b9fdfc1991e3860eecbcaa2da8080a0ed8c90ad5952c76a592f31c6b7b65d87b1089d447cde9cd46fad00d658f2e7c880" \ No newline at end of file diff --git a/test/local/testdb/!trie_db!0x4ef305cd67b97b743669faa6b70fab3c29d545df173e5798da1a3bef0efeb5d9 b/test/local/testdb/!trie_db!0x4ef305cd67b97b743669faa6b70fab3c29d545df173e5798da1a3bef0efeb5d9 deleted file mode 100644 index b0cc304a09..0000000000 --- a/test/local/testdb/!trie_db!0x4ef305cd67b97b743669faa6b70fab3c29d545df173e5798da1a3bef0efeb5d9 +++ /dev/null @@ -1 +0,0 @@ -"0xf6940000000000000000000000000000000000000000a0372afacecb5d397370430cfa7481601253172b63d77e80278ab3d4895b20001b" \ No newline at end of file diff --git a/test/local/testdb/!trie_db!0x5072a6c1e2e016ff4e5b6b35caf68673a43b7a13bb1fa82f2643bad3e4d1c253 b/test/local/testdb/!trie_db!0x5072a6c1e2e016ff4e5b6b35caf68673a43b7a13bb1fa82f2643bad3e4d1c253 deleted file mode 100644 index d711d7bdda..0000000000 --- a/test/local/testdb/!trie_db!0x5072a6c1e2e016ff4e5b6b35caf68673a43b7a13bb1fa82f2643bad3e4d1c253 +++ /dev/null @@ -1 +0,0 @@ -"0xf8d180a05911f24d96912350de50f297c2d34d5d10e136757bf4cfff5fa41bfca219554aa05911f24d96912350de50f297c2d34d5d10e136757bf4cfff5fa41bfca219554aa05911f24d96912350de50f297c2d34d5d10e136757bf4cfff5fa41bfca219554aa05911f24d96912350de50f297c2d34d5d10e136757bf4cfff5fa41bfca219554aa05911f24d96912350de50f297c2d34d5d10e136757bf4cfff5fa41bfca219554aa05911f24d96912350de50f297c2d34d5d10e136757bf4cfff5fa41bfca219554a80808080808080808080" \ No newline at end of file diff --git a/test/local/testdb/!trie_db!0x54350d30b48d6984395cf168b20e31952619100e01a31e619f91393e34d80ab5 b/test/local/testdb/!trie_db!0x54350d30b48d6984395cf168b20e31952619100e01a31e619f91393e34d80ab5 deleted file mode 100644 index d244c1c9ca..0000000000 --- a/test/local/testdb/!trie_db!0x54350d30b48d6984395cf168b20e31952619100e01a31e619f91393e34d80ab5 +++ /dev/null @@ -1 +0,0 @@ -"0xf85e95200000000000000000000000000000000000000001b846f8448080a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" \ No newline at end of file diff --git a/test/local/testdb/!trie_db!0x575dc2642e9386612af4e066b4f7c9d6a216d0ac795355860ac66dea8d88b767 b/test/local/testdb/!trie_db!0x575dc2642e9386612af4e066b4f7c9d6a216d0ac795355860ac66dea8d88b767 deleted file mode 100644 index 653fe7f610..0000000000 --- a/test/local/testdb/!trie_db!0x575dc2642e9386612af4e066b4f7c9d6a216d0ac795355860ac66dea8d88b767 +++ /dev/null @@ -1 +0,0 @@ -"0xf851a04ef305cd67b97b743669faa6b70fab3c29d545df173e5798da1a3bef0efeb5d98080808080a02f7ed65fe19e553cc2c9e20f9afcff45ff959c6bacb6fb2276bac7bee70ed2ce80808080808080808080" \ No newline at end of file diff --git a/test/local/testdb/!trie_db!0x57de8e0787e5eb6e29bb1f509cf31f345ea3265c2faf00b6dcb883876eb89cc1 b/test/local/testdb/!trie_db!0x57de8e0787e5eb6e29bb1f509cf31f345ea3265c2faf00b6dcb883876eb89cc1 deleted file mode 100644 index bef364264a..0000000000 --- a/test/local/testdb/!trie_db!0x57de8e0787e5eb6e29bb1f509cf31f345ea3265c2faf00b6dcb883876eb89cc1 +++ /dev/null @@ -1 +0,0 @@ -"0xf86694207306090abab3a6e1400e9345bc60c78a8bef57b84ff84d0289056bc46233a5737ffea056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" \ No newline at end of file diff --git a/test/local/testdb/!trie_db!0x5911f24d96912350de50f297c2d34d5d10e136757bf4cfff5fa41bfca219554a b/test/local/testdb/!trie_db!0x5911f24d96912350de50f297c2d34d5d10e136757bf4cfff5fa41bfca219554a deleted file mode 100644 index 73bd6fefee..0000000000 --- a/test/local/testdb/!trie_db!0x5911f24d96912350de50f297c2d34d5d10e136757bf4cfff5fa41bfca219554a +++ /dev/null @@ -1 +0,0 @@ -"0xf84920b846f8448080a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" \ No newline at end of file diff --git a/test/local/testdb/!trie_db!0x5e287c9213b0dcabfdf484092e2b6133f6826e045c391501e80374760de69320 b/test/local/testdb/!trie_db!0x5e287c9213b0dcabfdf484092e2b6133f6826e045c391501e80374760de69320 deleted file mode 100644 index bb473d2c68..0000000000 --- a/test/local/testdb/!trie_db!0x5e287c9213b0dcabfdf484092e2b6133f6826e045c391501e80374760de69320 +++ /dev/null @@ -1 +0,0 @@ -"0xf85180a05911f24d96912350de50f297c2d34d5d10e136757bf4cfff5fa41bfca219554aa05911f24d96912350de50f297c2d34d5d10e136757bf4cfff5fa41bfca219554a8080808080808080808080808080" \ No newline at end of file diff --git a/test/local/testdb/!trie_db!0x607f7405b317ab741f6607ab7dc65de14684038e90bfb07417c7ce7ef669c0eb b/test/local/testdb/!trie_db!0x607f7405b317ab741f6607ab7dc65de14684038e90bfb07417c7ce7ef669c0eb deleted file mode 100644 index 14c34e3a0b..0000000000 --- a/test/local/testdb/!trie_db!0x607f7405b317ab741f6607ab7dc65de14684038e90bfb07417c7ce7ef669c0eb +++ /dev/null @@ -1 +0,0 @@ -"0xf8b1a04ef305cd67b97b743669faa6b70fab3c29d545df173e5798da1a3bef0efeb5d98080808080a02f7ed65fe19e553cc2c9e20f9afcff45ff959c6bacb6fb2276bac7bee70ed2ce80a0a9c678e4e10500df2d3c913e87ce2f1b38b12409f5c65078b201783d6280e764808080a069b7a8125fdd1dac0b872648905e0e1410b8859b9fdfc1991e3860eecbcaa2da8080a0ed8c90ad5952c76a592f31c6b7b65d87b1089d447cde9cd46fad00d658f2e7c880" \ No newline at end of file diff --git a/test/local/testdb/!trie_db!0x62ab8b4e7f7569eb032e637fcdcfd5b561442347326a3ffca40ae225d0029d4c b/test/local/testdb/!trie_db!0x62ab8b4e7f7569eb032e637fcdcfd5b561442347326a3ffca40ae225d0029d4c deleted file mode 100644 index f81615a67a..0000000000 --- a/test/local/testdb/!trie_db!0x62ab8b4e7f7569eb032e637fcdcfd5b561442347326a3ffca40ae225d0029d4c +++ /dev/null @@ -1 +0,0 @@ -"0xf59310000000000000000000000000000000000000a0372afacecb5d397370430cfa7481601253172b63d77e80278ab3d4895b20001b" \ No newline at end of file diff --git a/test/local/testdb/!trie_db!0x67bcaf1d60f75a22d49b037c88c00e44733094250664cbfc9d9bbf9eda3ca745 b/test/local/testdb/!trie_db!0x67bcaf1d60f75a22d49b037c88c00e44733094250664cbfc9d9bbf9eda3ca745 deleted file mode 100644 index aa17b399f2..0000000000 --- a/test/local/testdb/!trie_db!0x67bcaf1d60f75a22d49b037c88c00e44733094250664cbfc9d9bbf9eda3ca745 +++ /dev/null @@ -1 +0,0 @@ -"0xf6941000000000000000000000000000000000000000a0fe3d828c2803bd586bc34641752157c205a391ec9a1b730031bccc77a9276b02" \ No newline at end of file diff --git a/test/local/testdb/!trie_db!0x69b7a8125fdd1dac0b872648905e0e1410b8859b9fdfc1991e3860eecbcaa2da b/test/local/testdb/!trie_db!0x69b7a8125fdd1dac0b872648905e0e1410b8859b9fdfc1991e3860eecbcaa2da deleted file mode 100644 index a5ae13c607..0000000000 --- a/test/local/testdb/!trie_db!0x69b7a8125fdd1dac0b872648905e0e1410b8859b9fdfc1991e3860eecbcaa2da +++ /dev/null @@ -1 +0,0 @@ -"0xf8669435fdf4076b8f3a5357c5e395ab970b5b54098fefb84ff84d8089056bc75e2d63100000a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" \ No newline at end of file diff --git a/test/local/testdb/!trie_db!0x7559bb4901892880aec39ee574a87e32492071f22d0a42e184892f6b225df9b3 b/test/local/testdb/!trie_db!0x7559bb4901892880aec39ee574a87e32492071f22d0a42e184892f6b225df9b3 deleted file mode 100644 index d2640902c7..0000000000 --- a/test/local/testdb/!trie_db!0x7559bb4901892880aec39ee574a87e32492071f22d0a42e184892f6b225df9b3 +++ /dev/null @@ -1 +0,0 @@ -"0xf86694201d4e623d10f9fba5db95830f7d3839406c6af2b84ff84d8089056bc75e2d63100000a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" \ No newline at end of file diff --git a/test/local/testdb/!trie_db!0x77ecddca419c697a6aae21fa9a02f85475aaa5b109d41dc35f1893dc5bb3cf72 b/test/local/testdb/!trie_db!0x77ecddca419c697a6aae21fa9a02f85475aaa5b109d41dc35f1893dc5bb3cf72 deleted file mode 100644 index 292c5e8dd7..0000000000 --- a/test/local/testdb/!trie_db!0x77ecddca419c697a6aae21fa9a02f85475aaa5b109d41dc35f1893dc5bb3cf72 +++ /dev/null @@ -1 +0,0 @@ -"0xf6941000000000000000000000000000000000000000a0372afacecb5d397370430cfa7481601253172b63d77e80278ab3d4895b20001b" \ No newline at end of file diff --git a/test/local/testdb/!trie_db!0x7ad6cee5cd3acb548b1af064970c97eb75bbb1508b7a4570eda4b87ef76d7988 b/test/local/testdb/!trie_db!0x7ad6cee5cd3acb548b1af064970c97eb75bbb1508b7a4570eda4b87ef76d7988 deleted file mode 100644 index 48711c84aa..0000000000 --- a/test/local/testdb/!trie_db!0x7ad6cee5cd3acb548b1af064970c97eb75bbb1508b7a4570eda4b87ef76d7988 +++ /dev/null @@ -1 +0,0 @@ -"0xf86694207306090abab3a6e1400e9345bc60c78a8bef57b84ff84d0189056bc5e0308441bfffa056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" \ No newline at end of file diff --git a/test/local/testdb/!trie_db!0x81f3d60fe1f6e52b48917d15245c68de1b6642ace21159e538d0a96bf1254460 b/test/local/testdb/!trie_db!0x81f3d60fe1f6e52b48917d15245c68de1b6642ace21159e538d0a96bf1254460 deleted file mode 100644 index 1b356a2269..0000000000 --- a/test/local/testdb/!trie_db!0x81f3d60fe1f6e52b48917d15245c68de1b6642ace21159e538d0a96bf1254460 +++ /dev/null @@ -1 +0,0 @@ -"0xf871a062ab8b4e7f7569eb032e637fcdcfd5b561442347326a3ffca40ae225d0029d4c808080808080808080808080a07559bb4901892880aec39ee574a87e32492071f22d0a42e184892f6b225df9b380a0de941f06d6f99270e651ece36725c555afc7fdfc4855bd86add2cdcdec4a4de380" \ No newline at end of file diff --git a/test/local/testdb/!trie_db!0x83097864ebee0be81925b10976490ab2c71a0643e9f16e16807811b7112ed032 b/test/local/testdb/!trie_db!0x83097864ebee0be81925b10976490ab2c71a0643e9f16e16807811b7112ed032 deleted file mode 100644 index fe96610e1c..0000000000 --- a/test/local/testdb/!trie_db!0x83097864ebee0be81925b10976490ab2c71a0643e9f16e16807811b7112ed032 +++ /dev/null @@ -1 +0,0 @@ -"0xf6941000000000000000000000000000000000000000a098ad39fb8468cbcb93a0990018ded196413ef989c0062341ff886889f39ab41b" \ No newline at end of file diff --git a/test/local/testdb/!trie_db!0x84893185cfd51214257490b3ad18415a1fc08d2ffab89a852e0067d6957b09d4 b/test/local/testdb/!trie_db!0x84893185cfd51214257490b3ad18415a1fc08d2ffab89a852e0067d6957b09d4 deleted file mode 100644 index 8da3080907..0000000000 --- a/test/local/testdb/!trie_db!0x84893185cfd51214257490b3ad18415a1fc08d2ffab89a852e0067d6957b09d4 +++ /dev/null @@ -1 +0,0 @@ -"0xf8d1a04172dc20058c99d8b96489c11e5400d677f12fb8da2a5650efa9b9f63838560080a0fa71baff75cc869d687859edac6345402a902e385a2829b9bded7697113e9836808080a02f7ed65fe19e553cc2c9e20f9afcff45ff959c6bacb6fb2276bac7bee70ed2ce80a0a9c678e4e10500df2d3c913e87ce2f1b38b12409f5c65078b201783d6280e764808080a069b7a8125fdd1dac0b872648905e0e1410b8859b9fdfc1991e3860eecbcaa2da8080a0ed8c90ad5952c76a592f31c6b7b65d87b1089d447cde9cd46fad00d658f2e7c880" \ No newline at end of file diff --git a/test/local/testdb/!trie_db!0x8a083a05cf77f2f36f6c055ac468fc88ab99335759bb47d22b0947229f0b170a b/test/local/testdb/!trie_db!0x8a083a05cf77f2f36f6c055ac468fc88ab99335759bb47d22b0947229f0b170a deleted file mode 100644 index d1b4951ea9..0000000000 --- a/test/local/testdb/!trie_db!0x8a083a05cf77f2f36f6c055ac468fc88ab99335759bb47d22b0947229f0b170a +++ /dev/null @@ -1 +0,0 @@ -"0xf6941000000000000000000000000000000000000000a05e287c9213b0dcabfdf484092e2b6133f6826e045c391501e80374760de69320" \ No newline at end of file diff --git a/test/local/testdb/!trie_db!0x98ad39fb8468cbcb93a0990018ded196413ef989c0062341ff886889f39ab41b b/test/local/testdb/!trie_db!0x98ad39fb8468cbcb93a0990018ded196413ef989c0062341ff886889f39ab41b deleted file mode 100644 index 43f3b84b55..0000000000 --- a/test/local/testdb/!trie_db!0x98ad39fb8468cbcb93a0990018ded196413ef989c0062341ff886889f39ab41b +++ /dev/null @@ -1 +0,0 @@ -"0xf8b180a05911f24d96912350de50f297c2d34d5d10e136757bf4cfff5fa41bfca219554aa05911f24d96912350de50f297c2d34d5d10e136757bf4cfff5fa41bfca219554aa05911f24d96912350de50f297c2d34d5d10e136757bf4cfff5fa41bfca219554aa05911f24d96912350de50f297c2d34d5d10e136757bf4cfff5fa41bfca219554aa05911f24d96912350de50f297c2d34d5d10e136757bf4cfff5fa41bfca219554a8080808080808080808080" \ No newline at end of file diff --git a/test/local/testdb/!trie_db!0xa2616c2f24b3c29c29fedec4acc5c3d803aa7cb7818017520686266074832155 b/test/local/testdb/!trie_db!0xa2616c2f24b3c29c29fedec4acc5c3d803aa7cb7818017520686266074832155 deleted file mode 100644 index 9a729c7235..0000000000 --- a/test/local/testdb/!trie_db!0xa2616c2f24b3c29c29fedec4acc5c3d803aa7cb7818017520686266074832155 +++ /dev/null @@ -1 +0,0 @@ -"0xf8f1a081f3d60fe1f6e52b48917d15245c68de1b6642ace21159e538d0a96bf125446080a0fa71baff75cc869d687859edac6345402a902e385a2829b9bded7697113e98368080a0a692717e52ff2d01d314834d9a639e60fad01fac4634d78c7203c6916e3f17fca0d9268c1bf3f59a12714b735a83b70e89208877a149724ace5df5eb9ad355259e80a0a9c678e4e10500df2d3c913e87ce2f1b38b12409f5c65078b201783d6280e764808080a069b7a8125fdd1dac0b872648905e0e1410b8859b9fdfc1991e3860eecbcaa2da8080a0ed8c90ad5952c76a592f31c6b7b65d87b1089d447cde9cd46fad00d658f2e7c880" \ No newline at end of file diff --git a/test/local/testdb/!trie_db!0xa6221bba43ca4638c5b4ffdc6cccea1f3b4b170d53b9b92f92bde5573b585bfc b/test/local/testdb/!trie_db!0xa6221bba43ca4638c5b4ffdc6cccea1f3b4b170d53b9b92f92bde5573b585bfc deleted file mode 100644 index b3daf459a0..0000000000 --- a/test/local/testdb/!trie_db!0xa6221bba43ca4638c5b4ffdc6cccea1f3b4b170d53b9b92f92bde5573b585bfc +++ /dev/null @@ -1 +0,0 @@ -"0xf866942030a553fc93768f612722bb8c2ec78ac90b3bbcb84ff84d8089056bc75e2d63100000a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" \ No newline at end of file diff --git a/test/local/testdb/!trie_db!0xa692717e52ff2d01d314834d9a639e60fad01fac4634d78c7203c6916e3f17fc b/test/local/testdb/!trie_db!0xa692717e52ff2d01d314834d9a639e60fad01fac4634d78c7203c6916e3f17fc deleted file mode 100644 index 9828484e51..0000000000 --- a/test/local/testdb/!trie_db!0xa692717e52ff2d01d314834d9a639e60fad01fac4634d78c7203c6916e3f17fc +++ /dev/null @@ -1 +0,0 @@ -"0xf866943aeda56215b167893e80b4fe645ba6d5bab767deb84ff84d8089056bc75e2d63100000a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" \ No newline at end of file diff --git a/test/local/testdb/!trie_db!0xa9c678e4e10500df2d3c913e87ce2f1b38b12409f5c65078b201783d6280e764 b/test/local/testdb/!trie_db!0xa9c678e4e10500df2d3c913e87ce2f1b38b12409f5c65078b201783d6280e764 deleted file mode 100644 index d6700e8f8a..0000000000 --- a/test/local/testdb/!trie_db!0xa9c678e4e10500df2d3c913e87ce2f1b38b12409f5c65078b201783d6280e764 +++ /dev/null @@ -1 +0,0 @@ -"0xf86694321aea9a577a9b44299b9c15c88cf3087f3b5544b84ff84d8089056bc75e2d63100000a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" \ No newline at end of file diff --git a/test/local/testdb/!trie_db!0xae94d36357988098ed9618487d91f45503cb88cf28b802043c5acd7304f6bb45 b/test/local/testdb/!trie_db!0xae94d36357988098ed9618487d91f45503cb88cf28b802043c5acd7304f6bb45 deleted file mode 100644 index 728d1fda30..0000000000 --- a/test/local/testdb/!trie_db!0xae94d36357988098ed9618487d91f45503cb88cf28b802043c5acd7304f6bb45 +++ /dev/null @@ -1 +0,0 @@ -"0xf85120b84ef84c808829a3a217d4fa4000a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" \ No newline at end of file diff --git a/test/local/testdb/!trie_db!0xb01c8885a7276794acae6562c3d06a54045f4f69e8e1c0cacb05e11a7d2a23af b/test/local/testdb/!trie_db!0xb01c8885a7276794acae6562c3d06a54045f4f69e8e1c0cacb05e11a7d2a23af deleted file mode 100644 index 6718004a0c..0000000000 --- a/test/local/testdb/!trie_db!0xb01c8885a7276794acae6562c3d06a54045f4f69e8e1c0cacb05e11a7d2a23af +++ /dev/null @@ -1 +0,0 @@ -"0xf59310000000000000000000000000000000000000a0cdf6e1aa543f388bd6340e20dfd8b00714aaf2cfcce46aa43a023e0e8fa65bfd" \ No newline at end of file diff --git a/test/local/testdb/!trie_db!0xb84b045ddcae6d0562d0293dde2f88f4eb01aa731d5495387f414c8e471c7b18 b/test/local/testdb/!trie_db!0xb84b045ddcae6d0562d0293dde2f88f4eb01aa731d5495387f414c8e471c7b18 deleted file mode 100644 index 88830f7199..0000000000 --- a/test/local/testdb/!trie_db!0xb84b045ddcae6d0562d0293dde2f88f4eb01aa731d5495387f414c8e471c7b18 +++ /dev/null @@ -1 +0,0 @@ -"0xf86694207306090abab3a6e1400e9345bc60c78a8bef57b84ff84d8089056bc75e2d63100000a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" \ No newline at end of file diff --git a/test/local/testdb/!trie_db!0xbf2795b671509d37d9d2b1bab1ab2419a5e09b9aede2d9e29b8d45535217abf1 b/test/local/testdb/!trie_db!0xbf2795b671509d37d9d2b1bab1ab2419a5e09b9aede2d9e29b8d45535217abf1 deleted file mode 100644 index 86e95b4739..0000000000 --- a/test/local/testdb/!trie_db!0xbf2795b671509d37d9d2b1bab1ab2419a5e09b9aede2d9e29b8d45535217abf1 +++ /dev/null @@ -1 +0,0 @@ -"0xf866943932b7a2355d6fecc4b5c0b6bd44cc31df247a2eb84ff84d8089056bc75e2d63100000a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" \ No newline at end of file diff --git a/test/local/testdb/!trie_db!0xc4f6a1f0b681632674246c126fd0b2eb33764bdb3ec5763b93968f205314944c b/test/local/testdb/!trie_db!0xc4f6a1f0b681632674246c126fd0b2eb33764bdb3ec5763b93968f205314944c deleted file mode 100644 index 4ce9496e0a..0000000000 --- a/test/local/testdb/!trie_db!0xc4f6a1f0b681632674246c126fd0b2eb33764bdb3ec5763b93968f205314944c +++ /dev/null @@ -1 +0,0 @@ -"0xf6941000000000000000000000000000000000000000a0c74b55ae25b522915216f62ea6ffcd436f6c6d4fe14a771042f7d4cea5cfa92f" \ No newline at end of file diff --git a/test/local/testdb/!trie_db!0xc72aa692983dcb3230af2fa59915a1b661eb90e0ad6b61bc8135e9e14af02608 b/test/local/testdb/!trie_db!0xc72aa692983dcb3230af2fa59915a1b661eb90e0ad6b61bc8135e9e14af02608 deleted file mode 100644 index bc1c309076..0000000000 --- a/test/local/testdb/!trie_db!0xc72aa692983dcb3230af2fa59915a1b661eb90e0ad6b61bc8135e9e14af02608 +++ /dev/null @@ -1 +0,0 @@ -"0xf871a04ef305cd67b97b743669faa6b70fab3c29d545df173e5798da1a3bef0efeb5d98080808080a02f7ed65fe19e553cc2c9e20f9afcff45ff959c6bacb6fb2276bac7bee70ed2ce8080808080808080a0ed8c90ad5952c76a592f31c6b7b65d87b1089d447cde9cd46fad00d658f2e7c880" \ No newline at end of file diff --git a/test/local/testdb/!trie_db!0xc74b55ae25b522915216f62ea6ffcd436f6c6d4fe14a771042f7d4cea5cfa92f b/test/local/testdb/!trie_db!0xc74b55ae25b522915216f62ea6ffcd436f6c6d4fe14a771042f7d4cea5cfa92f deleted file mode 100644 index 3c3d65f236..0000000000 --- a/test/local/testdb/!trie_db!0xc74b55ae25b522915216f62ea6ffcd436f6c6d4fe14a771042f7d4cea5cfa92f +++ /dev/null @@ -1 +0,0 @@ -"0xf89180a05911f24d96912350de50f297c2d34d5d10e136757bf4cfff5fa41bfca219554aa05911f24d96912350de50f297c2d34d5d10e136757bf4cfff5fa41bfca219554aa05911f24d96912350de50f297c2d34d5d10e136757bf4cfff5fa41bfca219554aa05911f24d96912350de50f297c2d34d5d10e136757bf4cfff5fa41bfca219554a808080808080808080808080" \ No newline at end of file diff --git a/test/local/testdb/!trie_db!0xcdf6e1aa543f388bd6340e20dfd8b00714aaf2cfcce46aa43a023e0e8fa65bfd b/test/local/testdb/!trie_db!0xcdf6e1aa543f388bd6340e20dfd8b00714aaf2cfcce46aa43a023e0e8fa65bfd deleted file mode 100644 index 80ebef10bd..0000000000 --- a/test/local/testdb/!trie_db!0xcdf6e1aa543f388bd6340e20dfd8b00714aaf2cfcce46aa43a023e0e8fa65bfd +++ /dev/null @@ -1 +0,0 @@ -"0xf90111a0ae94d36357988098ed9618487d91f45503cb88cf28b802043c5acd7304f6bb45a05911f24d96912350de50f297c2d34d5d10e136757bf4cfff5fa41bfca219554aa05911f24d96912350de50f297c2d34d5d10e136757bf4cfff5fa41bfca219554aa05911f24d96912350de50f297c2d34d5d10e136757bf4cfff5fa41bfca219554aa05911f24d96912350de50f297c2d34d5d10e136757bf4cfff5fa41bfca219554aa05911f24d96912350de50f297c2d34d5d10e136757bf4cfff5fa41bfca219554aa05911f24d96912350de50f297c2d34d5d10e136757bf4cfff5fa41bfca219554aa05911f24d96912350de50f297c2d34d5d10e136757bf4cfff5fa41bfca219554a808080808080808080" \ No newline at end of file diff --git a/test/local/testdb/!trie_db!0xd9268c1bf3f59a12714b735a83b70e89208877a149724ace5df5eb9ad355259e b/test/local/testdb/!trie_db!0xd9268c1bf3f59a12714b735a83b70e89208877a149724ace5df5eb9ad355259e deleted file mode 100644 index 4327d711ee..0000000000 --- a/test/local/testdb/!trie_db!0xd9268c1bf3f59a12714b735a83b70e89208877a149724ace5df5eb9ad355259e +++ /dev/null @@ -1 +0,0 @@ -"0xf8518080a0b84b045ddcae6d0562d0293dde2f88f4eb01aa731d5495387f414c8e471c7b18a0a6221bba43ca4638c5b4ffdc6cccea1f3b4b170d53b9b92f92bde5573b585bfc80808080808080808080808080" \ No newline at end of file diff --git a/test/local/testdb/!trie_db!0xde941f06d6f99270e651ece36725c555afc7fdfc4855bd86add2cdcdec4a4de3 b/test/local/testdb/!trie_db!0xde941f06d6f99270e651ece36725c555afc7fdfc4855bd86add2cdcdec4a4de3 deleted file mode 100644 index 7b7f004ebc..0000000000 --- a/test/local/testdb/!trie_db!0xde941f06d6f99270e651ece36725c555afc7fdfc4855bd86add2cdcdec4a4de3 +++ /dev/null @@ -1 +0,0 @@ -"0xf86694204f2ac550a1b4e2280d04c21cea7ebd822934b5b84ff84d8089056bc75e2d63100000a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" \ No newline at end of file diff --git a/test/local/testdb/!trie_db!0xdeb8f2fa902116659c4439f256f7f21226617577101caae5e93f9ce5eb2bc082 b/test/local/testdb/!trie_db!0xdeb8f2fa902116659c4439f256f7f21226617577101caae5e93f9ce5eb2bc082 deleted file mode 100644 index e084c412cd..0000000000 --- a/test/local/testdb/!trie_db!0xdeb8f2fa902116659c4439f256f7f21226617577101caae5e93f9ce5eb2bc082 +++ /dev/null @@ -1 +0,0 @@ -"0xf891a04ef305cd67b97b743669faa6b70fab3c29d545df173e5798da1a3bef0efeb5d98080808080a02f7ed65fe19e553cc2c9e20f9afcff45ff959c6bacb6fb2276bac7bee70ed2ce8080808080a069b7a8125fdd1dac0b872648905e0e1410b8859b9fdfc1991e3860eecbcaa2da8080a0ed8c90ad5952c76a592f31c6b7b65d87b1089d447cde9cd46fad00d658f2e7c880" \ No newline at end of file diff --git a/test/local/testdb/!trie_db!0xed57d93087e1a79ee9e3f6eadfc3d2affdb420d73dc4ecf07c5a21b22a221a98 b/test/local/testdb/!trie_db!0xed57d93087e1a79ee9e3f6eadfc3d2affdb420d73dc4ecf07c5a21b22a221a98 deleted file mode 100644 index 0f4ee6f979..0000000000 --- a/test/local/testdb/!trie_db!0xed57d93087e1a79ee9e3f6eadfc3d2affdb420d73dc4ecf07c5a21b22a221a98 +++ /dev/null @@ -1 +0,0 @@ -"0xf6941000000000000000000000000000000000000000a05072a6c1e2e016ff4e5b6b35caf68673a43b7a13bb1fa82f2643bad3e4d1c253" \ No newline at end of file diff --git a/test/local/testdb/!trie_db!0xed8c90ad5952c76a592f31c6b7b65d87b1089d447cde9cd46fad00d658f2e7c8 b/test/local/testdb/!trie_db!0xed8c90ad5952c76a592f31c6b7b65d87b1089d447cde9cd46fad00d658f2e7c8 deleted file mode 100644 index 7559b00aec..0000000000 --- a/test/local/testdb/!trie_db!0xed8c90ad5952c76a592f31c6b7b65d87b1089d447cde9cd46fad00d658f2e7c8 +++ /dev/null @@ -1 +0,0 @@ -"0xf86694317f52151ebef6c7334fad080c5704d77216b732b84ff84d8089056bc75e2d63100000a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" \ No newline at end of file diff --git a/test/local/testdb/!trie_db!0xef7657bce0047464187afc62b8a367e2c9ea4065f6fea7229b9484d17b6192be b/test/local/testdb/!trie_db!0xef7657bce0047464187afc62b8a367e2c9ea4065f6fea7229b9484d17b6192be deleted file mode 100644 index 42f432bc46..0000000000 --- a/test/local/testdb/!trie_db!0xef7657bce0047464187afc62b8a367e2c9ea4065f6fea7229b9484d17b6192be +++ /dev/null @@ -1 +0,0 @@ -"0xf866942091ef87e392377ec08e7c08eb105ef5448eced5b84ff84d8089056bc75e2d63100000a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" \ No newline at end of file diff --git a/test/local/testdb/!trie_db!0xf1ced711647c94bb97082b1730301b377f5c238369dfc2d598b6aa9d14004ccb b/test/local/testdb/!trie_db!0xf1ced711647c94bb97082b1730301b377f5c238369dfc2d598b6aa9d14004ccb deleted file mode 100644 index 043dc90b72..0000000000 --- a/test/local/testdb/!trie_db!0xf1ced711647c94bb97082b1730301b377f5c238369dfc2d598b6aa9d14004ccb +++ /dev/null @@ -1 +0,0 @@ -"0xf866942032b7a2355d6fecc4b5c0b6bd44cc31df247a2eb84ff84d8089056bc75e2d63100000a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" \ No newline at end of file diff --git a/test/local/testdb/!trie_db!0xfa71baff75cc869d687859edac6345402a902e385a2829b9bded7697113e9836 b/test/local/testdb/!trie_db!0xfa71baff75cc869d687859edac6345402a902e385a2829b9bded7697113e9836 deleted file mode 100644 index 8675e0b5d8..0000000000 --- a/test/local/testdb/!trie_db!0xfa71baff75cc869d687859edac6345402a902e385a2829b9bded7697113e9836 +++ /dev/null @@ -1 +0,0 @@ -"0xf85180a0ef7657bce0047464187afc62b8a367e2c9ea4065f6fea7229b9484d17b6192be80808080808080a0f1ced711647c94bb97082b1730301b377f5c238369dfc2d598b6aa9d14004ccb80808080808080" \ No newline at end of file diff --git a/test/local/testdb/!trie_db!0xfe3d828c2803bd586bc34641752157c205a391ec9a1b730031bccc77a9276b02 b/test/local/testdb/!trie_db!0xfe3d828c2803bd586bc34641752157c205a391ec9a1b730031bccc77a9276b02 deleted file mode 100644 index 19ee58cc26..0000000000 --- a/test/local/testdb/!trie_db!0xfe3d828c2803bd586bc34641752157c205a391ec9a1b730031bccc77a9276b02 +++ /dev/null @@ -1 +0,0 @@ -"0xf87180a05911f24d96912350de50f297c2d34d5d10e136757bf4cfff5fa41bfca219554aa05911f24d96912350de50f297c2d34d5d10e136757bf4cfff5fa41bfca219554aa05911f24d96912350de50f297c2d34d5d10e136757bf4cfff5fa41bfca219554a80808080808080808080808080" \ No newline at end of file diff --git a/test/local/time-adjust.js b/test/local/time-adjust.js deleted file mode 100644 index 52b3acd7a7..0000000000 --- a/test/local/time-adjust.js +++ /dev/null @@ -1,100 +0,0 @@ -const assert = require("assert-match"); -const { gte, lte } = require("assert-match/matchers"); -const initializeTestProvider = require("../helpers/web3/initializeTestProvider"); - -describe("Time adjustment", function() { - let context, timestampBeforeJump; - const SECONDSTOJUMP = 5 * 60 * 60; - const startTime = new Date("Wed Aug 24 2016 00:00:00 GMT-0700 (PDT)"); - - before("Setting up accounts and provider", async function() { - context = await initializeTestProvider({ - time: startTime - }); - }); - - before("get current time", async function() { - const { web3 } = context; - - const { timestamp } = await web3.eth.getBlock("latest"); - timestampBeforeJump = timestamp; - }); - - it("should mine the first block at the time provided", async function() { - const { web3 } = context; - - const { timestamp } = await web3.eth.getBlock(0); - - // give ourselves a 25ms window for this to succeed - const acceptableStartTime = (startTime / 1000) | 0; - const acceptableEndTime = acceptableStartTime + 25; - assert.deepEqual(timestamp, gte(acceptableStartTime)); - assert.deepEqual(timestamp, lte(acceptableEndTime)); - }); - - it("should jump 5 hours", async function() { - this.timeout(5000); // this is timing out on travis for some reason :-( - const { web3, send } = context; - - // Adjust time - await send("evm_increaseTime", SECONDSTOJUMP); - - // Mine a block so new time is recorded. - await send("evm_mine", null); - - const { timestamp } = await web3.eth.getBlock("latest"); - const secondsJumped = timestamp - timestampBeforeJump; - - // Somehow it jumps an extra 18 seconds, ish, when run inside the whole - // test suite. It might have something to do with when the before block - // runs and when the test runs. Likely the last block didn't occur for - // awhile. - assert(secondsJumped >= SECONDSTOJUMP); - }); - - it("should mine a block at the given timestamp", async function() { - const { web3, send } = context; - - // Adjust time - const expectedMinedTimestamp = 1000000; - - await send("evm_mine", expectedMinedTimestamp); - - const { timestamp } = await web3.eth.getBlock("latest"); - assert.strictEqual(timestamp, expectedMinedTimestamp); - }); - - it("should revert time adjustments when snapshot is reverted", async function() { - const { provider, send } = context; - - const originalTimeAdjustment = provider.manager.state.blockchain.timeAdjustment; - - await send("evm_snapshot"); - // jump forward another 5 hours - await send("evm_increaseTime", SECONDSTOJUMP); - - const currentTimeAdjustment = provider.manager.state.blockchain.timeAdjustment; - assert.strictEqual(currentTimeAdjustment, originalTimeAdjustment + SECONDSTOJUMP); - - // Mine a block so new time is recorded. - await send("evm_mine", null); - await send("evm_revert", 1); - - const revertedTimeAdjustment = provider.manager.state.blockchain.timeAdjustment; - assert.strictEqual(revertedTimeAdjustment, originalTimeAdjustment); - }); - - it("should allow setting of time", async function() { - const { web3, send } = context; - - const { timestamp: previousTime } = await web3.eth.getBlock("latest"); - - await send("evm_setTime", new Date(previousTime - SECONDSTOJUMP)); - - // Mine a block so new time is recorded. - await send("evm_mine", null); - - const { timestamp } = await web3.eth.getBlock("latest"); - assert(previousTime > timestamp); - }); -}); diff --git a/test/local/transaction-data.js b/test/local/transaction-data.js deleted file mode 100644 index baa47369fe..0000000000 --- a/test/local/transaction-data.js +++ /dev/null @@ -1,52 +0,0 @@ -const assert = require("assert"); -const bootstrap = require("../helpers/contract/bootstrap"); - -describe("Transaction Data", () => { - // There was a bug that caused a data value of 0x1 to get converted to an empty buffer. - // technically `0x1` is invalid for the data field, but geth supports it, so now we - // do too. - // These tests invoke a contract that checks that msg.data has a value. If the length - // of `msg.data` is `0` the transaction does not revert, otherwise it does. - let context; - - before("Setting up web3 and contract", async function() { - this.timeout(10000); - const contractRef = { - contractFiles: ["TransactionData"], - contractSubdirectory: "transaction-data" - }; - context = await bootstrap(contractRef); - }); - - it("should revert with correctly formatted input for data", async() => { - const { instance, accounts, web3 } = context; - - await assert.rejects( - web3.eth.sendTransaction({ - from: accounts[0], - to: instance._address, - gas: 31000, - data: "0x01", - value: 1 - }), - /VM Exception while processing transaction: revert/, - "Call did not fail execution like it was supposed to" - ); - }); - - it("should revert with incorrectly formatted input for data", async() => { - const { instance, accounts, web3 } = context; - - await assert.rejects( - web3.eth.sendTransaction({ - from: accounts[0], - to: instance._address, - gas: 31000, - data: "0x1", - value: 1 - }), - /VM Exception while processing transaction: revert/, - "Call did not fail execution like it was supposed to" - ); - }); -}); diff --git a/test/local/transaction-rejection.js b/test/local/transaction-rejection.js deleted file mode 100644 index 11e1859847..0000000000 --- a/test/local/transaction-rejection.js +++ /dev/null @@ -1,143 +0,0 @@ -const bootstrap = require("../helpers/contract/bootstrap"); -const assert = require("assert"); - -describe("Transaction rejection", function() { - let context; - - before("Setting up web3 and contract", async function() { - this.timeout(10000); - - const contractRef = { - contractFiles: ["EstimateGas"], - contractSubdirectory: "gas" - }; - - const ganacheProviderOptions = { - // important: we want to make sure we get tx rejections as rpc errors even - // if we don't want runtime errors as RPC erros - vmErrorsOnRPCResponse: false - }; - - context = await bootstrap(contractRef, ganacheProviderOptions); - }); - - before("lock account 1", async function() { - const { accounts, web3 } = context; - await web3.eth.personal.lockAccount(accounts[1]); - }); - - it("should reject transaction if nonce is incorrect", async function() { - await testTransactionForRejection( - { - nonce: 0xffff - }, - "the tx doesn't have the correct nonce" - ); - }); - - it("should reject transaction if from account is missing", async function() { - await testTransactionForRejection( - { - from: undefined - }, - "from not found; is required" - ); - }); - - it("should reject transaction if from account is invalid/unknown", async function() { - await testTransactionForRejection( - { - from: "0x0000000000000000000000000000000000000001" - }, - "sender account not recognized" - ); - }); - - it("should reject transaction if from known account which is locked", async function() { - const { accounts } = context; - await testTransactionForRejection( - { - from: accounts[1] - }, - "signer account is locked" - ); - }); - - it("should reject transaction if gas limit exceeds block gas limit", async function() { - await testTransactionForRejection( - { - gas: 0xffffffff - }, - "Exceeds block gas limit" - ); - }); - - it("should reject transaction if insufficient funds", async function() { - const { web3 } = context; - await testTransactionForRejection( - { - value: web3.utils.toWei("100000", "ether") - }, - "sender doesn't have enough funds to send tx" - ); - }); - - let counter = 1; - async function testTransactionForRejection(paramsOverride, expectedMessage) { - const { accounts, instance, provider, web3 } = context; - // this is a special `send` fn that doesn't reject and ignores the callback `error` param - const send = async(method, ...params) => - new Promise((resolve) => - provider.send( - { - id: counter++, - jsonrpc: "2.0", - method, - params: [...params] - // we ignore the error because we just want to check the response obj for these tests - }, - (_err, response) => resolve(response) - ) - ); - - const params = Object.assign( - { - from: accounts[0], - to: instance.options.address, - data: - "0x91ea8a0554696d0000000000000000000000000000000000000000000000000" + - "00000000041206772656174206775790000000000000000000000000000000000" + - "000000000000000000000000000000000000000000000000000000000000000000000005" - }, - paramsOverride - ); - - // don't send with web3 because it'll inject its own checks - const response = await send("eth_sendTransaction", params).catch((e) => ({ error: e })); - - if (response.error) { - if (response.error.message) { - assert( - response.error.message.startsWith(expectedMessage), - `Expected error message matching ${expectedMessage}, got ${response.error.message}` - ); - } else { - assert.fail(new Error("Error was returned which had no message")); - } - } else if (response.result) { - const receipt = await web3.eth.getTransactionReceipt(response.result); - if (!receipt.status) { - assert.fail(new Error("TX rejections should return error, but returned receipt with falsey status instead")); - } else { - assert.fail( - new Error( - `TX should have rejected prior to running. Instead transaction ran successfully (receipt.status == - ${receipt.status})` - ) - ); - } - } else { - assert.fail(new Error("eth_sendTransaction responded with empty RPC response")); - } - } -}); diff --git a/test/local/transaction.js b/test/local/transaction.js deleted file mode 100644 index c7590bb1ce..0000000000 --- a/test/local/transaction.js +++ /dev/null @@ -1,55 +0,0 @@ -const assert = require("assert"); -const { BN } = require("ethereumjs-util"); -const Transaction = require("../../lib/utils/transaction"); - -describe("Transaction", function() { - it("Should adhere to EIP-155", function() { - const gasPrice = 20 * 10 ** 9; // 20000000000 - const value = `0x${new BN(10).pow(new BN(18)).toString("hex")}`; - - // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md - const privateKey = Buffer.from("46".repeat(32), "hex"); - - const txParams = { - nonce: 9, - gasPrice, - gasLimit: 21000, - to: `0x${"35".repeat(20)}`, - value, - data: "", - chainId: 1 // EIP 155 chainId - mainnet: 1, ropsten: 3 - }; - - const tx = new Transaction(txParams); - // Signing data - assert.strictEqual( - tx.serialize().toString("hex"), - "ec098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080018080", - "Signing data is incorrect" - ); - - const txHash = tx.hash(); - // Signing hash - assert.strictEqual( - txHash.toString("hex"), - "daf5a779ae972f972197303d7b574746c7ef83eadac0f2791ad23db92e4c8e53", - "Signing hash is incorrect" - ); - - tx.sign(privateKey); - // Signed Tx - assert.strictEqual( - tx.serialize().toString("hex"), - // eslint-disable-next-line max-len - "f86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a028ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276a067cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83", - "Signed TX is incorrect" - ); - - // Tx hash - assert.strictEqual( - tx.hash().toString("hex"), - "33469b22e9f636356c4160a87eb19df52b7412e8eac32a4a55ffe88ea8350788", - "Tx hash is incorrect" - ); - }); -}); diff --git a/test/local/transasction-ordering.js b/test/local/transasction-ordering.js deleted file mode 100644 index 2ce680cf5d..0000000000 --- a/test/local/transasction-ordering.js +++ /dev/null @@ -1,68 +0,0 @@ -const assert = require("assert"); -const to = require("../../lib/utils/to.js"); -const initializeTestProvider = require("../helpers/web3/initializeTestProvider"); - -describe("Transaction Ordering", function() { - let context; - - before("Setting up accounts and provider", async function() { - context = await initializeTestProvider(); - context.sendTransaction = (txData) => - new Promise((resolve) => context.web3.eth.sendTransaction(txData).on("transactionHash", resolve)); - }); - - beforeEach(async function() { - await context.send("miner_stop"); - }); - - afterEach(async function() { - await context.send("miner_start", 1); - }); - - it("should order queued transactions correctly by nonce before adding to the block", async function() { - const { accounts, send, web3, sendTransaction } = context; - - const txData = { - to: accounts[1], - from: accounts[0], - value: 0x1, - nonce: 0, - gas: 21000 - }; - - await sendTransaction(txData); - - txData.nonce = 1; - await sendTransaction(txData); - - await send("miner_start", 1); - - const block = await web3.eth.getBlock("latest"); - assert.strictEqual(block.transactions.length, 2, "Latest block should have two transactions"); - }); - - it("should order queued transactions correctly by price before adding to the block", async function() { - const { accounts, send, web3, sendTransaction } = context; - - const txData = { - to: accounts[1], - from: accounts[0], - value: 0x1, - gas: 21000, - gasPrice: 0x1 - }; - - await sendTransaction(txData); - - txData.gasPrice = 2; - txData.from = accounts[1]; - await sendTransaction(txData); - - send("miner_start", 1); - - const block = await web3.eth.getBlock("latest", true); - assert.strictEqual(block.transactions.length, 2, "Latest block should have two transactions"); - assert.strictEqual(to.number(block.transactions[0].gasPrice), 2); - assert.strictEqual(to.number(block.transactions[1].gasPrice), 1); - }); -}); diff --git a/test/local/unlimitedContractSize.js b/test/local/unlimitedContractSize.js deleted file mode 100644 index 877c4b70e4..0000000000 --- a/test/local/unlimitedContractSize.js +++ /dev/null @@ -1,68 +0,0 @@ -const assert = require("assert"); -const initializeTestProvider = require("../helpers/web3/initializeTestProvider"); -const randomInteger = require("../helpers/utils/generateRandomInteger"); -const { compile, deploy } = require("../helpers/contract/compileAndDeploy"); - -const SEED_RANGE = 1000000; -const seed = randomInteger(SEED_RANGE); - -describe("Unlimited Contract Size", function() { - const contract = {}; - - before("compile contract", async function() { - this.timeout(10000); - const contractSubdirectory = "customContracts"; - const contractFilename = "LargeContract"; - const subcontractFiles = []; - const { abi, bytecode } = await compile(contractFilename, subcontractFiles, contractSubdirectory); - contract.abi = abi; - contract.bytecode = bytecode; - }); - - describe("Disallow Unlimited Contract Size", function() { - let context; - - before("Setup provider to disallow unlimited contract size", async function() { - const ganacheOptions = { - seed, - allowUnlimitedContractSize: false, - gasLimit: 2e7 - }; - context = await initializeTestProvider(ganacheOptions); - }); - - it("should fail deployment", async function() { - this.timeout(10000); - const { web3 } = context; - const { abi, bytecode } = contract; - await assert.rejects( - deploy(abi, bytecode, web3, { gas: 2e7 }), - /VM Exception while processing transaction: out of gas/, - "should not be able to deploy a very large contract" - ); - }); - }); - - describe("Allow Unlimited Contract Size", function() { - let context; - - before("Setup provider to allow unlimited contract size", async function() { - const ganacheOptions = { - seed, - allowUnlimitedContractSize: true, - gasLimit: 2e7 - }; - - context = await initializeTestProvider(ganacheOptions); - }); - - it("should succeed deployment", async function() { - const { web3 } = context; - const { abi, bytecode } = contract; - await assert.doesNotReject( - deploy(abi, bytecode, web3, { gas: 2e7 }), - "should be able to deploy a very large contract" - ); - }); - }); -}); diff --git a/test/local/vm.js b/test/local/vm.js deleted file mode 100644 index 33e02b81a4..0000000000 --- a/test/local/vm.js +++ /dev/null @@ -1,37 +0,0 @@ -const assert = require("assert"); -const bootstrap = require("../helpers/contract/bootstrap"); - -describe("revert opcode", function() { - let context; - before("Setting up web3 and contract", async function() { - this.timeout(10000); - const logger = { - log: function(message) {} - }; - - const contractRef = { - contractFiles: ["Revert"], - contractSubdirectory: "revert" - }; - - const ganacheProviderOptions = { - logger, - seed: "1337" - }; - - context = await bootstrap(contractRef, ganacheProviderOptions); - }); - - it("should return a transaction receipt with status 0 on REVERT", async function() { - const { accounts, instance, web3 } = context; - - const promise = instance.methods.alwaysReverts(5).send({ from: accounts[0] }); - const hash = await promise.catch((err) => err.hashes[0]); - const receipt = await web3.eth.getTransactionReceipt(hash); - - await assert.rejects(promise, (err) => err.results[hash].error === "revert", "Expected error result not returned."); - - assert.notStrictEqual(receipt, null, "Transaction receipt shouldn't be null"); - assert.strictEqual(receipt.status, false, "Reverted (failed) transactions should have a status of FALSE."); - }); -}); diff --git a/test/local/whisper.js b/test/local/whisper.js deleted file mode 100644 index bca1d98c51..0000000000 --- a/test/local/whisper.js +++ /dev/null @@ -1,10 +0,0 @@ -const assert = require("assert"); -const initializeTestProvider = require("../helpers/web3/initializeTestProvider"); - -describe("Whisper", function() { - it("should call get whisper version (shh_version)", async function() { - const { web3 } = await initializeTestProvider(); - const result = await web3.shh.getVersion(); - assert.strictEqual(result, "2", "Whisper version should be 2"); - }); -}); diff --git a/test/smoke/forking/infura/simple.js b/test/smoke/forking/infura/simple.js deleted file mode 100644 index a79a9beb8e..0000000000 --- a/test/smoke/forking/infura/simple.js +++ /dev/null @@ -1,155 +0,0 @@ -const Web3 = require("web3"); -var assert = require("assert"); -const Ganache = require(process.env.TEST_BUILD - ? "../../../../build/ganache.core." + process.env.TEST_BUILD + ".js" - : "../../../../index.js"); - -const logger = { - log: function(msg) { - /* console.log(msg) */ - } -}; - -describe("Simple Infura", () => { - let INFURA_KEY; - - before(function() { - if (typeof process.env.INFURA_KEY === "undefined" || process.env.INFURA_KEY === "") { - this.skip(); - } else { - INFURA_KEY = process.env.INFURA_KEY; - } - }); - - it("forks mainnet https", async() => { - // https://etherscan.io/block/10661638 - const blockHeight = 10661638; - const numTransactions = 204; - const network = "mainnet"; - - const web3 = new Web3(); - const provider = Ganache.provider({ - fork: `https://${network}.infura.io/v3/${INFURA_KEY}@${blockHeight}`, - logger - }); - web3.setProvider(provider); - - const blockHeightAfterFork = await web3.eth.getBlockNumber(); - assert.strictEqual(blockHeightAfterFork, blockHeight + 1); - - const block = await web3.eth.getBlock(blockHeight); - assert.strictEqual(block.transactions.length, numTransactions); - - await new Promise((resolve) => provider.close(resolve)); - }).timeout(5000); - - it("forks mainnet wss", async() => { - // https://etherscan.io/block/10661638 - const blockHeight = 10661638; - const numTransactions = 204; - const network = "mainnet"; - - const web3 = new Web3(); - const provider = Ganache.provider({ - fork: `wss://${network}.infura.io/ws/v3/${INFURA_KEY}@${blockHeight}`, - logger - }); - web3.setProvider(provider); - - const blockHeightAfterFork = await web3.eth.getBlockNumber(); - assert.strictEqual(blockHeightAfterFork, blockHeight + 1); - - const block = await web3.eth.getBlock(blockHeight); - assert.strictEqual(block.transactions.length, numTransactions); - - await new Promise((resolve) => provider.close(resolve)); - }).timeout(5000); - - it("forks goerli https", async() => { - // https://goerli.etherscan.io/block/3226587 - const blockHeight = 3226587; - const numTransactions = 1; - const network = "goerli"; - - const web3 = new Web3(); - const provider = Ganache.provider({ - fork: `https://${network}.infura.io/v3/${INFURA_KEY}@${blockHeight}`, - logger - }); - web3.setProvider(provider); - - const blockHeightAfterFork = await web3.eth.getBlockNumber(); - assert.strictEqual(blockHeightAfterFork, blockHeight + 1); - - const block = await web3.eth.getBlock(blockHeight); - assert.strictEqual(block.transactions.length, numTransactions); - - await new Promise((resolve) => provider.close(resolve)); - }).timeout(5000); - - it("forks ropsten https", async() => { - // https://ropsten.etherscan.io/block/8500030 - const blockHeight = 8500030; - const numTransactions = 53; - const network = "ropsten"; - - const web3 = new Web3(); - const provider = Ganache.provider({ - fork: `https://${network}.infura.io/v3/${INFURA_KEY}@${blockHeight}`, - logger - }); - web3.setProvider(provider); - - const blockHeightAfterFork = await web3.eth.getBlockNumber(); - assert.strictEqual(blockHeightAfterFork, blockHeight + 1); - - const block = await web3.eth.getBlock(blockHeight); - assert.strictEqual(block.transactions.length, numTransactions); - - await new Promise((resolve) => provider.close(resolve)); - }).timeout(5000); - - it("forks rinkeby https", async() => { - // https://rinkeby.etherscan.io/block/7019987 - const blockHeight = 7019987; - const numTransactions = 11; - const network = "rinkeby"; - - const web3 = new Web3(); - const provider = Ganache.provider({ - fork: `https://${network}.infura.io/v3/${INFURA_KEY}@${blockHeight}`, - logger - }); - web3.setProvider(provider); - - const blockHeightAfterFork = await web3.eth.getBlockNumber(); - assert.strictEqual(blockHeightAfterFork, blockHeight + 1); - - const block = await web3.eth.getBlock(blockHeight); - assert.strictEqual(block.transactions.length, numTransactions); - - await new Promise((resolve) => provider.close(resolve)); - }).timeout(5000); - - it("forks kovan https", async() => { - // https://kovan.etherscan.io/block/20255583 - const blockHeight = 20255583; - const numTransactions = 3; - const network = "kovan"; - - const web3 = new Web3(); - const provider = Ganache.provider({ - fork: `https://${network}.infura.io/v3/${INFURA_KEY}@${blockHeight}`, - logger - }); - web3.setProvider(provider); - - const blockHeightAfterFork = await web3.eth.getBlockNumber(); - assert.strictEqual(blockHeightAfterFork, blockHeight + 1); - - const block = await web3.eth.getBlock(blockHeight); - assert.strictEqual(block.transactions.length, numTransactions); - - await new Promise((resolve) => provider.close(resolve)); - }).timeout(5000); -}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000000..9bba1c6888 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,34 @@ +{ + "compilerOptions": { + "outDir": "lib", + "alwaysStrict": true, + "declaration": true, + "skipLibCheck": true, + "module": "CommonJS", + "esModuleInterop": true, + "target": "ES2020", + "moduleResolution": "node", + "noErrorTruncation": true, + "sourceMap": true, + "strict": true, + /** `noImplicitAny: false` and `strictNullChecks: false` are temporary during initial rapid development. */ + "strictNullChecks": false, + "noImplicitAny": false, + "newLine": "lf", + "lib": ["ES2020"], + "typeRoots": ["./node_modules/@types", "src/@types"], + "experimentalDecorators": true, + "paths": { + "@ganache/cli/cli": ["../cli/lib/cli"] + }, + "plugins": [ + { + "transform": "ts-transformer-inline-file/transformer" + }, + { + "transform": "@zerollup/ts-transform-paths", + "exclude": "*" + } + ] + } +} diff --git a/typings/index.d.ts b/typings/index.d.ts deleted file mode 100644 index 65cfb02c1f..0000000000 --- a/typings/index.d.ts +++ /dev/null @@ -1,79 +0,0 @@ -declare module "godmode-ganache-core" { - import { Server as HttpServer } from "http"; - export interface JsonRpcPayload { - jsonrpc: string; - method: string; - params: any[]; - id?: string | number; - } - - export interface JsonRpcResponse { - jsonrpc: string; - id: number; - result?: any; - error?: string; - } - - namespace Ganache { - export interface IProviderOptions { - account_keys_path?: string; - accounts?: object[]; - allowUnlimitedContractSize?: boolean; - blockTime?: number; - db_path?: string; - debug?: boolean; - default_balance_ether?: number; - fork?: string | object; - fork_block_number?: string | number; - forkCacheSize?: number; - gasLimit?: string | number; - gasPrice?: string; - hardfork?: "byzantium" | "constantinople" | "petersburg" | "istanbul" | "muirGlacier"; - hd_path?: string; - locked?: boolean; - logger?: { - log(msg: string): void; - }; - mnemonic?: string; - network_id?: number; - networkId?: number; - port?: number; - seed?: any; - time?: Date; - total_accounts?: number; - unlocked_accounts?: string[]; - verbose?: boolean; - vmErrorsOnRPCResponse?: boolean; - ws?: boolean; - } - - export interface IServerOptions extends IProviderOptions { - keepAliveTimeout?: number; - } - - export function provider(opts?: IProviderOptions): Provider; - export function server(opts?: IServerOptions): Server; - - export interface Server extends HttpServer { - provider: Provider - } - - export interface Provider { - send( - payload: JsonRpcPayload, - callback: (error: Error | null, result?: JsonRpcResponse) => void - ): void; - - on(type: string, callback: () => void): void; - - once(type: string, callback: () => void): void; - - removeListener(type: string, callback: () => void): void; - - removeAllListeners(type: string): void; - - close: (callback: Function) => void; - } - } - export default Ganache; -} diff --git a/webpack/base.webpack.config.js b/webpack/base.webpack.config.js deleted file mode 100644 index 4cad9f8769..0000000000 --- a/webpack/base.webpack.config.js +++ /dev/null @@ -1,72 +0,0 @@ -const { merge } = require("lodash"); -const { resolve } = require("path"); -const { IgnorePlugin } = require("webpack"); -const TerserPlugin = require("terser-webpack-plugin"); - -const outputDir = resolve(__dirname, "..", "build"); - -module.exports = (override) => { - return merge( - {}, - { - output: { - path: outputDir - }, - devtool: "source-map", - externals: [ - (context, request, callback) => { - // webpack these modules: - // we actually only care about scrypt and eth-block-tracker here, as those are the only native modules - // but webpack won't detect them if we don't traverse the dependency tree to get to them - if (/^(ethereumjs-wallet|scrypt|web3|web3-eth|web3-eth-accounts|eth-block-tracker)(\/.*)?$/.test(request)) { - return callback(); - } - - // we want to webpack all local files (files starting with a .) - if (/^\./.test(request)) { - return callback(); - } - - // we don't want to webpack any other modules - return callback(null, "commonjs " + request); - } - ], - resolve: { - alias: { - // eth-block-tracker is es6 but automatically builds an es5 version for us on install. - "eth-block-tracker": "eth-block-tracker/dist/es5/index.js", - - // replace native `scrypt` module with pure js `js-scrypt` - scrypt: "js-scrypt", - - // replace native `secp256k1` with pure js `elliptic.js` - secp256k1: "secp256k1/elliptic.js" - } - }, - plugins: [ - // ignore these plugins completely - new IgnorePlugin(/^(?:electron|ws)$/) - ], - optimization: { - minimizer: [ - new TerserPlugin({ - // make it go fast - cache: true, - // and event faster - parallel: true, - // Must be set to true if using source-maps in production, which we are - sourceMap: true, - terserOptions: { - mangle: { - // some gas tests fail if we mangle fn names. so don't. - keep_fnames: true - } - } - }) - ] - }, - mode: "production" - }, - override - ); -}; diff --git a/webpack/node/core.webpack.config.js b/webpack/node/core.webpack.config.js deleted file mode 100644 index 2cdf7afbf2..0000000000 --- a/webpack/node/core.webpack.config.js +++ /dev/null @@ -1,17 +0,0 @@ -const { join } = require("path"); -const applyBaseConfig = require("../base.webpack.config"); - -const outputDir = join(__dirname, "..", "..", "build"); -const outputFilename = "ganache.core.node.js"; - -module.exports = applyBaseConfig({ - entry: "./public-exports.js", - target: "node", - output: { - path: outputDir, - filename: outputFilename, - library: "Ganache", - libraryTarget: "umd", - umdNamedDefine: true - } -}); diff --git a/webpack/node/provider.webpack.config.js b/webpack/node/provider.webpack.config.js deleted file mode 100644 index 629bd43252..0000000000 --- a/webpack/node/provider.webpack.config.js +++ /dev/null @@ -1,17 +0,0 @@ -const { join } = require("path"); -const applyBaseConfig = require("../base.webpack.config"); - -const outputDir = join(__dirname, "..", "..", "build"); -const outputFilename = "ganache.provider.node.js"; - -module.exports = applyBaseConfig({ - entry: "./lib/provider.js", - target: "node", - output: { - path: outputDir, - filename: outputFilename, - library: "GanacheProvider", - libraryTarget: "umd", - umdNamedDefine: true - } -}); diff --git a/webpack/node/server.webpack.config.js b/webpack/node/server.webpack.config.js deleted file mode 100644 index 4853c9c497..0000000000 --- a/webpack/node/server.webpack.config.js +++ /dev/null @@ -1,17 +0,0 @@ -const { join } = require("path"); -const applyBaseConfig = require("../base.webpack.config"); - -const outputDir = join(__dirname, "..", "..", "build"); -const outputFilename = "ganache.server.node.js"; - -module.exports = applyBaseConfig({ - entry: "./lib/server.js", - target: "node", - output: { - path: outputDir, - filename: outputFilename, - library: "GanacheServer", - libraryTarget: "umd", - umdNamedDefine: true - } -}); diff --git a/webpack/web-experimental/core.webpack.config.js b/webpack/web-experimental/core.webpack.config.js deleted file mode 100644 index fec6402a2d..0000000000 --- a/webpack/web-experimental/core.webpack.config.js +++ /dev/null @@ -1,17 +0,0 @@ -const { join } = require("path"); -const applyBaseConfig = require("./webbase.webpack.config"); - -const outputDir = join(__dirname, "..", "..", "build"); -const outputFilename = "ganache.core.web-experimental.js"; - -module.exports = applyBaseConfig({ - entry: ["core-js/fn/promise", "./index.js"], - target: "web", - output: { - path: outputDir, - filename: outputFilename, - library: "Ganache", - libraryTarget: "umd", - umdNamedDefine: true - } -}); diff --git a/webpack/web-experimental/provider.webpack.config.js b/webpack/web-experimental/provider.webpack.config.js deleted file mode 100644 index 50e5b6be67..0000000000 --- a/webpack/web-experimental/provider.webpack.config.js +++ /dev/null @@ -1,17 +0,0 @@ -const { join } = require("path"); -const applyBaseConfig = require("./webbase.webpack.config"); - -const outputDir = join(__dirname, "..", "..", "build"); -const outputFilename = "ganache.provider.web-experimental.js"; - -module.exports = applyBaseConfig({ - entry: "./lib/provider.js", - target: "web", - output: { - path: outputDir, - filename: outputFilename, - library: "GanacheProvider", - libraryTarget: "umd", - umdNamedDefine: true - } -}); diff --git a/webpack/web-experimental/server.webpack.config.js b/webpack/web-experimental/server.webpack.config.js deleted file mode 100644 index c269bfc0a9..0000000000 --- a/webpack/web-experimental/server.webpack.config.js +++ /dev/null @@ -1,17 +0,0 @@ -const { join } = require("path"); -const applyBaseConfig = require("./webbase.webpack.config"); - -const outputDir = join(__dirname, "..", "..", "build"); -const outputFilename = "ganache.server.web-experimental.js"; - -module.exports = applyBaseConfig({ - entry: "./lib/server.js", - target: "web", - output: { - path: outputDir, - filename: outputFilename, - library: "GanacheServer", - libraryTarget: "umd", - umdNamedDefine: true - } -}); diff --git a/webpack/web-experimental/webbase.webpack.config.js b/webpack/web-experimental/webbase.webpack.config.js deleted file mode 100644 index 9e174b34a5..0000000000 --- a/webpack/web-experimental/webbase.webpack.config.js +++ /dev/null @@ -1,16 +0,0 @@ -const { merge } = require("lodash"); -const applyBaseConfig = require("../base.webpack.config"); - -module.exports = (override) => { - return merge( - {}, - applyBaseConfig({ - resolve: { - alias: { - fs: "browserfs/dist/shims/fs.js" - } - } - }), - override - ); -};