diff --git a/app/package-lock.json b/app/package-lock.json index 946a791..f838b58 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -215,6 +215,41 @@ "defer-to-connect": "^1.0.1" } }, + "@truffle/blockchain-utils": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/@truffle/blockchain-utils/-/blockchain-utils-0.0.11.tgz", + "integrity": "sha512-9MyQ/20M96clhIcC7fVFIckGSB8qMsmcdU6iYt98HXJ9GOLNKsCaJFz1OVsJncVreYwTUhoEXTrVBc8zrmPDJQ==" + }, + "@truffle/contract-schema": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@truffle/contract-schema/-/contract-schema-3.3.3.tgz", + "integrity": "sha512-4bvcEoGycopJBPoCiqHP5Q72/1t/ixYS/pVHru+Rzvad641BgvoGrkd4YnyJ+E/MVb4ZLrndL7whmdGqV5B7SA==", + "requires": { + "ajv": "^6.10.0", + "crypto-js": "^3.1.9-1", + "debug": "^4.1.0" + }, + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "@truffle/error": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@truffle/error/-/error-0.0.6.tgz", + "integrity": "sha512-QUM9ZWiwlXGixFGpV18g5I6vua6/r+ZV9W/5DQA5go9A3eZUNPHPaTKMIQPJLYn6+ZV5jg5H28zCHq56LHF3yA==" + }, "@types/bn.js": { "version": "4.11.6", "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-4.11.6.tgz", @@ -571,6 +606,11 @@ "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", "dev": true }, + "aes-js": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz", + "integrity": "sha1-4h3xCtbCBTKVvLuNq0Cwnb6ofk0=" + }, "ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -621,6 +661,11 @@ "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=" + }, "anymatch": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", @@ -904,6 +949,39 @@ "dev": true, "optional": true }, + "bl": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz", + "integrity": "sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==", + "requires": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.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" + } + }, + "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" + } + } + } + }, "blakejs": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.1.0.tgz", @@ -958,6 +1036,11 @@ } } }, + "bootstrap": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.0.tgz", + "integrity": "sha512-Io55IuQY3kydzHtbGvQya3H+KorS/M9rSNyfCGCg9WZ4pyT/lCxIlpJgG1GXW/PswzC84Tr2fBYi+7+jFVQQBw==" + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -1116,6 +1199,30 @@ "ieee754": "^1.1.13" } }, + "buffer-alloc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "requires": { + "buffer-alloc-unsafe": "^1.1.0", + "buffer-fill": "^1.0.0" + } + }, + "buffer-alloc-unsafe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==" + }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=" + }, + "buffer-fill": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=" + }, "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", @@ -1474,8 +1581,7 @@ "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 + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" }, "commondir": { "version": "1.0.1", @@ -1751,6 +1857,11 @@ "randomfill": "^1.0.3" } }, + "crypto-js": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.3.0.tgz", + "integrity": "sha512-DIT51nX0dCfKltpRiXV+/TVZq+Qq2NgF4644+K7Ttnla7zEzqc+kjJyiB96BHNyUTBxyjzRcZYpUdZa+QAqi6Q==" + }, "cyclist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz", @@ -1793,6 +1904,43 @@ "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" }, + "decompress": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress/-/decompress-4.2.1.tgz", + "integrity": "sha512-e48kc2IjU+2Zw8cTb6VZcJQ3lgVbS4uuB1TfCHbiZIP/haNXm+SVyhu+87jts5/3ROpd82GSVCoNs/z8l4ZOaQ==", + "requires": { + "decompress-tar": "^4.0.0", + "decompress-tarbz2": "^4.0.0", + "decompress-targz": "^4.0.0", + "decompress-unzip": "^4.0.1", + "graceful-fs": "^4.1.10", + "make-dir": "^1.0.0", + "pify": "^2.3.0", + "strip-dirs": "^2.0.0" + }, + "dependencies": { + "make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "requires": { + "pify": "^3.0.0" + }, + "dependencies": { + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" + } + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + } + } + }, "decompress-response": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", @@ -1801,6 +1949,77 @@ "mimic-response": "^1.0.0" } }, + "decompress-tar": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/decompress-tar/-/decompress-tar-4.1.1.tgz", + "integrity": "sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ==", + "requires": { + "file-type": "^5.2.0", + "is-stream": "^1.1.0", + "tar-stream": "^1.5.2" + } + }, + "decompress-tarbz2": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz", + "integrity": "sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A==", + "requires": { + "decompress-tar": "^4.1.0", + "file-type": "^6.1.0", + "is-stream": "^1.1.0", + "seek-bzip": "^1.0.5", + "unbzip2-stream": "^1.0.9" + }, + "dependencies": { + "file-type": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-6.2.0.tgz", + "integrity": "sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg==" + } + } + }, + "decompress-targz": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/decompress-targz/-/decompress-targz-4.1.1.tgz", + "integrity": "sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w==", + "requires": { + "decompress-tar": "^4.1.1", + "file-type": "^5.2.0", + "is-stream": "^1.1.0" + } + }, + "decompress-unzip": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/decompress-unzip/-/decompress-unzip-4.0.1.tgz", + "integrity": "sha1-3qrM39FK6vhVePczroIQ+bSEj2k=", + "requires": { + "file-type": "^3.8.0", + "get-stream": "^2.2.0", + "pify": "^2.3.0", + "yauzl": "^2.4.2" + }, + "dependencies": { + "file-type": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", + "integrity": "sha1-JXoHg4TR24CHvESdEH1SpSZyuek=" + }, + "get-stream": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz", + "integrity": "sha1-Xzj5PzRgCWZu4BUKBUFn+Rvdld4=", + "requires": { + "object-assign": "^4.0.1", + "pinkie-promise": "^2.0.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + } + } + }, "deep-equal": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", @@ -2356,6 +2575,53 @@ "rlp": "^2.2.3" } }, + "ethers": { + "version": "4.0.48", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-4.0.48.tgz", + "integrity": "sha512-sZD5K8H28dOrcidzx9f8KYh8083n5BexIO3+SbE4jK83L85FxtpXZBCQdXb8gkg+7sBqomcLhhkU7UHL+F7I2g==", + "requires": { + "aes-js": "3.0.0", + "bn.js": "^4.4.0", + "elliptic": "6.5.3", + "hash.js": "1.1.3", + "js-sha3": "0.5.7", + "scrypt-js": "2.0.4", + "setimmediate": "1.0.4", + "uuid": "2.0.1", + "xmlhttprequest": "1.8.0" + }, + "dependencies": { + "hash.js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.3.tgz", + "integrity": "sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA==", + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.0" + } + }, + "js-sha3": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.5.7.tgz", + "integrity": "sha1-DU/9gALVMzqrr0oj7tL2N0yfKOc=" + }, + "scrypt-js": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-2.0.4.tgz", + "integrity": "sha512-4KsaGcPnuhtCZQCxFxN3GVYIhKFPTdLd8PLC552XwbMndtD0cjRFAhDuuydXQ0h08ZfPgzqe6EKHozpuH74iDw==" + }, + "setimmediate": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.4.tgz", + "integrity": "sha1-IOgd5iLUoCWIzgyNqJc8vPHTE48=" + }, + "uuid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.1.tgz", + "integrity": "sha1-wqMN7bPlNdcsz4LjQ5QaULqFM6w=" + } + } + }, "ethjs-unit": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/ethjs-unit/-/ethjs-unit-0.1.6.tgz", @@ -2636,12 +2902,25 @@ "websocket-driver": ">=0.5.1" } }, + "fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", + "requires": { + "pend": "~1.2.0" + } + }, "figgy-pudding": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz", "integrity": "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==", "dev": true }, + "file-type": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz", + "integrity": "sha1-LdvqfHP/42No365J3DOMBYwritY=" + }, "fill-range": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", @@ -2834,6 +3113,11 @@ } } }, + "fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, "fs-extra": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", @@ -3585,6 +3869,11 @@ "resolved": "https://registry.npmjs.org/is-hex-prefixed/-/is-hex-prefixed-1.0.0.tgz", "integrity": "sha1-fY035q135dEnFIkTxXPggtd39VQ=" }, + "is-natural-number": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-4.0.1.tgz", + "integrity": "sha1-q5124dtM7VHjXeDHLr7PCfc0zeg=" + }, "is-negative-zero": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", @@ -3711,8 +4000,7 @@ "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "isexe": { "version": "2.0.0", @@ -3740,6 +4028,11 @@ "is-object": "^1.0.1" } }, + "jquery": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.5.1.tgz", + "integrity": "sha512-XwIBPqcMn57FxfT+Go5pzySnm4KWkT1Tv7gjrpT1srtf8Weynl6R273VJ5GjkRb51IzMp5nbaPjJXMWeju2MKg==" + }, "js-sha3": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", @@ -3869,8 +4162,7 @@ "lodash": { "version": "4.17.20", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", - "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", - "dev": true + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" }, "loglevel": { "version": "1.7.1", @@ -4221,6 +4513,11 @@ } } }, + "nan": { + "version": "2.14.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", + "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==" + }, "nano-json-stream-parser": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/nano-json-stream-parser/-/nano-json-stream-parser-0.1.2.tgz", @@ -4766,6 +5063,11 @@ "sha.js": "^2.4.8" } }, + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=" + }, "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", @@ -4787,14 +5089,12 @@ "pinkie": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", - "dev": true + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" }, "pinkie-promise": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "dev": true, "requires": { "pinkie": "^2.0.0" } @@ -4877,8 +5177,7 @@ "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 + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, "promise-inflight": { "version": "1.0.1", @@ -5006,6 +5305,11 @@ "safe-buffer": "^5.1.0" } }, + "randomhex": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/randomhex/-/randomhex-0.1.5.tgz", + "integrity": "sha1-us7vmCMpCRQA8qKRLGzQLxCU9YU=" + }, "range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -5271,6 +5575,11 @@ "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==" }, + "scryptsy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/scryptsy/-/scryptsy-2.1.0.tgz", + "integrity": "sha512-1CdSqHQowJBnMAFyPEBRfqag/YP9OF394FV+4YREIJX4ljD7OxvQRDayyoyyCk+senRjSkP6VnUNQmVQqB6g7w==" + }, "secp256k1": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.2.tgz", @@ -5281,6 +5590,14 @@ "node-gyp-build": "^4.2.0" } }, + "seek-bzip": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.6.tgz", + "integrity": "sha512-e1QtP3YL5tWww8uKaOCQ18UxIT2laNBXHjV/S2WYCiK4udiv8lkG89KRIoCjUagnAmCBurjF4zEVX2ByBbnCjQ==", + "requires": { + "commander": "^2.8.1" + } + }, "select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", @@ -5973,6 +6290,14 @@ "ansi-regex": "^4.1.0" } }, + "strip-dirs": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-2.1.0.tgz", + "integrity": "sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g==", + "requires": { + "is-natural-number": "^4.0.1" + } + }, "strip-eof": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", @@ -6090,6 +6415,44 @@ } } }, + "tar-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", + "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", + "requires": { + "bl": "^1.0.0", + "buffer-alloc": "^1.2.0", + "end-of-stream": "^1.0.0", + "fs-constants": "^1.0.0", + "readable-stream": "^2.3.0", + "to-buffer": "^1.1.1", + "xtend": "^4.0.0" + }, + "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" + } + }, + "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" + } + } + } + }, "terser": { "version": "4.8.0", "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz", @@ -6134,6 +6497,11 @@ } } }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + }, "through2": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", @@ -6196,6 +6564,11 @@ "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", "dev": true }, + "to-buffer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", + "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==" + }, "to-object-path": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", @@ -6257,6 +6630,936 @@ "punycode": "^2.1.1" } }, + "truffle-contract": { + "version": "4.0.31", + "resolved": "https://registry.npmjs.org/truffle-contract/-/truffle-contract-4.0.31.tgz", + "integrity": "sha512-u3q+p1wiX5C2GpnluGx/d2iaJk7bcWshk2/TohiJyA2iQiTfkS7M4n9D9tY3JqpXR8PmD/TrA69RylO0RhITFA==", + "requires": { + "@truffle/blockchain-utils": "^0.0.11", + "@truffle/contract-schema": "^3.0.14", + "@truffle/error": "^0.0.6", + "bignumber.js": "^7.2.1", + "ethers": "^4.0.0-beta.1", + "truffle-interface-adapter": "^0.2.5", + "web3": "1.2.1", + "web3-core-promievent": "1.2.1", + "web3-eth-abi": "1.2.1", + "web3-utils": "1.2.1" + }, + "dependencies": { + "@types/node": { + "version": "10.17.51", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.51.tgz", + "integrity": "sha512-KANw+MkL626tq90l++hGelbl67irOJzGhUJk6a1Bt8QHOeh9tztJx+L0AqttraWKinmZn7Qi5lJZJzx45Gq0dg==" + }, + "bignumber.js": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-7.2.1.tgz", + "integrity": "sha512-S4XzBk5sMB+Rcb/LNcpzXr57VRTxgAvaAEDAl1AwRx27j00hT84O6OkteE7u8UB3NuaaygCRrEpqox4uDOrbdQ==" + }, + "bn.js": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" + }, + "eventemitter3": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", + "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==" + }, + "get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" + }, + "hash.js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.3.tgz", + "integrity": "sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA==", + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.0" + } + }, + "js-sha3": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.5.7.tgz", + "integrity": "sha1-DU/9gALVMzqrr0oj7tL2N0yfKOc=" + }, + "oboe": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/oboe/-/oboe-2.1.4.tgz", + "integrity": "sha1-IMiM2wwVNxuwQRklfU/dNLCqSfY=", + "requires": { + "http-https": "^1.0.0" + } + }, + "p-cancelable": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.3.0.tgz", + "integrity": "sha512-RVbZPLso8+jFeq1MfNvgXtCRED2raz/dKpacfTNxsx6pLEpEomM7gah6VeHSYV3+vo0OAi4MkArtQcWWXuQoyw==" + }, + "prepend-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=" + }, + "scrypt-js": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-2.0.3.tgz", + "integrity": "sha1-uwBAvgMEPamgEqLOqfyfhSz8h9Q=" + }, + "semver": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.2.0.tgz", + "integrity": "sha512-jdFC1VdUGT/2Scgbimf7FSx9iJLXoqfglSF+gJeuNWVpiE37OIbc1jywR/GJyFdz3mnkz2/id0L0J/cr0izR5A==" + }, + "swarm-js": { + "version": "0.1.39", + "resolved": "https://registry.npmjs.org/swarm-js/-/swarm-js-0.1.39.tgz", + "integrity": "sha512-QLMqL2rzF6n5s50BptyD6Oi0R1aWlJC5Y17SRIVXRj6OR1DRIPM7nepvrxxkjA1zNzFz6mUOMjfeqeDaWB7OOg==", + "requires": { + "bluebird": "^3.5.0", + "buffer": "^5.0.5", + "decompress": "^4.0.0", + "eth-lib": "^0.1.26", + "fs-extra": "^4.0.2", + "got": "^7.1.0", + "mime-types": "^2.1.16", + "mkdirp-promise": "^5.0.1", + "mock-fs": "^4.1.0", + "setimmediate": "^1.0.5", + "tar": "^4.0.2", + "xhr-request-promise": "^0.1.2" + }, + "dependencies": { + "got": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/got/-/got-7.1.0.tgz", + "integrity": "sha512-Y5WMo7xKKq1muPsxD+KmrR8DH5auG7fBdDVueZwETwV6VytKyU9OX/ddpq2/1hp1vIPvVb4T81dKQz3BivkNLw==", + "requires": { + "decompress-response": "^3.2.0", + "duplexer3": "^0.1.4", + "get-stream": "^3.0.0", + "is-plain-obj": "^1.1.0", + "is-retry-allowed": "^1.0.0", + "is-stream": "^1.0.0", + "isurl": "^1.0.0-alpha5", + "lowercase-keys": "^1.0.0", + "p-cancelable": "^0.3.0", + "p-timeout": "^1.1.1", + "safe-buffer": "^5.0.1", + "timed-out": "^4.0.0", + "url-parse-lax": "^1.0.0", + "url-to-options": "^1.0.1" + } + } + } + }, + "url-parse-lax": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", + "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", + "requires": { + "prepend-http": "^1.0.1" + } + }, + "uuid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.1.tgz", + "integrity": "sha1-wqMN7bPlNdcsz4LjQ5QaULqFM6w=" + }, + "web3": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/web3/-/web3-1.2.1.tgz", + "integrity": "sha512-nNMzeCK0agb5i/oTWNdQ1aGtwYfXzHottFP2Dz0oGIzavPMGSKyVlr8ibVb1yK5sJBjrWVnTdGaOC2zKDFuFRw==", + "requires": { + "web3-bzz": "1.2.1", + "web3-core": "1.2.1", + "web3-eth": "1.2.1", + "web3-eth-personal": "1.2.1", + "web3-net": "1.2.1", + "web3-shh": "1.2.1", + "web3-utils": "1.2.1" + } + }, + "web3-bzz": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/web3-bzz/-/web3-bzz-1.2.1.tgz", + "integrity": "sha512-LdOO44TuYbGIPfL4ilkuS89GQovxUpmLz6C1UC7VYVVRILeZS740FVB3j9V4P4FHUk1RenaDfKhcntqgVCHtjw==", + "requires": { + "got": "9.6.0", + "swarm-js": "0.1.39", + "underscore": "1.9.1" + } + }, + "web3-core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/web3-core/-/web3-core-1.2.1.tgz", + "integrity": "sha512-5ODwIqgl8oIg/0+Ai4jsLxkKFWJYE0uLuE1yUKHNVCL4zL6n3rFjRMpKPokd6id6nJCNgeA64KdWQ4XfpnjdMg==", + "requires": { + "web3-core-helpers": "1.2.1", + "web3-core-method": "1.2.1", + "web3-core-requestmanager": "1.2.1", + "web3-utils": "1.2.1" + } + }, + "web3-core-helpers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/web3-core-helpers/-/web3-core-helpers-1.2.1.tgz", + "integrity": "sha512-Gx3sTEajD5r96bJgfuW377PZVFmXIH4TdqDhgGwd2lZQCcMi+DA4TgxJNJGxn0R3aUVzyyE76j4LBrh412mXrw==", + "requires": { + "underscore": "1.9.1", + "web3-eth-iban": "1.2.1", + "web3-utils": "1.2.1" + } + }, + "web3-core-method": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/web3-core-method/-/web3-core-method-1.2.1.tgz", + "integrity": "sha512-Ghg2WS23qi6Xj8Od3VCzaImLHseEA7/usvnOItluiIc5cKs00WYWsNy2YRStzU9a2+z8lwQywPYp0nTzR/QXdQ==", + "requires": { + "underscore": "1.9.1", + "web3-core-helpers": "1.2.1", + "web3-core-promievent": "1.2.1", + "web3-core-subscriptions": "1.2.1", + "web3-utils": "1.2.1" + } + }, + "web3-core-promievent": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/web3-core-promievent/-/web3-core-promievent-1.2.1.tgz", + "integrity": "sha512-IVUqgpIKoeOYblwpex4Hye6npM0aMR+kU49VP06secPeN0rHMyhGF0ZGveWBrGvf8WDPI7jhqPBFIC6Jf3Q3zw==", + "requires": { + "any-promise": "1.3.0", + "eventemitter3": "3.1.2" + } + }, + "web3-core-requestmanager": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/web3-core-requestmanager/-/web3-core-requestmanager-1.2.1.tgz", + "integrity": "sha512-xfknTC69RfYmLKC+83Jz73IC3/sS2ZLhGtX33D4Q5nQ8yc39ElyAolxr9sJQS8kihOcM6u4J+8gyGMqsLcpIBg==", + "requires": { + "underscore": "1.9.1", + "web3-core-helpers": "1.2.1", + "web3-providers-http": "1.2.1", + "web3-providers-ipc": "1.2.1", + "web3-providers-ws": "1.2.1" + } + }, + "web3-core-subscriptions": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/web3-core-subscriptions/-/web3-core-subscriptions-1.2.1.tgz", + "integrity": "sha512-nmOwe3NsB8V8UFsY1r+sW6KjdOS68h8nuh7NzlWxBQT/19QSUGiERRTaZXWu5BYvo1EoZRMxCKyCQpSSXLc08g==", + "requires": { + "eventemitter3": "3.1.2", + "underscore": "1.9.1", + "web3-core-helpers": "1.2.1" + } + }, + "web3-eth": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/web3-eth/-/web3-eth-1.2.1.tgz", + "integrity": "sha512-/2xly4Yry5FW1i+uygPjhfvgUP/MS/Dk+PDqmzp5M88tS86A+j8BzKc23GrlA8sgGs0645cpZK/999LpEF5UdA==", + "requires": { + "underscore": "1.9.1", + "web3-core": "1.2.1", + "web3-core-helpers": "1.2.1", + "web3-core-method": "1.2.1", + "web3-core-subscriptions": "1.2.1", + "web3-eth-abi": "1.2.1", + "web3-eth-accounts": "1.2.1", + "web3-eth-contract": "1.2.1", + "web3-eth-ens": "1.2.1", + "web3-eth-iban": "1.2.1", + "web3-eth-personal": "1.2.1", + "web3-net": "1.2.1", + "web3-utils": "1.2.1" + } + }, + "web3-eth-abi": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/web3-eth-abi/-/web3-eth-abi-1.2.1.tgz", + "integrity": "sha512-jI/KhU2a/DQPZXHjo2GW0myEljzfiKOn+h1qxK1+Y9OQfTcBMxrQJyH5AP89O6l6NZ1QvNdq99ThAxBFoy5L+g==", + "requires": { + "ethers": "4.0.0-beta.3", + "underscore": "1.9.1", + "web3-utils": "1.2.1" + }, + "dependencies": { + "elliptic": { + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.3.3.tgz", + "integrity": "sha1-VILZZG1UvLif19mU/J4ulWiHbj8=", + "requires": { + "bn.js": "^4.4.0", + "brorand": "^1.0.1", + "hash.js": "^1.0.0", + "inherits": "^2.0.1" + } + }, + "ethers": { + "version": "4.0.0-beta.3", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-4.0.0-beta.3.tgz", + "integrity": "sha512-YYPogooSknTwvHg3+Mv71gM/3Wcrx+ZpCzarBj3mqs9njjRkrOo2/eufzhHloOCo3JSoNI4TQJJ6yU5ABm3Uog==", + "requires": { + "@types/node": "^10.3.2", + "aes-js": "3.0.0", + "bn.js": "^4.4.0", + "elliptic": "6.3.3", + "hash.js": "1.1.3", + "js-sha3": "0.5.7", + "scrypt-js": "2.0.3", + "setimmediate": "1.0.4", + "uuid": "2.0.1", + "xmlhttprequest": "1.8.0" + } + }, + "setimmediate": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.4.tgz", + "integrity": "sha1-IOgd5iLUoCWIzgyNqJc8vPHTE48=" + } + } + }, + "web3-eth-accounts": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/web3-eth-accounts/-/web3-eth-accounts-1.2.1.tgz", + "integrity": "sha512-26I4qq42STQ8IeKUyur3MdQ1NzrzCqPsmzqpux0j6X/XBD7EjZ+Cs0lhGNkSKH5dI3V8CJasnQ5T1mNKeWB7nQ==", + "requires": { + "any-promise": "1.3.0", + "crypto-browserify": "3.12.0", + "eth-lib": "0.2.7", + "scryptsy": "2.1.0", + "semver": "6.2.0", + "underscore": "1.9.1", + "uuid": "3.3.2", + "web3-core": "1.2.1", + "web3-core-helpers": "1.2.1", + "web3-core-method": "1.2.1", + "web3-utils": "1.2.1" + }, + "dependencies": { + "eth-lib": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/eth-lib/-/eth-lib-0.2.7.tgz", + "integrity": "sha1-L5Pxex4jrsN1nNSj/iDBKGo/wco=", + "requires": { + "bn.js": "^4.11.6", + "elliptic": "^6.4.0", + "xhr-request-promise": "^0.1.2" + } + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + } + } + }, + "web3-eth-contract": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/web3-eth-contract/-/web3-eth-contract-1.2.1.tgz", + "integrity": "sha512-kYFESbQ3boC9bl2rYVghj7O8UKMiuKaiMkxvRH5cEDHil8V7MGEGZNH0slSdoyeftZVlaWSMqkRP/chfnKND0g==", + "requires": { + "underscore": "1.9.1", + "web3-core": "1.2.1", + "web3-core-helpers": "1.2.1", + "web3-core-method": "1.2.1", + "web3-core-promievent": "1.2.1", + "web3-core-subscriptions": "1.2.1", + "web3-eth-abi": "1.2.1", + "web3-utils": "1.2.1" + } + }, + "web3-eth-ens": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/web3-eth-ens/-/web3-eth-ens-1.2.1.tgz", + "integrity": "sha512-lhP1kFhqZr2nnbu3CGIFFrAnNxk2veXpOXBY48Tub37RtobDyHijHgrj+xTh+mFiPokyrapVjpFsbGa+Xzye4Q==", + "requires": { + "eth-ens-namehash": "2.0.8", + "underscore": "1.9.1", + "web3-core": "1.2.1", + "web3-core-helpers": "1.2.1", + "web3-core-promievent": "1.2.1", + "web3-eth-abi": "1.2.1", + "web3-eth-contract": "1.2.1", + "web3-utils": "1.2.1" + } + }, + "web3-eth-iban": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/web3-eth-iban/-/web3-eth-iban-1.2.1.tgz", + "integrity": "sha512-9gkr4QPl1jCU+wkgmZ8EwODVO3ovVj6d6JKMos52ggdT2YCmlfvFVF6wlGLwi0VvNa/p+0BjJzaqxnnG/JewjQ==", + "requires": { + "bn.js": "4.11.8", + "web3-utils": "1.2.1" + } + }, + "web3-eth-personal": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/web3-eth-personal/-/web3-eth-personal-1.2.1.tgz", + "integrity": "sha512-RNDVSiaSoY4aIp8+Hc7z+X72H7lMb3fmAChuSBADoEc7DsJrY/d0R5qQDK9g9t2BO8oxgLrLNyBP/9ub2Hc6Bg==", + "requires": { + "web3-core": "1.2.1", + "web3-core-helpers": "1.2.1", + "web3-core-method": "1.2.1", + "web3-net": "1.2.1", + "web3-utils": "1.2.1" + } + }, + "web3-net": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/web3-net/-/web3-net-1.2.1.tgz", + "integrity": "sha512-Yt1Bs7WgnLESPe0rri/ZoPWzSy55ovioaP35w1KZydrNtQ5Yq4WcrAdhBzcOW7vAkIwrsLQsvA+hrOCy7mNauw==", + "requires": { + "web3-core": "1.2.1", + "web3-core-method": "1.2.1", + "web3-utils": "1.2.1" + } + }, + "web3-providers-http": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/web3-providers-http/-/web3-providers-http-1.2.1.tgz", + "integrity": "sha512-BDtVUVolT9b3CAzeGVA/np1hhn7RPUZ6YYGB/sYky+GjeO311Yoq8SRDUSezU92x8yImSC2B+SMReGhd1zL+bQ==", + "requires": { + "web3-core-helpers": "1.2.1", + "xhr2-cookies": "1.1.0" + } + }, + "web3-providers-ipc": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/web3-providers-ipc/-/web3-providers-ipc-1.2.1.tgz", + "integrity": "sha512-oPEuOCwxVx8L4CPD0TUdnlOUZwGBSRKScCz/Ws2YHdr9Ium+whm+0NLmOZjkjQp5wovQbyBzNa6zJz1noFRvFA==", + "requires": { + "oboe": "2.1.4", + "underscore": "1.9.1", + "web3-core-helpers": "1.2.1" + } + }, + "web3-providers-ws": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/web3-providers-ws/-/web3-providers-ws-1.2.1.tgz", + "integrity": "sha512-oqsQXzu+ejJACVHy864WwIyw+oB21nw/pI65/sD95Zi98+/HQzFfNcIFneF1NC4bVF3VNX4YHTNq2I2o97LAiA==", + "requires": { + "underscore": "1.9.1", + "web3-core-helpers": "1.2.1", + "websocket": "github:web3-js/WebSocket-Node#polyfill/globalThis" + } + }, + "web3-shh": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/web3-shh/-/web3-shh-1.2.1.tgz", + "integrity": "sha512-/3Cl04nza5kuFn25bV3FJWa0s3Vafr5BlT933h26xovQ6HIIz61LmvNQlvX1AhFL+SNJOTcQmK1SM59vcyC8bA==", + "requires": { + "web3-core": "1.2.1", + "web3-core-method": "1.2.1", + "web3-core-subscriptions": "1.2.1", + "web3-net": "1.2.1" + } + }, + "web3-utils": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.2.1.tgz", + "integrity": "sha512-Mrcn3l58L+yCKz3zBryM6JZpNruWuT0OCbag8w+reeNROSGVlXzUQkU+gtAwc9JCZ7tKUyg67+2YUGqUjVcyBA==", + "requires": { + "bn.js": "4.11.8", + "eth-lib": "0.2.7", + "ethjs-unit": "0.1.6", + "number-to-bn": "1.7.0", + "randomhex": "0.1.5", + "underscore": "1.9.1", + "utf8": "3.0.0" + }, + "dependencies": { + "eth-lib": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/eth-lib/-/eth-lib-0.2.7.tgz", + "integrity": "sha1-L5Pxex4jrsN1nNSj/iDBKGo/wco=", + "requires": { + "bn.js": "^4.11.6", + "elliptic": "^6.4.0", + "xhr-request-promise": "^0.1.2" + } + } + } + }, + "websocket": { + "version": "github:web3-js/WebSocket-Node#ef5ea2f41daf4a2113b80c9223df884b4d56c400", + "from": "github:web3-js/WebSocket-Node#polyfill/globalThis", + "requires": { + "debug": "^2.2.0", + "es5-ext": "^0.10.50", + "nan": "^2.14.0", + "typedarray-to-buffer": "^3.1.5", + "yaeti": "^0.0.6" + } + } + } + }, + "truffle-interface-adapter": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/truffle-interface-adapter/-/truffle-interface-adapter-0.2.5.tgz", + "integrity": "sha512-EL39OpP8FcZ99ne1Rno3jImfb92Nectd4iVsZzoEUCBfbwHe7sr0k+i45guoruSoP8nMUE81Mov2s8I5pi6d9Q==", + "requires": { + "bn.js": "^4.11.8", + "ethers": "^4.0.32", + "lodash": "^4.17.13", + "web3": "1.2.1" + }, + "dependencies": { + "@types/node": { + "version": "10.17.51", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.51.tgz", + "integrity": "sha512-KANw+MkL626tq90l++hGelbl67irOJzGhUJk6a1Bt8QHOeh9tztJx+L0AqttraWKinmZn7Qi5lJZJzx45Gq0dg==" + }, + "eventemitter3": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", + "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==" + }, + "get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" + }, + "hash.js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.3.tgz", + "integrity": "sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA==", + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.0" + } + }, + "js-sha3": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.5.7.tgz", + "integrity": "sha1-DU/9gALVMzqrr0oj7tL2N0yfKOc=" + }, + "oboe": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/oboe/-/oboe-2.1.4.tgz", + "integrity": "sha1-IMiM2wwVNxuwQRklfU/dNLCqSfY=", + "requires": { + "http-https": "^1.0.0" + } + }, + "p-cancelable": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.3.0.tgz", + "integrity": "sha512-RVbZPLso8+jFeq1MfNvgXtCRED2raz/dKpacfTNxsx6pLEpEomM7gah6VeHSYV3+vo0OAi4MkArtQcWWXuQoyw==" + }, + "prepend-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=" + }, + "scrypt-js": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-2.0.3.tgz", + "integrity": "sha1-uwBAvgMEPamgEqLOqfyfhSz8h9Q=" + }, + "semver": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.2.0.tgz", + "integrity": "sha512-jdFC1VdUGT/2Scgbimf7FSx9iJLXoqfglSF+gJeuNWVpiE37OIbc1jywR/GJyFdz3mnkz2/id0L0J/cr0izR5A==" + }, + "swarm-js": { + "version": "0.1.39", + "resolved": "https://registry.npmjs.org/swarm-js/-/swarm-js-0.1.39.tgz", + "integrity": "sha512-QLMqL2rzF6n5s50BptyD6Oi0R1aWlJC5Y17SRIVXRj6OR1DRIPM7nepvrxxkjA1zNzFz6mUOMjfeqeDaWB7OOg==", + "requires": { + "bluebird": "^3.5.0", + "buffer": "^5.0.5", + "decompress": "^4.0.0", + "eth-lib": "^0.1.26", + "fs-extra": "^4.0.2", + "got": "^7.1.0", + "mime-types": "^2.1.16", + "mkdirp-promise": "^5.0.1", + "mock-fs": "^4.1.0", + "setimmediate": "^1.0.5", + "tar": "^4.0.2", + "xhr-request-promise": "^0.1.2" + }, + "dependencies": { + "got": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/got/-/got-7.1.0.tgz", + "integrity": "sha512-Y5WMo7xKKq1muPsxD+KmrR8DH5auG7fBdDVueZwETwV6VytKyU9OX/ddpq2/1hp1vIPvVb4T81dKQz3BivkNLw==", + "requires": { + "decompress-response": "^3.2.0", + "duplexer3": "^0.1.4", + "get-stream": "^3.0.0", + "is-plain-obj": "^1.1.0", + "is-retry-allowed": "^1.0.0", + "is-stream": "^1.0.0", + "isurl": "^1.0.0-alpha5", + "lowercase-keys": "^1.0.0", + "p-cancelable": "^0.3.0", + "p-timeout": "^1.1.1", + "safe-buffer": "^5.0.1", + "timed-out": "^4.0.0", + "url-parse-lax": "^1.0.0", + "url-to-options": "^1.0.1" + } + } + } + }, + "url-parse-lax": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", + "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", + "requires": { + "prepend-http": "^1.0.1" + } + }, + "uuid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.1.tgz", + "integrity": "sha1-wqMN7bPlNdcsz4LjQ5QaULqFM6w=" + }, + "web3": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/web3/-/web3-1.2.1.tgz", + "integrity": "sha512-nNMzeCK0agb5i/oTWNdQ1aGtwYfXzHottFP2Dz0oGIzavPMGSKyVlr8ibVb1yK5sJBjrWVnTdGaOC2zKDFuFRw==", + "requires": { + "web3-bzz": "1.2.1", + "web3-core": "1.2.1", + "web3-eth": "1.2.1", + "web3-eth-personal": "1.2.1", + "web3-net": "1.2.1", + "web3-shh": "1.2.1", + "web3-utils": "1.2.1" + } + }, + "web3-bzz": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/web3-bzz/-/web3-bzz-1.2.1.tgz", + "integrity": "sha512-LdOO44TuYbGIPfL4ilkuS89GQovxUpmLz6C1UC7VYVVRILeZS740FVB3j9V4P4FHUk1RenaDfKhcntqgVCHtjw==", + "requires": { + "got": "9.6.0", + "swarm-js": "0.1.39", + "underscore": "1.9.1" + } + }, + "web3-core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/web3-core/-/web3-core-1.2.1.tgz", + "integrity": "sha512-5ODwIqgl8oIg/0+Ai4jsLxkKFWJYE0uLuE1yUKHNVCL4zL6n3rFjRMpKPokd6id6nJCNgeA64KdWQ4XfpnjdMg==", + "requires": { + "web3-core-helpers": "1.2.1", + "web3-core-method": "1.2.1", + "web3-core-requestmanager": "1.2.1", + "web3-utils": "1.2.1" + } + }, + "web3-core-helpers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/web3-core-helpers/-/web3-core-helpers-1.2.1.tgz", + "integrity": "sha512-Gx3sTEajD5r96bJgfuW377PZVFmXIH4TdqDhgGwd2lZQCcMi+DA4TgxJNJGxn0R3aUVzyyE76j4LBrh412mXrw==", + "requires": { + "underscore": "1.9.1", + "web3-eth-iban": "1.2.1", + "web3-utils": "1.2.1" + } + }, + "web3-core-method": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/web3-core-method/-/web3-core-method-1.2.1.tgz", + "integrity": "sha512-Ghg2WS23qi6Xj8Od3VCzaImLHseEA7/usvnOItluiIc5cKs00WYWsNy2YRStzU9a2+z8lwQywPYp0nTzR/QXdQ==", + "requires": { + "underscore": "1.9.1", + "web3-core-helpers": "1.2.1", + "web3-core-promievent": "1.2.1", + "web3-core-subscriptions": "1.2.1", + "web3-utils": "1.2.1" + } + }, + "web3-core-promievent": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/web3-core-promievent/-/web3-core-promievent-1.2.1.tgz", + "integrity": "sha512-IVUqgpIKoeOYblwpex4Hye6npM0aMR+kU49VP06secPeN0rHMyhGF0ZGveWBrGvf8WDPI7jhqPBFIC6Jf3Q3zw==", + "requires": { + "any-promise": "1.3.0", + "eventemitter3": "3.1.2" + } + }, + "web3-core-requestmanager": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/web3-core-requestmanager/-/web3-core-requestmanager-1.2.1.tgz", + "integrity": "sha512-xfknTC69RfYmLKC+83Jz73IC3/sS2ZLhGtX33D4Q5nQ8yc39ElyAolxr9sJQS8kihOcM6u4J+8gyGMqsLcpIBg==", + "requires": { + "underscore": "1.9.1", + "web3-core-helpers": "1.2.1", + "web3-providers-http": "1.2.1", + "web3-providers-ipc": "1.2.1", + "web3-providers-ws": "1.2.1" + } + }, + "web3-core-subscriptions": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/web3-core-subscriptions/-/web3-core-subscriptions-1.2.1.tgz", + "integrity": "sha512-nmOwe3NsB8V8UFsY1r+sW6KjdOS68h8nuh7NzlWxBQT/19QSUGiERRTaZXWu5BYvo1EoZRMxCKyCQpSSXLc08g==", + "requires": { + "eventemitter3": "3.1.2", + "underscore": "1.9.1", + "web3-core-helpers": "1.2.1" + } + }, + "web3-eth": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/web3-eth/-/web3-eth-1.2.1.tgz", + "integrity": "sha512-/2xly4Yry5FW1i+uygPjhfvgUP/MS/Dk+PDqmzp5M88tS86A+j8BzKc23GrlA8sgGs0645cpZK/999LpEF5UdA==", + "requires": { + "underscore": "1.9.1", + "web3-core": "1.2.1", + "web3-core-helpers": "1.2.1", + "web3-core-method": "1.2.1", + "web3-core-subscriptions": "1.2.1", + "web3-eth-abi": "1.2.1", + "web3-eth-accounts": "1.2.1", + "web3-eth-contract": "1.2.1", + "web3-eth-ens": "1.2.1", + "web3-eth-iban": "1.2.1", + "web3-eth-personal": "1.2.1", + "web3-net": "1.2.1", + "web3-utils": "1.2.1" + } + }, + "web3-eth-abi": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/web3-eth-abi/-/web3-eth-abi-1.2.1.tgz", + "integrity": "sha512-jI/KhU2a/DQPZXHjo2GW0myEljzfiKOn+h1qxK1+Y9OQfTcBMxrQJyH5AP89O6l6NZ1QvNdq99ThAxBFoy5L+g==", + "requires": { + "ethers": "4.0.0-beta.3", + "underscore": "1.9.1", + "web3-utils": "1.2.1" + }, + "dependencies": { + "elliptic": { + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.3.3.tgz", + "integrity": "sha1-VILZZG1UvLif19mU/J4ulWiHbj8=", + "requires": { + "bn.js": "^4.4.0", + "brorand": "^1.0.1", + "hash.js": "^1.0.0", + "inherits": "^2.0.1" + } + }, + "ethers": { + "version": "4.0.0-beta.3", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-4.0.0-beta.3.tgz", + "integrity": "sha512-YYPogooSknTwvHg3+Mv71gM/3Wcrx+ZpCzarBj3mqs9njjRkrOo2/eufzhHloOCo3JSoNI4TQJJ6yU5ABm3Uog==", + "requires": { + "@types/node": "^10.3.2", + "aes-js": "3.0.0", + "bn.js": "^4.4.0", + "elliptic": "6.3.3", + "hash.js": "1.1.3", + "js-sha3": "0.5.7", + "scrypt-js": "2.0.3", + "setimmediate": "1.0.4", + "uuid": "2.0.1", + "xmlhttprequest": "1.8.0" + } + }, + "setimmediate": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.4.tgz", + "integrity": "sha1-IOgd5iLUoCWIzgyNqJc8vPHTE48=" + } + } + }, + "web3-eth-accounts": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/web3-eth-accounts/-/web3-eth-accounts-1.2.1.tgz", + "integrity": "sha512-26I4qq42STQ8IeKUyur3MdQ1NzrzCqPsmzqpux0j6X/XBD7EjZ+Cs0lhGNkSKH5dI3V8CJasnQ5T1mNKeWB7nQ==", + "requires": { + "any-promise": "1.3.0", + "crypto-browserify": "3.12.0", + "eth-lib": "0.2.7", + "scryptsy": "2.1.0", + "semver": "6.2.0", + "underscore": "1.9.1", + "uuid": "3.3.2", + "web3-core": "1.2.1", + "web3-core-helpers": "1.2.1", + "web3-core-method": "1.2.1", + "web3-utils": "1.2.1" + }, + "dependencies": { + "eth-lib": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/eth-lib/-/eth-lib-0.2.7.tgz", + "integrity": "sha1-L5Pxex4jrsN1nNSj/iDBKGo/wco=", + "requires": { + "bn.js": "^4.11.6", + "elliptic": "^6.4.0", + "xhr-request-promise": "^0.1.2" + } + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + } + } + }, + "web3-eth-contract": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/web3-eth-contract/-/web3-eth-contract-1.2.1.tgz", + "integrity": "sha512-kYFESbQ3boC9bl2rYVghj7O8UKMiuKaiMkxvRH5cEDHil8V7MGEGZNH0slSdoyeftZVlaWSMqkRP/chfnKND0g==", + "requires": { + "underscore": "1.9.1", + "web3-core": "1.2.1", + "web3-core-helpers": "1.2.1", + "web3-core-method": "1.2.1", + "web3-core-promievent": "1.2.1", + "web3-core-subscriptions": "1.2.1", + "web3-eth-abi": "1.2.1", + "web3-utils": "1.2.1" + } + }, + "web3-eth-ens": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/web3-eth-ens/-/web3-eth-ens-1.2.1.tgz", + "integrity": "sha512-lhP1kFhqZr2nnbu3CGIFFrAnNxk2veXpOXBY48Tub37RtobDyHijHgrj+xTh+mFiPokyrapVjpFsbGa+Xzye4Q==", + "requires": { + "eth-ens-namehash": "2.0.8", + "underscore": "1.9.1", + "web3-core": "1.2.1", + "web3-core-helpers": "1.2.1", + "web3-core-promievent": "1.2.1", + "web3-eth-abi": "1.2.1", + "web3-eth-contract": "1.2.1", + "web3-utils": "1.2.1" + } + }, + "web3-eth-iban": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/web3-eth-iban/-/web3-eth-iban-1.2.1.tgz", + "integrity": "sha512-9gkr4QPl1jCU+wkgmZ8EwODVO3ovVj6d6JKMos52ggdT2YCmlfvFVF6wlGLwi0VvNa/p+0BjJzaqxnnG/JewjQ==", + "requires": { + "bn.js": "4.11.8", + "web3-utils": "1.2.1" + }, + "dependencies": { + "bn.js": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" + } + } + }, + "web3-eth-personal": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/web3-eth-personal/-/web3-eth-personal-1.2.1.tgz", + "integrity": "sha512-RNDVSiaSoY4aIp8+Hc7z+X72H7lMb3fmAChuSBADoEc7DsJrY/d0R5qQDK9g9t2BO8oxgLrLNyBP/9ub2Hc6Bg==", + "requires": { + "web3-core": "1.2.1", + "web3-core-helpers": "1.2.1", + "web3-core-method": "1.2.1", + "web3-net": "1.2.1", + "web3-utils": "1.2.1" + } + }, + "web3-net": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/web3-net/-/web3-net-1.2.1.tgz", + "integrity": "sha512-Yt1Bs7WgnLESPe0rri/ZoPWzSy55ovioaP35w1KZydrNtQ5Yq4WcrAdhBzcOW7vAkIwrsLQsvA+hrOCy7mNauw==", + "requires": { + "web3-core": "1.2.1", + "web3-core-method": "1.2.1", + "web3-utils": "1.2.1" + } + }, + "web3-providers-http": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/web3-providers-http/-/web3-providers-http-1.2.1.tgz", + "integrity": "sha512-BDtVUVolT9b3CAzeGVA/np1hhn7RPUZ6YYGB/sYky+GjeO311Yoq8SRDUSezU92x8yImSC2B+SMReGhd1zL+bQ==", + "requires": { + "web3-core-helpers": "1.2.1", + "xhr2-cookies": "1.1.0" + } + }, + "web3-providers-ipc": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/web3-providers-ipc/-/web3-providers-ipc-1.2.1.tgz", + "integrity": "sha512-oPEuOCwxVx8L4CPD0TUdnlOUZwGBSRKScCz/Ws2YHdr9Ium+whm+0NLmOZjkjQp5wovQbyBzNa6zJz1noFRvFA==", + "requires": { + "oboe": "2.1.4", + "underscore": "1.9.1", + "web3-core-helpers": "1.2.1" + } + }, + "web3-providers-ws": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/web3-providers-ws/-/web3-providers-ws-1.2.1.tgz", + "integrity": "sha512-oqsQXzu+ejJACVHy864WwIyw+oB21nw/pI65/sD95Zi98+/HQzFfNcIFneF1NC4bVF3VNX4YHTNq2I2o97LAiA==", + "requires": { + "underscore": "1.9.1", + "web3-core-helpers": "1.2.1", + "websocket": "github:web3-js/WebSocket-Node#polyfill/globalThis" + } + }, + "web3-shh": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/web3-shh/-/web3-shh-1.2.1.tgz", + "integrity": "sha512-/3Cl04nza5kuFn25bV3FJWa0s3Vafr5BlT933h26xovQ6HIIz61LmvNQlvX1AhFL+SNJOTcQmK1SM59vcyC8bA==", + "requires": { + "web3-core": "1.2.1", + "web3-core-method": "1.2.1", + "web3-core-subscriptions": "1.2.1", + "web3-net": "1.2.1" + } + }, + "web3-utils": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.2.1.tgz", + "integrity": "sha512-Mrcn3l58L+yCKz3zBryM6JZpNruWuT0OCbag8w+reeNROSGVlXzUQkU+gtAwc9JCZ7tKUyg67+2YUGqUjVcyBA==", + "requires": { + "bn.js": "4.11.8", + "eth-lib": "0.2.7", + "ethjs-unit": "0.1.6", + "number-to-bn": "1.7.0", + "randomhex": "0.1.5", + "underscore": "1.9.1", + "utf8": "3.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" + }, + "eth-lib": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/eth-lib/-/eth-lib-0.2.7.tgz", + "integrity": "sha1-L5Pxex4jrsN1nNSj/iDBKGo/wco=", + "requires": { + "bn.js": "^4.11.6", + "elliptic": "^6.4.0", + "xhr-request-promise": "^0.1.2" + } + } + } + }, + "websocket": { + "version": "github:web3-js/WebSocket-Node#ef5ea2f41daf4a2113b80c9223df884b4d56c400", + "from": "github:web3-js/WebSocket-Node#polyfill/globalThis", + "requires": { + "debug": "^2.2.0", + "es5-ext": "^0.10.50", + "nan": "^2.14.0", + "typedarray-to-buffer": "^3.1.5", + "yaeti": "^0.0.6" + } + } + } + }, "tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", @@ -6315,6 +7618,15 @@ "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==" }, + "unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "requires": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, "underscore": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz", @@ -6628,7 +7940,10 @@ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", "dev": true, - "optional": true + "optional": true, + "requires": { + "nan": "^2.12.1" + } }, "is-binary-path": { "version": "1.0.1", @@ -7178,7 +8493,10 @@ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", "dev": true, - "optional": true + "optional": true, + "requires": { + "nan": "^2.12.1" + } }, "is-binary-path": { "version": "1.0.1", @@ -7427,6 +8745,11 @@ "cookiejar": "^2.1.1" } }, + "xmlhttprequest": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz", + "integrity": "sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw=" + }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -7475,6 +8798,15 @@ "camelcase": "^5.0.0", "decamelize": "^1.2.0" } + }, + "yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", + "requires": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } } } } diff --git a/app/package.json b/app/package.json index 1d264b1..267f66e 100644 --- a/app/package.json +++ b/app/package.json @@ -15,7 +15,10 @@ "webpack-dev-server": "^3.11.0" }, "dependencies": { + "bootstrap": "^4.5.3", + "jquery": "^3.5.1", "pouchdb-browser": "^7.2.2", + "truffle-contract": "^4.0.31", "web3": "^1.2.4" } } diff --git a/app/src/gameData.js b/app/src/gameData.js new file mode 100644 index 0000000..8f989f9 --- /dev/null +++ b/app/src/gameData.js @@ -0,0 +1,70 @@ +const PouchDB = require("pouchdb-browser"); +const { gameStatusEnum } = require("./gameUtil"); +const pouchDB = PouchDB.default.defaults(); +let db; + +init = () => { + db = new pouchDB("rps"); +}; + +setGameData = (id, playerOne, playerTwo, choice, mask, maskTimestamp, stake, deadline, stakedFromWinnings) => { + const game = { + _id: id, + playerOne, + playerTwo, + choice, + mask, + maskTimestamp, + stake, + deadline, + stakedFromWinnings, + }; + return game; +}; + +saveGame = async (game) => { + return await db.put(game); +}; + +getGame = async (gameId) => { + return await db.get(gameId); +}; + +updateGame = async (gameId, prop, val) => { + const game = await db.get(`${gameId}`); + game[`${prop}`] = val; + return await db.put(game); +}; + +deleteGame = async (gameId) => { + const game = await db.get(`${gameId}`); + return await db.remove(game); +}; + +deleteAllGames = async () => { + const rps = await fetchData(); + if (rps !== undefined) { + rps.rows.map(async (x) => { + await deleteGame(x.id.toString()); + }); + } +}; + +fetchData = async function () { + try { + return await db.allDocs({ include_docs: true }); + } catch (ex) { + console.error("fetchData error: ", ex); + } +}; + +module.exports = { + init, + setGameData, + saveGame, + deleteGame, + updateGame, + getGame, + fetchData, + deleteAllGames, +}; diff --git a/app/src/gameState.js b/app/src/gameState.js new file mode 100644 index 0000000..b211e64 --- /dev/null +++ b/app/src/gameState.js @@ -0,0 +1,145 @@ +const gameData = require("./gameData"); +const gameUtil = require("./gameUtil"); + +gameListRefresh = async function (activeAddress, blockTimeStamp) { + try { + $("#gamesTableContainer").show(); + $("#noGamesBanner").hide().html(``); + const docs = await gameData.fetchData(); + + document.getElementById("gamesTableData").innerHTML = + docs !== undefined && docs.total_rows > 0 + ? docs.rows + .filter((x) => x.doc.playerOne === activeAddress || x.doc.playerTwo === activeAddress) + .map((y) => gameUtil.createHtmlGameRow(y.doc, activeAddress, blockTimeStamp)) + .join("\n") + : ""; + + if (document.getElementById("gamesTableData").innerHTML === "") { + $("#gamesTableContainer").hide(); + $("#noGamesBanner").show().html(`
You have no games
`); + } + } catch (ex) { + console.error("fetchData error: ", ex); + } +}; + +$(function () { + $('[data-toggle="tooltip"]').tooltip(); +}); + +$("#playModal").on("show.bs.modal", function (event) { + let button = $(event.relatedTarget); + const gameId = button.data("gameid"); + const gameStake = button.data("gamestake"); + let modal = $(this); + modal.find("#play_gameId").val(gameId); + modal.find("#play_gamestake").val(`${gameStake}`); + modal.find(".modal-title").text("Play - Game = " + `${gameId.substring(0, 8)} ...`); +}); + +$("#revealModal").on("show.bs.modal", async function (event) { + let button = $(event.relatedTarget); + const gameId = button.data("gameid"); + const revealer = button.data("revealer"); + + let modal = $(this); + modal.find("#reveal_gameId").val(gameId); + modal.find("#reveal_revealer").val(revealer); + modal.find(".modal-title").text("Reveal Game " + `${gameId.substring(0, 8)} ...`); + + await displayRevealState(gameId, revealer); +}); + +$("#settleModal").on("show.bs.modal", async function (event) { + let button = $(event.relatedTarget); + const gameId = button.data("gameid"); + let modal = $(this); + modal.find("#settle_gameId").val(button.data("gameid")); + modal.find(".modal-title").text("Settle Game " + `${gameId.substring(0, 8)} ...`); +}); + +$("#payoutModal").on("show.bs.modal", async function (event) { + const winnings = $("#winningsAmount").html(); + $(this).find("#payout_balance").html(`You get : ${winnings} `); +}); + +displayRevealState = async (gameId, revealer) => { + const game = await gameData.getGame(gameId); + + document.getElementById( + "reveal_yourchoice_html" + ).innerHTML = ``; + + document.getElementById( + "reveal_theirchoice_html" + ).innerHTML = ``; + + document.getElementById("reveal_outcome_html").innerHTML = solveGame(game.choiceOne, game.choiceTwo); +}; + +solveGame = (revealer_choice, opponent_choice) => { + switch ((revealer_choice + 3 - opponent_choice) % 3) { + case 0: + return ``; + case 1: + return ``; + case 2: + return ``; + default: + return ``; + } +}; + +getChoiceIcon = (choice) => { + switch (choice) { + case "0": + return ``; + case "1": + return ``; + case "2": + return ``; + case "3": + return ``; + default: + return ``; + } +}; + +gamesCount = async function () { + try { + const docs = await gameData.fetchData(); + const games = docs.rows.filter((x) => x.status !== gameUtil.gameStatusEnum.finished); + console.log("ALL games|", games); + + // const games = (await gameData.fetchData()).rows; + + const allGames = games.length; + const activeGames = games.filter((x) => x.doc.status !== gameUtil.gameStatusEnum.finished).length; + return [allGames, activeGames]; + // return [1, 2]; + } catch (ex) { + console.error("gamesCount error: ", ex); + } +}; + +accountGamesCount = async function (_account) { + try { + const docs = await gameData.fetchData(); + const games = docs.rows.filter((x) => x.doc.playerOne === _account || x.doc.playerTwo === _account); + console.log("games|", games); + const allGames = games.length; + const activeGames = games.filter((x) => x.doc.status !== gameUtil.gameStatusEnum.finished).length; + return [allGames, activeGames]; + } catch (ex) { + console.error("accountGamesCount error: ", ex); + } +}; + +module.exports = { + gameListRefresh, + gamesCount, + accountGamesCount, +}; diff --git a/app/src/gameUtil.js b/app/src/gameUtil.js new file mode 100644 index 0000000..eea7935 --- /dev/null +++ b/app/src/gameUtil.js @@ -0,0 +1,130 @@ +const gameStatusEnum = { created: 1, played: 2, revealed: 3, playexpired: 4, revealexpired: 5, finished: 6 }; + +getEvent = (minedTxRecepit, eventIndex) => minedTxRecepit.logs[eventIndex].topics; + +getGameLifeTimeSeconds = (days, hours, minutes) => { + return 3600 * 24 * days + 3600 * hours + 60 * minutes; +}; + +setTxProgress = async (progress, delay) => { + const percentVal = `${progress}%`; + setTimeout( + () => { + $(".progress-bar").css("width", percentVal); + $(".progress-bar").html(percentVal); + }, + delay ? delay : 500 + ); + + if (progress == "0") { + $(".progress").css("display", "none"); + } +}; + +updateUI = async (txObj, txName, $txStatus) => { + if (!txObj.receipt.status) { + console.error("Wrong status"); + console.error(txObj.receipt); + await $txStatus.html(`There was an error in the ${txName} transaction execution, status not 1`, `error`); + } else if (txObj.receipt.logs.length == 0) { + console.log("Empty logs"); + console.log(txObj.receipt); + await $txStatus.html(`There was an error in the ${txName} transaction, missing expected event`, `error`); + } else { + await $txStatus.html(`${txName} transaction executed`, ``); + } +}; + +secondsToDisplayString = (seconds) => { + let days = Math.floor(seconds / (24 * 60 * 60)); + seconds -= days * (24 * 60 * 60); + let hours = Math.floor(seconds / (60 * 60)); + seconds -= hours * (60 * 60); + let minutes = Math.floor(seconds / 60); + seconds -= minutes * 60; + + let dayStr = days === 0 ? `` : days === 1 ? `1d` : `${days}d`; + let hourStr = hours === 0 ? `` : hours === 1 ? `1h` : `${hours}h`; + let minStr = minutes === 0 ? `` : minutes === 1 ? `1m` : `${minutes}m`; + return `${dayStr} ${hourStr} ${minStr}`; +}; + +createHtmlGameRow = (game, activeAddress, blockTimeStamp) => { + return `${activeAddress == game.playerOne ? game.playerTwoLabel : game.playerOneLabel} + ${game.stakeInEther} + ${makeExpiryButton(game.playDeadline, game.status, blockTimeStamp)} + ${makeExpiryButton(game.revealDeadline, game.status, blockTimeStamp)} + ${makeActionButton(game, activeAddress, blockTimeStamp)} + `; +}; + +makeExpiryButton = (deadline, status, blockTimeStamp) => { + const expiredStateElem = ``; + return status === gameStatusEnum.finished || deadline < blockTimeStamp + ? expiredStateElem + : `${secondsToDisplayString(deadline - blockTimeStamp)}`; +}; + +makeActionButton = (game, activeAddress, blockTimeStamp) => { + const settleActionButton = ``; + const playActionButton = ``; + const waitActionButton = ``; + const expiredActionButton = ``; + const finishedActionButton = ``; + const revealActionButton = ``; + + const isPlayerOne = activeAddress === game.playerOne; + const playHasExpired = game.playDeadline < blockTimeStamp; + const revealHasExpired = game.revealDeadline < blockTimeStamp; + + if (revealHasExpired && game.status != gameStatusEnum.finished) { + return settleActionButton; + } + switch (game.status) { + case gameStatusEnum.created: + if (isPlayerOne) { + return playHasExpired ? (revealHasExpired ? settleActionButton : revealActionButton) : waitActionButton; + } else { + return playHasExpired ? finishedActionButton : playActionButton; + } + + case gameStatusEnum.played: + if (isPlayerOne) { + return revealHasExpired ? settleActionButton : revealActionButton; + } else { + return revealHasExpired ? settleActionButton : waitActionButton; + } + + case gameStatusEnum.playexpired: + return isPlayerOne ? (revealHasExpired ? settleActionButton : revealActionButton) : expiredActionButton; + + case gameStatusEnum.revealexpired: + return settleActionButton; + + case gameStatusEnum.finished: + return finishedActionButton; + + case gameStatusEnum.expired: + return settleActionButton; + + default: + return ""; + } +}; + +module.exports = { + getEvent, + getGameLifeTimeSeconds, + setTxProgress, + updateUI, + secondsToDisplayString, + createHtmlGameRow, + makeActionButton, + gameStatusEnum, +}; diff --git a/app/src/index.html b/app/src/index.html index bd69fd7..2c6652b 100644 --- a/app/src/index.html +++ b/app/src/index.html @@ -1,29 +1,455 @@ - Rock Paper Scissors + A Classic Game - + + - - + -

Rock Paper Scissors

+
+ +
+
+
+

ROCK PAPER SCISSORS

+
+
+
+
+ + +
+
+
+
+ DAPP +
+
+
+
+ Balance +
+
+ +
+
+
+
+
+
+ Active Games +
+
+ +
+
+
+
+
+
+ All Games +
+
+ +
+
+
+
+
+
+ +
+ +
+
+   + +
+
+
+
+
+
+ Account +
+
+ +
+
+
+
+
+
+ Winnings +
+
+ +
+
+
+
+
+
+ All Games +
+
+ +
+
+
+
+
+
+ Active Games +
+
+ +
+
+
+
+
+
+
+ + +
+
+
+ + +
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+ + +
+
+ +
+
+
+ +
+
+ + +
+
+
+
+ + + +
+
+ +
+ + + + + + + + + + + + +
PlayerStakedPlay ExpiresReveal Expires
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/index.js b/app/src/index.js index 158f15e..214b9ab 100644 --- a/app/src/index.js +++ b/app/src/index.js @@ -1,26 +1,444 @@ const Web3 = require("web3"); -const rockPaperScissorsJson = require("../../build/contracts/RockPaperScissors.json"); +const $ = require("jquery"); const truffleContract = require("truffle-contract"); +const rockPaperScissorsJson = require("../../build/contracts/RockPaperScissors.json"); +const lib = require("./validation"); +const gameUtil = require("./gameUtil"); +const gameData = require("./gameData"); +const gameState = require("./gameState"); const App = { web3: null, activeAccount: null, wallets: [], rockPaperScissors: truffleContract(rockPaperScissorsJson), + dbInit: null, + GAME_MASK_TIMESTAMP_SLACK: null, + GAME_MASK_BLOCK_SLACK: null, + GAME_MIN_STAKE: null, + GAME_MIN_STAKE_ETH: null, + GAME_MIN_CUTOFF_INTERVAL: null, + GAME_MAX_CUTOFF_INTERVAL: null, - start: async function () { - const { web3 } = this; + create: async function () { + $("#btnCreate").prop("disabled", true); + const { getGameLifeTimeSeconds } = gameUtil; + const { maskChoice, create, games } = await this.rockPaperScissors.deployed(); + + const creator = this.activeAccount.address; + const opponent = $("#create_counterparty").val(); + const choice = $("#create_chosen").val(); + const mask = $("#create_mask").val(); + const stakeInEther = $("#create_stake").val(); + const days = $("#create_gameDays").val(); + const hours = $("#create_gameHours").val(); + const minutes = $("#create_gameMinutes").val(); + const gameLifetime = getGameLifeTimeSeconds(days, hours, minutes); + + const isValid = await lib.createIsValidated( + GAME_MIN_STAKE_ETH, + gameLifetime, + GAME_MIN_CUTOFF_INTERVAL, + GAME_MAX_CUTOFF_INTERVAL + ); + await gameUtil.setTxProgress("25"); + + if (!isValid) { + $("#btnCreate").prop("disabled", true); + //return; + } else { + const stake = this.web3.utils.toWei(stakeInEther); + const bytes32Mask = this.web3.utils.asciiToHex(mask); + $("#btnCreate").prop("disabled", false); + const latestBlock = await this.web3.eth.getBlock("latest"); + const maskBlockNo = latestBlock.number; + const maskTimestamp = latestBlock.timestamp; + + const maskedChoice = await maskChoice.call(choice, bytes32Mask, creator, maskTimestamp, true, maskBlockNo); + await gameUtil.setTxProgress("50", 1000); + + const createTxParmsObj = { from: creator, value: stake }; + + //simulate send tx + try { + await create.call(opponent, maskedChoice, stake, gameLifetime, createTxParmsObj); + await gameUtil.setTxProgress("75", 1000); + } catch (error) { + console.error("create call: ", error); + await gameUtil.setTxProgress("0"); + return; + } + + //send tx + const txObj = await create(opponent, maskedChoice, stake, gameLifetime, createTxParmsObj).on("transactionHash", (txHash) => + $("#txStatus").html(`create Tx pending : [ ${txHash} ]`) + ); + + //Post-mining + await gameUtil.updateUI(txObj, "Create", $("#txStatus")); + await gameUtil.setTxProgress("0"); + + //Fetch game from blockchain (get 'Game' variables) + const theGame = await games(maskedChoice); + + //save game to 'database' + const game = { + _id: maskedChoice, + playerOne: creator, + playerOneLabel: this.getAddressLabel(creator), + choiceOne: choice, + maskOne: bytes32Mask, + maskBlockNo: maskBlockNo, + maskTimestampOne: maskTimestamp, + + playerTwo: opponent, + playerTwoLabel: this.getAddressLabel(opponent), + choiceTwo: 0, + + stake: stake, + stakeInEther: stakeInEther, + playDeadline: Number(theGame.playDeadline), + revealDeadline: Number(theGame.revealDeadline), + stakedFromWinnings: false, + status: gameUtil.gameStatusEnum.created, + }; + await gameData.saveGame(game); + await this.refreshGames(); + } + }, + + play: async function () { + $("#btnPlay").prop("disabled", true); + const { play, winnings } = await this.rockPaperScissors.deployed(); + const opponent = this.activeAccount.address; + const gameId = $("#play_gameId").val(); + const choice = $("#play_chosen").val(); + const amountToStake = $("#play_stake").val(); + const game = await gameData.getGame(gameId); + const stakeBN = this.web3.utils.toBN(game.stake); + const latestBlockTimestamp = (await this.web3.eth.getBlock("latest")).timestamp; + const winningsAmount = await winnings.call(this.activeAccount.address); + + if (game.playDeadline < latestBlockTimestamp) { + $("#play_deadline_Error").html("Play deadline has expired").css("color", "red"); + await gameData.updateGame(gameId, "status", gameUtil.gameStatusEnum.expired); + await this.refreshGames(); + return; + } + + const _amountToStake = amountToStake === "" ? this.web3.utils.toBN(0) : this.web3.utils.toWei(amountToStake); + + if (!(await lib.playIsValidated(choice, _amountToStake, game.stake, winningsAmount))) { + $("#btnPlay").prop("disabled", true); + } else { + $("#btnPlay").prop("disabled", false); + + await gameUtil.setTxProgress("50", 1000); + const playTxParmsObj = { from: opponent, value: stakeBN }; + + //simulate send tx + try { + await play.call(gameId, choice, playTxParmsObj); + await gameUtil.setTxProgress("75", 1000); + } catch (error) { + console.error("play call: ", error); + } + + //send play tx + const txObj = await play(gameId, choice, playTxParmsObj).on("transactionHash", (txHash) => + $("#txStatus").html(`enrolAndCommit Tx pending : [ ${txHash} ]`) + ); + + //Post-mining + await gameUtil.updateUI(txObj, "Play", $("#txStatus")); + await gameUtil.setTxProgress("100"); + + //update game + await gameData.updateGame(gameId, "status", gameUtil.gameStatusEnum.played); + await gameData.updateGame(gameId, "stake", stakeBN.add(stakeBN)); + await gameData.updateGame(gameId, "choiceTwo", choice); + await gameUtil.setTxProgress("0"); + + await this.refreshGames(); + } + }, + + reveal: async function () { + const gameId = $("#reveal_gameId").val(); + const revealer = this.activeAccount.address; + const { reveal } = await this.rockPaperScissors.deployed(); + const game = await gameData.getGame(gameId); + const latestBlockTimestamp = (await this.web3.eth.getBlock("latest")).timestamp; + + if (game.revealDeadline < latestBlockTimestamp) { + await gameData.updateGame(gameId, "status", gameUtil.gameStatusEnum.expired); + await this.refreshGames(); + return; + } + + await gameUtil.setTxProgress("25"); + const revealTxParmsObj = { from: revealer }; + + //sim tx + try { + await reveal.call(gameId, game.choiceOne, game.maskOne, game.maskTimestampOne, game.maskBlockNo, revealTxParmsObj); + await gameUtil.setTxProgress("50"); + } catch (error) { + console.error("reveal call: ", error); + } + + //send tx + const txObj = await reveal( + gameId, + game.choiceOne, + game.maskOne, + game.maskTimestampOne, + game.maskBlockNo, + revealTxParmsObj + ).on("transactionHash", (txHash) => $("#txStatus").html(`reveal Tx pending : [ ${txHash} ]`)); + + //post-mining + await gameUtil.updateUI(txObj, "Reveal", $("#txStatus")); + await gameUtil.setTxProgress("100"); + + //update game db + await gameData.updateGame(gameId, "status", gameUtil.gameStatusEnum.finished); + await gameUtil.setTxProgress("0"); + await this.refreshGames(); + }, + + settle: async function () { + const gameId = $("#settle_gameId").val(); + const { settle } = await this.rockPaperScissors.deployed(); + const blockTimeStamp = (await this.web3.eth.getBlock("latest")).timestamp; + const game = await gameData.getGame(gameId); + await gameUtil.setTxProgress("25"); + + if (game.deadline > blockTimeStamp) return; + + try { + await settle.call(gameId, { from: this.activeAccount.address }); + await gameUtil.setTxProgress("50"); + } catch (ex) { + console.error("settle call error ", ex); + } + + const txRecepit = await settle(gameId, { from: this.activeAccount.address }).on("transactionHash", (txHash) => + $("#txStatus").html(`reveal Tx pending : [ ${txHash} ]`) + ); + + //post-mining + await gameUtil.updateUI(txRecepit, "Settle", $("#txStatus")); + await gameUtil.setTxProgress("100"); + + await gameData.updateGame(gameId, "status", gameUtil.gameStatusEnum.finished); + await gameUtil.setTxProgress("0"); + await this.refreshGames(); + }, + + withdraw: async function () { + const payee = this.activeAccount.address; + const { withdraw } = await this.rockPaperScissors.deployed(); + try { + await gameUtil.setTxProgress("25"); + await withdraw.call({ from: payee }); + await gameUtil.setTxProgress("50"); + } catch (ex) { + console.error("payout call error: ", ex); + } + + const txRcpt = await withdraw({ from: payee }).on("transactionHash", (txHash) => + $("#txStatus").html(`payout Tx pending : [ ${txHash} ]`) + ); + + await gameUtil.setTxProgress("70"); + await gameUtil.updateUI(txRcpt, "Payout", $("#txStatus")); + await gameUtil.setTxProgress("100"); + await this.showWinnings(); + await this.showContractBalance(); + await this.refreshGames(); + }, + + start: async function () { + const { web3, $ } = this; try { this.rockPaperScissors.setProvider(web3.currentProvider); await this.setUpApp(); } catch (error) { + console.log(error); console.error("Could not connect to contract or chain."); } }, setUpApp: async function () { - console.log("in setUpApp()"); + const { web3 } = this; + + const labelArray = ["Deployer", "Alice", "Bob", "Carol", "Dennis", "Erin", "Fred", "Gina", "Homer", "Jillian"]; + const addressSelector = document.getElementById("addressSelector"); + + web3.eth + .getAccounts() + .then((accounts) => { + if (accounts.length == 0) { + throw new Error("No accounts with which to transact"); + } + return accounts; + }) + .then((accountList) => { + for (i = 0; i < 10; i++) { + let address = accountList[i]; + let label = i < 10 ? labelArray[i] : `${accountList[i].slice(0, 6)}...`; + // let label = labelArray[i]; + this.wallets.push({ i, address, label }); + + if (i !== 0) { + var option = document.createElement("option"); + option.value = address; + option.label = `${label} - ${address}`; + addressSelector.add(option); + } + } + }) + .catch(console.error); + const deployed = await this.rockPaperScissors.deployed(); + GAME_MIN_STAKE = (await deployed.MIN_STAKE()).toNumber(); + GAME_MIN_STAKE_ETH = this.web3.utils.fromWei(GAME_MIN_STAKE.toString(), "ether"); + GAME_MIN_CUTOFF_INTERVAL = (await deployed.MIN_CUTOFF_INTERVAL()).toNumber(); + GAME_MAX_CUTOFF_INTERVAL = (await deployed.MAX_CUTOFF_INTERVAL()).toNumber(); + + await this.showContractBalance(); + await this.showGameVariables(); + await gameData.init(); + this.setActiveWallet(); + + await this.refreshGames(); + }, + + updateFromChain: async function () { + const blockTimeStamp = (await this.web3.eth.getBlock("latest")).timestamp; + const { games, MIN_STAKE } = await this.rockPaperScissors.deployed(); + + const docs = await gameData.fetchData(); + docs !== undefined && docs.total_rows > 0 + ? docs.rows.map(async (x) => { + if (x.doc.status < gameUtil.gameStatusEnum.finished) { + //get game data from blockchain directly + let chainData = await games(x.id); + console.log(chainData); + + //update Dapp database's 'status' + if (blockTimeStamp > chainData.playDeadline) { + await gameData.updateGame(x.id, "status", gameUtil.gameStatusEnum.playexpired); + } + + if (blockTimeStamp > chainData.revealDeadline) { + await gameData.updateGame(x.id, "status", gameUtil.gameStatusEnum.revealexpired); + } + } + }) + : []; + }, + + refreshGames: async function () { + const _now = (await this.web3.eth.getBlock("latest")).timestamp; + await this.updateFromChain(); + await gameState.gameListRefresh($("#addressSelector option:selected").attr("value"), _now); + await this.showAccountBalance(); + await this.showWinnings(); + const allGames = await gameState.gamesCount(); + console.log("allGames", allGames); + $("#dappAllGames").html(allGames[0]); + $("#dappActiveGames").html(allGames[1]); + + const accountGames = await gameState.accountGamesCount(this.activeAccount.address); + $("#activeAccountAllGames").html(accountGames[0]); + $("#activeAccountActiveGames").html(accountGames[1]); + console.log("accountGames", accountGames); + //await gameData.deleteAllGames(); + }, + + copyTextToClipBoard: async function (elem) { + await navigator.clipboard.writeText(document.getElementById(elem).innerText); + }, + + matchStake: function (_from, _to) { + const x = $(`#${_from}`).val(); + const stakeElem = $(`#${_to}`); + stakeElem.val(x.toString()); + }, + + showContractBalance: async function () { + const deployed = await this.rockPaperScissors.deployed(); + const balanceInWei = await this.web3.eth.getBalance(deployed.address); + const balanceInEther = this.web3.utils.fromWei(balanceInWei, "ether"); + $("#contractBalance").html(`${parseFloat(balanceInEther).toFixed(4)} ETH`); + }, + + showAccountBalance: async function () { + const accountBalanceInEther = await this.web3.utils.fromWei( + await this.web3.eth.getBalance(this.activeAccount.address), + "ether" + ); + $("#activeAccountBalance").html(accountBalanceInEther); + }, + + showWinnings: async function () { + const { winnings } = await this.rockPaperScissors.deployed(); + const winningsInEther = this.web3.utils.fromWei(await winnings.call(this.activeAccount.address), "ether"); + $("#winningsAmount").html(`${winningsInEther}`); + }, + + choiceSelected: function (id) { + $("#create_choices button").removeClass("btn-success").addClass("btn-secondary"); + const chosenElem = $(`#${id}`); + chosenElem.addClass("btn-success"); + $("#create_chosen").val(chosenElem.val()); + $("#btnCreate").prop("disabled", false); + }, + + playChoiceSelected: function (id) { + $("#play_choices button").removeClass("btn-success").addClass("btn-secondary"); + const chosenElem = $(`#${id}`); + chosenElem.addClass("btn-success"); + $("#play_chosen").val(chosenElem.val()); + $("#btnPlay").prop("disabled", false); + }, + + setActiveWallet: async function () { + const active = $("#addressSelector option:selected"); + const activeAddress = active.attr("value"); + document.getElementById("activeWallet").innerHTML = active.attr("label").split(" - ")[0]; + document.getElementById("activeWalletAddress").innerHTML = activeAddress; + + const activeWalletObj = this.wallets.find((x) => x.address === activeAddress.toString()); + this.activeAccount = activeWalletObj; + this.showWinnings(); + + await this.refreshGames(); + }, + + getAddressLabel: function (address) { + try { + const active = $("#addressSelector option:selected"); + document.getElementById("activeWallet").innerHTML = active.attr("label").split(" - ")[0]; + const activeWalletObj = this.wallets.find((x) => x.address === address.toString()); + return activeWalletObj.label; + } catch (err) { + console.error("getAddressLabel::Error:", err); + return ""; + } + }, + + showGameVariables: async function () { + const minStakeText = `Minimum Stake ${GAME_MIN_STAKE_ETH} ETH`; + $("#create_min_stake_message").html(minStakeText); + $("#play_min_stake_message").html(minStakeText); + $("#create_gameLength_limits").html( + `Minimum ${gameUtil.secondsToDisplayString(GAME_MIN_CUTOFF_INTERVAL)}, Maximum ${gameUtil.secondsToDisplayString( + GAME_MAX_CUTOFF_INTERVAL + )}` + ); }, }; diff --git a/app/src/validation.js b/app/src/validation.js new file mode 100644 index 0000000..e319530 --- /dev/null +++ b/app/src/validation.js @@ -0,0 +1,73 @@ +playIsValidated = async (choice, amountToStake, gameStaked, winnings) => { + $("#play_chosen_Error").html(""); + $("#play_deadline_Error").html(""); + $("#play_stake_Error").html(""); + + let isValid = true; + + if (!choice) { + $("#play_chosen_Error").html("Game choice is required").css("color", "red"); + isValid = false; + } + + if (amountToStake) + if (amountToStake + winnings < gameStaked) { + $("#play_stake_Error").html("Insufficient funds to stake").css("color", "red"); + isValid = false; + } + + return isValid; +}; + +createIsValidated = async (GAME_MIN_STAKE, gameLifeTime, gameMinLifeTime, gameMaxLifeTime) => { + $("#create_counterparty_Error").html(""); + $("#create_stake_Error").html(""); + $("#create_mask_Error").html(""); + $("#create_chosen_Error").html(""); + $("#create_gameLength_Error").html(""); + let isValid = true; + + if (!$("#create_counterparty").val()) { + $("#create_counterparty_Error").html("Opponent address is required").css("color", "red"); + isValid = false; + } + + if ($("#create_stake").val() < GAME_MIN_STAKE) { + $("#create_stake_Error").html("Insufficient stake").css("color", "red"); + isValid = false; + } + + if (!$("#create_mask").val()) { + $("#create_mask_Error").html("Choice mask is required").css("color", "red"); + + isValid = false; + } + + if (!$("#create_chosen").val()) { + $("#create_chosen_Error").html("Game choice is required").css("color", "red"); + isValid = false; + } + + let lifeTimeError = validateGameLifeTime(gameLifeTime, gameMinLifeTime, gameMaxLifeTime); + + if (lifeTimeError) { + $("#create_gameLength_Error").html(lifeTimeError).css("color", "red"); + isValid = false; + } + + return isValid; +}; + +validateGameLifeTime = (totalSeconds, gameMinLifeTime, gameMaxLifeTime) => { + if (totalSeconds < gameMinLifeTime) { + return "Game Length below required minimum."; + } else if (totalSeconds > gameMaxLifeTime) { + return "Game Length above maximum value."; + } + return ""; +}; + +module.exports = { + createIsValidated, + playIsValidated, +}; diff --git a/contracts/RockPaperScissors.sol b/contracts/RockPaperScissors.sol index 2bbc33f..a4f86cc 100644 --- a/contracts/RockPaperScissors.sol +++ b/contracts/RockPaperScissors.sol @@ -6,8 +6,7 @@ import "./SafeMath.sol"; contract RockPaperScissors is Ownable { using SafeMath for uint; - - uint public latestGameId; + struct Game { uint stake; uint playDeadline; @@ -20,7 +19,7 @@ contract RockPaperScissors is Ownable { address opponent; Choice opponentChoice; } - mapping(uint => Game) public games; + mapping(bytes32 => Game) public games; mapping(address => uint) public winnings; enum Choice {NONE, ROCK, PAPER, SCISSORS} @@ -30,15 +29,14 @@ contract RockPaperScissors is Ownable { uint constant public MASK_TIMESTAMP_SLACK = 10; uint constant public MASK_BLOCK_SLACK = 1; uint constant public MIN_STAKE = 1000000000000000; //10e15 wei or 0.001 ETH - uint constant public MIN_CUTOFF_INTERVAL = 1 hours; + uint constant public MIN_CUTOFF_INTERVAL = 1 minutes; uint constant public MAX_CUTOFF_INTERVAL = 10 days; - event LogGameCreated(uint indexed gameId, address indexed opponent, uint indexed playDeadline, uint staked); - event LogGamePlayed(uint indexed gameId, address indexed player, Choice indexed choice); - event LogChoiceRevealed(uint indexed gameId, address indexed revealer, Choice choice); - event LogWinningsBalanceChanged(address indexed player, uint indexed old, uint indexed latest); - event LogGameFinished(uint indexed gameId, Outcome indexed outcome, uint indexed stake); - event LogWithdrawal(address indexed withdrawer, uint indexed withdrawn); + event LogGameCreated(bytes32 indexed gameId, address indexed opponent, uint indexed playDeadline, uint staked); + event LogGamePlayed(bytes32 indexed gameId, address indexed player, Choice choice); + event LogWinningsBalanceChanged(address indexed player, uint old, uint latest); + event LogGameFinished(bytes32 indexed gameId, Outcome indexed outcome, uint stake, address settler); + event LogWithdrawal(address indexed withdrawer, uint withdrawn); constructor() public {} @@ -55,28 +53,28 @@ contract RockPaperScissors is Ownable { public view returns (bytes32 maskedChoice) { - require(choice != Choice.NONE, "RockPaperScissors::maskChoice:game move choice can not be NONE"); - require(mask != NULL_BYTES, "RockPaperScissors::maskChoice:mask can not be empty"); - require(masker != address(0), "RockPaperScissors::maskChoice:masker can not be null address"); - - if(maskingOnly){ - require((block.number) + MASK_BLOCK_SLACK >= blockNo && blockNo >= (block.number) - MASK_BLOCK_SLACK,"RockPaperScissors::maskChoice:blockNo is invalid"); + if(maskingOnly){ + require(choice != Choice.NONE, "RockPaperScissors::maskChoice:game move choice can not be NONE"); + require(mask != NULL_BYTES, "RockPaperScissors::maskChoice:mask can not be empty"); + require(masker != address(0), "RockPaperScissors::maskChoice:masker can not be null address"); + require((block.number).add(MASK_BLOCK_SLACK) >= blockNo && blockNo >= (block.number).sub(MASK_BLOCK_SLACK),"RockPaperScissors::maskChoice:blockNo is invalid"); require((block.timestamp).sub(MASK_TIMESTAMP_SLACK) <= maskTimestamp, "RockPaperScissors::maskChoice:maskTimestamp below minimum, use latest block timestamp"); require((block.timestamp).add(MASK_TIMESTAMP_SLACK) >= maskTimestamp, "RockPaperScissors::maskChoice:maskTimestamp above maximum, use latest block timestamp"); }else{ - require( block.timestamp >= (maskTimestamp).add(MIN_CUTOFF_INTERVAL), "RockPaperScissors::maskChoice:Invalid maskTimestamp for reveal"); + require(block.timestamp >= (maskTimestamp).add(MIN_CUTOFF_INTERVAL), "RockPaperScissors::maskChoice:Invalid maskTimestamp for reveal"); } return keccak256(abi.encodePacked(address(this), choice, mask, masker, maskTimestamp)); } function create(address opponent, bytes32 maskedChoice, uint toStake, uint playCutoffInterval) payable public { - require(msg.sender != opponent); - require(opponent != address(0)); - require(maskedChoice != NULL_BYTES); - require(toStake >= MIN_STAKE); - require(playCutoffInterval >= MIN_CUTOFF_INTERVAL); - require(playCutoffInterval <= MAX_CUTOFF_INTERVAL); + require(msg.sender != opponent,"RockPaperScissors::create:game creator and opponenet can not identical"); + require(opponent != address(0),"RockPaperScissors::create:opponent address can not be empty"); + require(maskedChoice != NULL_BYTES,"RockPaperScissors::create:masked choice can not be empty"); + require(toStake >= MIN_STAKE,"RockPaperScissors::create:insufficient stake"); + require(playCutoffInterval >= MIN_CUTOFF_INTERVAL,"RockPaperScissors::create:cutoff interval below minimum"); + require(playCutoffInterval <= MAX_CUTOFF_INTERVAL,"RockPaperScissors::create:cutoff interval above maximum"); + require(games[maskedChoice].playDeadline == 0, "RockPaperScissors::create:game already exists"); uint balance = winnings[msg.sender]; //SLOAD uint newBalance = balance.add(msg.value).sub(toStake, "RockPaperScissors::create:Insuffcient balance to stake"); //SLOAD @@ -86,17 +84,18 @@ contract RockPaperScissors is Ownable { } uint _playDeadline = block.timestamp.add(playCutoffInterval); - Game storage game = games[latestGameId += 1];//SSTORE, SLOAD + Game storage game = games[maskedChoice];//SSTORE, SLOAD + game.creator = msg.sender; //SSTORE game.stake = toStake; //SSTORE game.creatorMaskedChoice = maskedChoice; //SSTORE game.opponent = opponent; //SSTORE game.playDeadline = _playDeadline; //SSTORE game.revealDeadline = _playDeadline.add(playCutoffInterval); //SSTORE - emit LogGameCreated(latestGameId, opponent, _playDeadline, toStake); + emit LogGameCreated(maskedChoice, opponent, _playDeadline, toStake); } - function play(uint gameId, Choice choice) payable public { + function play(bytes32 gameId, Choice choice) payable public { require(Choice.NONE != choice); require(msg.sender == games[gameId].opponent); //SLOAD require(block.timestamp <= games[gameId].playDeadline); //SLOAD @@ -113,51 +112,57 @@ contract RockPaperScissors is Ownable { LogGamePlayed(gameId, msg.sender, choice); } - function reveal(uint gameId, Choice choice, bytes32 mask, uint maskTimestamp) public { + function reveal(bytes32 gameId, Choice choice, bytes32 mask, uint maskTimestamp, uint maskBlockNo) public { Game storage game = games[gameId]; - require(game.opponentChoice != Choice.NONE || block.timestamp > game.playDeadline, "RockPaperScissors::reveal:opponent has not played or playDeadline not expired"); - require(block.timestamp <= game.revealDeadline); - require(maskChoice(choice, mask, msg.sender, maskTimestamp, false, block.number) == game.creatorMaskedChoice, "RockPaperScissors::reveal:masked choice does not match"); + require(game.opponentChoice != Choice.NONE || block.timestamp > game.playDeadline, "RockPaperScissors::reveal:opponent has not played or playDeadline not expired"); //SLOAD, SLOAD + require(block.timestamp <= game.revealDeadline,"RockPaperScissors::reveal:reveal deadline has expired");//SLOAD + require(maskChoice(choice, mask, msg.sender, maskTimestamp, false, maskBlockNo) == gameId, "RockPaperScissors::reveal:masked choice does not match"); finish(gameId, resolve(choice, game.opponentChoice), game.creator, game.opponent, game.stake); delete games[gameId]; } - function settle(uint gameId) public { + function settle(bytes32 gameId) public { require(block.timestamp > games[gameId].revealDeadline); Game storage game = games[gameId]; finish(gameId, resolve(game.creatorChoice, game.opponentChoice), game.creator, game.opponent, game.stake);// 5 * SLOAD delete games[gameId]; } + + function finish(bytes32 gameId, Outcome outcome, address creator, address opponent, uint pay) internal { + bool isDraw = outcome == Outcome.DRAW; + bool isNone = outcome == Outcome.NONE; - function finish(uint gameId, Outcome outcome, address creator, address opponent, uint pay) internal { - bool isDraw = outcome == Outcome.DRAW; - - pay = isDraw ? pay : pay.add(pay); - - if((isDraw || outcome == Outcome.WIN) && pay != 0){ + uint owed = (isDraw || isNone) ? pay : pay.add(pay); + + if((isDraw || isNone || outcome == Outcome.WIN) && pay != 0){ uint creatorBalance = winnings[creator]; //SLOAD - uint newCreatorBalance = creatorBalance.add(pay); + uint newCreatorBalance = owed.add(creatorBalance); winnings[creator] = newCreatorBalance; //SSTORE emit LogWinningsBalanceChanged(creator, creatorBalance, newCreatorBalance); } if((isDraw || outcome == Outcome.LOSE) && pay != 0){ uint opponentBalance = winnings[opponent]; //SLOAD - uint newOpponentBalance =opponentBalance.add(pay); + uint newOpponentBalance = owed.add(opponentBalance); winnings[opponent] = newOpponentBalance; //SSTORE emit LogWinningsBalanceChanged(opponent, opponentBalance, newOpponentBalance); } - emit LogGameFinished(gameId, outcome, pay); + emit LogGameFinished(gameId, outcome, owed, msg.sender); } - function resolve(Choice creatorChoice, Choice opponentChoice) public pure returns(Outcome outcome){ - if(opponentChoice == Choice.NONE){ - return Outcome.WIN; - }else if(opponentChoice != Choice.NONE && creatorChoice == Choice.NONE){ - return Outcome.LOSE; - }else{ - return Outcome(SafeMath.mod(uint(creatorChoice).add(3).sub(uint(opponentChoice)), 3).add(1)); + function resolve(Choice creatorChoice, Choice opponentChoice) public pure returns(Outcome outcome){ + if(creatorChoice != Choice.NONE ) + { + return opponentChoice != Choice.NONE ? + Outcome(SafeMath.mod(uint(creatorChoice).add(3).sub(uint(opponentChoice)), 3).add(1)) + :Outcome.WIN; + } + else + { + return opponentChoice != Choice.NONE ? + Outcome.LOSE + :Outcome.NONE; } } diff --git a/package-lock.json b/package-lock.json index 115705b..a9ae07e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,6 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", - "dev": true, "requires": { "assertion-error": "^1.1.0", "check-error": "^1.0.2", @@ -21,26 +20,34 @@ "type-detect": "^4.0.5" } }, + "chai-bn": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/chai-bn/-/chai-bn-0.2.1.tgz", + "integrity": "sha512-01jt2gSXAw7UYFPT5K8d7HYjdXj2vyeIuE+0T/34FWzlNcVbs1JkPxRu7rYMfQnJhrHT8Nr6qjSf5ZwwLU2EYg==", + "dev": true + }, "check-error": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", - "dev": true + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=" }, "deep-eql": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", - "dev": true, "requires": { "type-detect": "^4.0.0" } }, + "dot-notes": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/dot-notes/-/dot-notes-3.1.1.tgz", + "integrity": "sha1-eufhqUgTRSnzdosvsT9Mj6kqV88=" + }, "get-func-name": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", - "dev": true + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=" }, "lodash.isequal": { "version": "4.5.0", @@ -50,8 +57,7 @@ "pathval": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", - "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", - "dev": true + "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=" }, "truffle-assertions": { "version": "0.9.2", @@ -65,8 +71,7 @@ "type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==" } } } diff --git a/test/create.js b/test/create.js index 9ccc87f..be34260 100644 --- a/test/create.js +++ b/test/create.js @@ -42,6 +42,7 @@ contract("RockPaperScissors", (accounts) => { cutOff: MIN_CUTOFF_INTERVAL, msgsender: creator, msgvalue: MIN_STAKE, + error: "creator and opponent address can not identical", }, { opponent: NULL_ADDRESS, @@ -50,6 +51,7 @@ contract("RockPaperScissors", (accounts) => { cutOff: MIN_CUTOFF_INTERVAL, msgsender: creator, msgvalue: MIN_STAKE, + error: "oppponent address can not be empty", }, { opponent: opponent, @@ -58,6 +60,7 @@ contract("RockPaperScissors", (accounts) => { cutOff: MIN_CUTOFF_INTERVAL, msgsender: creator, msgvalue: MIN_STAKE, + error: "masked choice can not be empty", }, { opponent: opponent, @@ -66,6 +69,7 @@ contract("RockPaperScissors", (accounts) => { cutOff: MIN_CUTOFF_INTERVAL, msgsender: creator, msgvalue: MIN_STAKE > 0 ? MIN_STAKE - 1 : 0, + error: "insufficent balance to stake", }, { opponent: opponent, @@ -74,6 +78,7 @@ contract("RockPaperScissors", (accounts) => { cutOff: MIN_CUTOFF_INTERVAL - 1, msgsender: creator, msgvalue: MIN_STAKE, + error: "cut off deadline interval below minimum", }, { opponent: opponent, @@ -82,6 +87,7 @@ contract("RockPaperScissors", (accounts) => { cutOff: MAX_CUTOFF_INTERVAL + 1, msgsender: creator, msgvalue: MIN_STAKE, + error: "cut off deadline interval above maximum", }, ]; } @@ -99,20 +105,71 @@ contract("RockPaperScissors", (accounts) => { .call({ from: creator }); }); - it("reverts when given invalid parameters", async () => { - revertSituations().forEach(async (d) => { + it("reverts oppponent address is empty", async () => { + await truffleAssert.reverts( + rockPaperScissors.contract.methods + .create(NULL_ADDRESS, maskedChoice, MIN_STAKE, MIN_CUTOFF_INTERVAL) + .send({ from: creator, value: MIN_STAKE }) + ); + }); + + it("reverts when creator and opponent address can not identical ", async () => { + await truffleAssert.reverts( + rockPaperScissors.contract.methods + .create(creator, maskedChoice, MIN_STAKE, MIN_CUTOFF_INTERVAL) + .send({ from: creator, value: MIN_STAKE }) + ); + }); + + it("reverts when masked choice is empty", async () => { + await truffleAssert.reverts( + rockPaperScissors.contract.methods + .create(opponent, NULL_BYTES, MIN_STAKE, MIN_CUTOFF_INTERVAL) + .send({ from: creator, value: MIN_STAKE }) + ); + }); + + it("reverts when given insufficent balance to stake", async () => { + if (MIN_STAKE > 0) { await truffleAssert.reverts( rockPaperScissors.contract.methods - .create(d.opponent, d.maskedChoice, d.toStake, d.cutOff) - .send({ from: d.msgsender, value: d.msgvalue }) + .create(opponent, maskedChoice, MIN_STAKE, MIN_CUTOFF_INTERVAL) + .send({ from: creator, value: MIN_STAKE - 1 }) ); - }); + } }); - it("should create and set game to storage", async () => { + it("reverts if a game with gameId already exists", async () => { //Arrange - const priorId = (await rockPaperScissors.latestGameId.call()).toNumber(); + await rockPaperScissors.contract.methods + .create(opponent, maskedChoice, MIN_STAKE, MIN_CUTOFF_INTERVAL) + .send({ from: creator, value: MIN_STAKE, gas }); + //Assert + await truffleAssert.reverts( + rockPaperScissors.contract.methods + .create(opponent, maskedChoice, MIN_STAKE, MIN_CUTOFF_INTERVAL) + .send({ from: creator, value: MIN_STAKE, gas }) + ); + }); + + it("reverts when cut off deadline interval is below minimum", async () => { + await truffleAssert.reverts( + rockPaperScissors.contract.methods + .create(opponent, maskedChoice, MIN_STAKE, MIN_CUTOFF_INTERVAL - 1) + .send({ from: creator, value: MIN_STAKE }) + ); + }); + + it("reverts when cut off deadline interval is above maximum", async () => { + await truffleAssert.reverts( + rockPaperScissors.contract.methods + .create(opponent, maskedChoice, MIN_STAKE, MAX_CUTOFF_INTERVAL + 1) + .send({ from: creator, value: MIN_STAKE }) + ); + }); + + it("should create and set game to storage", async () => { //Act const txReceipt = await rockPaperScissors.contract.methods .create(opponent, maskedChoice, MIN_STAKE, MIN_CUTOFF_INTERVAL) @@ -120,7 +177,7 @@ contract("RockPaperScissors", (accounts) => { //Assert assert.isDefined(txReceipt, "transaction is not mined"); - const game = await rockPaperScissors.games.call(priorId + 1); + const game = await rockPaperScissors.games.call(maskedChoice); assert.isDefined(game, "game has not been written to storage"); const txTimestamp = (await web3.eth.getBlock(txReceipt.blockNumber)).timestamp; @@ -137,12 +194,11 @@ contract("RockPaperScissors", (accounts) => { .create(opponent, maskedChoice, MIN_STAKE, MIN_CUTOFF_INTERVAL) .send({ from: creator, value: MIN_STAKE, gas }); - const gameId = await rockPaperScissors.latestGameId.call(); const txTimestamp = (await web3.eth.getBlock(txReceipt.blockNumber)).timestamp; //Assert eventAssert.eventIsEmitted(txReceipt, "LogGameCreated"); - eventAssert.parameterIsValid(txReceipt, "LogGameCreated", "gameId", gameId, "LogGameCreated gameId incorrect"); + eventAssert.parameterIsValid(txReceipt, "LogGameCreated", "gameId", maskedChoice, "LogGameCreated gameId incorrect"); eventAssert.parameterIsValid(txReceipt, "LogGameCreated", "opponent", opponent, "LogGameCreated opponent incorrect"); eventAssert.parameterIsValid( txReceipt, diff --git a/test/edgeCases.js b/test/edgeCases.js new file mode 100644 index 0000000..77c8a4c --- /dev/null +++ b/test/edgeCases.js @@ -0,0 +1,241 @@ +const RockPaperScissors = artifacts.require("RockPaperScissors"); +const truffleAssert = require("truffle-assertions"); +const timeHelper = require("../util/timeHelper"); +const eventAssert = require("../util/eventAssertionHelper"); +const chai = require("chai"); + +const { BN } = web3.utils.BN; +const { assert } = chai; +chai.use(require("chai-bn")(BN)); + +contract("RockPaperScissors", (accounts) => { + let snapShotId; + before(async () => { + it("TestRPC must have adequate number of addresses", () => { + assert.isAtLeast(accounts.length, 4, "Test has enough addresses"); + }); + snapShotId = (await timeHelper.takeSnapshot()).id; + }); + + let rockPaperScissors; + let MIN_CUTOFF_INTERVAL; + let MAX_CUTOFF_INTERVAL; + let MASK_TIMESTAMP_SLACK; + let MASK_BLOCK_SLACK; + let maskTimestamp; + let maskBlockNo; + let gameId; + const deployer = accounts[0]; + const creator = accounts[1]; + const opponent = accounts[2]; + const gas = 4000000; + const CHOICE = { + NONE: 0, + ROCK: 1, + PAPER: 2, + SCISSORS: 3, + }; + const mask = web3.utils.fromAscii("1c04ddc043e"); + const _stake = web3.utils.toWei("0.01", "ether"); + + describe("edge case tests", () => { + async function maskChoice(_choice) { + const block = await web3.eth.getBlock("latest"); + maskTimestamp = block.timestamp; + maskBlockNo = block.number; + return await rockPaperScissors.contract.methods + .maskChoice(_choice, mask, creator, maskTimestamp, true, maskBlockNo) + .call({ from: creator }); + } + + async function createGame(_maskedChoice, cutOffInterval) { + await rockPaperScissors.contract.methods + .create(opponent, _maskedChoice, _stake, cutOffInterval) + .send({ from: creator, value: _stake, gas: gas }); + } + + async function setGameVariables(gameId) { + const game = await rockPaperScissors.games.call(gameId); + assert.isDefined(game, "game has not been written to storage"); + + playDeadline = Number(game.playDeadline); + revealDeadline = Number(game.revealDeadline); + } + + async function playGame(_choice) { + await rockPaperScissors.contract.methods.play(gameId, _choice).send({ from: opponent, value: _stake, gas }); + } + + async function reveal(_choice) { + const txReceipt = await rockPaperScissors.contract.methods + .reveal(gameId, _choice, mask, maskTimestamp, maskBlockNo) + .send({ from: creator, gas }); + assert.isDefined(txReceipt, "reveal Tx has not been mined"); + } + + async function calculateGasSpent(txReceipt) { + const tx = await web3.eth.getTransaction(txReceipt.transactionHash); + const bn_gasPrice = new BN(tx.gasPrice); + const bn_gasAmount = new BN(txReceipt.gasUsed); + return Number(bn_gasPrice.mul(bn_gasAmount)); + } + + beforeEach("deploy a fresh contract, create game, advance block & timestamp", async () => { + rockPaperScissors = await RockPaperScissors.new({ from: deployer }); + MIN_STAKE = (await rockPaperScissors.MIN_STAKE.call()).toNumber(); + MIN_CUTOFF_INTERVAL = (await rockPaperScissors.MIN_CUTOFF_INTERVAL.call()).toNumber(); + MAX_CUTOFF_INTERVAL = (await rockPaperScissors.MAX_CUTOFF_INTERVAL.call()).toNumber(); + MASK_TIMESTAMP_SLACK = (await rockPaperScissors.MASK_TIMESTAMP_SLACK.call()).toNumber(); + MASK_BLOCK_SLACK = (await rockPaperScissors.MASK_BLOCK_SLACK.call()).toNumber(); + }); + + it("should play maximum gamelifetime game to completion", async () => { + //Arrange + const winningsBefore = Number(await rockPaperScissors.winnings.call(creator)); + + //Act + gameId = await maskChoice(CHOICE.SCISSORS); + await createGame(gameId, MAX_CUTOFF_INTERVAL); + await setGameVariables(gameId); + await playGame(CHOICE.PAPER); + await timeHelper.advanceTimeAndBlock(MAX_CUTOFF_INTERVAL); + await reveal(CHOICE.SCISSORS); + + //Assert + const winningsAfter = Number(await rockPaperScissors.winnings.call(creator)); + assert.strictEqual(Number(winningsAfter), _stake * 2, "creator winnings is incorrect"); + }); + + it("should play maximum timestamp Slack game to completion", async () => { + //Arrange + const block = await web3.eth.getBlock("latest"); + maskTimestamp = block.timestamp + MASK_TIMESTAMP_SLACK; + gameId = await rockPaperScissors.contract.methods + .maskChoice(CHOICE.SCISSORS, mask, creator, maskTimestamp, true, block.number) + .call({ from: creator }); + + //Act + const winningsBefore = Number(await rockPaperScissors.winnings.call(creator)); + await createGame(gameId, MAX_CUTOFF_INTERVAL); + await setGameVariables(gameId); + await playGame(CHOICE.PAPER); + await timeHelper.advanceTimeAndBlock(MAX_CUTOFF_INTERVAL); + await reveal(CHOICE.SCISSORS); + + //Assert + const winningsAfter = Number(await rockPaperScissors.winnings.call(creator)); + assert.strictEqual(winningsAfter - winningsBefore, _stake * 2, "creator winnings is incorrect"); + }); + + it("should play minimum timestamp Slack game to completion", async () => { + //Arrange + const block = await web3.eth.getBlock("latest"); + maskTimestamp = block.timestamp - MASK_TIMESTAMP_SLACK; + gameId = await rockPaperScissors.contract.methods + .maskChoice(CHOICE.SCISSORS, mask, creator, maskTimestamp, true, block.number) + .call({ from: creator }); + + //Act + const winningsBefore = Number(await rockPaperScissors.winnings.call(creator)); + await createGame(gameId, MAX_CUTOFF_INTERVAL); + await setGameVariables(gameId); + await playGame(CHOICE.PAPER); + await timeHelper.advanceTimeAndBlock(MAX_CUTOFF_INTERVAL); + await reveal(CHOICE.SCISSORS); + + //Assert + const winningsAfter = Number(await rockPaperScissors.winnings.call(creator)); + assert.strictEqual(winningsAfter - winningsBefore, _stake * 2, "creator winnings is incorrect"); + }); + + it("should play maximum block number Slack game to completion", async () => { + //Arrange + const block = await web3.eth.getBlock("latest"); + const cutoff = (MAX_CUTOFF_INTERVAL + MIN_CUTOFF_INTERVAL) / 2; + maskTimestamp = block.timestamp; + gameId = await rockPaperScissors.contract.methods + .maskChoice(CHOICE.SCISSORS, mask, creator, maskTimestamp, true, block.number + MASK_BLOCK_SLACK) + .call({ from: creator }); + + //Act + const winningsBefore = Number(await rockPaperScissors.winnings.call(creator)); + await createGame(gameId, cutoff); + await setGameVariables(gameId); + await playGame(CHOICE.PAPER); + await timeHelper.advanceTimeAndBlock(cutoff); + await reveal(CHOICE.SCISSORS); + + //Assert + const winningsAfter = Number(await rockPaperScissors.winnings.call(creator)); + assert.strictEqual(winningsAfter - winningsBefore, _stake * 2, "creator winnings is incorrect"); + }); + + it("should play minumum block number Slack game to completion", async () => { + //Arrange + const block = await web3.eth.getBlock("latest"); + const cutoff = (MAX_CUTOFF_INTERVAL + MIN_CUTOFF_INTERVAL) / 2; + maskTimestamp = block.timestamp; + gameId = await rockPaperScissors.contract.methods + .maskChoice(CHOICE.SCISSORS, mask, creator, maskTimestamp, true, block.number - MASK_BLOCK_SLACK) + .call({ from: creator }); + + //Act + const winningsBefore = Number(await rockPaperScissors.winnings.call(creator)); + await createGame(gameId, cutoff); + await setGameVariables(gameId); + await playGame(CHOICE.PAPER); + await timeHelper.advanceTimeAndBlock(cutoff); + await reveal(CHOICE.SCISSORS); + + //Assert + const winningsAfter = Number(await rockPaperScissors.winnings.call(creator)); + assert.strictEqual(winningsAfter - winningsBefore, _stake * 2, "creator winnings is incorrect"); + }); + + it("should play maximum timestamp Slack and block number game to completion", async () => { + //Arrange + const block = await web3.eth.getBlock("latest"); + maskTimestamp = block.timestamp + MASK_TIMESTAMP_SLACK; + gameId = await rockPaperScissors.contract.methods + .maskChoice(CHOICE.SCISSORS, mask, creator, maskTimestamp, true, block.number + MASK_BLOCK_SLACK) + .call({ from: creator }); + + //Act + const winningsBefore = Number(await rockPaperScissors.winnings.call(creator)); + await createGame(gameId, MAX_CUTOFF_INTERVAL); + await setGameVariables(gameId); + await playGame(CHOICE.PAPER); + await timeHelper.advanceTimeAndBlock(MAX_CUTOFF_INTERVAL); + await reveal(CHOICE.SCISSORS); + + //Assert + const winningsAfter = Number(await rockPaperScissors.winnings.call(creator)); + assert.strictEqual(winningsAfter - winningsBefore, _stake * 2, "creator winnings is incorrect"); + }); + + it("should play minimum timestamp Slack and block number game to completion", async () => { + //Arrange + const block = await web3.eth.getBlock("latest"); + maskTimestamp = block.timestamp - MASK_TIMESTAMP_SLACK; + gameId = await rockPaperScissors.contract.methods + .maskChoice(CHOICE.SCISSORS, mask, creator, maskTimestamp, true, block.number - MASK_BLOCK_SLACK) + .call({ from: creator }); + + //Act + const winningsBefore = Number(await rockPaperScissors.winnings.call(creator)); + await createGame(gameId, MAX_CUTOFF_INTERVAL); + await setGameVariables(gameId); + await playGame(CHOICE.PAPER); + await timeHelper.advanceTimeAndBlock(MAX_CUTOFF_INTERVAL); + await reveal(CHOICE.SCISSORS); + + //Assert + const winningsAfter = Number(await rockPaperScissors.winnings.call(creator)); + assert.strictEqual(winningsAfter - winningsBefore, _stake * 2, "creator winnings is incorrect"); + }); + + after(async () => { + await timeHelper.revertToSnapShot(snapShotId); + }); + }); +}); diff --git a/test/maskChoice.js b/test/maskChoice.js index 8aa6c81..35ec27f 100644 --- a/test/maskChoice.js +++ b/test/maskChoice.js @@ -6,7 +6,7 @@ const { assert } = chai; contract("RockPaperScissors", (accounts) => { let snapShotId; - before("sdfs", async () => { + before("", async () => { it("TestRPC must have adequate number of addresses", async () => { assert.isAtLeast(accounts.length, 2, "Test has enough addresses"); }); @@ -31,6 +31,8 @@ contract("RockPaperScissors", (accounts) => { describe("maskChoice tests", () => { before("deploy a fresh contract", async () => { rockPaperScissors = await RockPaperScissors.new({ from: deployer }); + MASK_TIMESTAMP_SLACK = (await rockPaperScissors.MASK_TIMESTAMP_SLACK.call()).toNumber(); + MASK_BLOCK_SLACK = (await rockPaperScissors.MASK_BLOCK_SLACK.call()).toNumber(); }); it("should generate a valid maskedChoice - Happy path", async () => { @@ -62,97 +64,84 @@ contract("RockPaperScissors", (accounts) => { assert.strictEqual(web3SoliditySha3Value, soliditykeccak256Value, "web3 and keccak256 generated value don't match"); }); - async function revertSituations() { + it("reverts when maskTimestamp is above maximum allowed", async () => { const block = await web3.eth.getBlock("latest"); - MASK_TIMESTAMP_SLACK = (await rockPaperScissors.MASK_TIMESTAMP_SLACK.call()).toNumber(); - MASK_BLOCK_SLACK = (await rockPaperScissors.MASK_BLOCK_SLACK.call()).toNumber(); - const no = block.number; - const stamp = block.timestamp; - return [ - { - choice: CHOICE.PAPER, - mask: mask, - masker: maskerAddress, - timestamp: stamp + MASK_TIMESTAMP_SLACK + 1, - maskingOnly: true, - blockNo: no, - error: "RockPaperScissors::maskChoice:maskTimestamp above maximum, use latest block timestamp", - }, - { - choice: CHOICE.PAPER, - mask: mask, - masker: maskerAddress, - timestamp: stamp - MASK_TIMESTAMP_SLACK - 1, - maskingOnly: true, - blockNo: no, - error: "RockPaperScissors::maskChoice:maskTimestamp below minimum, use latest block timestamp", - }, - { - choice: CHOICE.PAPER, - mask: mask, - masker: maskerAddress, - timestamp: stamp, - maskingOnly: true, - blockNo: no + MASK_BLOCK_SLACK + 1, - error: "RockPaperScissors::maskChoice:blockNo is invalid", - }, - { - choice: CHOICE.PAPER, - mask: mask, - masker: maskerAddress, - timestamp: stamp, - maskingOnly: true, - blockNo: no - MASK_BLOCK_SLACK - 1, - error: "RockPaperScissors::maskChoice:blockNo is invalid", - }, - { - choice: CHOICE.PAPER, - mask: mask, - masker: maskerAddress, - timestamp: stamp, - maskingOnly: false, - blockNo: no, - error: "RockPaperScissors::maskChoice:Invalid maskTimestamp for reveal", - }, - { - choice: CHOICE.NONE, - mask: mask, - masker: maskerAddress, - timestamp: stamp, - maskingOnly: true, - blockNo: no, - error: "RockPaperScissors::maskChoice:game move choice can not be NONE", - }, - { - choice: CHOICE.PAPER, - mask: NULL_BYTES, - masker: maskerAddress, - timestamp: stamp, - maskingOnly: true, - blockNo: no, - error: "RockPaperScissors::maskChoice:mask can not be empty", - }, - { - choice: CHOICE.PAPER, - mask: mask, - masker: NULL_ADDRESS, - timestamp: stamp, - maskingOnly: true, - blockNo: no, - error: "RockPaperScissors::maskChoice:masker can not be null address", - }, - ]; - } - - it("reverts when given invalid parameters", async () => { - const data = await revertSituations(); - data.forEach(async (d) => { - await truffleAssert.reverts( - rockPaperScissors.contract.methods - .maskChoice(d.choice, d.mask, d.masker, d.timestamp, d.maskingOnly, d.blockNo) - .call({ from: maskerAddress }) - ); - }); + await truffleAssert.reverts( + rockPaperScissors.contract.methods + .maskChoice(CHOICE.ROCK, mask, maskerAddress, block.timestamp + MASK_TIMESTAMP_SLACK + 1, true, block.number) + .call({ from: maskerAddress }), + "RockPaperScissors::maskChoice:maskTimestamp above maximum, use latest block timestamp" + ); + }); + + it("reverts when maskTimestamp is below minimum allowed", async () => { + const block = await web3.eth.getBlock("latest"); + await truffleAssert.reverts( + rockPaperScissors.contract.methods + .maskChoice(CHOICE.PAPER, mask, maskerAddress, block.timestamp - MASK_TIMESTAMP_SLACK - 1, true, block.number) + .call({ from: maskerAddress }), + "RockPaperScissors::maskChoice:maskTimestamp below minimum, use latest block timestamp" + ); + }); + + it("reverts when blockNo is above allowed slack", async () => { + const block = await web3.eth.getBlock("latest"); + await truffleAssert.reverts( + rockPaperScissors.contract.methods + .maskChoice(CHOICE.PAPER, mask, maskerAddress, block.timestamp, true, block.number + MASK_BLOCK_SLACK + 1) + .call({ from: maskerAddress }), + "RockPaperScissors::maskChoice:blockNo is invalid" + ); + }); + + it("reverts when blockNo is below allowed slack", async () => { + const block = await web3.eth.getBlock("latest"); + await truffleAssert.reverts( + rockPaperScissors.contract.methods + .maskChoice(CHOICE.PAPER, mask, maskerAddress, block.timestamp, true, block.number - MASK_BLOCK_SLACK - 1) + .call({ from: maskerAddress }), + "RockPaperScissors::maskChoice:blockNo is invalid" + ); + }); + + it("reverts when maskTimestamp for reveal is invalid", async () => { + const block = await web3.eth.getBlock("latest"); + await truffleAssert.reverts( + rockPaperScissors.contract.methods + .maskChoice(CHOICE.PAPER, mask, maskerAddress, block.timestamp, false, block.number) + .call({ from: maskerAddress }), + "RockPaperScissors::maskChoice:Invalid maskTimestamp for reveal" + ); + }); + + it("reverts when choice is NONE", async () => { + const block = await web3.eth.getBlock("latest"); + await truffleAssert.reverts( + rockPaperScissors.contract.methods + .maskChoice(CHOICE.NONE, mask, maskerAddress, block.timestamp, true, block.number) + .call({ from: maskerAddress }), + "RockPaperScissors::maskChoice:game move choice can not be NONE" + ); + }); + + it("reverts when mask can is empty", async () => { + const block = await web3.eth.getBlock("latest"); + await truffleAssert.reverts( + rockPaperScissors.contract.methods + .maskChoice(CHOICE.SCISSORS, NULL_BYTES, maskerAddress, block.timestamp, true, block.number) + .call({ from: maskerAddress }), + "RockPaperScissors::maskChoice:mask can not be empty" + ); + }); + + it("reverts when opponent address is empty", async () => { + const block = await web3.eth.getBlock("latest"); + await truffleAssert.reverts( + rockPaperScissors.contract.methods + .maskChoice(CHOICE.SCISSORS, mask, NULL_ADDRESS, block.timestamp, true, block.number) + .call({ from: maskerAddress }), + "RockPaperScissors::maskChoice:masker can not be null address" + ); }); after(async () => { diff --git a/test/play.js b/test/play.js index e9bd942..01c5487 100644 --- a/test/play.js +++ b/test/play.js @@ -45,13 +45,13 @@ contract("RockPaperScissors", (accounts) => { maskedChoice = await rockPaperScissors.contract.methods .maskChoice(CHOICE.ROCK, mask, creator, block.timestamp, true, block.number) .call({ from: creator }); + gameId = maskedChoice; /*create game*/ const txReceipt = await rockPaperScissors.contract.methods .create(opponent, maskedChoice, MIN_STAKE, MIN_CUTOFF_INTERVAL) .send({ from: creator, value: MIN_STAKE, gas: gas }); - gameId = (await rockPaperScissors.latestGameId.call()).toNumber(); const game = await rockPaperScissors.games.call(gameId); assert.isDefined(game, "beforeEach - game has not been written to storage"); @@ -59,38 +59,26 @@ contract("RockPaperScissors", (accounts) => { await timeHelper.advanceTimeAndBlock(timestampSkipSeconds); }); - function revertSituations() { - return [ - { - gameId: gameId, - choice: CHOICE.NONE, - msgsender: opponent, - msgvalue: MIN_STAKE, - error: "", - }, - { - gameId: gameId, - choice: CHOICE.SCISSORS, - msgsender: somebody, - msgvalue: MIN_STAKE, - error: "", - }, - { - gameId: gameId, - choice: CHOICE.SCISSORS, - msgsender: opponent, - msgvalue: MIN_STAKE > 0 ? MIN_STAKE - 1 : 0, - error: "RockPaperScissors::play:Insuffcient balance to stake", - }, - ]; - } - - it("reverts when given invalid parameters", async () => { - revertSituations().forEach(async (d) => { + it("should revert given CHOICE.NONE as choice", async () => { + await truffleAssert.reverts( + rockPaperScissors.contract.methods.play(gameId, CHOICE.NONE).send({ from: opponent, value: MIN_STAKE, gas: gas }) + ); + }); + + it("should revert given incorrect opponent address", async () => { + await truffleAssert.reverts( + rockPaperScissors.contract.methods.play(gameId, CHOICE.SCISSORS).send({ from: somebody, value: MIN_STAKE, gas: gas }) + ); + }); + + it("should revert sent with insuffcient balance to stake", async () => { + if (MIN_STAKE > 0) { await truffleAssert.reverts( - rockPaperScissors.contract.methods.play(gameId, d.choice).send({ from: d.msgsender, value: d.msgvalue, gas: gas }) + rockPaperScissors.contract.methods + .play(gameId, CHOICE.SCISSORS) + .send({ from: somebody, value: MIN_STAKE - 1, gas: gas }) ); - }); + } }); it("should play and set choice to storage", async () => { diff --git a/test/resolve.js b/test/resolve.js index d8100cd..7d5db0e 100644 --- a/test/resolve.js +++ b/test/resolve.js @@ -3,7 +3,7 @@ const timeHelper = require("../util/timeHelper"); const chai = require("chai"); const { assert } = chai; -contract("RockPaperScissors::resolve", (accounts) => { +contract("RockPaperScissors", (accounts) => { let snapShotId; before(async () => { it("TestRPC must have adequate number of addresses", () => { @@ -39,8 +39,8 @@ contract("RockPaperScissors::resolve", (accounts) => { { c1: CHOICE.NONE, c2: CHOICE.ROCK, r: OUTCOME.LOSE }, { c1: CHOICE.NONE, c2: CHOICE.PAPER, r: OUTCOME.LOSE }, { c1: CHOICE.NONE, c2: CHOICE.SCISSORS, r: OUTCOME.LOSE }, - //should never happen - { c1: CHOICE.NONE, c2: CHOICE.NONE, r: OUTCOME.WIN }, + //player1 did not reveal, player2 did not play + { c1: CHOICE.NONE, c2: CHOICE.NONE, r: OUTCOME.NONE }, ]; describe("resolve tests", () => { @@ -48,10 +48,9 @@ contract("RockPaperScissors::resolve", (accounts) => { rockPaperScissors = await RockPaperScissors.new({ from: deployer }); }); - it("should resolve game outcome", async () => { - testData.forEach(async (d) => { - const result = await rockPaperScissors.contract.methods.resolve(d.c1, d.c2).call({ from: deployer }); - assert.equal(d.r, Number(result)); + testData.forEach(async (d) => { + it(`should resolve to [${d.r}] given [${d.c1}, ${d.c2}]`, async () => { + assert.strictEqual(d.r, Number(await rockPaperScissors.contract.methods.resolve(d.c1, d.c2).call({ from: deployer }))); }); }); diff --git a/test/reveal.js b/test/reveal.js index 39297e0..3cdf933 100644 --- a/test/reveal.js +++ b/test/reveal.js @@ -18,6 +18,7 @@ contract("RockPaperScissors", (accounts) => { let MIN_STAKE; let MIN_CUTOFF_INTERVAL; let maskTimestamp; + let maskBlockNo; let maskedChoice; let playDeadline; let revealDeadline; @@ -49,16 +50,17 @@ contract("RockPaperScissors", (accounts) => { /*create masked choice*/ const block = await web3.eth.getBlock("latest"); maskTimestamp = block.timestamp; + maskBlockNo = block.number; maskedChoice = await rockPaperScissors.contract.methods - .maskChoice(creatorChoice, mask, creator, maskTimestamp, true, block.number) + .maskChoice(creatorChoice, mask, creator, maskTimestamp, true, maskBlockNo) .call({ from: creator }); + gameId = maskedChoice; /*create game*/ const txReceipt = await rockPaperScissors.contract.methods .create(opponent, maskedChoice, MIN_STAKE, MIN_CUTOFF_INTERVAL) .send({ from: creator, value: MIN_STAKE, gas: gas }); - gameId = (await rockPaperScissors.latestGameId.call()).toNumber(); const game = await rockPaperScissors.games.call(gameId); assert.isDefined(game, "beforeEach - game has not been written to storage"); @@ -85,7 +87,9 @@ contract("RockPaperScissors", (accounts) => { //Assert await truffleAssert.reverts( - rockPaperScissors.contract.methods.reveal(0, creatorChoice, NULL_BYTES, maskTimestamp).send({ from: creator, gas }) + rockPaperScissors.contract.methods + .reveal(NULL_BYTES, creatorChoice, NULL_BYTES, maskTimestamp, maskBlockNo) + .send({ from: creator, gas }) ); }); @@ -96,7 +100,7 @@ contract("RockPaperScissors", (accounts) => { //Assert await truffleAssert.reverts( rockPaperScissors.contract.methods - .reveal(gameId + 2, creatorChoice, NULL_BYTES, maskTimestamp) + .reveal(web3.utils.fromAscii("random"), creatorChoice, NULL_BYTES, maskTimestamp, maskBlockNo) .send({ from: creator, gas }) ); }); @@ -107,7 +111,9 @@ contract("RockPaperScissors", (accounts) => { //Assert await truffleAssert.reverts( - rockPaperScissors.contract.methods.reveal(gameId, creatorChoice, NULL_BYTES, maskTimestamp).send({ from: creator, gas }) + rockPaperScissors.contract.methods + .reveal(gameId, creatorChoice, NULL_BYTES, maskTimestamp, maskBlockNo) + .send({ from: creator, gas }) ); }); @@ -118,7 +124,9 @@ contract("RockPaperScissors", (accounts) => { //Assert await truffleAssert.reverts( - rockPaperScissors.contract.methods.reveal(gameId, creatorChoice, mask, maskTimestamp).send({ from: creator, gas }) + rockPaperScissors.contract.methods + .reveal(gameId, creatorChoice, mask, maskTimestamp, maskBlockNo) + .send({ from: creator, gas }) ); }); @@ -132,7 +140,9 @@ contract("RockPaperScissors", (accounts) => { //Assert await truffleAssert.reverts( - rockPaperScissors.contract.methods.reveal(gameId, creatorChoice, mask, maskTimestamp).send({ from: creator, gas }) + rockPaperScissors.contract.methods + .reveal(gameId, creatorChoice, mask, maskTimestamp, maskBlockNo) + .send({ from: creator, gas }) ); }); @@ -143,7 +153,7 @@ contract("RockPaperScissors", (accounts) => { //Assert await truffleAssert.reverts( rockPaperScissors.contract.methods - .reveal(gameId, (creatorChoice + 1) % 3, mask, maskTimestamp) + .reveal(gameId, (creatorChoice + 1) % 3, mask, maskTimestamp, maskBlockNo) .send({ from: creator, gas }) ); }); @@ -154,7 +164,9 @@ contract("RockPaperScissors", (accounts) => { //Assert await truffleAssert.reverts( - rockPaperScissors.contract.methods.reveal(gameId, creatorChoice, NULL_BYTES, maskTimestamp).send({ from: creator, gas }) + rockPaperScissors.contract.methods + .reveal(gameId, creatorChoice, NULL_BYTES, maskTimestamp, maskBlockNo) + .send({ from: creator, gas }) ); }); @@ -164,7 +176,7 @@ contract("RockPaperScissors", (accounts) => { //Assert await truffleAssert.reverts( - rockPaperScissors.contract.methods.reveal(gameId, creatorChoice, mask, 0).send({ from: creator, gas }) + rockPaperScissors.contract.methods.reveal(gameId, creatorChoice, mask, 0, maskBlockNo).send({ from: creator, gas }) ); }); @@ -174,7 +186,7 @@ contract("RockPaperScissors", (accounts) => { //Act const txReceipt = await rockPaperScissors.contract.methods - .reveal(gameId, creatorChoice, mask, maskTimestamp) + .reveal(gameId, creatorChoice, mask, maskTimestamp, maskBlockNo) .send({ from: creator, gas }); //Assert @@ -191,7 +203,7 @@ contract("RockPaperScissors", (accounts) => { //Act const txReceipt = await rockPaperScissors.contract.methods - .reveal(gameId, creatorChoice, mask, maskTimestamp) + .reveal(gameId, creatorChoice, mask, maskTimestamp, maskBlockNo) .send({ from: creator, gas }); //Assert @@ -199,6 +211,7 @@ contract("RockPaperScissors", (accounts) => { eventAssert.parameterIsValid(txReceipt, "LogGameFinished", "gameId", gameId, "LogGameFinished gameId incorrect"); eventAssert.parameterIsValid(txReceipt, "LogGameFinished", "outcome", OUTCOME.WIN, "LogGameFinished player incorrect"); eventAssert.parameterIsValid(txReceipt, "LogGameFinished", "stake", MIN_STAKE * 2, "LogGameFinished stake incorrect"); + eventAssert.parameterIsValid(txReceipt, "LogGameFinished", "settler", creator, "LogGameFinished settler incorrect"); }); after(async () => { diff --git a/test/settle.js b/test/settle.js index d987b38..0a598de 100644 --- a/test/settle.js +++ b/test/settle.js @@ -17,8 +17,6 @@ contract("RockPaperScissors", (accounts) => { let rockPaperScissors; let MIN_STAKE; let MIN_CUTOFF_INTERVAL; - let maskTimestamp; - let maskedChoice; let gameId; const timestampSkipSeconds = 15; const deployer = accounts[0]; @@ -40,21 +38,19 @@ contract("RockPaperScissors", (accounts) => { async function maskChoice() { /*create masked choice*/ const block = await web3.eth.getBlock("latest"); - maskTimestamp = block.timestamp; - maskedChoice = await rockPaperScissors.contract.methods - .maskChoice(creatorChoice, mask, creator, maskTimestamp, true, block.number) + return await rockPaperScissors.contract.methods + .maskChoice(creatorChoice, mask, creator, block.timestamp, true, block.number) .call({ from: creator }); } - async function createGame() { + async function createGame(_maskedChoice) { /*create game*/ const txReceipt = await rockPaperScissors.contract.methods - .create(opponent, maskedChoice, MIN_STAKE, MIN_CUTOFF_INTERVAL) + .create(opponent, _maskedChoice, MIN_STAKE, MIN_CUTOFF_INTERVAL) .send({ from: creator, value: MIN_STAKE, gas: gas }); } async function setGameVariables() { - gameId = (await rockPaperScissors.latestGameId.call()).toNumber(); const game = await rockPaperScissors.games.call(gameId); assert.isDefined(game, "game has not been written to storage"); @@ -76,8 +72,8 @@ contract("RockPaperScissors", (accounts) => { rockPaperScissors = await RockPaperScissors.new({ from: deployer }); MIN_STAKE = (await rockPaperScissors.MIN_STAKE.call()).toNumber(); MIN_CUTOFF_INTERVAL = (await rockPaperScissors.MIN_CUTOFF_INTERVAL.call()).toNumber(); - await maskChoice(); - await createGame(); + gameId = await maskChoice(); + await createGame(gameId); await setGameVariables(); await timeHelper.advanceTimeAndBlock(timestampSkipSeconds); }); @@ -88,8 +84,7 @@ contract("RockPaperScissors", (accounts) => { await timeHelper.advanceTimeAndBlock(1); //Act const block = await web3.eth.getBlock("latest"); - const id = await rockPaperScissors.latestGameId.call(); - const game = await rockPaperScissors.games.call(id); + const game = await rockPaperScissors.games.call(gameId); assert.isTrue( block.timestamp < Number(game.revealDeadline), "Arrange Eror: block timestamp is not less than revealDeadline" @@ -102,14 +97,16 @@ contract("RockPaperScissors", (accounts) => { //Arrange await playGame(MIN_CUTOFF_INTERVAL); await timeHelper.advanceTimeAndBlock(MIN_CUTOFF_INTERVAL); + //Act - const _gameId = await rockPaperScissors.latestGameId.call(); - const txReceipt = await rockPaperScissors.contract.methods.settle(_gameId).send({ from: creator, gas }); + const txReceipt = await rockPaperScissors.contract.methods.settle(gameId).send({ from: creator, gas }); + //Assert eventAssert.eventIsEmitted(txReceipt, "LogGameFinished"); eventAssert.parameterIsValid(txReceipt, "LogGameFinished", "gameId", gameId, "LogGameFinished gameId incorrect"); eventAssert.parameterIsValid(txReceipt, "LogGameFinished", "outcome", OUTCOME.LOSE, "LogGameFinished outcome incorrect"); eventAssert.parameterIsValid(txReceipt, "LogGameFinished", "stake", MIN_STAKE * 2, "LogGameFinished stake incorrect"); + eventAssert.parameterIsValid(txReceipt, "LogGameFinished", "settler", creator, "LogGameFinished settler incorrect"); }); it("should settle creator as loser for an unrevealed game", async () => { @@ -122,14 +119,14 @@ contract("RockPaperScissors", (accounts) => { eventAssert.parameterIsValid(txReceipt, "LogGameFinished", "outcome", OUTCOME.LOSE, "LogGameFinished outcome incorrect"); }); - it("should settle creator as winner for an unplayed game", async () => { + it("should settle game as None for an unplayed game", async () => { //Arrange await timeHelper.advanceTimeAndBlock(MIN_CUTOFF_INTERVAL * 2); //Act const txReceipt = await rockPaperScissors.contract.methods.settle(gameId).send({ from: creator, gas }); //Assert assert.isDefined(txReceipt, "settle transaction is not mined"); - eventAssert.parameterIsValid(txReceipt, "LogGameFinished", "outcome", OUTCOME.WIN, "LogGameFinished outcome incorrect"); + eventAssert.parameterIsValid(txReceipt, "LogGameFinished", "outcome", OUTCOME.NONE, "LogGameFinished outcome incorrect"); }); after(async () => { diff --git a/test/withdraw.js b/test/withdraw.js new file mode 100644 index 0000000..8e715be --- /dev/null +++ b/test/withdraw.js @@ -0,0 +1,181 @@ +const RockPaperScissors = artifacts.require("RockPaperScissors"); +const truffleAssert = require("truffle-assertions"); +const timeHelper = require("../util/timeHelper"); +const eventAssert = require("../util/eventAssertionHelper"); +const chai = require("chai"); + +const { BN } = web3.utils.BN; +const { assert } = chai; +chai.use(require("chai-bn")(BN)); + +contract("RockPaperScissors", (accounts) => { + let snapShotId; + before(async () => { + it("TestRPC must have adequate number of addresses", () => { + assert.isAtLeast(accounts.length, 4, "Test has enough addresses"); + }); + snapShotId = (await timeHelper.takeSnapshot()).id; + }); + + let rockPaperScissors; + let MIN_CUTOFF_INTERVAL; + let maskTimestamp; + let maskBlockNo; + let gameId; + const deployer = accounts[0]; + const creator = accounts[1]; + const opponent = accounts[2]; + const somebody = accounts[3]; + const gas = 4000000; + const CHOICE = { + NONE: 0, + ROCK: 1, + PAPER: 2, + SCISSORS: 3, + }; + const mask = web3.utils.fromAscii("1c04ddc043e"); + const _stake = web3.utils.toWei("0.01", "ether"); + + describe("withdraw tests", () => { + async function maskChoice(_choice) { + const block = await web3.eth.getBlock("latest"); + maskTimestamp = block.timestamp; + maskBlockNo = block.number; + return await rockPaperScissors.contract.methods + .maskChoice(_choice, mask, creator, maskTimestamp, true, maskBlockNo) + .call({ from: creator }); + } + + async function createGame(_maskedChoice) { + gameId = _maskedChoice; + await rockPaperScissors.contract.methods + .create(opponent, _maskedChoice, _stake, MIN_CUTOFF_INTERVAL) + .send({ from: creator, value: _stake, gas: gas }); + } + + async function setGameVariables() { + const game = await rockPaperScissors.games.call(gameId); + assert.isDefined(game, "game has not been written to storage"); + + playDeadline = Number(game.playDeadline); + revealDeadline = Number(game.revealDeadline); + } + + async function playGame(_choice) { + await rockPaperScissors.contract.methods.play(gameId, _choice).send({ from: opponent, value: _stake, gas }); + } + + async function reveal(_choice) { + const txReceipt = await rockPaperScissors.contract.methods + .reveal(gameId, _choice, mask, maskTimestamp, maskBlockNo) + .send({ from: creator, gas }); + assert.isDefined(txReceipt, "reveal Tx has not been mined"); + } + + async function calculateGasSpent(txReceipt) { + const tx = await web3.eth.getTransaction(txReceipt.transactionHash); + const bn_gasPrice = new BN(tx.gasPrice); + const bn_gasAmount = new BN(txReceipt.gasUsed); + return Number(bn_gasPrice.mul(bn_gasAmount)); + } + + beforeEach("deploy a fresh contract, create game, advance block & timestamp", async () => { + rockPaperScissors = await RockPaperScissors.new({ from: deployer }); + MIN_STAKE = (await rockPaperScissors.MIN_STAKE.call()).toNumber(); + MIN_CUTOFF_INTERVAL = (await rockPaperScissors.MIN_CUTOFF_INTERVAL.call()).toNumber(); + }); + + it("reverts if winnings balance is zero", async () => { + //Arrange + const winningBalance = Number(await rockPaperScissors.winnings.call(somebody)); + assert.isTrue(winningBalance === 0, "balance is not zero"); + + //Act, Assert + await truffleAssert.reverts(rockPaperScissors.contract.methods.withdraw().send({ from: creator })); + }); + + it("should emit LogWithdrawal event", async () => { + //Arrange + await createGame(await maskChoice(CHOICE.SCISSORS)); + await setGameVariables(); + await playGame(CHOICE.PAPER); + await timeHelper.advanceTimeAndBlock(MIN_CUTOFF_INTERVAL); + await reveal(CHOICE.SCISSORS); + const winningsBefore = Number(await rockPaperScissors.winnings.call(creator)); + + //Act + const txReceipt = await rockPaperScissors.contract.methods.withdraw().send({ from: creator }); + + //Assert + eventAssert.eventIsEmitted(txReceipt, "LogWithdrawal"); + eventAssert.parameterIsValid(txReceipt, "LogWithdrawal", "withdrawer", creator, "LogWithdrawal withdrawer incorrect"); + eventAssert.parameterIsValid(txReceipt, "LogWithdrawal", "withdrawn", winningsBefore, "LogWithdrawal withdrawn incorrect"); + }); + + it("should withdraw staked when game creator Wins", async () => { + //Arrange + await createGame(await maskChoice(CHOICE.SCISSORS)); + await setGameVariables(); + await playGame(CHOICE.PAPER); + await timeHelper.advanceTimeAndBlock(MIN_CUTOFF_INTERVAL); + await reveal(CHOICE.SCISSORS); + const balanceBefore = new BN(await web3.eth.getBalance(creator)); + const expected = _stake * 2; + + //Act + const txReceipt = await rockPaperScissors.contract.methods.withdraw().send({ from: creator }); + const balanceAfter = new BN(await web3.eth.getBalance(creator)); + const gasCost = new BN(await calculateGasSpent(txReceipt)); + + //Assert + assert.equal(Number(balanceAfter.add(gasCost).sub(balanceBefore)), expected, "creator winnings withdrawal failed"); + }); + + it("should allow both players to withdraw half stake when game Draws", async () => { + //Arrange + await createGame(await maskChoice(CHOICE.PAPER)); + await setGameVariables(); + await playGame(CHOICE.PAPER); + await timeHelper.advanceTimeAndBlock(MIN_CUTOFF_INTERVAL); + await reveal(CHOICE.PAPER); + const balanceBefore1 = new BN(await web3.eth.getBalance(creator)); + const balanceBefore2 = new BN(await web3.eth.getBalance(opponent)); + const expected = _stake; + + //Act + const txReceipt1 = await rockPaperScissors.contract.methods.withdraw().send({ from: creator }); + const txReceipt2 = await rockPaperScissors.contract.methods.withdraw().send({ from: opponent }); + const balanceAfter1 = new BN(await web3.eth.getBalance(creator)); + const balanceAfter2 = new BN(await web3.eth.getBalance(opponent)); + const gasCost1 = new BN(await calculateGasSpent(txReceipt1)); + const gasCost2 = new BN(await calculateGasSpent(txReceipt2)); + + //Assert + assert.equal(Number(balanceAfter2.add(gasCost2).sub(balanceBefore2)), expected, "opponent withdrawal failed"); + assert.equal(Number(balanceAfter1.add(gasCost1).sub(balanceBefore1)), expected, "creator withdrawal failed"); + }); + + it("should withdraw staked when game opponent Wins", async () => { + //Arrange + await createGame(await maskChoice(CHOICE.PAPER)); + await setGameVariables(); + await playGame(CHOICE.SCISSORS); + await timeHelper.advanceTimeAndBlock(MIN_CUTOFF_INTERVAL); + await reveal(CHOICE.PAPER); + const balanceBefore = new BN(await web3.eth.getBalance(opponent)); + const expected = _stake * 2; + + //Act + const txReceipt = await rockPaperScissors.contract.methods.withdraw().send({ from: opponent }); + const balanceAfter = new BN(await web3.eth.getBalance(opponent)); + const gasCost = new BN(await calculateGasSpent(txReceipt)); + + //Assert + assert.strictEqual(Number(balanceAfter.add(gasCost).sub(balanceBefore)), expected, "opponent winnings withdrawal failed"); + }); + + after(async () => { + await timeHelper.revertToSnapShot(snapShotId); + }); + }); +});