diff --git a/CHANGELOG.md b/CHANGELOG.md index 25d59bf31..aab103a00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +## 1.0.2 + - Features Added + - Support Multi-call Generic Scheme + - Use subgraph v40_0 and arc.js 0.2.76 + - Added xGEN / GEN bridge + + - Bugs Fixed + - Fix competiotion tab crash + - Better name for some Competition plugins + - Fix wrong link when sharing a proposal + - Ignore Network and GraphQL errors up to 10 attempts in a row + ## 1.0.1 - Bugs Fixed - Alchemy should not crash immediately when there is a subgraph error. It will retry for approximately 2.5 minutes. diff --git a/README.md b/README.md index bfe5e21db..9e51fc8ed 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ https://kovan.v1.alchemy.do - alchemy v1 on kovan **Alchemy 2.0** :fire: -Alchemy v2 source code can be found under [master-2](https://github.com/daostack/alchemy/tree/master-2) branch. +Alchemy 2.0 source code can be found [here](https://github.com/daostack/alchemy_2). https://alchemy.do - alchemy 2.0 on mainnet diff --git a/docker-compose.yml b/docker-compose.yml index df8e4fbb6..32935a064 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -47,18 +47,18 @@ services: GRAPH_MAX_IPFS_FILE_BYTES: 900000 ipfs: - image: daostack/test-env-ipfs:3.0.38 + image: daostack/test-env-ipfs:3.0.41 ports: - 5001:5001 postgres: - image: daostack/test-env-postgres:3.0.38 + image: daostack/test-env-postgres:3.0.41 ports: - 9432:5432 environment: POSTGRES_PASSWORD: 'letmein' ganache: - image: daostack/test-env-ganache:3.0.38 + image: daostack/test-env-ganache:3.0.41 ports: - 8545:8545 diff --git a/package-lock.json b/package-lock.json index c747b6494..98631de19 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "alchemy-client", - "version": "1.0.1", + "version": "1.0.2", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1800,12 +1800,12 @@ } }, "@daostack/arc": { - "version": "0.0.1-rc.41", - "resolved": "https://registry.npmjs.org/@daostack/arc/-/arc-0.0.1-rc.41.tgz", - "integrity": "sha512-U/M+ZhEV/4f/+zwZOjYNv67bbP386qSvas8LUB31bV0nzxQKhjG+8JzKKmxpCqz0ixLuzMG7/pJcn3dG2MF5Kg==", + "version": "0.0.1-rc.47", + "resolved": "https://registry.npmjs.org/@daostack/arc/-/arc-0.0.1-rc.47.tgz", + "integrity": "sha512-ElzdZPmZUMgQS+RdzgQc+MTjl3w3C55i50YhYZC41hiqQX1d/w6LhSUHg59L8UCPnEdmOlhoDpoE/91M1Hc5pg==", "dev": true, "requires": { - "@daostack/infra": "0.0.1-rc.15", + "@daostack/infra": "0.0.1-rc.19", "math": "0.0.3", "openzeppelin-solidity": "2.4.0", "truffle-flattener": "^1.4.2" @@ -1829,9 +1829,9 @@ } }, "@daostack/arc.js": { - "version": "0.2.74", - "resolved": "https://registry.npmjs.org/@daostack/arc.js/-/arc.js-0.2.74.tgz", - "integrity": "sha512-M3DbrmqUSYh+BlEPiuhaI1A+HZRwwjL2O/zEVOukPaxjlvImzMuKFAiH4DaXmOjlOkNrl+LJevvHc8J+3n5dAg==", + "version": "0.2.76", + "resolved": "https://registry.npmjs.org/@daostack/arc.js/-/arc.js-0.2.76.tgz", + "integrity": "sha512-ChOGQGGe040wCZAkWjXhBmp1q8pR5ev09dUQNpdzdVc0o7GCYhPdLzJ/T1HkFarpiRvpm4NqNxA0O+2YXltgZg==", "requires": { "apollo-cache-inmemory": "^1.6.5", "apollo-client": "^2.6.8", @@ -1853,30 +1853,22 @@ } }, "@daostack/infra": { - "version": "0.0.1-rc.15", - "resolved": "https://registry.npmjs.org/@daostack/infra/-/infra-0.0.1-rc.15.tgz", - "integrity": "sha512-th/nb1okI7qDNxMCILDdX8I+31zIDvgfDJlPhe/dTPUAC3Zr55WCcrUa8oS7cOb6G1ki7OdGycDCbfnu2ndWgg==", + "version": "0.0.1-rc.19", + "resolved": "https://registry.npmjs.org/@daostack/infra/-/infra-0.0.1-rc.19.tgz", + "integrity": "sha512-Izvhc+WSzo6FAqPpuO7trrpd0Eeawf2TvqIAuCcCHoAbQ4IYYdtcF127IMhdFWJqXYadIYsDnqTPdYzX0xPC0w==", "dev": true, "requires": { "ethereumjs-abi": "^0.6.5", - "openzeppelin-solidity": "2.3.0" - }, - "dependencies": { - "openzeppelin-solidity": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/openzeppelin-solidity/-/openzeppelin-solidity-2.3.0.tgz", - "integrity": "sha512-QYeiPLvB1oSbDt6lDQvvpx7k8ODczvE474hb2kLXZBPKMsxKT1WxTCHBYrCU7kS7hfAku4DcJ0jqOyL+jvjwQw==", - "dev": true - } + "openzeppelin-solidity": "2.4.0" } }, "@daostack/migration": { - "version": "0.0.1-rc.41-v4", - "resolved": "https://registry.npmjs.org/@daostack/migration/-/migration-0.0.1-rc.41-v4.tgz", - "integrity": "sha512-96dq8A8oUSAIcFRTZzxvDEZj8S57UClnG5R088Lj9ORGgdp3xQH6+KRi+lwdTsySSOzvdGzgeG7o2FIAwu75Ig==", + "version": "0.0.1-rc.47-v0", + "resolved": "https://registry.npmjs.org/@daostack/migration/-/migration-0.0.1-rc.47-v0.tgz", + "integrity": "sha512-hiCM2Yc6NMPyMAVCGK4d+C+Bt7G9DUENMRsj9q0K2xOO79UUIs11KRye5CtMrzrN8efLgzPIsrrZIvI9H40dRw==", "dev": true, "requires": { - "@daostack/arc": "0.0.1-rc.41", + "@daostack/arc": "0.0.1-rc.47", "@daostack/arc-hive": "0.0.1-rc.4", "ethereumjs-wallet": "^0.6.3", "fstream": "^1.0.12", @@ -2009,9 +2001,9 @@ }, "dependencies": { "@types/node": { - "version": "10.17.32", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.32.tgz", - "integrity": "sha512-EUq+cjH/3KCzQHikGnNbWAGe548IFLSm93Vl8xA7EuYEEATiyOVDyEVuGkowL7c9V69FF/RiZSAOCFPApMs/ig==", + "version": "10.17.43", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.43.tgz", + "integrity": "sha512-F7xV2kxZGb3seVP3UQt3msHcoDCtDi8WNO/UCzNLhRwaYVT4yJO1ndcV+vCTnY+jiAVqyLZq/VJbRE/AhwqEag==", "dev": true } } @@ -3912,9 +3904,9 @@ "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==" }, "@solidity-parser/parser": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.6.2.tgz", - "integrity": "sha512-kUVUvrqttndeprLoXjI5arWHeiP3uh4XODAKbG+ZaWHCVQeelxCbnXBeWxZ2BPHdXgH0xR9dU1b916JhDhbgAA==", + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.8.1.tgz", + "integrity": "sha512-DF7H6T8I4lo2IZOE2NZwt3631T8j1gjpQLjmvY2xBNK50c4ltslR4XPKwT6RkeSd4+xCAK0GHC/k7sbRDBE4Yw==", "dev": true }, "@stablelib/utf8": { @@ -8186,8 +8178,7 @@ "array-find-index": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", - "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", - "dev": true + "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=" }, "array-flatten": { "version": "1.1.1", @@ -12484,7 +12475,6 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", - "dev": true, "requires": { "array-find-index": "^1.0.1" } @@ -12650,6 +12640,15 @@ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" }, + "decamelize-keys": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", + "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=", + "requires": { + "decamelize": "^1.1.0", + "map-obj": "^1.0.0" + } + }, "decode-uri-component": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", @@ -14928,6 +14927,229 @@ } } }, + "ethereum-input-data-decoder": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/ethereum-input-data-decoder/-/ethereum-input-data-decoder-0.3.1.tgz", + "integrity": "sha512-zl3QLiHeGG94Ru9uvBeX80ZLj62YXew9JVBUR2QiU2o+wJ9kkRdTAcLD3MJq+4guabj4U2H8s3pBlamrbRzpvA==", + "requires": { + "bn.js": "^4.11.8", + "buffer": "^5.2.1", + "ethereumjs-abi": "^0.6.7", + "ethers": "^4.0.27", + "is-buffer": "^2.0.3", + "meow": "^5.0.0" + }, + "dependencies": { + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=" + }, + "camelcase-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-4.2.0.tgz", + "integrity": "sha1-oqpfsa9oh1glnDLBQUJteJI7m3c=", + "requires": { + "camelcase": "^4.1.0", + "map-obj": "^2.0.0", + "quick-lru": "^1.0.0" + } + }, + "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" + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "requires": { + "locate-path": "^2.0.0" + } + }, + "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" + } + }, + "indent-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", + "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=" + }, + "js-sha3": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.5.7.tgz", + "integrity": "sha1-DU/9gALVMzqrr0oj7tL2N0yfKOc=" + }, + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "map-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-2.0.0.tgz", + "integrity": "sha1-plzSkIepJZi4eRJXpSPgISIqwfk=" + }, + "meow": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-5.0.0.tgz", + "integrity": "sha512-CbTqYU17ABaLefO8vCU153ZZlprKYWDljcndKKDCFcYQITzWCXZAVk4QMFZPgvzrnUQ3uItnIE/LoUOwrT15Ig==", + "requires": { + "camelcase-keys": "^4.0.0", + "decamelize-keys": "^1.0.0", + "loud-rejection": "^1.0.0", + "minimist-options": "^3.0.1", + "normalize-package-data": "^2.3.4", + "read-pkg-up": "^3.0.0", + "redent": "^2.0.0", + "trim-newlines": "^2.0.0", + "yargs-parser": "^10.0.0" + } + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=" + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "requires": { + "pify": "^3.0.0" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" + }, + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + } + }, + "read-pkg-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", + "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^3.0.0" + } + }, + "redent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-2.0.0.tgz", + "integrity": "sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo=", + "requires": { + "indent-string": "^3.0.0", + "strip-indent": "^2.0.0" + } + }, + "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=" + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" + }, + "strip-indent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz", + "integrity": "sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=" + }, + "trim-newlines": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-2.0.0.tgz", + "integrity": "sha1-tAPQuRvlDDMd/EuC7s6yLD3hbSA=" + }, + "uuid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.1.tgz", + "integrity": "sha1-wqMN7bPlNdcsz4LjQ5QaULqFM6w=" + }, + "yargs-parser": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", + "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", + "requires": { + "camelcase": "^4.1.0" + } + } + } + }, "ethereumjs-abi": { "version": "0.6.8", "resolved": "https://registry.npmjs.org/ethereumjs-abi/-/ethereumjs-abi-0.6.8.tgz", @@ -17915,8 +18137,7 @@ "hosted-git-info": { "version": "2.8.8", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", - "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", - "dev": true + "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==" }, "hpack.js": { "version": "2.1.6", @@ -46227,8 +46448,7 @@ "json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" }, "json-parse-even-better-errors": { "version": "2.3.1", @@ -47418,7 +47638,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", - "dev": true, "requires": { "currently-unhandled": "^0.4.1", "signal-exit": "^3.0.0" @@ -47525,8 +47744,7 @@ "map-obj": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", - "dev": true + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=" }, "map-or-similar": { "version": "1.5.0", @@ -48234,6 +48452,15 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" }, + "minimist-options": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-3.0.2.tgz", + "integrity": "sha512-FyBrT/d0d4+uiZRbqznPXqw3IpZZG3gl3wKWiX784FycUKVwBt0uLBFkQrtE4tZOrgo78nZp2jnKz3L65T5LdQ==", + "requires": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0" + } + }, "minipass": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", @@ -49255,7 +49482,6 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, "requires": { "hosted-git-info": "^2.1.4", "resolve": "^1.10.0", @@ -49266,8 +49492,7 @@ "semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" } } }, @@ -53971,6 +54196,11 @@ "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", "dev": true }, + "quick-lru": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-1.1.0.tgz", + "integrity": "sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g=" + }, "radio-symbol": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/radio-symbol/-/radio-symbol-2.0.0.tgz", @@ -57122,7 +57352,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", - "dev": true, "requires": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" @@ -57131,14 +57360,12 @@ "spdx-exceptions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", - "dev": true + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==" }, "spdx-expression-parse": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, "requires": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" @@ -57147,8 +57374,7 @@ "spdx-license-ids": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", - "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", - "dev": true + "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==" }, "spdy": { "version": "4.0.2", @@ -58781,13 +59007,13 @@ } }, "truffle-flattener": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/truffle-flattener/-/truffle-flattener-1.4.4.tgz", - "integrity": "sha512-S/WmvubzlUj1mn56wEI6yo1bmPpKDNdEe5rtyVC1C5iNfZWobD/V69pAYI15IBDJrDqUyh+iXgpTkzov50zpQw==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/truffle-flattener/-/truffle-flattener-1.5.0.tgz", + "integrity": "sha512-vmzWG/L5OXoNruMV6u2l2IaheI091e+t+fFCOR9sl46EE3epkSRIwGCmIP/EYDtPsFBIG7e6exttC9/GlfmxEQ==", "dev": true, "requires": { "@resolver-engine/imports-fs": "^0.2.2", - "@solidity-parser/parser": "^0.6.0", + "@solidity-parser/parser": "^0.8.0", "find-up": "^2.1.0", "mkdirp": "^1.0.4", "tsort": "0.0.1" @@ -59733,7 +59959,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, "requires": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" diff --git a/package.json b/package.json index 83a96937c..8d66e9e6e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "alchemy-client", - "version": "1.0.1", + "version": "1.0.2", "description": "An app for collaborative networks (DAOs), based on the DAO stack.", "author": "DAOstack", "license": "GPL-3.0", @@ -79,7 +79,7 @@ "dependencies": { "3box": "1.17.1", "@burner-wallet/burner-connect-provider": "^0.1.1", - "@daostack/arc.js": "0.2.74", + "@daostack/arc.js": "0.2.76", "@dorgtech/daocreator-ui": "^1.0.13", "@fortawesome/fontawesome-svg-core": "^1.2.10", "@fortawesome/free-brands-svg-icons": "^5.6.1", @@ -96,6 +96,7 @@ "disqus-react": "^1.0.5", "eth-ens-namehash": "^2.0.8", "ethereum-blockies-png": "^0.1.3", + "ethereum-input-data-decoder": "^0.3.1", "ethereumjs-abi": "^0.6.8", "express": "^4.16.4", "formik": "^1.5.2", @@ -145,7 +146,7 @@ }, "devDependencies": { "@babel/plugin-syntax-dynamic-import": "^7.7.4", - "@daostack/migration": "0.0.1-rc.41-v4", + "@daostack/migration": "0.0.1-rc.47-v0", "@storybook/addon-info": "^5.0.10", "@storybook/react": "^5.0.10", "@types/chai": "^4.1.7", diff --git a/src/arc.ts b/src/arc.ts index a362c36a7..8ddc6b5dd 100644 --- a/src/arc.ts +++ b/src/arc.ts @@ -1,7 +1,6 @@ import { NotificationStatus } from "reducers/notifications"; import { getNetworkId, getNetworkName, targetedNetwork } from "./lib/util"; import { settings, USE_CONTRACTINFOS_CACHE } from "./settings"; -import { RetryLink } from "apollo-link-retry"; import { Address, Arc } from "@daostack/arc.js"; import Web3Modal, { getProviderInfo, IProviderInfo } from "web3modal"; import { Observable } from "rxjs"; @@ -132,19 +131,6 @@ export async function initializeArc(provider?: any): Promise { const readonly = typeof provider === "string"; - // https://www.apollographql.com/docs/link/links/retry/ - const retryLink = new RetryLink({ - attempts: (count) => { - return (count !== 10); - }, - delay: () => { - // This will give a random delay between retries between the range of 5 to 30 seconds. - return Math.floor(Math.random() * (30000 - 5000 + 1) + 5000); - }, - }); - - arcSettings.graphqlRetryLink = retryLink; - // if there is no existing arc, we create a new one if ((window as any).arc) { arc = (window as any).arc; diff --git a/src/components/Proposal/Create/CreateProposal.scss b/src/components/Proposal/Create/CreateProposal.scss index 15f131d81..73a56cb05 100644 --- a/src/components/Proposal/Create/CreateProposal.scss +++ b/src/components/Proposal/Create/CreateProposal.scss @@ -7,6 +7,28 @@ display: none !important; } +fieldset { + margin: 30px 0px; +} + +.removeFieldSet { + float: right; + border: none; + color: $accent-2; + background: none; +} + +.addFieldSet { + border: none; + background: none; + color: $sky; +} + +.removeFieldSet:hover, +.addFieldSet:hover { + opacity: 0.7; +} + .createProposalWrapper { border-radius: 15px 15px 0 0; background-color: $white; @@ -15,7 +37,6 @@ left: 50%; transform: translate(-50%, -50%); box-shadow: $shadow-3; - height: calc(100% - 100px); .header { background-color: $navy; @@ -58,6 +79,7 @@ } form { + select, input { width: 100%; margin-top: 3px; @@ -114,6 +136,23 @@ } } + .addContract { + position: relative; + } + + .encodedDataMultiCall { + overflow-wrap: break-word; + margin: 5px 0 5px 0px; + border: 1px solid; + padding: 20px; + width: 490px; // TEMP - SHOULD BE DYNAMIC + } + + input[type=checkbox] { + width: 15px; + height: auto; + } + .proposerIsAdminCheckbox { label { display: inline-block; @@ -371,7 +410,7 @@ .containerNoSidebar { width: 562px; overflow-y: auto; - height: calc(100% - 90px); + max-height: calc(100vh - 200px); padding: 20px 30px 30px 30px; } @@ -382,7 +421,7 @@ content: ""; clear: both; overflow: auto; - height: calc(100% - 40px); + max-height: calc(100vh - 200px); .sidebar { width: 199px; diff --git a/src/components/Proposal/Create/CreateProposalPage.tsx b/src/components/Proposal/Create/CreateProposalPage.tsx index 35cc46d80..645ff323b 100644 --- a/src/components/Proposal/Create/CreateProposalPage.tsx +++ b/src/components/Proposal/Create/CreateProposalPage.tsx @@ -3,6 +3,7 @@ import { getArc } from "arc"; import CreateKnownGenericSchemeProposal from "components/Proposal/Create/SchemeForms/CreateKnownGenericSchemeProposal"; import CreateSchemeRegistrarProposal from "components/Proposal/Create/SchemeForms/CreateSchemeRegistrarProposal"; import CreateUnknownGenericSchemeProposal from "components/Proposal/Create/SchemeForms/CreateUnknownGenericSchemeProposal"; +import CreateGenericMultiCallProposal from "components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal"; import Loading from "components/Shared/Loading"; import withSubscription, { ISubscriptionProps } from "components/Shared/withSubscription"; import { GenericSchemeRegistry } from "genericSchemeRegistry"; @@ -138,6 +139,8 @@ class CreateProposalPage extends React.Component { } else { createSchemeComponent = ; } + } else if (scheme.name === "GenericSchemeMultiCall") { + createSchemeComponent = ; } return ( diff --git a/src/components/Proposal/Create/SchemeForms/ABIService.ts b/src/components/Proposal/Create/SchemeForms/ABIService.ts new file mode 100644 index 000000000..cef924be9 --- /dev/null +++ b/src/components/Proposal/Create/SchemeForms/ABIService.ts @@ -0,0 +1,149 @@ +import { AbiItem } from "web3-utils"; +import { SortService } from "lib/sortService"; +const Web3 = require("web3"); +import axios from "axios"; +import { targetedNetwork } from "lib/util"; +const InputDataDecoder = require("ethereum-input-data-decoder"); +import { isArrayParameter } from "./Validators"; + +export interface IAllowedAbiItem extends AbiItem { + name: string; + type: "function"; +} + +export interface IAbiItemExtended extends IAllowedAbiItem { + action: string; + methodSignature: string; +} + +export interface IDecodedData { + method: string; + inputs: Array; + names: Array; + types: Array; +} + +/** + * Given a contract address returns the URL to fetch the ABI data accroding the current network + * @param {string} contractAddress + * @returns {string} URL + */ +const getUrl = (contractAddress: string): string => { + const network = targetedNetwork(); + if (network === "xdai"){ + return `https://blockscout.com/poa/xdai/api?module=contract&action=getabi&address=${contractAddress}`; + } + else { + const prefix = (network === "main" || network === "ganache") ? "" : `-${network}`; // we consider 'ganache' as 'main' + return `https://api${prefix}.etherscan.io/api?module=contract&action=getabi&address=${contractAddress}&apikey=${process.env.ETHERSCAN_API_KEY}`; + } +}; + +const getSignatureHash = (signature: string): string => { + return Web3.utils.keccak256(signature).toString(); +}; + +const getMethodSignature = ({ inputs, name }: AbiItem): string => { + const params = inputs?.map((x) => x.type).join(","); + return `${name}(${params})`; +}; + +const getMethodAction = ({ stateMutability }: AbiItem): "read" | "write" => { + if (!stateMutability) { + return "write"; + } + + return ["view", "pure"].includes(stateMutability) ? "read" : "write"; +}; + +const getMethodSignatureAndSignatureHash = (method: AbiItem,): { methodSignature: string; signatureHash: string } => { + const methodSignature = getMethodSignature(method); + const signatureHash = getSignatureHash(methodSignature); + return { methodSignature, signatureHash }; +}; + +const isAllowedABIMethod = ({ name, type }: AbiItem): boolean => { + return type === "function" && !!name; +}; + +/** + * Given valid ABI returns write functions with all thier data. + * @param {AbiItem[]} abi + */ +export const extractABIMethods = (abi: AbiItem[]): IAbiItemExtended[] => { + const allowedAbiItems = abi.filter(isAllowedABIMethod) as IAllowedAbiItem[]; + + return allowedAbiItems.map((method): IAbiItemExtended => ({ + action: getMethodAction(method), + ...getMethodSignatureAndSignatureHash(method), + ...method, + })) + .filter((method) => method.action === "write") + .sort(({ name: a }, { name: b }) => SortService.evaluateString(a, b, 1)); +}; + +/** + * Given contract address returns it's ABI data. + * @param {string} contractAddress + */ +export const getABIByContract = async (contractAddress: string): Promise> => { + const url = getUrl(contractAddress); + try { + const response = await axios({ url: url, method: "GET" }).then(res => { return res.data; }); + if (response.status === "0") { + return []; + } + return JSON.parse(response.result); + } catch (e) { + // eslint-disable-next-line no-console + console.error("Failed to retrieve ABI", e); + return []; + } +}; + +/** + * Given ABI, function name and it's parameters values returns the encoded data as string, otherwise returns an error. + * @param {array} abi ABI methods array + * @param {string} name Method name + * @param {array} values array of ABI parameters values. + * @returns {string} The encoded data + */ +export const encodeABI = (abi: Array, name: string, values: Array): string => { + const web3 = new Web3; + const contract = new web3.eth.Contract(abi); + const interfaceABI = contract.options.jsonInterface; + + /** + * The web3 encodeFunctionCall expects arrays and booleans as objects and not as strings. + * If we have parameters that represent an array or a boolean value, parse them to objects. + */ + let parsedValues = []; + if (values) { + parsedValues = values.map(value => { + if (isArrayParameter(value) || value === "true" || value === "false") { + try { + return JSON.parse(value); + } catch (e) { return; } + } + return value; + }); + } + + try { + const methodToSend = interfaceABI.filter((method: any) => method.name === name && method.inputs.length === values.length); + return web3.eth.abi.encodeFunctionCall(methodToSend[0], parsedValues); + } catch (error) { + return error.reason; + } +}; + +/** + * Given an ABI data and callData returns the decoded data object + * @param {Array} abi + * @param {string} callData + * @returns {IDecodedData} The decoded data + */ +export const decodeABI = (abi: Array, callData: string): IDecodedData => { + const decoder = new InputDataDecoder(abi); + return decoder.decodeData(callData); +}; diff --git a/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx b/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx new file mode 100644 index 000000000..79deb9087 --- /dev/null +++ b/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx @@ -0,0 +1,518 @@ +import { ISchemeState } from "@daostack/arc.js"; +import { createProposal } from "actions/arcActions"; +import { enableWalletProvider } from "arc"; +import { ErrorMessage, Field, Form, Formik, FormikProps, FieldArray } from "formik"; +import Analytics from "lib/analytics"; +import * as React from "react"; +import { connect } from "react-redux"; +import { showNotification, NotificationStatus } from "reducers/notifications"; +import { baseTokenName, isValidUrl, isAddress, linkToEtherScan, getContractName, toWei } from "lib/util"; +import { exportUrl, importUrlValues } from "lib/proposalUtils"; +import TagsSelector from "components/Proposal/Create/SchemeForms/TagsSelector"; +import TrainingTooltip from "components/Shared/TrainingTooltip"; +import * as css from "../CreateProposal.scss"; +import MarkdownField from "./MarkdownField"; +import HelpButton from "components/Shared/HelpButton"; +import { getABIByContract, extractABIMethods, encodeABI } from "./ABIService"; +import * as Validators from "./Validators"; + +interface IExternalProps { + daoAvatarAddress: string; + handleClose: () => any; + scheme: ISchemeState; + whitelistedContracts: Array; +} + +interface IDispatchProps { + createProposal: typeof createProposal; + showNotification: typeof showNotification; +} + +type AddContractError = "NOT_VALID_ADDRESS" | "CONTRACT_EXIST" | "ABI_DATA_ERROR" | ""; + +interface IAddContractStatus { + error: AddContractError; + message: string; +} + +interface IStateProps { + loading: boolean; + tags: Array; + addContractStatus: IAddContractStatus; + whitelistedContracts: Array; + userContracts: Array; +} + +type IProps = IExternalProps & IDispatchProps; + +const mapDispatchToProps = { + createProposal, + showNotification, +}; + +interface IContract { + address: string; // Contract address + value: number; // Token to send with the proposal + abi: any; // Contract ABI data + methods: any; // ABI write methods + method: string; // Selected method + params: any; // Method params + values: any; // Params values + callData: string; // The encoded data +} + +interface IFormValues { + description: string; + title: string; + url: string; + contracts: Array; + [key: string]: any; +} + +/** + * Given an array input type returns an input example string. + * @param {string} type The input type (e.g. unit256[], bool[], address[], ...) + * @returns {string} Input example string + */ +const typeArrayPlaceholder = (type: string): string => { + if (Validators.isAddressType(type)) { + return "e.g: [\"0xACa94ef8bD5ffEE41947b4585a84BdA5a3d3DA6E\",\"0x1dF62f291b2E969fB0849d99D9Ce41e2F137006e\"]"; + } + + if (Validators.isBooleanType(type)) { + return "e.g: [true, false, false, true]"; + } + + if (Validators.isUintType(type)) { + return "e.g: [1000, 212, 320000022, 23]"; + } + + if (Validators.isIntType(type)) { + return "e.g: [1000, -212, 1232, -1]"; + } + + if (Validators.isByteType(type)) { + return "e.g: [\"0xc00000000000000000000000000000000000\", \"0xc00000000000000000000000000000000001\"]"; + } + + return "e.g: [\"first value\", \"second value\", \"third value\"]"; +}; + +class CreateGenericMultiCallScheme extends React.Component { + + initialFormValues: IFormValues; + + constructor(props: IProps) { + super(props); + + this.handleSubmit = this.handleSubmit.bind(this); + this.initialFormValues = importUrlValues({ + description: "", + title: "", + url: "", + tags: [], + addContract: "", + userContracts: [], + contracts: [ + { + address: "", + value: 0, + abi: [], + methods: [], + method: "", + params: [], + values: [], + callData: "", + }, + ], + }); + this.state = { + loading: false, + tags: this.initialFormValues.tags, + addContractStatus: { error: "", message: "" }, + whitelistedContracts: this.props.whitelistedContracts?.map(contract => { return contract.toLowerCase(); }) ?? [], + userContracts: [], + }; + } + + public async handleSubmit(formValues: IFormValues, { setSubmitting }: any): Promise { + if (!await enableWalletProvider({ showNotification: this.props.showNotification })) { return; } + + const contractsToCall = []; + const callsData = []; + const values = []; + + for (const contract of formValues.contracts) { + contractsToCall.push(contract.address); + callsData.push(contract.callData); + values.push(toWei(Number(contract.value)).toString()); + } + + const proposalValues = { + title: formValues.title, + description: formValues.description, + contractsToCall: contractsToCall, + callsData: callsData, + values: values, + url: formValues.url, + dao: this.props.daoAvatarAddress, + scheme: this.props.scheme.address, + tags: this.state.tags, + }; + + setSubmitting(false); + await this.props.createProposal(proposalValues); + + Analytics.track("Submit Proposal", { + "DAO Address": this.props.daoAvatarAddress, + "Proposal Title": formValues.title, + "Scheme Address": this.props.scheme.address, + "Scheme Name": this.props.scheme.name, + }); + + this.props.handleClose(); + } + + // Exports data from form to a shareable url. + public exportFormValues(values: IFormValues) { + exportUrl({ ...values, ...this.state }); + this.props.showNotification(NotificationStatus.Success, "Exportable url is now in clipboard :)"); + } + + private onTagsChange = (tags: any[]): void => { + this.setState({ tags }); + } + + /** + * Given a contract address, checks whether it's valid, not exists in the current contract list and that the contract is verified with valid ABI data and write methods. + * If all checks are okay, pushes the contract address to the contract lists, otherwise returns an appropriate message. + * @param {string} contractToCall + */ + private verifyContract = async (contractToCall: string) => { + const addContractStatus: IAddContractStatus = { + error: "", + message: "", + }; + + const setAddContractStatus = (errorType: AddContractError, message: string) => { + addContractStatus.error = errorType; + addContractStatus.message = message; + }; + + if (contractToCall === "") { + setAddContractStatus("", ""); + } else if (!isAddress(contractToCall)) { + setAddContractStatus("NOT_VALID_ADDRESS", "Please enter a valid address"); + } else if (this.state.userContracts.includes(contractToCall)) { + setAddContractStatus("CONTRACT_EXIST", "Contract already exist!"); + } else { + this.setState({ loading: true }); + const abiData = await getABIByContract(contractToCall); + const abiMethods = extractABIMethods(abiData); + if (abiMethods.length > 0) { + this.state.userContracts.push(contractToCall); + setAddContractStatus("", "Contract added successfully!"); + } else { + setAddContractStatus("ABI_DATA_ERROR", abiData.length === 0 ? "No ABI found for target contract, please verify the " : "No write methods found for target "); + } + } + this.setState({ loading: false, addContractStatus: addContractStatus }); + } + + private getContractABI = async (contractToCall: string, setFieldValue: any, index: number) => { + setFieldValue(`contracts.${index}.method`, ""); // reset + setFieldValue(`contracts.${index}.callData`, ""); // reset + const abiData = await getABIByContract(contractToCall); + const abiMethods = extractABIMethods(abiData); + setFieldValue(`contracts.${index}.abi`, abiData); + setFieldValue(`contracts.${index}.methods`, abiMethods); + } + + private getMethodInputs = (abi: any, methods: any[], methodName: any, setFieldValue: any, index: number) => { + setFieldValue(`contracts.${index}.values`, ""); // reset + setFieldValue(`contracts.${index}.callData`, ""); // reset + const selectedMethod = methods.filter(method => method.methodSignature === methodName); + const abiParams = selectedMethod[0].inputs.map((input: any, j: number) => { + return { + id: j, + name: input.name, + type: input.type, + placeholder: `${input.name} (${input.type}) ${Validators.isArrayParameter(input.type) ? typeArrayPlaceholder(input.type) : ""}`, + methodSignature: input.methodSignature, + }; + }); + setFieldValue(`contracts.${index}.params`, abiParams); + if (abiParams.length === 0) { // If no params, generate the encoded data + setFieldValue(`contracts.${index}.callData`, encodeABI(abi, selectedMethod[0].name, [])); + } + } + + private abiInputChange = (abi: any, values: any, name: string, setFieldValue: any, index: number) => { + const encodedData = encodeABI(abi, name, values); + setFieldValue(`contracts.${index}.callData`, encodedData); + } + + public render(): RenderOutput { + const { handleClose } = this.props; + const { loading, addContractStatus, userContracts, whitelistedContracts } = this.state; + + const contracts = whitelistedContracts.length > 0 ? whitelistedContracts : userContracts; + const contractsOptions = contracts.map((address, index) => { + return ; + }); + + const fnDescription = () => (Short description of the proposal.
  • What are you proposing to do?
  • Why is it important?
  • How much will it cost the DAO?
  • When do you plan to deliver the work?
); + + return ( +
+ { + const errors: any = {}; + + const require = (name: string) => { + if (!(values as any)[name]) { + errors[name] = "Required"; + } + }; + + if (values.title.length > 120) { + errors.title = "Title is too long (max 120 characters)"; + } + + if (!isValidUrl(values.url)) { + errors.url = "Invalid URL"; + } + + require("title"); + require("description"); + + return errors; + }} + onSubmit={this.handleSubmit} + // eslint-disable-next-line react/jsx-no-bind + render={({ + errors, + touched, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + handleSubmit, + isSubmitting, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + setFieldTouched, + setFieldValue, + handleBlur, + values, + }: FormikProps) => +
+ + + + + + + + + { setFieldValue("description", value); }} + id="descriptionInput" + placeholder="Describe your proposal in greater detail" + name="description" + className={touched.description && errors.description ? css.error : null} + /> + + + + + +
+ +
+ + + + + + + {whitelistedContracts.length === 0 && +
+ + { setFieldValue("addContract", e.target.value); this.verifyContract(e.target.value.toLowerCase()); }} + disabled={loading ? true : false} + /> + {loading ? "Loading..." : addContractStatus.error === "ABI_DATA_ERROR" ? +
+ {addContractStatus.message} + contract +
: addContractStatus.message} +
} + + + {({ insert, remove, push }) => ( // eslint-disable-line @typescript-eslint/no-unused-vars +
+ { + values.contracts.length > 0 && values.contracts.map((contract: any, index: any) => ( +
+ {/* eslint-disable-next-line react/jsx-no-bind */} + {values.contracts.length > 1 && } + +
+ + +
+ +
+ + { setFieldValue(`contracts.${index}.address`, e.target.value); await this.getContractABI(e.target.value, setFieldValue, index); }} + component="select" + name={`contracts.${index}.address`} + type="text" + validate={Validators.requireValue} + > + + 0 ? "Whitelisted contracts" : "Custom contracts"}> + {contractsOptions} + + +
+ + { + values.contracts[index].address !== "" && +
+ + {values.contracts[index]?.methods?.length === 0 ? "Loading..." : + { setFieldValue(`contracts.${index}.method`, e.target.value); this.getMethodInputs(values.contracts[index].abi, values.contracts[index]?.methods, e.target.value, setFieldValue, index); }} + component="select" + name={`contracts.${index}.method`} + type="text" + validate={Validators.requireValue} + > + + {values.contracts[index]?.methods?.map((method: any, j: any) => ( + + ))} + } +
+ } + + { + values.contracts[index].method !== "" && +
+ {values.contracts[index].params.map((param: any, i: number) => ( + + + { handleBlur(e); this.abiInputChange(values.contracts[index].abi, values.contracts[index].values, values.contracts[index].method.split("(")[0], setFieldValue, index); }} + // eslint-disable-next-line react/jsx-no-bind + validate={(e: any) => Validators.validateParam(param.type, e)} + /> + + ))} +
+ } + + +
{values.contracts[index].callData}
+
+ )) + } + +
+ ) + } +
+ +
+ + + + + + + +
+ + } + /> +
+ ); + } +} + +export default connect(null, mapDispatchToProps)(CreateGenericMultiCallScheme); diff --git a/src/components/Proposal/Create/SchemeForms/Validators.ts b/src/components/Proposal/Create/SchemeForms/Validators.ts new file mode 100644 index 000000000..14fd4ea35 --- /dev/null +++ b/src/components/Proposal/Create/SchemeForms/Validators.ts @@ -0,0 +1,108 @@ +import { isHexStrict } from "web3-utils"; +import { isAddress } from "lib/util"; + +/** + * Functions to check an input core type + */ +export const isAddressType = (type: string): boolean => type.indexOf("address") === 0; +export const isBooleanType = (type: string): boolean => type.indexOf("bool") === 0; +export const isStringType = (type: string): boolean => type.indexOf("string") === 0; +export const isUintType = (type: string): boolean => type.indexOf("uint") === 0; +export const isIntType = (type: string): boolean => type.indexOf("int") === 0; +export const isByteType = (type: string): boolean => type.indexOf("byte") === 0; + +/** + * RegEx to check if a string ends with or surrounded by [] + * @param {string} parameter + * @returns {boolean} true if a string ends with [] or surround by [], otherwise returns false. + */ +export const isArrayParameter = (parameter: string): boolean => /(\[\d*])+$/.test(parameter) || /^\[.*\]$/.test(parameter); + +/** + * Given a value returns error message in case value is less than 0 or no value provided + * @param {any} value + * @returns {undefined | string} undefined if there is no error, otherwise a "Required" string. + */ +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types +export const requireValue = (value: any): string => { + let error; + if (value === "") { + error = "Required"; + } else if (value < 0) { + error = "Must be a non-negative value"; + } + return error; +}; + +/** + * Given an ABI method param type (address, byets32, unit256, bool, ...) and it's value, returns error message in case validation fails or no value provided. + * @param {string} type + * @param {string} value + * @returns {undefined | string} undefined if there is no error, otherwise a string representing the error message. + */ +export const validateParamValue = (type: string, value: string): undefined | string => { + if (isAddressType(type)) { + if (!isAddress(value)) { + return "Must be a valid address"; + } + } + + if (isBooleanType(type)) { + if (value !== "true" && value !== "false") { + return "Must be 'true' or 'false'"; + } + } + + if (isUintType(type)) { + if (/^\d+$/.test(value) === false) { + return "Must contain only positive digits"; + } + } + + if (isIntType(type)) { + if (!Number.isInteger(Number(value)) || value.includes(".")) { + return "Must be an integer"; + } + } + + if (isByteType(type)) { + if (!isHexStrict(value)) { + return "Must be an hex value"; + } + } + + return undefined; +}; + +/** + * Given an ABI method param type including array type (address, byets32, unit256, bool, bool[], ...) and it's value, returns error message in case validation fails or no value provided. + * This function is suitable to work with Formik validations. + * @param {string} type + * @param {string} value + * @returns {undefined | string} undefined if there is no error, otherwise a string representing the error message. + */ +export const validateParam = (type: string, value: string): undefined | string => { + let error; + if (!value) { + return "Required"; + } + + if (isArrayParameter(type)) { + try { + const values = JSON.parse(value); + if (!Array.isArray(values)) { + return "Make sure to surround the value with []"; + } else { + for (const value of values) { + if (validateParamValue(type, value) !== undefined) { + throw error; + } + } + } + } catch (e) { + return "Invalid format"; + } + } + + else return validateParamValue(type, value); +}; diff --git a/src/components/Proposal/ProposalDetailsPage.tsx b/src/components/Proposal/ProposalDetailsPage.tsx index 94d22c70b..f79ebf57f 100644 --- a/src/components/Proposal/ProposalDetailsPage.tsx +++ b/src/components/Proposal/ProposalDetailsPage.tsx @@ -336,7 +336,7 @@ class ProposalDetailsPage extends React.Component { {this.state.showShareModal ? : "" } diff --git a/src/components/Proposal/ProposalSummary/ProposalSummary.scss b/src/components/Proposal/ProposalSummary/ProposalSummary.scss index 57762392f..3026a9c92 100644 --- a/src/components/Proposal/ProposalSummary/ProposalSummary.scss +++ b/src/components/Proposal/ProposalSummary/ProposalSummary.scss @@ -149,6 +149,29 @@ height: 14px; } } + + .multiCallContractDetails { + border-bottom: 1px solid gray; + .valueText { + font-family: Monaco; + } + .paramWrapper { + margin-top: 5px; + } + .loadingMethodInfo { + display: flex; + align-items: center; + .loader { + background: url("../../../assets/images/Icon/buttonLoadingBlue.gif") no-repeat; + margin-right: 5px; + width: 15px; + height: 15px; + } + } + } + .multiCallContractDetails:last-child { + border-bottom: none; + } } .summaryDetails ul { diff --git a/src/components/Proposal/ProposalSummary/ProposalSummary.tsx b/src/components/Proposal/ProposalSummary/ProposalSummary.tsx index 71a4eb056..d4f41d661 100644 --- a/src/components/Proposal/ProposalSummary/ProposalSummary.tsx +++ b/src/components/Proposal/ProposalSummary/ProposalSummary.tsx @@ -8,6 +8,7 @@ import ProposalSummaryContributionReward from "./ProposalSummaryContributionRewa import ProposalSummaryKnownGenericScheme from "./ProposalSummaryKnownGenericScheme"; import ProposalSummarySchemeRegistrar from "./ProposalSummarySchemeRegistrar"; import ProposalSummaryUnknownGenericScheme from "./ProposalSummaryUnknownGenericScheme"; +import ProposalSummaryMultiCallGenericScheme from "./ProposalSummaryMultiCallGenericScheme"; interface IProps { beneficiaryProfile?: IProfileState; @@ -41,7 +42,8 @@ export default class ProposalSummary extends React.Component { } else { return ; } - + } else if (proposal.type === IProposalType.GenericSchemeMultiCall) { + return ; } else { return
Unknown proposal type
; } diff --git a/src/components/Proposal/ProposalSummary/ProposalSummaryMultiCallGenericScheme.tsx b/src/components/Proposal/ProposalSummary/ProposalSummaryMultiCallGenericScheme.tsx new file mode 100644 index 000000000..2a960c7d8 --- /dev/null +++ b/src/components/Proposal/ProposalSummary/ProposalSummaryMultiCallGenericScheme.tsx @@ -0,0 +1,134 @@ +import { IDAOState, IProposalState } from "@daostack/arc.js"; +import classNames from "classnames"; +import { linkToEtherScan, baseTokenName, truncateWithEllipses, buf2hex, getContractName, fromWei } from "lib/util"; +import * as React from "react"; +import { IProfileState } from "reducers/profilesReducer"; +import * as css from "./ProposalSummary.scss"; +import BN = require("bn.js"); +import CopyToClipboard from "components/Shared/CopyToClipboard"; +import { getABIByContract, decodeABI, IDecodedData } from "../Create/SchemeForms/ABIService"; +import * as Validators from "../Create/SchemeForms/Validators"; + +interface IProps { + beneficiaryProfile?: IProfileState; + detailView?: boolean; + dao: IDAOState; + proposal: IProposalState; + transactionModal?: boolean; +} + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +interface IState { +} + +interface IDecodedDataProps { + contract: string; + callData: string; +} + +const parseParamValue = (type: string, value: any) => { + if (Validators.isAddressType(type)) { + if (Validators.isArrayParameter(type)) { + value = value.map((element: any) => { + return `0x${element}\n`; + }); + return value; + } + return `0x${value}`; + } + if (Validators.isBooleanType(type)) { + return value.toString(); + } + if (Validators.isUintType(type) || Validators.isIntType(type)) { + return value.toString(10); + } + if (Validators.isByteType(type)) { + if (Validators.isArrayParameter(type)) { + value = value.map((element: any) => { + return `0x${buf2hex(element)}\n`; + }); + return value; + } + return `0x${buf2hex(value)}`; + } + + return value; +}; + +const parseMethodSignature = (decodedData: IDecodedData): string => { + const params = decodedData.names.map((name, index) => { + return `${name}: ${decodedData.types[index]}`; + }); + return `${decodedData.method} (${params})`; +}; + +const DecodedData = (props: IDecodedDataProps) => { + const [lodaing, setLoading] = React.useState(false); + const [decodedData, setDecodedData] = React.useState({method: "", inputs: [], names: [], types: []}); + + React.useEffect(() => { + const getAbi = async () => { + setLoading(true); + const abiData = await getABIByContract(props.contract); + setDecodedData(decodeABI(abiData, props.callData)); + setLoading(false); + }; + getAbi(); + }, []); + + const methodParams = decodedData.names.map((param: string, index: number) => { + return
{param}:
{parseParamValue(decodedData.types[index], decodedData.inputs[index])}
; + }); + + return ( +
+ {lodaing ?
Loading method info...
: + +
Method:
{parseMethodSignature(decodedData)}
+ {methodParams} +
} +
+ ); +}; + +export default class ProposalSummary extends React.Component { + + constructor(props: IProps) { + super(props); + } + + public render(): RenderOutput { + const { proposal, detailView, transactionModal } = this.props; + const tokenAmountToSend = proposal.genericSchemeMultiCall.values.reduce((a: BN, b: BN) => new BN(a).add(new BN(b))); + const proposalSummaryClass = classNames({ + [css.detailView]: detailView, + [css.transactionModal]: transactionModal, + [css.proposalSummary]: true, + [css.withDetails]: true, + }); + return ( +
+ + Generic Multicall + {fromWei(tokenAmountToSend) > 0 &&
> Sending {fromWei(tokenAmountToSend)} {baseTokenName()} <
} +
+ {detailView && +
+ { + proposal.genericSchemeMultiCall.contractsToCall.map((contract, index) => ( +
+

{`#${index + 1}`}

+

Contract: {getContractName(contract)} {`(${contract})`}

+

{baseTokenName()} value: {fromWei(proposal.genericSchemeMultiCall.values[index])}

+ +

Raw call data:

+
{truncateWithEllipses(proposal.genericSchemeMultiCall.callsData[index], 66)}
+
+ )) + } +
+ } +
+ ); + } +} diff --git a/src/components/Scheme/ContributionRewardExtRewarders/Competition/List.tsx b/src/components/Scheme/ContributionRewardExtRewarders/Competition/List.tsx index 4ae5e3f65..fbb6e28d3 100644 --- a/src/components/Scheme/ContributionRewardExtRewarders/Competition/List.tsx +++ b/src/components/Scheme/ContributionRewardExtRewarders/Competition/List.tsx @@ -9,7 +9,6 @@ import { getArc } from "arc"; import { CompetitionStatusEnum, CompetitionStatus } from "./utils"; import Card from "./Card"; import * as css from "./Competitions.scss"; -import { standardPolling } from "lib/util"; interface IExternalProps { daoState: IDAOState; @@ -127,7 +126,7 @@ export default withSubscription({ suggestions { ...CompetitionSuggestionFields } - votes { + votes { ...CompetitionVoteFields } } @@ -138,7 +137,7 @@ export default withSubscription({ `; const arc = await getArc(); - await arc.sendQuery(cacheQuery, standardPolling()); + await arc.sendQuery(cacheQuery); // end cache priming // TODO: next lines can use some cleanup up @@ -148,4 +147,3 @@ export default withSubscription({ ); }, }); - diff --git a/src/components/Scheme/SchemeInfoPage.tsx b/src/components/Scheme/SchemeInfoPage.tsx index 9e72fd3b7..9b5074827 100644 --- a/src/components/Scheme/SchemeInfoPage.tsx +++ b/src/components/Scheme/SchemeInfoPage.tsx @@ -214,6 +214,19 @@ export default class SchemeInfo extends React.Component {
: "" } + + {scheme.genericSchemeMultiCallParams ? +
+

Genesis Protocol Params -- Learn more

+
+
+ {renderVotingMachineLink(votingMachine)} + {renderGpParams(scheme.genericSchemeMultiCallParams.voteParams)} +
+
+
+ : "" + } ; } } diff --git a/src/components/Shared/withSubscription.tsx b/src/components/Shared/withSubscription.tsx index 4774796a3..60739a1bf 100644 --- a/src/components/Shared/withSubscription.tsx +++ b/src/components/Shared/withSubscription.tsx @@ -1,6 +1,7 @@ import * as React from "react"; import { combineLatest, Observable, Subscription } from "rxjs"; import { Subtract } from "utility-types"; +import { GRAPH_POLL_INTERVAL } from "../../settings"; function getDisplayName(wrappedComponent: any): string { return wrappedComponent.displayName || wrappedComponent.name || "Component"; @@ -92,7 +93,7 @@ const withSubscription = , Obse }; } - public async setupSubscription(observable?: Observable) { + public async setupSubscription(observable?: Observable, currentAttempt = 0) { this.teardownSubscription(); @@ -107,16 +108,27 @@ const withSubscription = , Obse this.subscription = this.observable.subscribe( (next: ObservableType) => { + currentAttempt = 0; // reset on success this.setState({ data: next, isLoading: false, }); }, (error: Error) => { - // eslint-disable-next-line no-console - console.error(getDisplayName(wrappedComponent), "Error in subscription", error); - // this will go to the error page - this.setState(() => { throw error; }); + /** + * The below condition is a workaround to avoid crashing Alchemy when a GraphQL error or a Network error occurs. + * This is due to the way Apollo Client works when such an error occurs - it fails and terminates the observable including the polling. + */ + if ((error.message.includes("GraphQL") || error.message.includes("Network")) && currentAttempt < 10) { + currentAttempt ++; + this.subscription.unsubscribe(); + setTimeout(this.setupSubscription.bind(this, observable, currentAttempt), GRAPH_POLL_INTERVAL); + } else { + // eslint-disable-next-line no-console + console.error(getDisplayName(wrappedComponent), "Error in subscription", error); + // this will go to the error page + this.setState(() => { throw error; }); + } }, () => { this.setState({ complete: true, diff --git a/src/genericSchemeRegistry/index.ts b/src/genericSchemeRegistry/index.ts index c2284bf67..fde798241 100644 --- a/src/genericSchemeRegistry/index.ts +++ b/src/genericSchemeRegistry/index.ts @@ -14,6 +14,7 @@ const registryLookupInfo = require("./schemes/RegistryLookup.json"); const co2kenInfo = require("./schemes/CO2ken.json"); const dXTokenRegistry = require("./schemes/dXTokenRegistry.json"); const dXswapGovernance = require("./schemes/DXswapGovernance.json"); +const balancerPoolManager = require("./schemes/BalancerPoolManager.json"); const KNOWNSCHEMES = [ dutchXInfo, @@ -26,6 +27,7 @@ const KNOWNSCHEMES = [ registryLookupInfo, dXTokenRegistry, dXswapGovernance, + balancerPoolManager, ]; const SCHEMEADDRESSES: {[network: string]: { [address: string]: any}} = { diff --git a/src/genericSchemeRegistry/schemes/BalancerPoolManager.json b/src/genericSchemeRegistry/schemes/BalancerPoolManager.json new file mode 100644 index 000000000..7b56d5fa8 --- /dev/null +++ b/src/genericSchemeRegistry/schemes/BalancerPoolManager.json @@ -0,0 +1,341 @@ +{ + "name": "BalancerPoolManager", + "addresses": { + "main": [ + "" + ], + "kovan": [ + "0x5086d91858a5aac4a770212720b297f38e0dd403" + ], + "private": [ + "" + ] + }, + "actions": [ + { + "id": "setPublicSwap", + "label": "Set Public Swap", + "description": "False to pause the pool", + "notes": "https://github.com/PrimeDAO/monorepo/blob/master/packages/contracts/contracts/schemes/BalancerProxy.sol", + "fields": [ + { + "label": "Set public swap to", + "name": "public swap", + "placeholder": "Boolean (true or false)" + } + ], + "abi": { + "constant": false, + "inputs": [ + { + "internalType": "bool", + "name": "publicSwap", + "type": "bool" + } + ], + "name": "setPublicSwap", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } + }, + { + "id": "setSwapFee", + "label": "Set Swap Fee", + "description": "Update the Swap Fee", + "notes": "https://github.com/PrimeDAO/monorepo/blob/master/packages/contracts/contracts/schemes/BalancerProxy.sol", + "fields": [ + { + "decimals": 18, + "label": "Set swap fee to", + "name": "amount", + "unit": "%", + "placeholder": "Swap fee (1.5)" + } + ], + "abi": { + "constant": false, + "inputs": [ + { + "internalType": "uint256", + "name": "swapFee", + "type": "uint256" + } + ], + "name": "setSwapFee", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } + }, + { + "id": "commitAddToken", + "label": "Add token", + "description": "First step in adding a new token to the balancer pool", + "notes": "https://github.com/PrimeDAO/monorepo/blob/master/packages/contracts/contracts/schemes/BalancerProxy.sol", + "fields": [ + { + "label": "Token address", + "name": "token", + "placeholder": "Address (0x0000…)" + }, + { + "decimals": 18, + "label": "Balance of the new token to add to the pool", + "name": "balance", + "unit": "token", + "placeholder": "Balance in token (1500)" + }, + { + "decimals": 18, + "label": "Expected denormalizedWeight", + "name": "Denormalized Weight", + "placeholder": "Denormalized Weight (1.5)" + } + ], + "abi": { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "balance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "denormalizedWeight", + "type": "uint256" + } + ], + "name": "commitAddToken", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } + }, + { + "id": "applyAddToken", + "label": "Confirm add token", + "description": "Second step in adding a new token to the balancer pool", + "notes": "https://github.com/PrimeDAO/monorepo/blob/master/packages/contracts/contracts/schemes/BalancerProxy.sol", + "fields": [ + ], + "abi": { + "constant": false, + "inputs": [], + "name": "applyAddToken", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } + }, + { + "id": "removeToken", + "label": "Remove token", + "description": "Remove a token from the pool", + "notes": "https://github.com/PrimeDAO/monorepo/blob/master/packages/contracts/contracts/schemes/BalancerProxy.sol", + "fields": [ + { + "label": "Token address", + "name": "token", + "placeholder": "Address (0x0000…)" + } + ], + "abi": { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "removeToken", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } + }, + { + "id": "updateWeight", + "label": "New weight", + "description": "Set the weight of a token", + "notes": "https://github.com/PrimeDAO/monorepo/blob/master/packages/contracts/contracts/schemes/BalancerProxy.sol", + "fields": [ + { + "label": "Token address", + "name": "token", + "placeholder": "Address (0x0000…)" + }, + { + "decimals": 18, + "label": "New weight", + "name": "new weight", + "unit": "unit", + "placeholder": "Weight (1500)" + } + ], + "abi": { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "newWeight", + "type": "uint256" + } + ], + "name": "updateWeight", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } + }, + { + "id": "updateWeightsGradually", + "label": "Update weights", + "description": "Sets token weights (to be gradually updated)", + "notes": "https://github.com/PrimeDAO/monorepo/blob/master/packages/contracts/contracts/schemes/BalancerProxy.sol", + "fields": [ + { + "label": "New weights in wei", + "name": "New weights that are going to be gradually updated", + "placeholder": "Weight (1000000000000000000)" + }, + { + "decimals": 18, + "label": "Start block", + "name": "Start update from block", + "unit": "unit", + "placeholder": "Block number (199199)" + }, + { + "decimals": 18, + "label": "End block", + "name": "End update at block", + "unit": "unit", + "placeholder": "Block number (399199)" + } + ], + "abi": { + "constant": false, + "inputs": [ + { + "internalType": "uint256[]", + "name": "newWeights", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "startBlock", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "endBlock", + "type": "uint256" + } + ], + "name": "updateWeightsGradually", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } + }, + { + "id": "joinPool", + "label": "Join pool", + "description": "Joins the pool by adding more tokens", + "notes": "https://github.com/PrimeDAO/monorepo/blob/master/packages/contracts/contracts/schemes/BalancerProxy.sol", + "fields": [ + { + "decimals": 18, + "label": "Pool tokens to recieve", + "name": "Number of pool tokens to receive", + "unit": "uint", + "placeholder": "Number (1000)" + }, + { + "label": "Max tokens to add in wei", + "name": "Max amount of asset tokens to spend", + "placeholder": "Amount (2000000000000000000)" + } + ], + "abi": { + "constant": false, + "inputs": [ + { + "internalType": "uint256", + "name": "poolAmountOut", + "type": "uint256" + }, + { + "internalType": "uint256[]", + "name": "maxAmountsIn", + "type": "uint256[]" + } + ], + "name": "joinPool", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } + }, + { + "id": "exitPool", + "label": "Exit pool", + "description": "Exits the pool by redeeming the tokens", + "notes": "https://github.com/PrimeDAO/monorepo/blob/master/packages/contracts/contracts/schemes/BalancerProxy.sol", + "fields": [ + { + "decimals": 18, + "label": "Pool tokens to redeem", + "name": "Number of pool tokens to redeem", + "placeholder": "Number (1000)" + }, + { + "label": "Max amount of asset tokens to receive in wei", + "name": "Minimum amount of asset tokens to receive", + "placeholder": "Amount (2000000000000000000)" + } + ], + "abi": { + "constant": false, + "inputs": [ + { + "internalType": "uint256", + "name": "poolAmountIn", + "type": "uint256" + }, + { + "internalType": "uint256[]", + "name": "minAmountsOut", + "type": "uint256[]" + } + ], + "name": "exitPool", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } + } + ] +} diff --git a/src/genericSchemeRegistry/schemes/ENSRegistry.json b/src/genericSchemeRegistry/schemes/ENSRegistry.json index 88270ca3b..d45f955dc 100644 --- a/src/genericSchemeRegistry/schemes/ENSRegistry.json +++ b/src/genericSchemeRegistry/schemes/ENSRegistry.json @@ -11,7 +11,8 @@ ], "private": [ "0x6b85757e0a41f837dbe0b6b4b5157858b62d9a66", - "0xd931b5ef6afb7995d01be71f69a7fcbbebe4f41f" + "0xd931b5ef6afb7995d01be71f69a7fcbbebe4f41f", + "0x99703166acf4b5c6ca9e878ca62feb0df19d7ff1" ] }, "actions": [ diff --git a/src/genericSchemeRegistry/schemes/RegistryLookup.json b/src/genericSchemeRegistry/schemes/RegistryLookup.json index 423411355..a69db86bf 100644 --- a/src/genericSchemeRegistry/schemes/RegistryLookup.json +++ b/src/genericSchemeRegistry/schemes/RegistryLookup.json @@ -13,7 +13,8 @@ ], "private": [ "0x9e32124cbecc2460e54735d356c0cb5ca8c8aa44", - "0x38d542f47b1b949146e3961ecd87872fdea49679" + "0x38d542f47b1b949146e3961ecd87872fdea49679", + "0x04197b7f05c53d81efce85af1bc31bdd21dacc8b" ] }, "actions": [ diff --git a/src/layouts/SidebarMenu.tsx b/src/layouts/SidebarMenu.tsx index 97123d6b0..5329c514a 100644 --- a/src/layouts/SidebarMenu.tsx +++ b/src/layouts/SidebarMenu.tsx @@ -229,6 +229,7 @@ class SidebarMenu extends React.Component { Feed +
  • xGEN / GEN
  • $ Buy GEN
      diff --git a/src/lib/schemeUtils.ts b/src/lib/schemeUtils.ts index 933543a8f..8d6f7d92c 100644 --- a/src/lib/schemeUtils.ts +++ b/src/lib/schemeUtils.ts @@ -50,6 +50,7 @@ export const KNOWN_SCHEME_NAMES = [ "UGenericScheme", "Competition", "ContributionRewardExt", + "GenericSchemeMultiCall", ]; export const PROPOSAL_SCHEME_NAMES = [ @@ -59,6 +60,7 @@ export const PROPOSAL_SCHEME_NAMES = [ "UGenericScheme", "Competition", "ContributionRewardExt", + "GenericSchemeMultiCall", ]; /** @@ -132,7 +134,7 @@ export function schemeName(scheme: ISchemeState|IContractInfo, fallback?: string /** * this will be "pretty" */ - name = rewarderContractName(scheme as ISchemeState); + name = rewarderContractName(scheme as ISchemeState, false); } else { name = alias ?? splitCamelCase(scheme.name); } diff --git a/src/lib/util.ts b/src/lib/util.ts index 37efed4e4..f8120d33e 100644 --- a/src/lib/util.ts +++ b/src/lib/util.ts @@ -706,3 +706,27 @@ export function safeMoment(dateSpecifier: moment.Moment | Date | number | string export const standardPolling = (fetchAllData = false) => { return { polling: true, pollInterval: GRAPH_POLL_INTERVAL, fetchAllData }; }; + + +/** + * Given an ArrayBuffer returns Hex string + * @param {Array} buffer + * @returns {string} Hex string + */ +export const buf2hex = (buffer: Array): string => { // buffer is an ArrayBuffer + return Array.prototype.map.call(new Uint8Array(buffer), (x: any) => ("00" + x.toString(16)).slice(-2)).join(""); +}; + +/** + * Given a contract address returns the contract name if available. + * @param {string} address + * @returns {string} Contract name + */ +export const getContractName = (address: string): string => { + const arc = getArc(); + try { + return arc.getContractInfo(address.toLowerCase()).name; + } catch (e) { + return "unknown name"; + } +}; diff --git a/src/subgraph_endpoints.json b/src/subgraph_endpoints.json index e74b22045..d5bd15883 100644 --- a/src/subgraph_endpoints.json +++ b/src/subgraph_endpoints.json @@ -1,12 +1,12 @@ { - "http_main": "https://api.thegraph.com/subgraphs/name/daostack/v39_8", - "ws_main": "wss://api.thegraph.com/subgraphs/name/daostack/v39_8", - "http_rinkeby": "https://api.thegraph.com/subgraphs/name/daostack/v39_8_rinkeby", - "ws_rinkeby": "wss://api.thegraph.com/subgraphs/name/daostack/v39_8_rinkeby", - "http_kovan": "https://api.thegraph.com/subgraphs/name/daostack/v39_8_kovan", - "ws_kovan": "wss://api.thegraph.com/subgraphs/name/daostack/v39_8_kovan", - "http_xdai": "https://api.thegraph.com/subgraphs/name/daostack/v39_8_xdai", - "ws_xdai": "wss://api.thegraph.com/subgraphs/name/daostack/v39_8_xdai", + "http_main": "https://api.thegraph.com/subgraphs/name/daostack/v40_0", + "ws_main": "wss://api.thegraph.com/subgraphs/name/daostack/v40_0", + "http_rinkeby": "https://api.thegraph.com/subgraphs/name/daostack/v40_0_rinkeby", + "ws_rinkeby": "wss://api.thegraph.com/subgraphs/name/daostack/v40_0_rinkeby", + "http_kovan": "https://api.thegraph.com/subgraphs/name/daostack/v40_0_kovan", + "ws_kovan": "wss://api.thegraph.com/subgraphs/name/daostack/v40_0_kovan", + "http_xdai": "https://api.thegraph.com/subgraphs/name/daostack/v40_0_xdai", + "ws_xdai": "wss://api.thegraph.com/subgraphs/name/daostack/v40_0_xdai", "http_ganache": "http://127.0.0.1:8000/subgraphs/name/daostack", "ws_ganache": "ws://127.0.0.1:8001/subgraphs/name/daostack" } diff --git a/test/integration/proposal.ts b/test/integration/proposal.ts index 10e659e19..a7b13db15 100644 --- a/test/integration/proposal.ts +++ b/test/integration/proposal.ts @@ -1,5 +1,5 @@ import * as uuid from "uuid"; -import { getContractAddresses, hideCookieAcceptWindow, hideTrainingTooltips, gotoDaoSchemes } from "./utils"; +import { getContractAddresses, hideCookieAcceptWindow, gotoDaoSchemes } from "./utils"; describe("Proposals", () => { let daoAddress: string; @@ -8,7 +8,6 @@ describe("Proposals", () => { before(() => { addresses = getContractAddresses(); daoAddress = addresses.dao.Avatar.toLowerCase(); - hideTrainingTooltips(); }); it("Create a proposal, vote for it, stake on it", async () => { diff --git a/test/integration/wdio.conf.js b/test/integration/wdio.conf.js index 784cb317b..cb43e908e 100644 --- a/test/integration/wdio.conf.js +++ b/test/integration/wdio.conf.js @@ -124,7 +124,7 @@ exports.config = { chrome: { // check for more recent versions of chrome driver here: // https://chromedriver.storage.googleapis.com/index.html - version: "85.0.4183.87", + version: "87.0.4280.20", arch: process.arch, baseURL: "https://chromedriver.storage.googleapis.com", }, @@ -139,7 +139,7 @@ exports.config = { chrome: { // check for more recent versions of chrome driver here: // https://chromedriver.storage.googleapis.com/index.html - version: "85.0.4183.87", + version: "87.0.4280.20", arch: process.arch, baseURL: "https://chromedriver.storage.googleapis.com", }, diff --git a/webpack.dev.config.js b/webpack.dev.config.js index 63580a41e..ab0833b3b 100644 --- a/webpack.dev.config.js +++ b/webpack.dev.config.js @@ -103,6 +103,7 @@ module.exports = merge(baseConfig, { ARC_IPFSPROVIDER_PROTOCOL : "", ARC_IPFSPROVIDER_API_PATH : "", INFURA_ID : "", + ETHERSCAN_API_KEY : "", MIXPANEL_TOKEN: "eac39430f2d26472411099a0407ad610", }) ] diff --git a/webpack.docker.config.js b/webpack.docker.config.js index 50cba6c54..a574a925b 100644 --- a/webpack.docker.config.js +++ b/webpack.docker.config.js @@ -104,6 +104,7 @@ module.exports = merge(baseConfig, { ARC_IPFSPROVIDER_PROTOCOL : "", ARC_IPFSPROVIDER_API_PATH : "", INFURA_ID : "", + ETHERSCAN_API_KEY : "", MIXPANEL_TOKEN: "" }), ] diff --git a/webpack.prod.config.js b/webpack.prod.config.js index 0e2e485f9..825fe02db 100644 --- a/webpack.prod.config.js +++ b/webpack.prod.config.js @@ -109,6 +109,7 @@ plugins: [ ARC_IPFSPROVIDER_PROTOCOL : "", ARC_IPFSPROVIDER_API_PATH : "", INFURA_ID : "", + ETHERSCAN_API_KEY : "", MIXPANEL_TOKEN: "", }),