diff --git a/hardhat.config.js b/hardhat.config.js index 61dfd9591e..efd5ce05cf 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -1,11 +1,48 @@ const moment = require('moment'); require('@nomiclabs/hardhat-truffle5'); require('hardhat-dependency-compiler'); -require('./node_modules/dxdao-contracts/scripts/deploy-dxvote'); +require('./node_modules/dxdao-contracts/scripts/deploy-dxdao-contracts'); require('@typechain/hardhat'); const MNEMONIC = - 'dxdao dxdao dxdao dxdao dxdao dxdao dxdao dxdao dxdao dxdao dxdao dxdao'; + 'cream core pear sure dinner indoor citizen divorce sudden captain subject remember'; + + +// # Accounts +// # ======== +// # Account #0: 0x9578e973bba0cc33bdbc93c7f77bb3fe6d47d68a (10000 ETH) +// # Private Key #0: 0x2edaf5755c340d57c68ab5c084a0afd867caafcbcf556838f404468e2ad0ea94 + +// # Account #1: 0xc5b20ade9c9cd5e0cc087c62b26b815a4bc1881f (10000 ETH) +// # Private Key #1: 0x40126ad770c1ff59937436ddab2872193c01d5353213d297fdb0ea2c13b5981e + +// # Account #2: 0xaf8eb8c3a5d9d900aa0b98e3df0bcc17d3c5f698 (10000 ETH) +// # Private Key #2: 0x4db6b61624bd4a9bf87ff59e7fca0991b02ff605664a3ad97dc237c84ba0e013 + +// # Account #3: 0x84eeb305da0a4309a696d43de9f79f04e66eb4f8 (10000 ETH) +// # Private Key #3: 0x6d8b1b46346a00fec52fd0e2edba75592e8814b11aec5815ec0f6b882e072131 + +// # Account #4: 0x1b929bdde0fb3b7b759696f23d6cac0963d326e6 (10000 ETH) +// # Private Key #4: 0x19ea21f217094f12da6bab83fe697f902caea0dcf5a2914d7c000b73938f7d85 + +// # Account #5: 0xd507743abcdb265f5fcef125e3f6cf7250cfe9da (10000 ETH) +// # Private Key #5: 0x6a944885ff4551fd546c59a2322a967af9906f596f60ecd110505c278f464f6e + +// # Account #6: 0x9af7a0d34fcf09a735ddaa03adc825398a6557ae (10000 ETH) +// # Private Key #6: 0x4299ee99407089bfc51e829734c0f6c1b366f515d5ddb5ece4f880a2f8fd430c + +// # Account #7: 0x2154cdc3632db21a2635819afa450f2dda08aebd (10000 ETH) +// # Private Key #7: 0x0e7ee7881e497062427ed392d310f09ca993fa964040c751cc383c10f55efc7c + +// # Account #8: 0x73c8825893ba6b197f883f60a20b4926c0f32a2c (10000 ETH) +// # Private Key #8: 0xd84954f2cea66fd01a872496f25ddb86db79ee81366609fbcff8087c9739b63a + +// # Account #9: 0x73d2888f96bc0eb530490ca5342d0c274d95654d (10000 ETH) +// # Private Key #9: 0xd20a2f6a6656d291ca4c4e6121b479db81b3b281e64707ff4a068acf549dc03c + +// # Account #10: 0xf8a3681248934f1139be67e0c22a6af450eb9d7c (10000 ETH) +// # Private Key #10: 0x8188d555d06262bfa3a343fa809b59b6368f02aa5a1ac5a3d2cb24e18e2b556e + const INFURA_API_KEY = process.env.REACT_APP_KEY_INFURA_API_KEY; const ALCHEMY_API_KEY = process.env.REACT_APP_KEY_ALCHEMY_API_KEY || ''; @@ -21,9 +58,9 @@ module.exports = { 'dxdao-contracts/contracts/dxdao/DxToken.sol', 'dxdao-contracts/contracts/dxvote/DXDVotingMachine.sol', 'dxdao-contracts/contracts/dxvote/WalletScheme.sol', - 'dxdao-contracts/contracts/dxvote/PermissionRegistry.sol', - 'dxdao-contracts/contracts/dxvote/utils/DXDVestingFactory.sol', - 'dxdao-contracts/contracts/dxvote/utils/DXdaoNFT.sol', + 'dxdao-contracts/contracts/dxvote/utils/ERC20VestingFactory.sol', + 'dxdao-contracts/contracts/dxvote/utils/ERC721Factory.sol', + 'dxdao-contracts/contracts/utils/PermissionRegistry.sol', 'dxdao-contracts/contracts/utils/Multicall.sol', 'dxdao-contracts/contracts/test/ERC20Mock.sol', 'dxdao-contracts/contracts/daostack/universalSchemes/ContributionReward.sol', diff --git a/package.json b/package.json index ee8cd1bd72..596878f769 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,10 @@ }, "dependencies": { "@0xsequence/multicall": "^0.35.5", + "@dnd-kit/core": "^5.0.3", + "@dnd-kit/modifiers": "^5.0.0", + "@dnd-kit/sortable": "^6.0.1", + "@dnd-kit/utilities": "^3.1.0", "@nomiclabs/hardhat-truffle5": "^2.0.5", "@nomiclabs/hardhat-web3": "^2.0.0", "@testing-library/jest-dom": "^5.16.2", @@ -54,7 +58,7 @@ "bignumber.js": "^9.0.2", "content-hash": "^2.5.2", "copy-to-clipboard": "^3.3.1", - "dxdao-contracts": "https://github.com/DXGovernance/dxdao-contracts.git#develop", + "dxdao-contracts": "https://github.com/DXGovernance/dxdao-contracts.git#12ea7f4e1ea070699d148d8c4ced4aaa88f08627", "eslint-plugin-cypress": "^2.12.1", "ether-swr": "^2.0.1", "ethers": "^5.5.2", diff --git a/scripts/dev.sh b/scripts/dev.sh index 081b959ad0..31429b2661 100755 --- a/scripts/dev.sh +++ b/scripts/dev.sh @@ -12,7 +12,7 @@ cleanup() { kill -9 $hardhat_pid fi } -mnemonic="dxdao dxdao dxdao dxdao dxdao dxdao dxdao dxdao dxdao dxdao dxdao dxdao" +mnemonic="cream core pear sure dinner indoor citizen divorce sudden captain subject remember" hardhat_running() { nc -z localhost 8545 @@ -20,67 +20,9 @@ hardhat_running() { start-hardhat_node() { - npx hardhat node > /dev/null & + yarn hardhat compile - # Account #0: 0x79706c8e413cdaee9e63f282507287b9ea9c0928 (10000 ETH) - # Private Key: 0xe408e147b1335674887c1ac7dc3c45de9762aa824cf6255fd8bd61fecf15f021 - # - # Account #1: 0xc73480525e9d1198d448ece4a01daea851f72a9d (10000 ETH) - # Private Key: 0x6c8a6a9a7dbad13d6b41089648ae4b7971116611e4acd8f052c478dd8c62673e - # - # Account #2: 0x3f943f38b2fbe1ee5daf0516cecfe4e0f8734351 (10000 ETH) - # Private Key: 0x0054b824c2083e7db09f36edb2ab24eb31f8276fa6cd62e30b42e3a185b37179 - # - # Account #3: 0xaf1a6415202453d79b84d8a9d055b8f9141f696b (10000 ETH) - # Private Key: 0x3688ff0d0a95dd8b90bc68739505b488ff4908291eeb36380a94227d22653ce3 - # - # Account #4: 0x02803e2cdff171d1910d178dac948c711937bd3f (10000 ETH) - # Private Key: 0x530caa05cf058253ed14a2e6ccc5dcb952f09c7bcdcfb4be37e572e85dcafd1e - # - # Account #5: 0x797c62953ef866a1ece855c4033cc5dc3c11290b (10000 ETH) - # Private Key: 0x88ae5259286213d051e99da743d79be5d69dc75ee317bc887f5996ff004b83a6 - # - # Account #6: 0x016f70459e4ba98e0d60a5f5855e117e8ff39cae (10000 ETH) - # Private Key: 0x68f5bc4b52a67b3d800d0d8832ae3b89067a3bbee68c66544147e312d996d994 - # - # Account #7: 0x073f4fdc12f805b8d98e58551fc10d0a71bbc7db (10000 ETH) - # Private Key: 0x9adc9dfdce8383a1634716bf7a2e317a145f37a176a7b42538ace5ac20e223a1 - # - # Account #8: 0x6829556f30899d70403947590ffe8900e8c0d0d7 (10000 ETH) - # Private Key: 0x13436bc37e24487c2f1739d1ce6b8271a8465fee93aa3685ce543e56a50e1692 - # - # Account #9: 0x2b410bcb3b8096164fa8c6a06220a66bfb77058d (10000 ETH) - # Private Key: 0x4fe097bbfe75d9531d253b7f917f89dcee6664d832e40a8d82d717602dfeeb6c - # - # Account #10: 0x309f75c54a57937a7a0c6eb9b36eb1dbca82407e (10000 ETH) - # Private Key: 0xb10da35e4abe181d1143aa28a7da6c5f303660b954cf283accfeba2dfb56ab51 - # - # Account #11: 0xec9d2d34ad6acda19ab8afe71595051b206e3e4d (10000 ETH) - # Private Key: 0xfdf0c66289fafea1803c64522d073d6cc9ec71ba76e945a7c207f1f5ebb8e3b1 - # - # Account #12: 0x40c23c536bad1fe206ce911114f2c70309a7e487 (10000 ETH) - # Private Key: 0x97c63b257e8f86e05ae5a7bbb025b02d179b8d00fb9fbcdbfcdf04dcf9173cf2 - # - # Account #13: 0x28d254f2ddb522c43a21d338e337fd8d2f820db2 (10000 ETH) - # Private Key: 0xcdef57c095755d77bbbb327a187e67039c62fe39425e29b3646d334f54d28808 - # - # Account #14: 0xaf7386ce842cc0cffef91361059b0ca9ae48d6a0 (10000 ETH) - # Private Key: 0x4739bf3390cd5be10d0f58d2c1e887a186b544af563fa62717a6c324b36fed59 - # - # Account #15: 0x46c18451aaead6a2cb888b5bd6193c0f2c402329 (10000 ETH) - # Private Key: 0xc6b5889c8fbd0f3304ddd53b85f056a32b8338f99e5b8877ecb1d1c5543c8d6a - # - # Account #16: 0xc707c8143a6e1274ae7f637946f685870925261f (10000 ETH) - # Private Key: 0x4b00e0c8e17e88d588b204121594f14d20d1abe50e280d599ff39d6b35c44533 - # - # Account #17: 0x5b14a88dbbb04abcb6e5bf6384491be8d939cf57 (10000 ETH) - # Private Key: 0x18eecce45e3211ce6ce967f66c404798e36e8298b4b5222ebf597b841ebd868a - # - # Account #18: 0x92d356240dda25d050aa441690b92b2fa0011b84 (10000 ETH) - # Private Key: 0xe53525f97971b006e14820a8a7b74f8aae375b6635735d89b4db2e4cbdf0e8e0 - # - # Account #19: 0x5a485c203d9537095a6be2acc5a7ad83805d301d (10000 ETH) - # Private Key: 0xb86f3287c11a77c7317c2484be2bd386816876ead8ceaf86971b7b7c1afbb12b + npx hardhat node > /dev/null & hardhat_pid=$! diff --git a/scripts/dev.ts b/scripts/dev.ts index 8a249efd04..41ba430914 100644 --- a/scripts/dev.ts +++ b/scripts/dev.ts @@ -13,38 +13,174 @@ async function main() { const web3 = hre.web3; const PermissionRegistry = await hre.artifacts.require('PermissionRegistry'); const ERC20Guild = await hre.artifacts.require('ERC20Guild'); + const SnapshotERC20Guild = await hre.artifacts.require('SnapshotERC20Guild'); + const accounts = await web3.eth.getAccounts(); const deployconfig = { - reputation: [ - { - address: '0x79706c8e413cdaee9e63f282507287b9ea9c0928', - amount: 6000, - }, - { - address: '0xc73480525e9d1198d448ece4a01daea851f72a9d', - amount: 4000, - }, - { - address: '0x3f943f38b2fbe1ee5daf0516cecfe4e0f8734351', - amount: 1000, + dao: { + reputation: [ + { + address: accounts[0], + amount: 6000, + }, + { + address: accounts[1], + amount: 4000, + }, + { + address: accounts[2], + amount: 1000, + }, + ], + contributionReward: { + queuedVoteRequiredPercentage: 50, + queuedVotePeriodLimit: moment.duration(10, 'minutes').asSeconds(), + boostedVotePeriodLimit: moment.duration(3, 'minutes').asSeconds(), + preBoostedVotePeriodLimit: moment.duration(1, 'minutes').asSeconds(), + thresholdConst: 2000, + quietEndingPeriod: moment.duration(0.5, 'minutes').asSeconds(), + proposingRepReward: 10, + votersReputationLossRatio: 100, + minimumDaoBounty: web3.utils.toWei('1'), + daoBountyConst: 100, }, - ], + + walletSchemes: [ + { + name: 'RegistrarWalletScheme', + doAvatarGenericCalls: true, + maxSecondsForExecution: moment.duration(31, 'days').asSeconds(), + maxRepPercentageChange: 0, + controllerPermissions: { + canGenericCall: true, + canUpgrade: true, + canRegisterSchemes: true, + }, + permissions: [], + queuedVoteRequiredPercentage: 75, + boostedVoteRequiredPercentage: 5 * 100, + queuedVotePeriodLimit: moment.duration(15, 'minutes').asSeconds(), + boostedVotePeriodLimit: moment.duration(5, 'minutes').asSeconds(), + preBoostedVotePeriodLimit: moment.duration(2, 'minutes').asSeconds(), + thresholdConst: 2000, + quietEndingPeriod: moment.duration(1, 'minutes').asSeconds(), + proposingRepReward: 0, + votersReputationLossRatio: 100, + minimumDaoBounty: web3.utils.toWei('10'), + daoBountyConst: 100, + }, + { + name: 'MasterWalletScheme', + doAvatarGenericCalls: true, + maxSecondsForExecution: moment.duration(31, 'days').asSeconds(), + maxRepPercentageChange: 40, + controllerPermissions: { + canGenericCall: true, + canUpgrade: false, + canChangeConstraints: false, + canRegisterSchemes: false, + }, + permissions: [ + { + asset: '0x0000000000000000000000000000000000000000', + to: 'DXDVotingMachine', + functionSignature: '0xaaaaaaaa', + value: + '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', + allowed: true, + }, + { + asset: '0x0000000000000000000000000000000000000000', + to: 'RegistrarWalletScheme', + functionSignature: '0xaaaaaaaa', + value: + '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', + allowed: true, + }, + { + asset: '0x0000000000000000000000000000000000000000', + to: 'ITSELF', + functionSignature: '0xaaaaaaaa', + value: + '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', + allowed: true, + }, + ], + queuedVoteRequiredPercentage: 50, + boostedVoteRequiredPercentage: 2 * 100, + queuedVotePeriodLimit: moment.duration(10, 'minutes').asSeconds(), + boostedVotePeriodLimit: moment.duration(3, 'minutes').asSeconds(), + preBoostedVotePeriodLimit: moment.duration(1, 'minutes').asSeconds(), + thresholdConst: 1500, + quietEndingPeriod: moment.duration(0.5, 'minutes').asSeconds(), + proposingRepReward: 0, + votersReputationLossRatio: 5, + minimumDaoBounty: web3.utils.toWei('1'), + daoBountyConst: 10, + }, + { + name: 'QuickWalletScheme', + doAvatarGenericCalls: false, + maxSecondsForExecution: moment.duration(31, 'days').asSeconds(), + maxRepPercentageChange: 1, + controllerPermissions: { + canGenericCall: false, + canUpgrade: false, + canChangeConstraints: false, + canRegisterSchemes: false, + }, + permissions: [ + { + asset: '0x0000000000000000000000000000000000000000', + to: '0xaAaAaAaaAaAaAaaAaAAAAAAAAaaaAaAaAaaAaaAa', + functionSignature: '0xaaaaaaaa', + value: + '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', + allowed: true, + }, + { + asset: 'DXD', + to: '0xaAaAaAaaAaAaAaaAaAAAAAAAAaaaAaAaAaaAaaAa', + functionSignature: '0xaaaaaaaa', + value: + '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', + allowed: true, + }, + ], + queuedVoteRequiredPercentage: 50, + boostedVoteRequiredPercentage: 10 * 100, + queuedVotePeriodLimit: moment.duration(5, 'minutes').asSeconds(), + boostedVotePeriodLimit: moment.duration(1, 'minutes').asSeconds(), + preBoostedVotePeriodLimit: moment + .duration(0.5, 'minutes') + .asSeconds(), + thresholdConst: 1300, + quietEndingPeriod: moment.duration(0.5, 'minutes').asSeconds(), + proposingRepReward: 0, + votersReputationLossRatio: 10, + minimumDaoBounty: web3.utils.toWei('0.1'), + daoBountyConst: 10, + }, + ], + }, tokens: [ { name: 'DXDao on localhost', symbol: 'DXD', + type: 'ERC20', + decimals: 18, distribution: [ { - address: '0x79706c8e413cdaee9e63f282507287b9ea9c0928', + address: accounts[0], amount: web3.utils.toWei('220'), }, { - address: '0xc73480525e9d1198d448ece4a01daea851f72a9d', + address: accounts[1], amount: web3.utils.toWei('50'), }, { - address: '0x3f943f38b2fbe1ee5daf0516cecfe4e0f8734351', + address: accounts[2], amount: web3.utils.toWei('10'), }, ], @@ -52,172 +188,50 @@ async function main() { { name: 'REPGuildToken', symbol: 'RGT', + type: 'ERC20SnapshotRep', + decimals: 18, distribution: [ { - address: '0x79706c8e413cdaee9e63f282507287b9ea9c0928', - amount: 1000, + address: accounts[0], + amount: web3.utils.toWei('200'), }, { - address: '0xc73480525e9d1198d448ece4a01daea851f72a9d', - amount: 4000, + address: accounts[1], + amount: web3.utils.toWei('50'), }, { - address: '0x3f943f38b2fbe1ee5daf0516cecfe4e0f8734351', - amount: 10000, + address: accounts[2], + amount: web3.utils.toWei('10'), }, ], }, { name: 'Snapshot Guild Token', symbol: 'SGT', + type: 'ERC20', + decimals: 18, distribution: [ { - address: '0x79706c8e413cdaee9e63f282507287b9ea9c0928', - amount: 1000, + address: accounts[0], + amount: web3.utils.toWei('200'), }, { - address: '0xc73480525e9d1198d448ece4a01daea851f72a9d', - amount: 4000, + address: accounts[1], + amount: web3.utils.toWei('40'), }, { - address: '0x3f943f38b2fbe1ee5daf0516cecfe4e0f8734351', - amount: 10000, + address: accounts[2], + amount: web3.utils.toWei('10'), }, ], }, ], - permissionRegistryDelay: moment.duration(10, 'minutes').asSeconds(), - - contributionReward: { - queuedVoteRequiredPercentage: 50, - queuedVotePeriodLimit: moment.duration(10, 'minutes').asSeconds(), - boostedVotePeriodLimit: moment.duration(3, 'minutes').asSeconds(), - preBoostedVotePeriodLimit: moment.duration(1, 'minutes').asSeconds(), - thresholdConst: 2000, - quietEndingPeriod: moment.duration(0.5, 'minutes').asSeconds(), - proposingRepReward: 10, - votersReputationLossRatio: 100, - minimumDaoBounty: web3.utils.toWei('1'), - daoBountyConst: 100, + guildRegistry: { + address: ZERO_ADDRESS, + owner: 'Avatar', }, - walletSchemes: [ - { - name: 'RegistrarWalletScheme', - doAvatarGenericCalls: true, - maxSecondsForExecution: moment.duration(31, 'days').asSeconds(), - maxRepPercentageChange: 0, - controllerPermissions: { - canGenericCall: true, - canUpgrade: true, - canRegisterSchemes: true, - }, - permissions: [], - queuedVoteRequiredPercentage: 75, - boostedVoteRequiredPercentage: 5 * 100, - queuedVotePeriodLimit: moment.duration(15, 'minutes').asSeconds(), - boostedVotePeriodLimit: moment.duration(5, 'minutes').asSeconds(), - preBoostedVotePeriodLimit: moment.duration(2, 'minutes').asSeconds(), - thresholdConst: 2000, - quietEndingPeriod: moment.duration(1, 'minutes').asSeconds(), - proposingRepReward: 0, - votersReputationLossRatio: 100, - minimumDaoBounty: web3.utils.toWei('10'), - daoBountyConst: 100, - }, - { - name: 'MasterWalletScheme', - doAvatarGenericCalls: true, - maxSecondsForExecution: moment.duration(31, 'days').asSeconds(), - maxRepPercentageChange: 40, - controllerPermissions: { - canGenericCall: true, - canUpgrade: false, - canChangeConstraints: false, - canRegisterSchemes: false, - }, - permissions: [ - { - asset: '0x0000000000000000000000000000000000000000', - to: 'DXDVotingMachine', - functionSignature: '0xaaaaaaaa', - value: - '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', - allowed: true, - }, - { - asset: '0x0000000000000000000000000000000000000000', - to: 'RegistrarWalletScheme', - functionSignature: '0xaaaaaaaa', - value: - '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', - allowed: true, - }, - { - asset: '0x0000000000000000000000000000000000000000', - to: 'ITSELF', - functionSignature: '0xaaaaaaaa', - value: - '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', - allowed: true, - }, - ], - queuedVoteRequiredPercentage: 50, - boostedVoteRequiredPercentage: 2 * 100, - queuedVotePeriodLimit: moment.duration(10, 'minutes').asSeconds(), - boostedVotePeriodLimit: moment.duration(3, 'minutes').asSeconds(), - preBoostedVotePeriodLimit: moment.duration(1, 'minutes').asSeconds(), - thresholdConst: 1500, - quietEndingPeriod: moment.duration(0.5, 'minutes').asSeconds(), - proposingRepReward: 0, - votersReputationLossRatio: 5, - minimumDaoBounty: web3.utils.toWei('1'), - daoBountyConst: 10, - }, - { - name: 'QuickWalletScheme', - doAvatarGenericCalls: false, - maxSecondsForExecution: moment.duration(31, 'days').asSeconds(), - maxRepPercentageChange: 1, - controllerPermissions: { - canGenericCall: false, - canUpgrade: false, - canChangeConstraints: false, - canRegisterSchemes: false, - }, - permissions: [ - { - asset: '0x0000000000000000000000000000000000000000', - to: '0xaAaAaAaaAaAaAaaAaAAAAAAAAaaaAaAaAaaAaaAa', - functionSignature: '0xaaaaaaaa', - value: - '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', - allowed: true, - }, - { - asset: 'DXD', - to: '0xaAaAaAaaAaAaAaaAaAAAAAAAAaaaAaAaAaaAaaAa', - functionSignature: '0xaaaaaaaa', - value: - '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', - allowed: true, - }, - ], - queuedVoteRequiredPercentage: 50, - boostedVoteRequiredPercentage: 10 * 100, - queuedVotePeriodLimit: moment.duration(5, 'minutes').asSeconds(), - boostedVotePeriodLimit: moment.duration(1, 'minutes').asSeconds(), - preBoostedVotePeriodLimit: moment.duration(0.5, 'minutes').asSeconds(), - thresholdConst: 1300, - quietEndingPeriod: moment.duration(0.5, 'minutes').asSeconds(), - proposingRepReward: 0, - votersReputationLossRatio: 10, - minimumDaoBounty: web3.utils.toWei('0.1'), - daoBountyConst: 10, - }, - ], - guilds: [ { token: 'DXD', @@ -248,7 +262,7 @@ async function main() { { token: 'SGT', contractName: 'SnapshotERC20Guild', - name: 'SnapshotGuild', + name: 'SnapshotERC20Guild', proposalTime: moment.duration(5, 'minutes').asSeconds(), timeForExecution: moment.duration(2, 'minutes').asSeconds(), votingPowerForProposalExecution: '50', @@ -260,12 +274,11 @@ async function main() { }, ], - startTimestampForActions: moment().subtract(26, 'minutes').unix(), - actions: [ { + timestamp: moment().subtract(26, 'minutes').unix(), type: 'transfer', - from: '0x79706c8e413cdaee9e63f282507287b9ea9c0928', + from: accounts[0], data: { asset: ZERO_ADDRESS, address: 'Avatar', @@ -274,7 +287,7 @@ async function main() { }, { type: 'transfer', - from: '0x79706c8e413cdaee9e63f282507287b9ea9c0928', + from: accounts[0], data: { asset: 'DXD', address: 'Avatar', @@ -284,7 +297,7 @@ async function main() { { type: 'transfer', - from: '0x79706c8e413cdaee9e63f282507287b9ea9c0928', + from: accounts[0], data: { asset: ZERO_ADDRESS, address: 'DXDGuild', @@ -293,7 +306,7 @@ async function main() { }, { type: 'transfer', - from: '0x79706c8e413cdaee9e63f282507287b9ea9c0928', + from: accounts[0], data: { asset: 'DXD', address: 'DXDGuild', @@ -303,7 +316,26 @@ async function main() { { type: 'transfer', - from: '0x79706c8e413cdaee9e63f282507287b9ea9c0928', + from: accounts[1], + data: { + asset: ZERO_ADDRESS, + address: 'SnapshotERC20Guild', + amount: web3.utils.toWei('10'), + }, + }, + { + type: 'transfer', + from: accounts[1], + data: { + asset: 'SGT', + address: 'SnapshotERC20Guild', + amount: web3.utils.toWei('10'), + }, + }, + + { + type: 'transfer', + from: accounts[0], data: { asset: ZERO_ADDRESS, address: 'REPGuild', @@ -313,12 +345,12 @@ async function main() { { type: 'proposal', - from: '0x3f943f38b2fbe1ee5daf0516cecfe4e0f8734351', + from: accounts[2], data: { to: ['PermissionRegistry'], callData: [ new web3.eth.Contract(PermissionRegistry.abi).methods - .setAdminPermission( + .setPermission( ZERO_ADDRESS, '0xE0FC07f3aC4F6AF1463De20eb60Cf1A764E259db', '0x1A0370A6f5b6cE96B1386B208a8519552eb714D9', @@ -335,9 +367,18 @@ async function main() { scheme: 'MasterWalletScheme', }, }, + { + type: 'approve', + from: accounts[2], + data: { + asset: 'DXD', + address: 'DXDVotingMachine', + amount: web3.utils.toWei('101'), + }, + }, { type: 'stake', - from: '0x3f943f38b2fbe1ee5daf0516cecfe4e0f8734351', + from: accounts[2], data: { proposal: '0', decision: '1', @@ -346,8 +387,8 @@ async function main() { }, { type: 'vote', - time: moment.duration(1, 'minutes').asSeconds(), - from: '0x3f943f38b2fbe1ee5daf0516cecfe4e0f8734351', + increaseTime: moment.duration(1, 'minutes').asSeconds(), + from: accounts[2], data: { proposal: '0', decision: '1', @@ -356,15 +397,15 @@ async function main() { }, { type: 'execute', - time: moment.duration(3, 'minutes').asSeconds(), - from: '0x3f943f38b2fbe1ee5daf0516cecfe4e0f8734351', + increaseTime: moment.duration(3, 'minutes').asSeconds(), + from: accounts[2], data: { proposal: '0', }, }, { type: 'redeem', - from: '0x3f943f38b2fbe1ee5daf0516cecfe4e0f8734351', + from: accounts[2], data: { proposal: '0', }, @@ -372,7 +413,7 @@ async function main() { { type: 'proposal', - from: '0x3f943f38b2fbe1ee5daf0516cecfe4e0f8734351', + from: accounts[2], data: { to: ['QuickWalletScheme'], callData: ['0x0'], @@ -383,9 +424,18 @@ async function main() { scheme: 'MasterWalletScheme', }, }, + { + type: 'approve', + from: accounts[2], + data: { + asset: 'DXD', + address: 'DXDVotingMachine', + amount: web3.utils.toWei('101'), + }, + }, { type: 'stake', - from: '0x3f943f38b2fbe1ee5daf0516cecfe4e0f8734351', + from: accounts[2], data: { proposal: '1', decision: '1', @@ -394,8 +444,8 @@ async function main() { }, { type: 'vote', - time: moment.duration(1, 'minutes').asSeconds(), - from: '0x3f943f38b2fbe1ee5daf0516cecfe4e0f8734351', + increaseTime: moment.duration(1, 'minutes').asSeconds(), + from: accounts[2], data: { proposal: '1', decision: '1', @@ -404,7 +454,7 @@ async function main() { }, { type: 'vote', - from: '0xc73480525e9d1198d448ece4a01daea851f72a9d', + from: accounts[1], data: { proposal: '1', decision: '2', @@ -414,9 +464,9 @@ async function main() { { type: 'proposal', - from: '0x3f943f38b2fbe1ee5daf0516cecfe4e0f8734351', + from: accounts[2], data: { - to: ['0x3f943f38b2fbe1ee5daf0516cecfe4e0f8734351'], + to: [accounts[2]], callData: ['0x0'], value: [web3.utils.toWei('1.5')], title: 'Proposal Test #2', @@ -429,7 +479,7 @@ async function main() { { type: 'approve', - from: '0x79706c8e413cdaee9e63f282507287b9ea9c0928', + from: accounts[0], data: { asset: 'DXD', address: 'DXDGuild-vault', @@ -438,7 +488,7 @@ async function main() { }, { type: 'guild-lockTokens', - from: '0x79706c8e413cdaee9e63f282507287b9ea9c0928', + from: accounts[0], data: { guildName: 'DXDGuild', amount: web3.utils.toWei('100'), @@ -446,16 +496,16 @@ async function main() { }, { type: 'guild-withdrawTokens', - from: '0x79706c8e413cdaee9e63f282507287b9ea9c0928', + increaseTime: moment.duration(10, 'minutes').asSeconds() + 1, + from: accounts[0], data: { guildName: 'DXDGuild', amount: web3.utils.toWei('10'), }, - time: moment.duration(10, 'minutes').asSeconds() + 1, }, { type: 'guild-createProposal', - from: '0x79706c8e413cdaee9e63f282507287b9ea9c0928', + from: accounts[0], data: { guildName: 'DXDGuild', to: ['DXDGuild'], @@ -479,7 +529,7 @@ async function main() { }, { type: 'guild-voteProposal', - from: '0xc73480525e9d1198d448ece4a01daea851f72a9d', + from: accounts[1], data: { guildName: 'DXDGuild', proposal: 0, @@ -488,20 +538,45 @@ async function main() { }, }, { - time: moment.duration(10, 'minutes').asSeconds(), type: 'guild-endProposal', - from: '0xc73480525e9d1198d448ece4a01daea851f72a9d', + increaseTime: moment.duration(10, 'minutes').asSeconds(), + from: accounts[1], data: { guildName: 'DXDGuild', proposal: 0, }, }, + { + type: 'guild-createProposal', + from: accounts[1], + data: { + guildName: 'SnapshotERC20Guild', + to: ['SnapshotERC20Guild'], + callData: [ + new web3.eth.Contract(SnapshotERC20Guild.abi).methods + .setPermission( + [ZERO_ADDRESS], + [ANY_ADDRESS], + [ANY_FUNC_SIGNATURE], + [web3.utils.toWei('5').toString()], + [true] + ) + .encodeABI(), + ], + value: ['0'], + totalActions: '1', + title: 'Proposal Test #1 to SnapshotERC20Guild', + description: + 'Allow call any address and function and send a max of 5 ETH per proposal', + }, + }, + { type: 'proposal', - from: '0x3f943f38b2fbe1ee5daf0516cecfe4e0f8734351', + from: accounts[2], data: { - to: ['0x3f943f38b2fbe1ee5daf0516cecfe4e0f8734351'], + to: [accounts[2]], callData: ['0x0'], value: [web3.utils.toWei('1.5')], title: 'Proposal Test #3', @@ -514,7 +589,7 @@ async function main() { ], }; - const { networkContracts, addresses } = await hre.run('deploy-dxvote', { + const networkContracts = await hre.run('deploy-dxdao-contracts', { deployconfig: JSON.stringify(deployconfig), }); @@ -573,7 +648,7 @@ async function main() { ], tokens: [ { - address: addresses.DXD, + address: networkContracts.addresses.DXD, name: 'DXdao on Localhost', decimals: 18, symbol: 'DXD', @@ -581,14 +656,36 @@ async function main() { logoURI: 'https://s2.coinmarketcap.com/static/img/coins/200x200/5589.png', }, + { + address: networkContracts.addresses.RGT, + name: 'REP Guild Token on Localhost', + decimals: 18, + symbol: 'RGT', + fetchPrice: true, + logoURI: + 'https://s2.coinmarketcap.com/static/img/coins/200x200/5589.png', + }, + { + address: networkContracts.addresses.SGT, + name: 'Snapshot Guild Token on Localhost', + decimals: 18, + symbol: 'SGT', + fetchPrice: true, + logoURI: + 'https://s2.coinmarketcap.com/static/img/coins/200x200/5589.png', + }, + ], + guilds: [ + networkContracts.addresses.DXDGuild, + networkContracts.addresses.REPGuild, + networkContracts.addresses.SnapshotGuild, ], - guilds: [addresses.DXDGuild, addresses.REPGuild, addresses.SnapshotGuild], }; mkdirSync(path.resolve(__dirname, '../src/configs/localhost'), { recursive: true, }); - await writeFileSync( + writeFileSync( path.resolve(__dirname, '../src/configs/localhost/config.json'), JSON.stringify(developConfig, null, 2) ); diff --git a/src/assets/images/info.svg b/src/assets/images/info.svg new file mode 100644 index 0000000000..6826fa3923 --- /dev/null +++ b/src/assets/images/info.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/assets/images/mint.svg b/src/assets/images/mint.svg new file mode 100644 index 0000000000..7ea9104747 --- /dev/null +++ b/src/assets/images/mint.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/Guilds/ActionsBuilder/Action/ViewMode.tsx b/src/components/Guilds/ActionsBuilder/Action.tsx similarity index 50% rename from src/components/Guilds/ActionsBuilder/Action/ViewMode.tsx rename to src/components/Guilds/ActionsBuilder/Action.tsx index 418c4332e4..eb4ce58bac 100644 --- a/src/components/Guilds/ActionsBuilder/Action/ViewMode.tsx +++ b/src/components/Guilds/ActionsBuilder/Action.tsx @@ -1,16 +1,28 @@ import styled, { css } from 'styled-components'; +import { CSS } from '@dnd-kit/utilities'; import { Box } from 'components/Guilds/common/Layout'; import { CardWrapper, Header } from 'components/Guilds/common/Card'; import { FiChevronDown, FiChevronUp } from 'react-icons/fi'; import { useState } from 'react'; import { Button } from 'components/Guilds/common/Button'; import { useDecodedCall } from 'hooks/Guilds/contracts/useDecodedCall'; -import { getInfoLineView, getSummaryView } from '../SupportedActions'; -import CallDetails from '../CallDetails'; -import { Call } from '../types'; +import { getInfoLineView, getSummaryView } from './SupportedActions'; +import CallDetails from './CallDetails'; +import { Call, DecodedAction } from './types'; +import Grip from './common/Grip'; +import EditButton from './common/EditButton'; +import { useSortable } from '@dnd-kit/sortable'; const CardWrapperWithMargin = styled(CardWrapper)` + position: relative; + background-color: ${({ theme }) => theme.colors.background}; margin-top: 0.8rem; + border: 1px solid; + border-color: ${({ dragging, theme }) => + dragging ? theme.colors.text : theme.colors.muted}; + z-index: ${({ dragging }) => (dragging ? 999 : 'initial')}; + box-shadow: ${({ dragging }) => + dragging ? '0px 4px 8px 0px rgba(0, 0, 0, 0.2)' : 'none'}; `; const CardHeader = styled(Header)` @@ -29,10 +41,10 @@ const CardLabel = styled(Box)` const ChevronIcon = styled.span` cursor: pointer; - height: 1.25rem; - width: 1.25rem; + height: 1.4rem; + width: 1.4rem; border-radius: 50%; - border: 1px solid ${({ theme }) => theme.colors.proposalText.grey}; + border: 1px solid ${({ theme }) => theme.colors.muted}; display: inline-flex; justify-content: center; align-items: center; @@ -64,12 +76,44 @@ const TabButton = styled(Button)` `} `; +const GripWithMargin = styled(Grip)` + margin-right: 1rem; +`; + +const EditButtonWithMargin = styled(EditButton)` + margin-right: 0.625rem; +`; + +const CardActions = styled.div` + display: flex; + justify-content: center; + align-items: center; +`; + interface ActionViewProps { - call: Call; + call?: Call; + decodedAction?: DecodedAction; + isEditable?: boolean; + onEdit?: (updatedCall: DecodedAction) => void; } -const ActionView: React.FC = ({ call }) => { - const { decodedCall } = useDecodedCall(call); +const ActionRow: React.FC = ({ + call, + decodedAction, + isEditable, +}) => { + const { + attributes, + listeners, + setNodeRef, + transform, + transition, + isDragging, + } = useSortable({ id: decodedAction?.id, disabled: !isEditable }); + + const { decodedCall: decodedCallFromCall } = useDecodedCall(call); + + const decodedCall = decodedCallFromCall || decodedAction.decodedCall; const [expanded, setExpanded] = useState(false); const [activeTab, setActiveTab] = useState(0); @@ -78,19 +122,34 @@ const ActionView: React.FC = ({ call }) => { const InfoLine = getInfoLineView(decodedCall?.callType); const ActionSummary = getSummaryView(decodedCall?.callType); + const dndStyles = { + transform: CSS.Translate.toString(transform), + transition, + }; + return ( - + - {InfoLine && } + {isEditable && } + + {InfoLine && } - setExpanded(!expanded)}> - {expanded ? ( - - ) : ( - - )} - + + {isEditable && Edit} + setExpanded(!expanded)}> + {expanded ? ( + + ) : ( + + )} + + {expanded && ( @@ -115,13 +174,13 @@ const ActionView: React.FC = ({ call }) => { {ActionSummary && activeTab === 0 && ( - + )} {(!ActionSummary || activeTab === 1) && ( - + )} @@ -130,4 +189,4 @@ const ActionView: React.FC = ({ call }) => { ); }; -export default ActionView; +export default ActionRow; diff --git a/src/components/Guilds/ActionsBuilder/Action/EditMode.tsx b/src/components/Guilds/ActionsBuilder/Action/EditMode.tsx deleted file mode 100644 index 57abe5084f..0000000000 --- a/src/components/Guilds/ActionsBuilder/Action/EditMode.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { getEditor } from '../SupportedActions'; -import { DecodedAction, DecodedCall } from '../types'; - -interface ActionEditorProps { - action: DecodedAction; - onChange: (updatedCall: DecodedAction) => void; -} - -const ActionEditor: React.FC = ({ action, onChange }) => { - const Editor = getEditor(action?.decodedCall?.callType); - - const updateCall = (updatedCall: DecodedCall) => { - onChange({ ...action, decodedCall: updatedCall }); - }; - - return ( - - ); -}; - -export default ActionEditor; diff --git a/src/components/Guilds/ActionsBuilder/EditMode.tsx b/src/components/Guilds/ActionsBuilder/EditMode.tsx deleted file mode 100644 index 97f8981dcd..0000000000 --- a/src/components/Guilds/ActionsBuilder/EditMode.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import styled from 'styled-components'; -import { Divider } from '../common/Divider'; -import { Box } from '../common/Layout'; -import OptionEditMode from './Option/EditMode'; -import AddButton from './common/AddButton'; -import { Option } from './types'; - -const AddOptionWrapper = styled(Box)` - padding: 1rem; -`; -interface EditModeProps { - options: Option[]; - onChange: (options: Option[]) => void; -} - -const EditMode: React.FC = ({ options, onChange }) => { - function addOption() { - onChange([ - ...options, - { - index: options.length, - label: `Option ${options.length + 1}`, - decodedActions: [], - }, - ]); - } - - function updateOption(index: number, option: Option) { - onChange(options.map((o, i) => (i === index ? option : o))); - } - - return ( - <> - {options?.map((option, idx) => ( - <> - updateOption(idx, updatedOption)} - /> - {idx !== options.length - 1 && } - - ))} - - - - - - - ); -}; - -export default EditMode; diff --git a/src/components/Guilds/ActionsBuilder/Option.tsx b/src/components/Guilds/ActionsBuilder/Option.tsx new file mode 100644 index 0000000000..82bce0a626 --- /dev/null +++ b/src/components/Guilds/ActionsBuilder/Option.tsx @@ -0,0 +1,162 @@ +import styled from 'styled-components'; +import { CSS } from '@dnd-kit/utilities'; + +import { ProposalOptionTag } from './common/ProposalOptionTag'; +import AddButton from './common/AddButton'; +import { DecodedAction, Option } from './types'; +import { useState } from 'react'; +import ActionModal from 'components/Guilds/ActionsModal'; +import Grip from './common/Grip'; +import DataTag from './common/DataTag'; +import EditButton from './common/EditButton'; +import ActionRow from './Action'; +import { Box } from 'components/Guilds/common/Layout'; +import { + SortableContext, + useSortable, + verticalListSortingStrategy, +} from '@dnd-kit/sortable'; + +export const OptionWrapper = styled(Box)` + position: relative; + background-color: ${({ theme }) => theme.colors.background}; + padding: 1rem; + border-top: 1px solid; + border-bottom: 1px solid; + border-color: ${({ dragging, theme }) => + dragging ? theme.colors.text : 'transparent'}; + z-index: ${({ dragging }) => (dragging ? 999 : 'initial')}; + box-shadow: ${({ dragging }) => + dragging ? '0px 4px 8px 0px rgba(0, 0, 0, 0.2)' : 'none'}; +`; + +export const DetailWrapper = styled(Box)` + padding: 0.5rem 0; + display: flex; + flex-direction: row; + justify-content: space-between; +`; + +export const Detail = styled(Box)` + display: inline-flex; + margin-right: 0.75rem; +`; + +const ActionsWrapper = styled.div` + margin-left: ${({ indented }) => (indented ? '1.75rem' : '0')}; +`; + +interface OptionRowProps { + option: Option; + isEditable?: boolean; + onChange?: (updatedOption: Option) => void; +} + +const OptionRow: React.FC = ({ + isEditable, + option, + onChange, +}) => { + const { + attributes, + listeners, + setNodeRef, + transform, + transition, + isDragging, + } = useSortable({ id: option.id }); + const [isActionsModalOpen, setIsActionsModalOpen] = useState(false); + + function addAction(action: DecodedAction) { + onChange({ + ...option, + decodedActions: [...option.decodedActions, action], + }); + } + + function updateAction(index: number, action: DecodedAction) { + const updatedActions = option?.decodedActions.map((a, i) => + index === i ? action : a + ); + onChange({ ...option, decodedActions: updatedActions }); + } + + const dndStyles = { + transform: CSS.Translate.toString(transform), + transition, + }; + + return ( + + +
+ {isEditable && ( + + + + )} + + + + + + {option?.decodedActions?.length || 'No'} on-chain{' '} + {option?.decodedActions?.length >= 2 ? 'actions' : 'action'} + + +
+ {isEditable && ( +
+ Edit +
+ )} +
+ + + {!isEditable && + option?.actions?.map((action, index) => ( + + ))} + + {isEditable && ( + action.id)} + strategy={verticalListSortingStrategy} + > + {option?.decodedActions?.map((action, index) => ( + updateAction(index, updatedAction)} + /> + ))} + + )} + + {isEditable && ( + setIsActionsModalOpen(true)} + /> + )} + + + { + addAction(action); + setIsActionsModalOpen(false); + }} + /> +
+ ); +}; + +export default OptionRow; diff --git a/src/components/Guilds/ActionsBuilder/Option/EditMode.tsx b/src/components/Guilds/ActionsBuilder/Option/EditMode.tsx deleted file mode 100644 index 8c460cb78d..0000000000 --- a/src/components/Guilds/ActionsBuilder/Option/EditMode.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { ProposalOptionTag } from '../common/ProposalOptionTag'; -import AddButton from '../common/AddButton'; -import ActionEditor from '../Action/EditMode'; -import { ActionCountLabel, DetailWrapper, OptionWrapper } from './styles'; -import { DecodedAction, Option } from '../types'; -import { useState } from 'react'; -import ActionModal from 'components/Guilds/ActionsModal'; - -interface OptionRowProps { - option: Option; - onChange?: (updatedOption: Option) => void; -} - -const OptionEditMode: React.FC = ({ option, onChange }) => { - const [isActionsModalOpen, setIsActionsModalOpen] = useState(false); - - function addAction(action: DecodedAction) { - onChange({ - ...option, - decodedActions: [...option.decodedActions, action], - }); - } - - function updateAction(index: number, action: DecodedAction) { - const updatedActions = option?.decodedActions.map((a, i) => - index === i ? action : a - ); - onChange({ ...option, decodedActions: updatedActions }); - } - - return ( - - - - - {option?.actions?.length || 'No'} on-chain{' '} - {option?.actions?.length > 2 ? 'actions' : 'action'} - - - - {option?.decodedActions?.map((action, index) => ( - updateAction(index, updatedAction)} - /> - ))} - - setIsActionsModalOpen(true)} - /> - - { - addAction(action); - setIsActionsModalOpen(false); - }} - /> - - ); -}; - -export default OptionEditMode; diff --git a/src/components/Guilds/ActionsBuilder/Option/ViewMode.tsx b/src/components/Guilds/ActionsBuilder/Option/ViewMode.tsx deleted file mode 100644 index 3e22ec2cbb..0000000000 --- a/src/components/Guilds/ActionsBuilder/Option/ViewMode.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { ProposalOptionTag } from '../common/ProposalOptionTag'; -import ActionView from '../Action/ViewMode'; -import { ActionCountLabel, DetailWrapper, OptionWrapper } from './styles'; -import { Option } from '../types'; - -interface OptionRowProps { - data: Option; -} - -const OptionViewMode: React.FC = ({ data }) => { - return ( - - - - - {data?.actions?.length || 'No'} on-chain{' '} - {data?.actions?.length >= 2 ? 'actions' : 'action'} - - - - {data?.actions?.map((action, index) => ( - - ))} - - ); -}; - -export default OptionViewMode; diff --git a/src/components/Guilds/ActionsBuilder/Option/styles.tsx b/src/components/Guilds/ActionsBuilder/Option/styles.tsx deleted file mode 100644 index 4ae71320b6..0000000000 --- a/src/components/Guilds/ActionsBuilder/Option/styles.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import styled from 'styled-components'; -import { Box } from 'components/Guilds/common/Layout'; - -export const ActionCountLabel = styled.span` - color: ${({ theme }) => theme.gray}; -`; - -export const OptionWrapper = styled(Box)` - padding: 1rem; -`; - -export const DetailWrapper = styled(Box)` - padding: 0.5rem 0; - display: flex; - flex-direction: row; - justify-content: space-between; -`; diff --git a/src/components/Guilds/ActionsBuilder/OptionsList.tsx b/src/components/Guilds/ActionsBuilder/OptionsList.tsx new file mode 100644 index 0000000000..afb900548e --- /dev/null +++ b/src/components/Guilds/ActionsBuilder/OptionsList.tsx @@ -0,0 +1,339 @@ +import styled, { useTheme } from 'styled-components'; +import { + closestCenter, + CollisionDetection, + DndContext, + DragEndEvent, + DragOverEvent, + DragStartEvent, + getFirstCollision, + MeasuringStrategy, + MouseSensor, + pointerWithin, + rectIntersection, + TouchSensor, + UniqueIdentifier, + useSensor, + useSensors, +} from '@dnd-kit/core'; +import { + arrayMove, + SortableContext, + verticalListSortingStrategy, +} from '@dnd-kit/sortable'; +import { Divider } from '../common/Divider'; +import { Box } from '../common/Layout'; +import OptionRow from './Option'; +import AddButton from './common/AddButton'; +import { DecodedAction, Option } from './types'; +import { useCallback, useEffect, useRef, useState } from 'react'; +import { + restrictToVerticalAxis, + restrictToFirstScrollableAncestor, +} from '@dnd-kit/modifiers'; + +const AddOptionWrapper = styled(Box)` + padding: 1rem; +`; + +interface OptionsListProps { + isEditable: boolean; + options: Option[]; + onChange: (options: Option[]) => void; +} + +const OptionsList: React.FC = ({ + isEditable, + options, + onChange, +}) => { + const [activeId, setActiveId] = useState(null); + const lastOverId = useRef(null); + const [clonedOptions, setClonedOptions] = useState(null); + const recentlyMovedToNewContainer = useRef(false); + + useEffect(() => { + requestAnimationFrame(() => { + recentlyMovedToNewContainer.current = false; + }); + }, [options]); + + const theme = useTheme(); + function addOption() { + onChange([ + ...options, + { + id: `option-${options.length}`, + label: `Option ${options.length + 1}`, + color: theme?.colors?.votes?.[options.length], + decodedActions: [], + }, + ]); + } + + function updateOption(index: number, option: Option) { + onChange(options.map((o, i) => (i === index ? option : o))); + } + + const sensors = useSensors(useSensor(MouseSensor), useSensor(TouchSensor)); + + const findContainer = (id: string) => { + if (options.find(option => option.id === id)) { + return id; + } + + return options + .map(option => ({ + id: option.id, + keys: option.decodedActions.map(action => action.id), + })) + .find(keyMap => keyMap.keys.includes(id))?.id; + }; + + function handleDragStart(event: DragStartEvent) { + setActiveId(event.active.id); + setClonedOptions(options); + } + + function handleDragOver({ active, over }: DragOverEvent) { + const overId = over?.id; + + if (!overId || options.find(option => option.id === active.id)) { + return; + } + + const overContainer = findContainer(overId); + const activeContainer = findContainer(active.id); + + if (!overContainer || !activeContainer) { + return; + } + + // When dragging an action between options, move the action over to the new option + if (activeContainer !== overContainer) { + const activeOption = options.find( + option => option.id === activeContainer + ); + const overOption = options.find(option => option.id === overContainer); + const activeActions = activeOption.decodedActions; + const overActions = overOption.decodedActions; + const overIndex = overActions.findIndex(item => item.id === overId); + const activeIndex = activeActions.findIndex( + item => item.id === active.id + ); + + let newIndex: number; + + if (options.find(option => option.id === overId)) { + newIndex = overActions.length + 1; + } else { + const isBelowOverItem = + over && + active.rect.current.translated && + active.rect.current.translated.top > over.rect.top + over.rect.height; + + const modifier = isBelowOverItem ? 1 : 0; + + newIndex = + overIndex >= 0 ? overIndex + modifier : overActions.length + 1; + } + + recentlyMovedToNewContainer.current = true; + + let activeAction: DecodedAction; + const updatedOptions = options + .map(option => { + if (option.id === activeContainer) { + activeAction = option.decodedActions[activeIndex]; + option.decodedActions = option.decodedActions.filter( + action => action.id !== active.id + ); + } + return option; + }) + .map(option => { + if (option.id === overContainer) { + const decodedActions = option.decodedActions; + option.decodedActions = [ + ...decodedActions.slice(0, newIndex), + activeAction, + ...decodedActions.slice(newIndex), + ]; + } + return option; + }); + onChange(updatedOptions); + } + } + + function handleDragEnd({ active, over }: DragEndEvent) { + // Reorder options + if (options.find(option => option.id === active.id) && over?.id) { + const items = [...options]; + const oldIndex = items.findIndex(item => item.id === active.id); + const newIndex = items.findIndex(item => item.id === over.id); + onChange(arrayMove(items, oldIndex, newIndex)); + } + + const activeContainer = findContainer(active.id); + if (!activeContainer) { + setActiveId(null); + return; + } + + const overContainer = over?.id ? findContainer(over?.id) : null; + if (!overContainer) { + setActiveId(null); + return; + } + + // Reorder actions + if (overContainer) { + const activeIndex = options + .find(option => option.id === activeContainer) + .decodedActions.findIndex(item => item.id === active.id); + const overIndex = options + .find(option => option.id === overContainer) + .decodedActions.findIndex(item => item.id === over.id); + + if (activeIndex !== overIndex) { + const newOptions = options.map(option => { + if (option.id === overContainer) { + option.decodedActions = arrayMove( + option.decodedActions, + activeIndex, + overIndex + ); + } + return option; + }); + + onChange(newOptions); + } + } + + setActiveId(null); + } + + const handleDragCancel = () => { + if (clonedOptions) { + // Reset items to their original state in case items have been + // Dragged across containers + onChange(clonedOptions); + } + + setActiveId(null); + setClonedOptions(null); + }; + + /** + * Custom collision detection strategy optimized for multiple containers + * + * - First, find any droppable containers intersecting with the pointer. + * - If there are none, find intersecting containers with the active draggable. + * - If there are no intersecting containers, return the last matched intersection + * + */ + const collisionDetectionStrategy: CollisionDetection = useCallback( + args => { + // --- Collision detection when dragging Options + if (activeId && options.find(option => option.id === activeId)) { + return closestCenter({ + ...args, + droppableContainers: args.droppableContainers.filter(container => + options.find(option => option.id === container.id) + ), + }); + } + + // --- Collision detection when dragging Actions + + // Start by finding any intersecting droppable + const pointerIntersections = pointerWithin(args); + const intersections = + pointerIntersections.length > 0 + ? // If there are droppables intersecting with the pointer, return those + pointerIntersections + : rectIntersection(args); + let overId = getFirstCollision(intersections, 'id'); + + if (overId != null) { + const overOption = options.find(option => option.id === overId); + if (overOption) { + const overOptionActions = overOption.decodedActions; + + // If a container is matched and it contains Actions + if (overOptionActions.length > 0) { + // Return the closest droppable within that container + overId = closestCenter({ + ...args, + droppableContainers: args.droppableContainers.filter( + container => + container.id !== overId && + overOptionActions.find(action => action.id === container.id) + ), + })[0]?.id; + } + } + + lastOverId.current = overId; + + return [{ id: overId }]; + } + + // When a draggable item moves to a new container, the layout may shift + // and the `overId` may become `null`. We manually set the cached `lastOverId` + // to the id of the draggable item that was moved to the new container, otherwise + // the previous `overId` will be returned which can cause items to incorrectly shift positions + if (recentlyMovedToNewContainer.current) { + lastOverId.current = activeId; + } + + // If no droppable is matched, return the last match + return lastOverId.current ? [{ id: lastOverId.current }] : []; + }, + [activeId, options] + ); + + return ( + + + {options?.map((option, idx) => ( + <> + updateOption(idx, updatedOption)} + isEditable={isEditable} + /> + {idx !== options.length - 1 && } + + ))} + + + {isEditable && ( + <> + + + + + + )} + + ); +}; + +export default OptionsList; diff --git a/src/components/Guilds/ActionsBuilder/SupportedActions/ERC20Transfer/ERC20TransferEditor.tsx b/src/components/Guilds/ActionsBuilder/SupportedActions/ERC20Transfer/ERC20TransferEditor.tsx index f427f20080..ff7676218d 100644 --- a/src/components/Guilds/ActionsBuilder/SupportedActions/ERC20Transfer/ERC20TransferEditor.tsx +++ b/src/components/Guilds/ActionsBuilder/SupportedActions/ERC20Transfer/ERC20TransferEditor.tsx @@ -1,36 +1,219 @@ -import { FiNavigation } from 'react-icons/fi'; -import { - EditorWrapper, - FooterWrapper, - HeaderWrapper, - IconWrapper, - TitleWrapper, -} from '../common/editor'; +import styled from 'styled-components'; +import { Input } from 'components/Guilds/common/Form/Input'; +import { FiChevronDown, FiX } from 'react-icons/fi'; +import Avatar from 'components/Guilds/Avatar'; +import { resolveUri } from 'utils/url'; +import { useWeb3React } from '@web3-react/core'; +import { useTokenList } from 'hooks/Guilds/tokens/useTokenList'; +import { useMemo, useState } from 'react'; import { ActionEditorProps } from '..'; -import Transfer from './Transfer'; -import AddButton from '../../common/AddButton'; - -const ERC20TransferEditor: React.FC = ({ - call, - contract, - updateCall, -}) => { +import { BigNumber, utils } from 'ethers'; +import { useERC20Info } from 'hooks/Guilds/ether-swr/erc20/useERC20Info'; +import useBigNumberToNumber from 'hooks/Guilds/conversions/useBigNumberToNumber'; +import useENSAvatar from 'hooks/Guilds/ether-swr/ens/useENSAvatar'; +import TokenPicker from 'components/Guilds/TokenPicker'; +import { Box } from 'components/Guilds/common/Layout'; +import { MAINNET_ID } from 'utils'; +import NumericalInput from 'components/Guilds/common/Form/NumericalInput'; +import { baseInputStyles } from 'components/Guilds/common/Form/Input'; + +const Control = styled(Box)` + display: flex; + flex-direction: column; + margin: 0.75rem 0; + width: 100%; +`; + +const ControlLabel = styled(Box)` + margin-bottom: 0.75rem; +`; + +const ControlRow = styled(Box)` + display: flex; + align-items: stretch; + height: 100%; +`; + +const Spacer = styled(Box)` + margin-right: 1rem; +`; + +const ClickableIcon = styled(Box)` + display: flex; + align-items: center; + cursor: pointer; +`; + +const TransferAmountInput = styled(NumericalInput)` + ${baseInputStyles} + display: flex; + align-items: center; + width: 100%; + &:hover, + &:focus { + border: 0.1rem solid ${({ theme }) => theme.colors.text}; + } +`; + +interface TransferState { + source: string; + tokenAddress: string; + amount: BigNumber; + destination: string; +} + +const Transfer: React.FC = ({ decodedCall, updateCall }) => { + const [isTokenPickerOpen, setIsTokenPickerOpen] = useState(false); + + const { chainId } = useWeb3React(); + + // parse transfer state from calls + const parsedData = useMemo(() => { + if (!decodedCall) return null; + return { + source: decodedCall.from, + tokenAddress: decodedCall.to, + amount: decodedCall.args._value, + destination: decodedCall.args._to, + }; + }, [decodedCall]); + + const validations = useMemo(() => { + return { + tokenAddress: utils.isAddress(parsedData?.tokenAddress), + amount: BigNumber.isBigNumber(parsedData?.amount), + destination: utils.isAddress(parsedData?.destination), + }; + }, [parsedData]); + + // Get token details from the token address + const { tokens } = useTokenList(chainId); + const token = useMemo(() => { + if (!parsedData?.tokenAddress || !tokens) return null; + + return tokens.find(({ address }) => address === parsedData.tokenAddress); + }, [tokens, parsedData]); + + const { data: tokenInfo } = useERC20Info(parsedData?.tokenAddress); + const roundedBalance = useBigNumberToNumber( + parsedData?.amount, + tokenInfo?.decimals, + 10 + ); + const { imageUrl: destinationAvatarUrl } = useENSAvatar( + parsedData?.destination, + MAINNET_ID + ); + + const setTransferAddress = (walletAddress: string) => { + updateCall({ + ...decodedCall, + args: { + ...decodedCall.args, + _to: walletAddress, + }, + }); + }; + + const setToken = (tokenAddress: string) => { + updateCall({ + ...decodedCall, + to: tokenAddress, + }); + }; + + const setAmount = (value: string) => { + const amount = value + ? utils.parseUnits(value, tokenInfo?.decimals || 18) + : null; + updateCall({ + ...decodedCall, + args: { + ...decodedCall.args, + _value: amount, + }, + }); + }; + return ( - - - - - - Transfers & Mint - - - - - - - - +
+ + Recipient + + + {validations.destination && ( + + )} +
+ } + iconRight={ + parsedData?.destination ? ( + setTransferAddress('')}> + + + ) : null + } + placeholder="Ethereum address" + onChange={e => setTransferAddress(e.target.value)} + /> + + + + + + Amount + + + + + + + + + Asset + setIsTokenPickerOpen(true)}> + + {parsedData?.tokenAddress && ( + + )} + + } + iconRight={} + readOnly + /> + + + + + setIsTokenPickerOpen(false)} + onSelect={tokenAddress => { + setToken(tokenAddress); + setIsTokenPickerOpen(false); + }} + /> + ); }; -export default ERC20TransferEditor; +export default Transfer; diff --git a/src/components/Guilds/ActionsBuilder/SupportedActions/ERC20Transfer/ERC20TransferInfoLine.tsx b/src/components/Guilds/ActionsBuilder/SupportedActions/ERC20Transfer/ERC20TransferInfoLine.tsx index 4f3e4476ca..2940510a05 100644 --- a/src/components/Guilds/ActionsBuilder/SupportedActions/ERC20Transfer/ERC20TransferInfoLine.tsx +++ b/src/components/Guilds/ActionsBuilder/SupportedActions/ERC20Transfer/ERC20TransferInfoLine.tsx @@ -9,20 +9,17 @@ import { MAINNET_ID, shortenAddress } from 'utils'; import { ActionViewProps } from '..'; import { Segment } from '../common/infoLine'; -const ERC20TransferInfoLine: React.FC = ({ - call, - decodedCall, -}) => { +const ERC20TransferInfoLine: React.FC = ({ decodedCall }) => { const parsedData = useMemo(() => { - if (!call || !decodedCall) return null; + if (!decodedCall) return null; return { - tokenAddress: call.to, + tokenAddress: decodedCall.to, amount: BigNumber.from(decodedCall.args._value), - source: call.from, + source: decodedCall.from, destination: decodedCall.args._to as string, }; - }, [call, decodedCall]); + }, [decodedCall]); const { data: tokenInfo } = useERC20Info(parsedData?.tokenAddress); const roundedBalance = useBigNumberToNumber( @@ -53,7 +50,11 @@ const ERC20TransferInfoLine: React.FC = ({ size={24} /> - {ensName || shortenAddress(parsedData?.destination)} + + {ensName || parsedData?.destination + ? shortenAddress(parsedData?.destination) + : ''} + ); }; diff --git a/src/components/Guilds/ActionsBuilder/SupportedActions/ERC20Transfer/ERC20TransferSummary.tsx b/src/components/Guilds/ActionsBuilder/SupportedActions/ERC20Transfer/ERC20TransferSummary.tsx index 19ee32aa69..ef30547a22 100644 --- a/src/components/Guilds/ActionsBuilder/SupportedActions/ERC20Transfer/ERC20TransferSummary.tsx +++ b/src/components/Guilds/ActionsBuilder/SupportedActions/ERC20Transfer/ERC20TransferSummary.tsx @@ -9,20 +9,17 @@ import { ActionViewProps } from '..'; import { Segment } from '../common/infoLine'; import { DetailCell, DetailHeader, DetailRow } from '../common/summary'; -const ERC20TransferSummary: React.FC = ({ - call, - decodedCall, -}) => { +const ERC20TransferSummary: React.FC = ({ decodedCall }) => { const parsedData = useMemo(() => { - if (!call || !decodedCall) return null; + if (!decodedCall) return null; return { - tokenAddress: call.to, + tokenAddress: decodedCall.to, amount: BigNumber.from(decodedCall.args._value), - source: call.from, + source: decodedCall.from, destination: decodedCall.args._to, }; - }, [call, decodedCall]); + }, [decodedCall]); const { data: tokenInfo } = useERC20Info(parsedData?.tokenAddress); const roundedBalance = useBigNumberToNumber( diff --git a/src/components/Guilds/ActionsBuilder/SupportedActions/ERC20Transfer/Transfer/index.tsx b/src/components/Guilds/ActionsBuilder/SupportedActions/ERC20Transfer/Transfer/index.tsx deleted file mode 100644 index ec581d0ea2..0000000000 --- a/src/components/Guilds/ActionsBuilder/SupportedActions/ERC20Transfer/Transfer/index.tsx +++ /dev/null @@ -1,244 +0,0 @@ -import styled from 'styled-components'; -import { Input } from 'components/Guilds/common/Form/Input'; -import { FiChevronDown, FiMoreHorizontal, FiX } from 'react-icons/fi'; -import { DetailWrapper } from '../../common/editor'; -import Avatar from 'components/Guilds/Avatar'; -import { resolveUri } from 'utils/url'; -import { useWeb3React } from '@web3-react/core'; -import { useTokenList } from 'hooks/Guilds/tokens/useTokenList'; -import { useMemo, useState } from 'react'; -import { ActionEditorProps } from '../..'; -import { BigNumber, utils } from 'ethers'; -import { useERC20Info } from 'hooks/Guilds/ether-swr/erc20/useERC20Info'; -import useBigNumberToNumber from 'hooks/Guilds/conversions/useBigNumberToNumber'; -import useENSAvatar from 'hooks/Guilds/ether-swr/ens/useENSAvatar'; -import TokenPicker from 'components/Guilds/TokenPicker'; -import { Box } from 'components/Guilds/common/Layout'; -import { Button } from 'components/Guilds/common/Button'; -import { MAINNET_ID } from 'utils'; -import NumericalInput from 'components/Guilds/common/Form/NumericalInput'; -import { baseInputStyles } from 'components/Guilds/common/Form/Input'; - -const Control = styled(Box)` - display: flex; - flex-direction: column; - margin: 0.75rem 0; - width: 100%; -`; - -const ControlLabel = styled(Box)` - margin-bottom: 0.75rem; -`; - -const ControlRow = styled(Box)` - display: flex; - align-items: stretch; - height: 100%; -`; - -const Spacer = styled(Box)` - margin-right: 1rem; -`; - -const MenuButton = styled(Button).attrs(() => ({ - variant: 'secondary', -}))` - border-radius: 50%; - height: 2.7rem; - width: 2.7rem; - padding: 0; - margin: 0; -`; - -const ClickableIcon = styled(Box)` - display: flex; - align-items: center; - cursor: pointer; -`; - -const TransferAmountInput = styled(NumericalInput)` - ${baseInputStyles} - display: flex; - align-items: center; - width: 100%; - &:hover, - &:focus { - border: 0.1rem solid ${({ theme }) => theme.colors.text}; - } -`; - -interface TransferState { - source: string; - tokenAddress: string; - amount: BigNumber; - destination: string; -} - -const Transfer: React.FC = ({ call, updateCall }) => { - const [isTokenPickerOpen, setIsTokenPickerOpen] = useState(false); - - const { chainId } = useWeb3React(); - - // parse transfer state from calls - const parsedData = useMemo(() => { - if (!call) return null; - - return { - source: call.from, - tokenAddress: call.to, - amount: call.args._value, - destination: call.args._to, - }; - }, [call]); - - const validations = useMemo(() => { - return { - tokenAddress: utils.isAddress(parsedData?.tokenAddress), - amount: BigNumber.isBigNumber(parsedData?.amount), - destination: utils.isAddress(parsedData?.destination), - }; - }, [parsedData]); - - // Get token details from the token address - const { tokens } = useTokenList(chainId); - const token = useMemo(() => { - if (!parsedData?.tokenAddress || !tokens) return null; - - return tokens.find(({ address }) => address === parsedData.tokenAddress); - }, [tokens, parsedData]); - - const { data: tokenInfo } = useERC20Info(parsedData?.tokenAddress); - const roundedBalance = useBigNumberToNumber( - parsedData?.amount, - tokenInfo?.decimals, - 10 - ); - const { imageUrl: destinationAvatarUrl } = useENSAvatar( - parsedData?.destination, - MAINNET_ID - ); - - const setTransferAddress = (walletAddress: string) => { - updateCall({ - ...call, - args: { - ...call.args, - _to: walletAddress, - }, - }); - }; - - const setToken = (tokenAddress: string) => { - updateCall({ - ...call, - to: tokenAddress, - }); - }; - - const setAmount = (value: string) => { - const amount = value - ? utils.parseUnits(value, tokenInfo?.decimals || 18) - : null; - updateCall({ - ...call, - args: { - ...call.args, - _value: amount, - }, - }); - }; - - return ( - - - Recipient - - - {validations.destination && ( - - )} - - } - iconRight={ - parsedData?.destination ? ( - setTransferAddress('')}> - - - ) : null - } - placeholder="Ethereum address" - onChange={e => setTransferAddress(e.target.value)} - /> - -
- - - -
-
-
- - - - Asset - setIsTokenPickerOpen(true)}> - - {parsedData?.tokenAddress && ( - - )} - - } - iconRight={} - readOnly - /> - - - - - - - Amount - - - -
- - - -
-
-
-
- - setIsTokenPickerOpen(false)} - onSelect={tokenAddress => { - setToken(tokenAddress); - setIsTokenPickerOpen(false); - }} - /> -
- ); -}; - -export default Transfer; diff --git a/src/components/Guilds/ActionsBuilder/SupportedActions/REPMint/REPMintEditor.tsx b/src/components/Guilds/ActionsBuilder/SupportedActions/REPMint/REPMintEditor.tsx new file mode 100644 index 0000000000..2c9bfb2241 --- /dev/null +++ b/src/components/Guilds/ActionsBuilder/SupportedActions/REPMint/REPMintEditor.tsx @@ -0,0 +1,135 @@ +import styled from 'styled-components'; +import { Input } from 'components/Guilds/common/Form/Input'; +import Avatar from 'components/Guilds/Avatar'; +import React, { useEffect } from 'react'; +import { ActionEditorProps } from '..'; +import { ethers } from 'ethers'; +import useENSAvatar from 'hooks/Guilds/ether-swr/ens/useENSAvatar'; +import { Box } from 'components/Guilds/common/Layout'; +import { shortenAddress, MAINNET_ID } from 'utils'; +import { baseInputStyles } from 'components/Guilds/common/Form/Input'; +import { ReactComponent as Info } from '../../../../../assets/images/info.svg'; +import StyledIcon from 'components/Guilds/common/SVG'; +import useBigNumberToNumber from 'hooks/Guilds/conversions/useBigNumberToNumber'; +import { useState } from 'react'; +import NumericalInput from 'components/Guilds/common/Form/NumericalInput'; +import { useTotalSupply } from 'hooks/Guilds/guild/useTotalSupply'; +import { useTokenData } from 'hooks/Guilds/guild/useTokenData'; + +const Control = styled(Box)` + display: flex; + flex-direction: column; + margin: 0.75rem 0; + width: 100%; +`; + +const ControlLabel = styled(Box)` + display: flex; + flex-direction: row; + margin-bottom: 0.75rem; + color: ${({ theme }) => theme.colors.proposalText.grey}; + font-size: ${({ theme }) => theme.fontSizes.body}; + font-weight: ${({ theme }) => theme.fontWeights.regular}; +`; + +const ControlRow = styled(Box)` + display: flex; + align-items: stretch; + height: 100%; +`; + +const RepMintInput = styled(NumericalInput)` + ${baseInputStyles} + display: flex; + align-items: center; + width: 100%; + &:hover, + &:focus { + border: 0.1rem solid ${({ theme }) => theme.colors.text}; + } +`; + +const Mint: React.FC = ({ decodedCall, updateCall }) => { + // parse transfer state from calls + const [repPercent, setRepPercent] = useState(0); + const [repAmount, setRepAmount] = useState(0); + const { parsedData } = useTotalSupply({ decodedCall }); + const { tokenData } = useTokenData(); + + const totalSupply = useBigNumberToNumber(tokenData?.totalSupply, 18); + + const { imageUrl } = useENSAvatar(parsedData?.toAddress, MAINNET_ID); + + const setCallDataAmount = (value: string) => { + const amount = value ? ethers.utils.parseUnits(value) : null; + updateCall({ + ...decodedCall, + args: { + ...decodedCall.args, + amount, + }, + }); + }; + + useEffect(() => { + setRepAmount((repPercent / 100) * totalSupply); + if (repAmount) { + setCallDataAmount(repAmount.toString()); + } + }, [repPercent, repAmount, totalSupply]); + + const handleRepChange = (e: number) => { + if (e) { + setRepPercent(e); + } + }; + return ( + + + + Recipient + + + + + } + readOnly + /> + + + + + + Reputation in % + + + + + + + + + + Reputation Amount + + + + + + + + ); +}; + +export default Mint; diff --git a/src/components/Guilds/ActionsBuilder/SupportedActions/REPMint/REPMintInfoLine.tsx b/src/components/Guilds/ActionsBuilder/SupportedActions/REPMint/REPMintInfoLine.tsx new file mode 100644 index 0000000000..1f460d2ab4 --- /dev/null +++ b/src/components/Guilds/ActionsBuilder/SupportedActions/REPMint/REPMintInfoLine.tsx @@ -0,0 +1,46 @@ +import Avatar from 'components/Guilds/Avatar'; +import useENSAvatar from 'hooks/Guilds/ether-swr/ens/useENSAvatar'; +import { FiArrowRight } from 'react-icons/fi'; +import { MAINNET_ID, shortenAddress } from 'utils'; +import { ActionViewProps } from '..'; +import { Segment } from '../common/infoLine'; +import { ReactComponent as Mint } from '../../../../../assets/images/mint.svg'; +import StyledIcon from 'components/Guilds/common/SVG'; +import styled from 'styled-components'; +import useBigNumberToNumber from 'hooks/Guilds/conversions/useBigNumberToNumber'; +import { useTotalSupply } from 'hooks/Guilds/guild/useTotalSupply'; +import { useTokenData } from 'hooks/Guilds/guild/useTokenData'; + +const StyledMintIcon = styled(StyledIcon)` + margin: 0; +`; + +const REPMintInfoLine: React.FC = ({ decodedCall }) => { + const { parsedData } = useTotalSupply({ decodedCall }); + const { tokenData } = useTokenData(); + + const totalSupply = useBigNumberToNumber(tokenData?.totalSupply, 18); + + const { ensName, imageUrl } = useENSAvatar(parsedData?.toAddress, MAINNET_ID); + + const roundedRepAmount = useBigNumberToNumber(parsedData?.amount, 16, 3); + const roundedRepPercent = roundedRepAmount / totalSupply; + + return ( + <> + + + + Mint {roundedRepPercent} % + + + + + + + {ensName || shortenAddress(parsedData?.toAddress)} + + ); +}; + +export default REPMintInfoLine; diff --git a/src/components/Guilds/ActionsBuilder/SupportedActions/REPMint/REPMintSummary.tsx b/src/components/Guilds/ActionsBuilder/SupportedActions/REPMint/REPMintSummary.tsx new file mode 100644 index 0000000000..90bd25a6ed --- /dev/null +++ b/src/components/Guilds/ActionsBuilder/SupportedActions/REPMint/REPMintSummary.tsx @@ -0,0 +1,44 @@ +import Avatar from 'components/Guilds/Avatar'; +import useBigNumberToNumber from 'hooks/Guilds/conversions/useBigNumberToNumber'; +import useENSAvatar from 'hooks/Guilds/ether-swr/ens/useENSAvatar'; +import { MAINNET_ID, shortenAddress } from 'utils'; +import { ActionViewProps } from '..'; +import { Segment } from '../common/infoLine'; +import { DetailCell, DetailHeader, DetailRow } from '../common/summary'; +import { useTotalSupply } from 'hooks/Guilds/guild/useTotalSupply'; +import { useTokenData } from 'hooks/Guilds/guild/useTokenData'; + +const REPMintSummary: React.FC = ({ decodedCall }) => { + const { parsedData } = useTotalSupply({ decodedCall }); + const { tokenData } = useTokenData(); + const { ensName, imageUrl } = useENSAvatar(parsedData?.toAddress, MAINNET_ID); + + const roundedRepAmount = useBigNumberToNumber(parsedData?.amount, 18, 3); + + return ( + <> + + Receiver + Amount + + + + + + + + {ensName || shortenAddress(parsedData?.toAddress)} + + + {roundedRepAmount} {tokenData?.name} + + + + ); +}; + +export default REPMintSummary; diff --git a/src/components/Guilds/ActionsBuilder/SupportedActions/common/editor.tsx b/src/components/Guilds/ActionsBuilder/SupportedActions/common/editor.tsx deleted file mode 100644 index 1d72dbcde4..0000000000 --- a/src/components/Guilds/ActionsBuilder/SupportedActions/common/editor.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { CardWrapper, Header } from 'components/Guilds/common/Card'; -import { Box } from 'components/Guilds/common/Layout'; -import styled from 'styled-components'; - -export const EditorWrapper = styled(CardWrapper)` - margin: 0.8rem 0; -`; - -export const HeaderWrapper = styled(Header)` - display: flex; - align-items: center; - margin: 0.875rem; -`; - -export const IconWrapper = styled.span` - display: flex; - margin-right: 0.875rem; -`; - -export const TitleWrapper = styled(Box)` - font-size: ${({ theme }) => theme.fontSizes.body}; - font-weight: ${({ theme }) => theme.fontWeights.medium}; -`; - -export const DetailWrapper = styled(Box)` - color: ${({ theme }) => theme.colors.proposalText.grey}; - padding: 1.25rem; - border-top: 1px solid ${({ theme }) => theme.colors.border.initial}; -`; - -export const FooterWrapper = styled(DetailWrapper)` - color: ${({ theme }) => theme.colors.text}; - padding: 0.75rem 1.25rem; -`; diff --git a/src/components/Guilds/ActionsBuilder/SupportedActions/index.tsx b/src/components/Guilds/ActionsBuilder/SupportedActions/index.tsx index effcf97efa..d49fda4769 100644 --- a/src/components/Guilds/ActionsBuilder/SupportedActions/index.tsx +++ b/src/components/Guilds/ActionsBuilder/SupportedActions/index.tsx @@ -1,19 +1,23 @@ import { BigNumber, utils } from 'ethers'; import { DeepPartial, RequireAtLeastOne } from 'utils/types'; -import { Call, DecodedAction, DecodedCall, SupportedAction } from '../types'; +import { DecodedAction, DecodedCall, SupportedAction } from '../types'; import ERC20ABI from '../../../../abis/ERC20.json'; +import ERC20SnapshotRep from '../../../../contracts/ERC20SnapshotRep.json'; import ERC20TransferEditor from './ERC20Transfer/ERC20TransferEditor'; import ERC20TransferInfoLine from './ERC20Transfer/ERC20TransferInfoLine'; import ERC20TransferSummary from './ERC20Transfer/ERC20TransferSummary'; - +import REPMintEditor from './REPMint/REPMintEditor'; +import REPMintInfoLine from './REPMint/REPMintInfoLine'; +import REPMintSummary from './REPMint/REPMintSummary'; +export interface SupportedActionMetadata { + title: string; +} export interface ActionViewProps { - call: Call; decodedCall: DecodedCall; } export interface ActionEditorProps { - contract: utils.Interface; - call: DecodedCall; + decodedCall: DecodedCall; updateCall: (updatedCall: DecodedCall) => void; } @@ -23,27 +27,33 @@ type SupportedActionViews = { }; type SupportedActionEditors = RequireAtLeastOne<{ - bulkEditor?: React.FC; editor?: React.FC; }>; export const supportedActions: Record< SupportedAction, - SupportedActionViews & SupportedActionEditors + SupportedActionViews & SupportedActionEditors & SupportedActionMetadata > = { [SupportedAction.ERC20_TRANSFER]: { + title: 'Transfers & Mint', infoLineView: ERC20TransferInfoLine, summaryView: ERC20TransferSummary, editor: ERC20TransferEditor, }, + [SupportedAction.REP_MINT]: { + title: 'Mint Reputation', + infoLineView: REPMintInfoLine, + summaryView: REPMintSummary, + editor: REPMintEditor, + }, [SupportedAction.GENERIC_CALL]: { + title: 'Generic Call', infoLineView: () =>
Generic Call
, editor: () =>
Generic Call Editor
, }, }; - -let ERC20Contract = new utils.Interface(ERC20ABI); - +const ERC20Contract = new utils.Interface(ERC20ABI); +const ERC20SnapshotRepContract = new utils.Interface(ERC20SnapshotRep.abi); export const defaultValues: Record< SupportedAction, DeepPartial @@ -60,6 +70,19 @@ export const defaultValues: Record< }, }, }, + [SupportedAction.REP_MINT]: { + contract: ERC20SnapshotRepContract, + decodedCall: { + function: ERC20SnapshotRepContract.getFunction('mint'), + to: '', + value: BigNumber.from(0), + args: { + to: '', + amount: BigNumber.from(0), + }, + }, + }, + [SupportedAction.GENERIC_CALL]: {}, }; diff --git a/src/components/Guilds/ActionsBuilder/ViewMode.tsx b/src/components/Guilds/ActionsBuilder/ViewMode.tsx deleted file mode 100644 index 87d46d5c09..0000000000 --- a/src/components/Guilds/ActionsBuilder/ViewMode.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { Option } from './types'; -import { Divider } from '../common/Divider'; -import OptionViewMode from './Option/ViewMode'; - -interface ViewModeProps { - options: Option[]; -} - -const ViewMode: React.FC = ({ options }) => { - return ( - <> - {options?.map((option, idx) => ( - <> - - {idx !== options.length - 1 && } - - ))} - - ); -}; - -export default ViewMode; diff --git a/src/components/Guilds/ActionsBuilder/common/DataTag/index.tsx b/src/components/Guilds/ActionsBuilder/common/DataTag/index.tsx new file mode 100644 index 0000000000..50eb4c7ff3 --- /dev/null +++ b/src/components/Guilds/ActionsBuilder/common/DataTag/index.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import styled from 'styled-components'; + +interface DataTagProps { + children: React.ReactElement; +} + +const DataTag = styled.span` + border-radius: 0.5rem; + padding: 0.25rem 0.5rem; + background: ${({ theme }) => theme.colors.muted}; + border: 1px solid ${({ theme }) => theme.colors.muted}; + color: ${({ color }) => color}; + border-color: ${({ color }) => color}; +`; + +export default DataTag; diff --git a/src/components/Guilds/ActionsBuilder/common/EditButton/index.tsx b/src/components/Guilds/ActionsBuilder/common/EditButton/index.tsx new file mode 100644 index 0000000000..6eb9d808a2 --- /dev/null +++ b/src/components/Guilds/ActionsBuilder/common/EditButton/index.tsx @@ -0,0 +1,13 @@ +import { Button } from 'components/Guilds/common/Button'; +import styled from 'styled-components'; + +const EditButton = styled(Button).attrs({ + variant: 'secondary', +})` + font-weight: ${({ theme }) => theme.fontWeights.medium}; + font-size: ${({ theme }) => theme.fontSizes.label}; + margin: 0; + padding: 0.25rem 0.75rem; +`; + +export default EditButton; diff --git a/src/components/Guilds/ActionsBuilder/common/Grip/index.tsx b/src/components/Guilds/ActionsBuilder/common/Grip/index.tsx new file mode 100644 index 0000000000..41924bab23 --- /dev/null +++ b/src/components/Guilds/ActionsBuilder/common/Grip/index.tsx @@ -0,0 +1,13 @@ +import styled from 'styled-components'; +import { FaGripVertical } from 'react-icons/fa'; + +const Grip = styled(FaGripVertical)` + cursor: grabbing; + color: ${({ theme }) => theme.colors.proposalText.lightGrey}; + + &:hover { + color: ${({ theme }) => theme.colors.text}; + } +`; + +export default Grip; diff --git a/src/components/Guilds/ActionsBuilder/common/ProposalOptionTag/ProposalOptionTag.tsx b/src/components/Guilds/ActionsBuilder/common/ProposalOptionTag/ProposalOptionTag.tsx index a40b734ad6..e7431aaa2b 100644 --- a/src/components/Guilds/ActionsBuilder/common/ProposalOptionTag/ProposalOptionTag.tsx +++ b/src/components/Guilds/ActionsBuilder/common/ProposalOptionTag/ProposalOptionTag.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import styled, { useTheme } from 'styled-components'; +import styled from 'styled-components'; import { Option } from '../../types'; interface ProposalActionTagProps { @@ -19,6 +19,5 @@ const Tag = styled.span` export const ProposalOptionTag: React.FC = ({ option, }) => { - const theme = useTheme(); - return {option.label}; + return {option.label}; }; diff --git a/src/components/Guilds/ActionsBuilder/common/ProposalOptionTag/__snapshots__/ProposalOptionTag.test.tsx.snap b/src/components/Guilds/ActionsBuilder/common/ProposalOptionTag/__snapshots__/ProposalOptionTag.test.tsx.snap index d6c594ddee..7ff986ff40 100644 --- a/src/components/Guilds/ActionsBuilder/common/ProposalOptionTag/__snapshots__/ProposalOptionTag.test.tsx.snap +++ b/src/components/Guilds/ActionsBuilder/common/ProposalOptionTag/__snapshots__/ProposalOptionTag.test.tsx.snap @@ -4,8 +4,7 @@ exports[`ProposalOptionTag Should match snapshot 1`] = `
For diff --git a/src/components/Guilds/ActionsBuilder/index.tsx b/src/components/Guilds/ActionsBuilder/index.tsx index 24287e2801..51bbc80295 100644 --- a/src/components/Guilds/ActionsBuilder/index.tsx +++ b/src/components/Guilds/ActionsBuilder/index.tsx @@ -1,21 +1,12 @@ -import styled from 'styled-components'; import { useState } from 'react'; import { Header as CardHeader } from '../common/Card'; -import { Button as CommonButton } from '../common/Button'; import SidebarCard, { SidebarCardHeaderSpaced, } from 'components/Guilds/SidebarCard'; -import EditMode from './EditMode'; -import ViewMode from './ViewMode'; +import OptionsList from './OptionsList'; import { Option } from './types'; import { bulkEncodeCallsFromOptions } from 'hooks/Guilds/contracts/useEncodedCall'; - -const Button = styled(CommonButton)` - font-weight: ${({ theme }) => theme.fontWeights.medium}; - font-size: ${({ theme }) => theme.fontSizes.label}; - margin: 0; - padding: 0.25rem 0.75rem; -`; +import EditButton from './common/EditButton'; interface ActionsBuilderProps { options: Option[]; @@ -28,14 +19,14 @@ export const ActionsBuilder: React.FC = ({ options, onChange, }) => { - const [actionsEditMode, setActionsEditMode] = useState(editable); + const [isEditable, setIsEditable] = useState(editable); - const onEdit = () => setActionsEditMode(true); + const onEdit = () => setIsEditable(true); const onSave = () => { const encodedOptions = bulkEncodeCallsFromOptions(options); onChange(encodedOptions); - setActionsEditMode(false); + setIsEditable(false); }; return ( @@ -44,21 +35,21 @@ export const ActionsBuilder: React.FC = ({ Actions {editable && ( - + {isEditable ? 'Save' : 'Edit'} + )} } > - {actionsEditMode ? ( - - ) : ( - - )} + ); }; diff --git a/src/components/Guilds/ActionsBuilder/types.ts b/src/components/Guilds/ActionsBuilder/types.ts index 84a2202f89..caf06a1f6f 100644 --- a/src/components/Guilds/ActionsBuilder/types.ts +++ b/src/components/Guilds/ActionsBuilder/types.ts @@ -3,6 +3,7 @@ import { utils } from 'ethers'; export enum SupportedAction { ERC20_TRANSFER = 'ERC20_TRANSFER', + REP_MINT = 'REP_MINT', GENERIC_CALL = 'GENERIC_CALL', } @@ -23,13 +24,15 @@ export interface DecodedCall { } export interface DecodedAction { + id: string; decodedCall: DecodedCall; contract: utils.Interface; } export interface Option { - index: number; + id: string; label: string; + color: string; actions?: Call[]; decodedActions?: DecodedAction[]; } diff --git a/src/components/Guilds/ActionsModal/ContractActionsList.tsx b/src/components/Guilds/ActionsModal/ContractActionsList.tsx index b367a0b9aa..fcb6d0eb04 100644 --- a/src/components/Guilds/ActionsModal/ContractActionsList.tsx +++ b/src/components/Guilds/ActionsModal/ContractActionsList.tsx @@ -1,46 +1,12 @@ -import styled from 'styled-components'; -import { Flex } from '../common/Layout'; -import { ContainerText } from '../common/Layout/Text'; -import { Button } from '../common/Button'; import { RegistryContract } from 'hooks/Guilds/contracts/useContractRegistry'; - -const Wrapper = styled(Flex)` - margin: 16px auto; -`; -const WrapperText = styled(ContainerText).attrs(() => ({ - variant: 'bold', -}))` - justify-content: left; - flex-direction: row; - width: 85%; - margin: 8px auto; - color: ${({ theme }) => theme.colors.proposalText.grey}; -`; - -const ExternalWrapper = styled(Flex)` - width: 100%; - margin: 8px auto; -`; - -const ButtonText = styled(ContainerText).attrs(() => ({ - variant: 'medium', -}))` - justify-content: space-between; - flex-direction: row; -`; - -const ActionsButton = styled(Button)` - width: 90%; - margin: 6px 0; - flex-direction: column; - justify-content: left; - border-radius: 10px; - &:active, - &:focus { - border: 2px solid ${({ theme }) => theme.colors.text}; - } -`; - +import { + ActionsButton, + ButtonDetail, + ButtonLabel, + SectionTitle, + SectionWrapper, + Wrapper, +} from './styles'; interface ContractActionsListProps { contract: RegistryContract; onSelect: (functionName: string) => void; @@ -52,18 +18,32 @@ const ContractActionsList: React.FC = ({ }) => { return ( - - 6 Actions + + + {contract.functions.length}{' '} + {contract.functions.length >= 2 ? 'Actions' : 'Action'} + {contract.functions.map(contractFunction => ( onSelect(contractFunction.functionName)} > - {contractFunction.title} - {contractFunction.functionName} + {contractFunction.title} + + {contractFunction.functionName}( + {contractFunction.params.reduce( + (acc, cur, i) => + acc.concat( + cur.type, + i === contractFunction.params.length - 1 ? '' : ', ' + ), + '' + )} + ) + ))} - + ); }; diff --git a/src/components/Guilds/ActionsModal/ContractsList.tsx b/src/components/Guilds/ActionsModal/ContractsList.tsx index b9d922033d..178cc052fd 100644 --- a/src/components/Guilds/ActionsModal/ContractsList.tsx +++ b/src/components/Guilds/ActionsModal/ContractsList.tsx @@ -1,9 +1,6 @@ import React from 'react'; -import styled from 'styled-components'; -import { Flex } from '../common/Layout'; -import { ContainerText } from '../common/Layout/Text'; -import { Button } from '../common/Button'; import { ReactComponent as Vector } from '../../../assets/images/vector.svg'; +import { ReactComponent as Mint } from '../../../assets/images/mint.svg'; import StyledIcon from '../common/SVG'; import { RegistryContract, @@ -11,57 +8,16 @@ import { } from 'hooks/Guilds/contracts/useContractRegistry'; import { useWeb3React } from '@web3-react/core'; import { SupportedAction } from '../ActionsBuilder/types'; - -const CoreWrapper = styled(Flex)` - width: 100%; - margin-bottom: 16px; -`; - -const ExternalWrapper = styled(Flex)` - width: 100%; -`; - -const Wrapper = styled(Flex)` - width: 100%; - margin: 24px auto; -`; - -const ActionsButton = styled(Button).attrs(() => ({ - variant: 'secondary', -}))` - width: 90%; - height: 40px; - margin: 6px 0; - flex-direction: row; - justify-content: left; - &:active, - &:focus { - border: 2px solid ${({ theme }) => theme.colors.text}; - } -`; - -const WrapperText = styled(ContainerText).attrs(() => ({ - variant: 'bold', -}))` - justify-content: left; - flex-direction: row; - width: 85%; - color: ${({ theme }) => theme.colors.proposalText.grey}; -`; - -const ButtonText = styled(ContainerText).attrs(() => ({ - variant: 'medium', -}))` - justify-content: space-between; - flex-direction: row; - color: ${({ theme }) => theme.colors.proposalText.grey}; -`; - -const ExternalButton = styled(ActionsButton).attrs(() => ({ - variant: 'secondary', -}))` - justify-content: space-between; -`; +import { + ActionsButton, + ButtonDetail, + ButtonLabel, + SectionTitle, + SectionWrapper, + Wrapper, +} from './styles'; +import useGuildImplementationTypeConfig from 'hooks/Guilds/guild/useGuildImplementationType'; +import { useParams } from 'react-router-dom'; interface ContractsListProps { onSelect: (contract: RegistryContract) => void; @@ -74,32 +30,49 @@ const ContractsList: React.FC = ({ }) => { const { chainId } = useWeb3React(); const { contracts } = useContractRegistry(chainId); - + const { guild_id: guildAddress } = + useParams<{ chain_name?: string; guild_id?: string }>(); + const { isRepGuild } = useGuildImplementationTypeConfig(guildAddress); return ( - - Core + + Core onSupportedActionSelect(SupportedAction.ERC20_TRANSFER) } > - - Transfer & Mint + + + Transfer & Mint + - - - External Contracts + {isRepGuild ? ( + onSupportedActionSelect(SupportedAction.REP_MINT)} + > + + + Mint REP + + + ) : null} + + + External Contracts {contracts?.map(contract => ( - onSelect(contract)}> - {contract.title} - + onSelect(contract)} + > + {contract.title} + {contract.functions?.length}{' '} {contract.functions?.length > 1 ? 'Actions' : 'Action'} - - + + ))} - + ); }; diff --git a/src/components/Guilds/ActionsModal/MintRepModal.tsx b/src/components/Guilds/ActionsModal/MintRepModal.tsx deleted file mode 100644 index 3e539124ca..0000000000 --- a/src/components/Guilds/ActionsModal/MintRepModal.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import styled from 'styled-components'; -import { Input } from '../../../components/Guilds/common/Form'; -import { useWeb3React } from '@web3-react/core'; -import { useState } from 'react'; -import { Flex } from '../common/Layout'; -import { ContainerText } from '../common/Layout/Text'; -import iconsByChain from '../common/ChainIcons'; - -const RepWrapper = styled(Flex)` - margin: 16px auto; -`; -const WrapperText = styled(ContainerText).attrs(() => ({ - variant: 'bold', -}))` - justify-content: left; - flex-direction: row; - width: 85%; - margin: 8px auto; - color: grey; -`; - -const ExternalWrapper = styled(Flex)` - width: 100%; - margin: 8px auto; -`; - -const MintReputationModal: React.FC = () => { - const [address, setAddress] = useState(''); - const [repAmount, setRepAmount] = useState(''); - const [repPercent, setRepPercent] = useState(''); - const { chainId } = useWeb3React(); - - return ( - - - Recipient - setAddress(e.target.value)} - icon={iconsByChain[chainId] || null} - size={24} - width="85%" - /> - - - Reputation Amount - setRepAmount(e.target.value)} - size={24} - width="85%" - /> - - - Reputation in % - setRepPercent(e.target.value)} - width="85%" - /> - - - ); -}; - -export default MintReputationModal; diff --git a/src/components/Guilds/ActionsModal/ParamsModal.tsx b/src/components/Guilds/ActionsModal/ParamsModal.tsx new file mode 100644 index 0000000000..44e100f9ec --- /dev/null +++ b/src/components/Guilds/ActionsModal/ParamsModal.tsx @@ -0,0 +1,42 @@ +import styled from 'styled-components'; +import { Input } from '../common/Form'; +import { useState } from 'react'; +import { ActionsButton, FormElement, FormLabel, Wrapper } from './styles'; +import { RegistryContractFunction } from 'hooks/Guilds/contracts/useContractRegistry'; + +const SubmitButton = styled(ActionsButton).attrs(() => ({ + variant: 'primary', +}))` + background-color: ${({ theme }) => theme.colors.button.primary}; + justify-content: center; +`; + +interface ParamsModalProps { + fn: RegistryContractFunction; +} + +const ParamsModal: React.FC = ({ fn }) => { + const [address, setAddress] = useState(''); + + return ( + + {fn.params.map(param => ( + + {param.description} + setAddress(e.target.value)} + size={24} + /> + + ))} + + + Add action + + + ); +}; + +export default ParamsModal; diff --git a/src/components/Guilds/ActionsModal/index.tsx b/src/components/Guilds/ActionsModal/index.tsx index d00f02a0d9..44292ac376 100644 --- a/src/components/Guilds/ActionsModal/index.tsx +++ b/src/components/Guilds/ActionsModal/index.tsx @@ -1,11 +1,33 @@ +import { utils } from 'ethers'; import { RegistryContract } from 'hooks/Guilds/contracts/useContractRegistry'; import React, { useState } from 'react'; import { useParams } from 'react-router-dom'; -import { defaultValues } from '../ActionsBuilder/SupportedActions'; -import { DecodedAction, SupportedAction } from '../ActionsBuilder/types'; +import styled from 'styled-components'; +import { + defaultValues, + getEditor, + supportedActions, +} from '../ActionsBuilder/SupportedActions'; +import { + DecodedAction, + DecodedCall, + SupportedAction, +} from '../ActionsBuilder/types'; +import { Button } from '../common/Button'; import { Modal } from '../common/Modal'; import ContractActionsList from './ContractActionsList'; import ContractsList from './ContractsList'; +import ParamsModal from './ParamsModal'; +import { useWeb3React } from '@web3-react/core'; + +export const EditorWrapper = styled.div` + margin: 1.25rem; +`; + +export const BlockButton = styled(Button)` + margin-top: 1rem; + width: 100%; +`; interface ActionModalProps { isOpen: boolean; @@ -19,11 +41,19 @@ const ActionModal: React.FC = ({ onAddAction, }) => { const { guild_id: guildId } = useParams<{ guild_id?: string }>(); + const { account: walletAddress } = useWeb3React(); + // Supported Actions + const [selectedAction, setSelectedAction] = useState(null); + const [selectedActionContract, setSelectedActionContract] = + useState(null); + // Generic calls const [selectedContract, setSelectedContract] = useState(null); const [selectedFunction, setSelectedFunction] = useState(null); + const [data, setData] = useState(null); + function getHeader() { if (selectedFunction) { return selectedContract.functions.find( @@ -35,12 +65,22 @@ const ActionModal: React.FC = ({ return selectedContract?.title; } + if (selectedAction) { + return supportedActions[selectedAction].title; + } + return 'Add action'; } function getContent() { if (selectedFunction) { - return null; + return ( + fn.functionName === selectedFunction + )} + /> + ); } if (selectedContract) { @@ -52,10 +92,20 @@ const ActionModal: React.FC = ({ ); } + if (selectedAction) { + const Editor = getEditor(selectedAction); + return ( + + + Save Action + + ); + } + return ( ); } @@ -65,16 +115,41 @@ const ActionModal: React.FC = ({ setSelectedFunction(null); } else if (selectedContract) { setSelectedContract(null); + } else if (selectedAction) { + setSelectedAction(null); + setSelectedActionContract(null); } + + setData(null); } - function addSupportedAction(action: SupportedAction) { - const defaultDecodedAction = defaultValues[action]; + function setSupportedAction(action: SupportedAction) { + const defaultDecodedAction = defaultValues[action] as DecodedAction; if (!defaultDecodedAction) return null; defaultDecodedAction.decodedCall.from = guildId; defaultDecodedAction.decodedCall.callType = action; - onAddAction(defaultDecodedAction as DecodedAction); + switch (action) { + case SupportedAction.REP_MINT: + defaultDecodedAction.decodedCall.args.to = walletAddress; + break; + } + setData(defaultDecodedAction.decodedCall); + setSelectedAction(action); + setSelectedActionContract(defaultDecodedAction.contract); + } + + function saveSupportedAction() { + if (!selectedAction || !data || !setSelectedActionContract) return; + + const decodedAction: DecodedAction = { + id: `action-${Math.random()}`, + decodedCall: data, + contract: selectedActionContract, + }; + + onAddAction(decodedAction); + setIsOpen(false); } return ( @@ -83,7 +158,7 @@ const ActionModal: React.FC = ({ onDismiss={() => setIsOpen(false)} header={getHeader()} maxWidth={300} - backnCross={!!selectedContract} + backnCross={!!selectedAction || !!selectedContract} prevContent={goBack} > {getContent()} diff --git a/src/components/Guilds/ActionsModal/styles.tsx b/src/components/Guilds/ActionsModal/styles.tsx new file mode 100644 index 0000000000..f781cd85c7 --- /dev/null +++ b/src/components/Guilds/ActionsModal/styles.tsx @@ -0,0 +1,58 @@ +import styled from 'styled-components'; +import { Button } from '../common/Button'; +import { ContainerText } from '../common/Layout/Text'; + +export const Wrapper = styled.div` + width: 100%; +`; + +export const SectionWrapper = styled.div` + margin: 1.5rem; +`; + +export const ActionsButton = styled(Button).attrs(() => ({ + variant: 'secondary', +}))` + background-color: transparent; + padding: ${({ vertical }) => (vertical ? '1rem' : '0.75rem 1rem')}; + width: 100%; + display: flex; + align-items: ${({ vertical }) => (vertical ? 'flex-start' : 'center')}; + justify-content: space-between; + flex-direction: ${({ vertical }) => (vertical ? 'column' : 'row')}; + border-radius: ${({ vertical }) => (vertical ? '0.625rem' : '2rem')}; + &:active, + &:focus { + border: 2px solid ${({ theme }) => theme.colors.text}; + } +`; + +export const SectionTitle = styled(ContainerText).attrs(() => ({ + variant: 'bold', +}))` + display: block; + color: ${({ theme }) => theme.colors.proposalText.grey}; + margin-bottom: 0.75rem; + font-size: 0.875rem; +`; + +export const ButtonLabel = styled.div` + display: flex; + align-items: center; +`; + +export const ButtonDetail = styled(ContainerText).attrs(() => ({ + variant: 'medium', +}))` + margin: ${({ vertical }) => (vertical ? '0.5rem 0 0 0' : '0')}; + color: ${({ theme }) => theme.colors.proposalText.grey}; +`; + +export const FormElement = styled.div` + margin: 1.5rem; +`; + +export const FormLabel = styled.div` + color: ${({ theme }) => theme.colors.proposalText.grey}; + margin-bottom: 0.75rem; +`; diff --git a/src/components/Guilds/ProposalPage/ProposalVoteCard/index.tsx b/src/components/Guilds/ProposalPage/ProposalVoteCard/index.tsx index b84009e26e..992058c69f 100644 --- a/src/components/Guilds/ProposalPage/ProposalVoteCard/index.tsx +++ b/src/components/Guilds/ProposalPage/ProposalVoteCard/index.tsx @@ -101,7 +101,6 @@ const ProposalVoteCard = () => { contract.setVote(proposalId, selectedAction, userVotingPower) ); }; - return ( { const memberMenuRef = useRef(null); useDetectBlur(memberMenuRef, () => setShowMenu(false)); + + const { isRepGuild } = useGuildImplementationType(guildAddress); return ( <> @@ -195,7 +198,7 @@ export const MemberActions = () => { Increase Voting Power - {isUnlockable && ( + {isUnlockable && !isRepGuild && ( Withdraw )} diff --git a/src/components/Guilds/StakeTokensModal/StakeTokens.tsx b/src/components/Guilds/StakeTokensModal/StakeTokens.tsx index 130557920f..f3094cbef9 100644 --- a/src/components/Guilds/StakeTokensModal/StakeTokens.tsx +++ b/src/components/Guilds/StakeTokensModal/StakeTokens.tsx @@ -2,6 +2,7 @@ import { useMemo, useState } from 'react'; import styled, { css } from 'styled-components'; import { FiArrowRight, FiInfo } from 'react-icons/fi'; import moment from 'moment'; +import { useHistory, useLocation } from 'react-router'; import { useParams } from 'react-router-dom'; import { useWeb3React } from '@web3-react/core'; @@ -20,6 +21,7 @@ import NumericalInput from '../common/Form/NumericalInput'; import useVotingPowerPercent from '../../../hooks/Guilds/guild/useVotingPowerPercent'; import useStringToBigNumber from '../../../hooks/Guilds/conversions/useStringToBigNumber'; import useBigNumberToNumber from '../../../hooks/Guilds/conversions/useBigNumberToNumber'; +import useGuildImplementationType from '../../../hooks/Guilds/guild/useGuildImplementationType'; import { Loading } from '../common/Loading'; const GuestContainer = styled.div` @@ -51,7 +53,7 @@ const DaoTitle = styled(Heading)` const InfoItem = styled.div` display: flex; font-size: ${({ theme }) => theme.fontSizes.body}; - color: ${({ theme }) => theme.colors.muted}; + color: ${({ theme }) => theme.colors.card.grey}; margin-bottom: 0.4rem; `; @@ -84,7 +86,7 @@ const BaseFont = css` const InfoLabel = styled.span` ${BaseFont} - color: ${({ theme }) => theme.colors.muted}; + color: ${({ theme }) => theme.colors.card.grey}; `; const InfoValue = styled.span` @@ -111,7 +113,7 @@ const StakeAmountInput = styled(NumericalInput)` font-family: inherit; `; -const ButtonLock = styled(Button)` +const ActionButton = styled(Button)` width: 100%; margin-top: 22px; self-align: flex-end; @@ -188,7 +190,9 @@ export const StakeTokens = () => { stakeAmountParsed?.add(guildConfig?.totalLocked), 3 ); - + const history = useHistory(); + const location = useLocation(); + const { isRepGuild } = useGuildImplementationType(guildAddress); return ( @@ -199,19 +203,51 @@ export const StakeTokens = () => { )} - - {guildConfig?.lockTime ? ( - `${moment - .duration(guildConfig.lockTime.toNumber(), 'seconds') - .humanize()} staking period` - ) : ( - - )}{' '} - + {!isRepGuild && ( + + {guildConfig?.lockTime ? ( + `${moment + .duration(guildConfig.lockTime.toNumber(), 'seconds') + .humanize()} staking period` + ) : ( + + )}{' '} + + )} - + {!isRepGuild && ( + + + Balance: + + {tokenBalance && tokenInfo ? ( + roundedBalance + ) : ( + + )}{' '} + {tokenInfo?.symbol || ( + + )} + + + + + + + + )} + {isRepGuild && ( - Balance: + Balance {tokenBalance && tokenInfo ? ( roundedBalance @@ -223,17 +259,7 @@ export const StakeTokens = () => { )} - - - - - + )} Your voting power @@ -260,41 +286,51 @@ export const StakeTokens = () => { )} - - Unlock Date - - {isStakeAmountValid ? ( - <> - - {moment() - .add(guildConfig.lockTime.toNumber(), 'seconds') - .format('MMM Do, YYYY - h:mm a')} - {' '} - - - ) : ( - '-' - )} - - - {stakeAmountParsed && tokenAllowance?.gte(stakeAmountParsed) ? ( - - Lock{' '} - {tokenInfo?.symbol || ( - - )} - + {!isRepGuild && ( + + Unlock Date + + {isStakeAmountValid ? ( + <> + + {moment() + .add(guildConfig.lockTime.toNumber(), 'seconds') + .format('MMM Do, YYYY - h:mm a')} + {' '} + + + ) : ( + '-' + )} + + + )} + {!isRepGuild ? ( + stakeAmountParsed && tokenAllowance?.gte(stakeAmountParsed) ? ( + + Lock{' '} + {tokenInfo?.symbol || ( + + )} + + ) : ( + + Approve{' '} + {tokenInfo?.symbol || ( + + )}{' '} + Spending + + ) ) : ( - history.push(location.pathname + '/proposalType')} > - Approve{' '} - {tokenInfo?.symbol || ( - - )}{' '} - Spending - + Mint Rep + )} ); diff --git a/src/components/Header/loadingNetwork.tsx b/src/components/Header/loadingNetwork.tsx new file mode 100644 index 0000000000..8bd4712e77 --- /dev/null +++ b/src/components/Header/loadingNetwork.tsx @@ -0,0 +1,51 @@ +import { observer } from 'mobx-react'; +import styled from 'styled-components'; +import dxdaoIcon from 'assets/images/DXdao.svg'; + +const NavWrapper = styled.div` + display: flex; + flex-direction: row; + justify-content: space-between; + width: 100%; + padding: 20px 0px 0px 0px; +`; + +const NavSection = styled.div` + display: flex; + flex-direction: row; + align-items: center; +`; + +const MenuItem = styled.div` + display: flex; + align-items: center; + color: var(--nav-text-light); + font-size: 16px; + line-height: 19px; + cursor: pointer; +`; + +const WarningDev = styled.div` + margin-left: 5px; + padding-top: 3px; + color: red; +`; + +const LoadingNetworkHeader = observer(() => { + const isTestingEnv = !window?.location?.href?.includes('dxvote.eth'); + + return ( + + + <> + + dxdao + {isTestingEnv && Testing Environment} + + + + + ); +}); + +export default LoadingNetworkHeader; diff --git a/src/components/Proposal/Votes/index.tsx b/src/components/Proposal/Votes/index.tsx index a7f957f847..f601cda488 100644 --- a/src/components/Proposal/Votes/index.tsx +++ b/src/components/Proposal/Votes/index.tsx @@ -37,7 +37,6 @@ import { verifySignedVote, } from 'utils'; import { utils } from 'ethers'; -import useJsonRpcProvider from 'hooks/Guilds/web3/useJsonRpcProvider'; const Votes = () => { const { @@ -46,7 +45,6 @@ const Votes = () => { daoStore, providerStore, daoService, - messageLoggerService, orbitDBService, }, } = useContext(); @@ -56,8 +54,6 @@ const Votes = () => { const [decision, setDecision] = useState(0); const [votePercentage, setVotePercentage] = useState(0); const [signedVotesOfProposal, setSignedVotesOfProposal] = useState([]); - const [loadingSignedRinkebyVotes, setLoadingSignedRinkebyVotes] = - useState(true); const [loadingSignedOrbitDBVotes, setLoadingSignedOrbitDBVotes] = useState(true); @@ -77,57 +73,6 @@ const Votes = () => { votingMachineOfProposal.address ].type == 'DXDVotingMachine'; - const rinkebyProvider = useJsonRpcProvider(4); - - messageLoggerService - .getMessages(signedVoteMessageId, rinkebyProvider) - .then(messagesEvents => { - console.debug('[Rinkeby Proposal messages]', messagesEvents); - messagesEvents.map(messagesEvent => { - if ( - messagesEvent.args && - messagesEvent.args.message.length > 0 && - messagesEvent.args.message.split(':').length > 6 - ) { - const signedVote = messagesEvent.args.message.split(':'); - const validSignature = verifySignedVote( - signedVote[1], - signedVote[2], - signedVote[3], - signedVote[4], - signedVote[5], - signedVote[6] - ); - - const alreadyAdded = - signedVotesOfProposal.findIndex(s => s.voter == signedVote[3]) > - -1 || - proposalEvents.votes.findIndex(s => s.voter == signedVote[3]) > -1; - - const repOfVoterForProposal = daoStore.getRepAt( - signedVote[3], - proposal.creationEvent.blockNumber - ).userRep; - - if ( - validSignature && - !alreadyAdded && - repOfVoterForProposal >= bnum(signedVote[5]) - ) { - signedVotesOfProposal.push({ - voter: signedVote[3], - vote: signedVote[4], - amount: bnum(signedVote[5]), - signature: signedVote[6], - source: 'rinkeby', - }); - } - } - }); - setSignedVotesOfProposal(signedVotesOfProposal); - setLoadingSignedRinkebyVotes(false); - }); - orbitDBService.getLogs(signedVoteMessageId).then(signedVoteMessages => { console.debug('[OrbitDB messages]', signedVoteMessages); signedVoteMessages.map(signedVoteMessageRaw => { @@ -272,12 +217,6 @@ const Votes = () => { utils.id(`dxvote:${proposalId}`), `signedVote:${voteDetails.votingMachine}:${voteDetails.proposalId}:${voteDetails.voter}:${voteDetails.decision}:${voteDetails.repAmount}:${voteSignature}` ); - if (voteDetails.networks[1]) - messageLoggerService.broadcast( - utils.id(`dxvote:${proposalId}`), - `signedVote:${voteDetails.votingMachine}:${voteDetails.proposalId}:${voteDetails.voter}:${voteDetails.decision}:${voteDetails.repAmount}:${voteSignature}`, - rinkebyProvider - ); } } else { daoService.vote( @@ -358,7 +297,7 @@ const Votes = () => { - {!loadingSignedRinkebyVotes && !loadingSignedOrbitDBVotes && ( + {!loadingSignedOrbitDBVotes && (
diff --git a/src/components/Web3ReactManager/index.tsx b/src/components/Web3ReactManager/index.tsx index 9cb116432b..5b006d3df8 100644 --- a/src/components/Web3ReactManager/index.tsx +++ b/src/components/Web3ReactManager/index.tsx @@ -7,6 +7,11 @@ import { useContext } from 'contexts'; import { DEFAULT_CHAIN_ID, useInterval, usePrevious } from 'utils'; import { InjectedConnector } from '@web3-react/injected-connector'; import { NetworkConnector } from '@web3-react/network-connector'; +import ThemeProvider, { GlobalStyle } from 'theme'; +import styled from 'styled-components'; +import LoadingNetworkHeader from '../Header/loadingNetwork'; +import { LoadingBox } from '../../pages/proposals/styles'; +import PulsingIcon from 'components/common/LoadingIcon'; const BLOKCHAIN_FETCH_INTERVAL = 10000; @@ -142,21 +147,40 @@ const Web3ReactManager = ({ children }) => { networkActive ? BLOKCHAIN_FETCH_INTERVAL : 10 ); + const Content = styled.div` + margin: auto; + height: 100%; + display: flex; + flex-direction: column; + justify-content: flex-start; + width: 85%; + `; + // on page load, do nothing until we've tried to connect to the injected connector if (!triedEager) { console.debug('[Web3ReactManager] Render: Eager load not tried'); - return null; - } - if (networkError) { + return ( + + + + + +
+ +
+
+
+
+ ); + } else if (networkError) { console.debug( '[Web3ReactManager] Render: Network error, showing modal error.' ); return null; } else { - console.debug( - '[Web3ReactManager] Render: Active network, render children', - { networkActive } - ); + console.debug('[Web3ReactManager] Render: Render children', { + networkActive, + }); return children; } }; diff --git a/src/configs/arbitrum/config.json b/src/configs/arbitrum/config.json index 6676fcf5e6..9598cbc5c2 100644 --- a/src/configs/arbitrum/config.json +++ b/src/configs/arbitrum/config.json @@ -1,8 +1,8 @@ { "cache": { "fromBlock": 338923, - "ipfsHash": "QmbRTtmYg9GWD6stfcL618bxTbuZoFa3tyySUfaTzRjA1v", - "toBlock": "6831600" + "ipfsHash": "QmRvp9A9FUVzoYdyDd7mbvz3x6oJSwRqPDcH7JFiNKFmRN", + "toBlock": "10026660" }, "contracts": { "avatar": "0x2B240b523f69b9aF3adb1C5924F6dB849683A394", diff --git a/src/configs/arbitrumTestnet/config.json b/src/configs/arbitrumTestnet/config.json index 465065a2e3..bc74ad5134 100644 --- a/src/configs/arbitrumTestnet/config.json +++ b/src/configs/arbitrumTestnet/config.json @@ -1,8 +1,8 @@ { "cache": { "fromBlock": 3995726, - "ipfsHash": "QmRiUXQ1yGhsDVp5QrePqNxe7WFFqzuZzGrAYEgT3RFAvY", - "toBlock": "10225604" + "ipfsHash": "QmXavj3tchxsqpXCPB4g18NikoyGd2c22syDF4Sijn3f8n", + "toBlock": "11296050" }, "contracts": { "avatar": "0x6370B3Ba7D9bAd94b35dC9073540E562bDe5Fdc3", diff --git a/src/configs/default.json b/src/configs/default.json index eca4d0c8f4..a7967d6975 100644 --- a/src/configs/default.json +++ b/src/configs/default.json @@ -1,7 +1,7 @@ { - "mainnet": "QmPcDmifixW4Cdxk9UjVejnzgzBP9urdHG7nrN83FVE5Yz", - "xdai": "QmdRR3roqvAW4iHbDCvoJQSCsqSobC8ngKAdtzsCAoDEh6", - "arbitrum": "QmUoRz433SzvwBnWta86SjL2CPrT7tm1efFTVdmCnm5hQp", - "rinkeby": "QmXwwMNZQEcabFCKmEfnyZNhBx9zCoNnD1Rkq5duvyMRku", - "arbitrumTestnet": "QmNcir6bUdevg587ceYWKtQdj5P2q9ASnCRG7sFbyQkpXi" + "mainnet": "QmfLMqd4Skt8v89u5NuhYGzDbdQsfWPeM9gSTwzg1za46p", + "xdai": "QmTXxeSJCTgkBg9eYrBkHQqFfMFfJN6yrKqLUKodkSBDSt", + "arbitrum": "QmQTAtR3nteZpGAHjArUk9Zu9F3UnXVmWgccXrVzzE6PGB", + "rinkeby": "QmSq3xW8cL9yPzqVJyJLKsiRaxmAKNnQ5H4zXCZDxLynZG", + "arbitrumTestnet": "QmUuwGiETkoytzoABzBS37LHqBPqMYTDEFHVkio8VrQepm" } \ No newline at end of file diff --git a/src/configs/mainnet/config.json b/src/configs/mainnet/config.json index 62b624c7ca..22be8495df 100644 --- a/src/configs/mainnet/config.json +++ b/src/configs/mainnet/config.json @@ -1,8 +1,8 @@ { "cache": { "fromBlock": 7219912, - "ipfsHash": "QmcFgHexZP5MygnFhJwHi5W7UFrSxGK5DSGNCTjmJM54NM", - "toBlock": "14270378" + "ipfsHash": "QmSiUzqh2W8EvBJWJ5BUQbzBZiYwN5JsotrZ6RjEueMeJD", + "toBlock": "14831037" }, "contracts": { "avatar": "0x519b70055af55a007110b4ff99b0ea33071c720a", diff --git a/src/configs/proposalTitles.json b/src/configs/proposalTitles.json index 20fce928d0..9611fca8f2 100644 --- a/src/configs/proposalTitles.json +++ b/src/configs/proposalTitles.json @@ -1521,5 +1521,271 @@ "0xfa759273534d116b354a0a4d801d1de0b2dd0b8e773f52bd33a159f90cd2488d": "First Month Developer Proposal Levotiate", "0xfb1fada10c1640cf81e8e0260370d4824b7edae31eab12be7ca6a93348bb2b61": "REP Boost - Discord: Kobello", "0xff6c045d9ab8f514b7dce6317ac1ff16786cad69bd4ae4f75985ff0f8828b678": "xDXD Buyback Order #130 for 11.42 WETH", - "0xffc0f4a64c8ec043238ca787e58177c2e798360c29893b7190f633116f6b4aab": "GP Relayer Trade: 300 xDAI for STAKE" + "0xffc0f4a64c8ec043238ca787e58177c2e798360c29893b7190f633116f6b4aab": "GP Relayer Trade: 300 xDAI for STAKE", + "0x168ca80db89e979aa8a54c64894b6ee8a86224e5d60a9526b36f022d88f88861": "1999 DXD Withdrawal from GPv1 Relayer", + "0x445db3d3973e01f6fca2018ffed0a982c91558d92c3fd02846c0adbdf55f87dc": "Decentral Labs Worker Proposal - 18th March to 18th May", + "0x6facdbca526b5eb3c4265c9d8e6bdadc5e7b5aeb8e5dd3d6d25fa3b2db1787d0": "Funding Gas Governance Refund Contract", + "0x818f357112024d61671175e650473b2dcfd5548f4a0f0975a1ccb71324d950bf": "1999 DXD Withdrawal Request from GPv1 Relayer", + "0xa3aab310917683f96a9546f0d87b1ecdf68550f04bb068c8ff66fb36d1b7aab7": "Update swapr.eth content hash to Swapr v1.0.0 Beta 11.1", + "0xadb3c9bfa7dc97d4ee93bb155c72ca27b35ac8d2d759074634e99fa3e2a746fb": "Sponsor The Global Governance Gathering (“GGG”) event by The DAOist in Amsterdam", + "0xae733d1e248b208b9f3bb1b9ba24b7b678dc6f9594afb018574e928787b6d7cc": "Decentral Labs Worker Proposal - 18th March to 18th May", + "0xddad4a2733571ab684c746477455983cdc0b336a6c8c54997e7500d3f25baa5e": "dlabs ETH Refund - MME", + "0xe328cc853e2b3040e827e933f708766de59e0edf304573197dcca49c377087c5": "(REDO) Sponsor The Global Governance Gathering (“GGG”) event by The DAOist in Amsterdam", + "0x073eb320293e2c3ef11eee02e6f2579e16265edc8f08e7d7e250107ab09e0689": "DAI payout Zett Period February 2022", + "0x093b73e99bb222a0c80e5b65c58ffe46b9d11fbe9a38f75b5a28ba8b2d7b4972": "LINK / GNO Epoch 12 - 13 SWPR farming campaign", + "0x122f3766bca165927f6696f2bed943198ca8a0f2f2ef420d22332319ab4027fd": "HAUS / WETH Epoch 12 - 13 SWPR farming campaign", + "0x1777d3d347124bc6d7f8337ea4066648bbc0173c57d3ae9c0e86e8f83f55924c": "Decentral Labs Worker Proposal - 18th of January to 18th of March (2nd part)", + "0x184630d11dad40bbe01e7714384ff1dff5ef736f451fb5381edef2803a1a1cf1": "WETH / XDAI Epoch 12 - 13 SWPR farming campaign", + "0x229079e05bdc9905fa1d8b19c33f7e4636ee31de809fe2b039284eabea8238a4": "MAI / XDAI Epoch 14 - 15 SWPR farming campaign", + "0x2a962b078d0452d70264772a05ff39b28163ae80c3b02a27b1e63bafba035c11": "0xKLOM.eth ETHDenver Stipend Update", + "0x2c8867dec2141c3d29060ba784d88296c67e545098e1eb7363035145579a1a8a": "Deposit 415 WETH & 1.1M xDai in Swapr Liquidity Relayer", + "0x316199049ec001cb1ed0afd4a7cdc4ee1921e57ba7994c7b0b8b520ccd7fc6bd": "Mirror Proposal of \"Sponsor The Global Governance Gathering (“GGG”) event by The DAOist in Amsterdam\" on Mainnet", + "0x39aa9535792dbd6d5365cf683a848c00d9c9b332b4286e0e4c53626f5844d613": "Dlabs Entity Formation and Maintenance Contributor Stipend", + "0x3b3c72675bb5459313a9f38e0408f0cd01e7706b83634ac9a95053904a4740dc": "RICE / XDAI Epoch 14 - 15 SWPR farming campaign", + "0x3cf57b232feda914d81261d4c0feb7a510b7858c107696af862a37601dc687f4": "0xKLOM.eth Arbitrum Gas and ETHDenver Stipend", + "0x3e6215137546560a24cc54bba94bba1ef3f3f348842287898f085eaa66e324d8": "Test Proposal to Withdraw LP Tokens from WETH/wxDai pool on Swapr [1/2]", + "0x3f131d40fb2c9e0d8d771ceeaa0fea5919261536152093fc0d02e8bd79e50438": "Approve Swapr farming campaign for COW/WETH pair", + "0x40f50475fca7988cf84c26733151cb2581f4ff6cdbced672ef8160d8679df6d1": "Provision 150 WETH and 390k xDai to Swapr", + "0x40f8b0847efd72688758d6ab7c58a84132bc5e68c57d91c8be1314bd2160e717": "Nathan, contributor proposal 3/5/22-4/30/22", + "0x43f857b195320e0464f53d8ecb33aab669dfe0e7f263419aa4ac2d10da193125": "Tammy -- Vested DXD Request (Dec. '20 - March '21)", + "0x477bd183f2a90a20252e183f5956266555659d392ed11266c39ce27c9c266cd9": "AGVE / XDAI Epoch 14 - 15 SWPR farming campaign", + "0x4bc42058824c4ca5f8cef6dd2b78716f54e9fa94dc021370009dffbcffa312b1": "Provision 150 WETH and 450k xDai to Swapr", + "0x4fd9fc08411a92fff1810b338806529a16d5a6b548df82c6b02c1d0a0b7aeb42": "HND / XDAI farming campaign", + "0x51e16df1dd0a6de9bacb6955e39366140160023e9306ac9a6268353ec6b2918c": "SkyMine Labs - Stipend and Reimbursement around ETHDenver", + "0x53fdeca53c7c6440f9006964293db70ac6d297dd2199043e5325f82882176fb2": "Dave - Stipend for Devconnect Amsterdam", + "0x58d1cf014cff7f19f921f4345bcc1ed9e3cbab9e6e6bee224c024c202fa5e0e2": "Test Proposal to Withdraw LP Tokens from WETH/xDai pool on Swapr [2/2]", + "0x59b36d189960953e0bce203532af7f717575387eeb0138d70b4d1fdbf743227a": "Milan V. Contributor Proposal [1/3/2022 - 14/3/2022]", + "0x5b7e7cdab1cc23e4c78e93cedef86c59278259dd5206d2674904fc1eae1a40e7": "Borisblock — Contributor Proposal [07/01/22 - 07/03/22] 2/2", + "0x5d258180fc5922f69c09de3963d3ff0688d4ab0a9a1b4763a33be0d33edd47ab": "xDXD Buyback Order #170 for 10.03 WETH", + "0x5eaa2e8fa9d38e928218a5f5aa23591cae0bbed5789710083e75e7647898f847": " Trade 50k WXDAI to USDC ", + "0x635cb345ad6242ef248cd4d1af4a6f4dfd279577d853db0f54a6461bf1d70dc3": "DXD / WETH Epoch 12 - 13 SWPR farming campaign", + "0x67a488fb18db9ffdd1f9af4d8c68bad02379a96595adba1808a5ca0d70d1adc1": " Provision 100 WETH and 260k xDai to Swapr ", + "0x6ae455e54c9684d3712476809ce97289cca71742160408a7d72c298fc9638663": "dlabs REP sync ", + "0x6c8eba77df089d83821b6dfda6dddb2327593d1fd3933d26a48c7baecb1db6c8": "Test Proposal to Withdraw LP Tokens from relayer to treasury", + "0x73e3ed3be86120677b2dd93401ce3c729f304bac6ba6b2164de0cdb9456882d5": "[Etherlabs AB] Payment Proposal [March 2022]", + "0x7571912e7a5c4b166d74468d44121bfa259a275ddc5124442708f5ba3dd55f7b": "Swap 34k WXDAI to USDC", + "0x7648bc00cdd1b9b46ebb928538aafd693bd300a2122f2215f0c7622671f39b40": "Luchux - Payment Proposal 15th Dec 2021 - 25th Jan 2022. ", + "0x7bc28c23bf1035d22b82b5b393f5dd7fc3280fa0e3a8e3d42da8647b62969807": "DXdao Discord Q4 REP Boost for Bodo#0453", + "0x80121caeb85c037f5fc606f489a4cff12622f6c05b0d85aa2ce8ce4469f5e3b2": "Second and Third Payments to Team Omega for Audit of DXgovernance", + "0x805a31ecf7626142b64e640d2475c5ce1cdd7b210df05f35fea4599cb44fc6c2": "HAUS / WETH Epoch 14 - 15 SWPR farming campaign", + "0x858bec2ad4bcd89c36c9380d4299b19be0512e2ab19b5ce961119a1c6ddc433b": "GNO / DXD Epoch 12 - 13 SWPR farming campaign", + "0x8bb1bef2c2e0a8c3dd8f45fae33cdcef0bf9063438427c3b35bb3436b7ba7dac": "GNO / XDAI Epoch 12 - 13 SWPR farming campaign", + "0x8c06598b21cce13359a28a4c52efdc3606103bd93340f27fc6061ffe50a4d3b4": "[DRAFT] Levotiate Contributor Proposal 10/01/2022 - 10/03/2022 - second half", + "0x8e744c18b25b75c4fa95881daa15bcd492ebd2ad3a16c212514c66e4064d0e50": "GNO / WETH Epoch 12 - 13 SWPR farming campaign", + "0x952d8b39ff8dd5f0620dbe426acb3d137d92819cbb0631c6f9de734ac1a93c9f": "[SWPR / XDAI] - Swap fee update to 1%", + "0x971bb957d01a52d68ac8d4a4ccf3e7fd0583e4528a665cfb6e99fa641da844c3": "AGVE / XDAI Epoch 12 - 13 SWPR farming campaign", + "0x982e4dcea86d80a72ea6da2dab6730e34b409bee35788b414ff032d265b03f9b": "Level K - Reimbursements for ETHDenver Expenses", + "0xa57ea0f9ba56d6677b71ca9ac705cdab6ae170a081d5329fad315c8d44f2fb49": "2nd Test Proposal to Withdraw LP Tokens from WETH/wxDai pool on Swapr", + "0xa7ae51e589abdda68f082f0ea3f1f51e8be0fa04fd1c75a5f45c855f5cfe97d5": "SWPR / XDAI Epoch 12 - 13 SWPR farming campaign", + "0xad3f172fe7897c78d8ee7aac2f67d32c9c39d00dae858c619e4bf268ccddb80c": "DXAmsterdam Contributor Stipend", + "0xb1c9bf50e3b89be56279e9576b6c1e2516434d9abf7a8924d638377e858b4af7": "AugustoL Ferburary 2022 Work Payout", + "0xb5c6c20600d0324cbe600cfc1874fa1570e58202130b032e9f7f5643f375aa1b": "December 2021", + "0xbb75003e90205f0b732008521af4b531e0774f851981dc02d480d3566f05b871": "Smute - Contributor Proposal ", + "0xbc458ca8240faacbdee78b4efb321789a875ad55ded1cfc5df2129159694089e": "HND / WETH Epoch 12 - 13 SWPR farming campaign", + "0xbc9570eb252d3c72b16f58e79f8f26ca081930db44be408320bf12630564a401": "MAI / XDAI Epoch 12 - 13 SWPR farming campaign", + "0xbfd76fa4faa658ce026a54fbb8baea741014cdcec964f7b7584ee71c52aa87c5": "[Swap Fee] - Update swap fee of DXD / WETH to 0.6%", + "0xcba81e2fa2af07bdfa3a68496839a4ef722747769e95a9b520afd1a3574e186d": "Swapr HND/WETH farming campaign cancelation", + "0xcdee5c66dc09174bbef55341eca5e917e8f9e1a5b4f0df03ee85acf23eb30fff": "AllyQ Contributor Stipend ETHDenver February 2022", + "0xcf22139313ac001366712573f84c680c2c69fbd60ac0fe0ef128a9aa9f37af94": "AugustoL - Reimbursements for ETHDenver Expenses", + "0xd0555152a26164e57d55484c0305f66fee84cd72967f7bc2f6cc4235216b8110": "Madusha - Contributor Proposal [01/03/2022 - 30/04/2022] - First Half", + "0xd4bb2bafc24e0fe9b1b1b58c08306e4dc3fce5a5ffd0dad4957bf879ef1c0a76": "[Etherlabs AB] Payment Proposal [ Jan / Feb / March 2022] - 2/2", + "0xdcc56db033ceaf0e8cba81b6583d7a0e7034572520b582535c20c78d29cdbd2f": "CRV / WETH Epoch 14 - 15 SWPR farming campaign", + "0xe2de5a2142a5e08a3a7513c758fe0955e52c3b13bf3c224b4cfdc32cbe585bed": "RICE / XDAI Epoch 12 - 13 SWPR farming campaign", + "0xe38ea8f4f843920e4f450ff0cfbc66d6158543a546119b72ce8493b732de007d": "Milton - ETH Denver Stipend", + "0xe41ab7ec3ac9cb672043efb936ca9993d5ca2d97c6aaa14e61945faacc65e2d6": "Violet Worker Proposal 31/01/2022-31/03/2022 2/2", + "0xea97c70d686535ad265bfee1396e629e85edb5d585055a60b8655cc37e542e22": "Send HNDXDAI TVL Carrot to Liquidity mining relayer", + "0xf3d30c8e23ad26f4e9a75dc4cd71fa6a08a4e665e52791f97726efb90f0da8a5": "Alchemy API Expenses", + "0xf4c3ff2d5bd8d2111491052da99755d12b5ae3989ec061f16ef1651726c8a739": "Vangrim Contributor Proposal [12 March - 11 May (2022)]", + "0xf6941ec63a38b5f2c6282f7629bb7c1ccccd45f1be11902d0fb0b077c3cc96d1": "Send 14.9 WETH to GP Relayer for xDXD buyback #6", + "0xf85a3a5d75d9772e60807e4f5bb0d0ceb8f6c65d0c93b92b2d2e42cd6cb25505": "Luchux - payment proposal - 15th December 2021, 25th January 2022", + "0xf8bb4742b850198584c7bee39437151f5a6f6987dd23c106080f437002d2a155": "[DRAFT] Levotiate Contributor Proposal 11/03/2022 - 11/05/2022", + "0xf9d1887b1308e51583c8064da44b5487817b36811368dc0fe9fe78454ed5e740": "CRV / WETH Epoch 12 - 13 SWPR farming campaign", + "0xfb1795ba0a2b4ee12007fabc5f1847f344b40fbce8476727a648814e2e802261": "WETH / WBTC Epoch 12 - 13 SWPR farming campaign", + "0xff32174f3f2ff80955d5075937f72d07f5bc18364946cc2f54bc57c5f42a29ec": "xDXD Buyback Order #8", + "0x21ced878e0a12ea293711f6ac88546a730bba9a35f4dc9347c0a589562329797": "Stake 2k ETH to StakeWise - Signal Proposal", + "0x3e8416ee56beef52e28ea49ebe2839db3e620e8d3c695cd9c9f19b83f79bff40": "Decentral Labs Worker Proposal - 18th March to 18th May (2nd Payment)", + "0x53c9666338692a720a3ee3841b49f58a4d580cf7ed11acb2d43a484c8d09bc88": "Transfer of Funds to GC Base 500k", + "0x545f71ed3ef6e9efbe9340f634bb480bafa50639edeb5df7b86e4061b9fcf726": "xDXD Buyback Order #155 for 13.46 WETH", + "0x556cc36c8b7f807ad9770df79199f53462b8f93c6060023d97ccb3efc532c38e": "Payout to Sigma Prime for Auditing Services", + "0x5b1fb2e66669ad69531309fcfe342f4a1bfd995eb6940de049a625a4948289a9": "Decentral Labs Worker Proposal - 18th March to 18th May (2nd Payment)", + "0x636be996d018fc346bcce52e41c1026d41cf5cfb6665418334606d19c6cd7625": "Claim Mainnet REP for SkyMine Labs - Nov-Dec 2021", + "0x69a881265303706f921c9c1ad25de4b0635ff288893455b34f18cdaf9e7f86de": "DXD Buyback Program Adjustments, Extension #8 and Transfer of 373 ETH", + "0x79fb75b821690a15eb00764020bdb32d08704a5d340ff32eeb46543ff1caadb3": "Update dxstats.eth content hash to DXstats v1.5.0", + "0x7cc440cc8f6026c15213c4c551721f85d00d3a0908915331d454a9a326bd23f1": "Madusha - Mainnet REP Sync", + "0x880d07de4907b66d72521faec4de98283b92e9beb093b8ff7cdcb6112363b334": "Caney Fork - ETH Denver Stipend + GEN reimbursement", + "0x9a1c901b918f40f48a9cc1344a7556526952d6b697a02fdb479bd1ded5ab7e39": "Movement of Funds to Gnosis Chain DAI", + "0x9c62869ae0347e7f65e18733e4bdf9f155e9d43b14250295757e88520e6a7043": "Movement of Funds to Gnosis Chain ETH", + "0xd2114f86cd669d2a34b529e822024d4b280f7427639f0399106c4ae81997a2d8": "DXD Buyback Extension #7 - 385 ETH", + "0xe41bca110d96136684a2e916521ea9201f5a3f273aeaa518ca479f36961285f4": "Update swapr.eth content hash to Swapr v1.0.0 Beta 12", + "0xec89d8b01cc2ff1c77e501d056f2c584fcf323b10488583ccfeaeba90a06fdc8": "(Venky) Sync REP from Gnosis chain to Mainnet", + "0x00d4d73fd9cf48d95c5456dbd9b367236128e92359d600d2b09ce4cc2cb95bd4": "Leonardo Berteotti Contributor Proposal [02/02/2022 - 18/02/2022]", + "0x020b7aa9daca82035281fedd3bcfb42b56a67122b5ff53301b2c34c4e1d4bfdc": "SkyMine Labs - Stipend and Reimbursement around ETH Week Amsterdam", + "0x027d890c43faf6a00c691e65bd57e0375fa29c60b6629a24906614db3f3954ba": "DXD Buyback Program Adjustments, Extension #8 and Transfer of 373 ETH [Mirror Proposal]", + "0x03633066a51aaa63db6da76fbb07f8835bad46d43f1f58e6805c937a94bafdf2": "xDXD Buyback Order #159 for 12.59 WETH", + "0x0437d812f9581c992c12cf015252ced24dcd475d34e0e41256cdb6abee45eb8a": "Zett Proposal April, May, June 2022, Payout Period #1 | Product Owner & UI/UX", + "0x06338668a6d047c452722c3cc5bf2aea39daec9e9f92a1c3b2338fbf0e9c9c74": "xDXD Buyback Order #180 for 11.04 WETH", + "0x0c86b647fc9f921b2d7594e80fcf9bb904a5cb09e526d8c423797f863c46d0a8": "DXdao Discord Q1 2022 REP Boost for Sarah", + "0x0dcf78fae64e3f5d105768fb4fbcf4fa021504c7254bd97966b3b81c59e85d5c": "xDXD Buyback Order #165 for 10.08 WETH", + "0x0df20cd1df98288189770f51c1ca09dfdb798a7701e0dca02b35b3dc6076fc97": "Member Balancer to Supply xDXdao with 10,000 xGEN for Continued Governance Operations", + "0x122e8aaf69ac6c2eb0b8576a01baa7f0b0d72775ac100e04fd111515686a182f": "Venky - DXAmsterdam - stipend", + "0x13f6f59e9684d54de3f27a16061f10a59f723d9fea5135510d6d3217d474102c": "xDXD Buyback Order #169 for 9.91 WETH", + "0x1b7634ab666c2a29bc85d830921c8c367a559b4c8bdcfa79b3e78e582f294e8c": "Adam Azad - Contributor Proposal March - April 2022 (late) - March payout", + "0x1cc9c1bc8f168029744113be1ea4205faf53a17983bfab722a42b72b3cf67457": "xDXD Buyback Order #181 for 10.81 WETH", + "0x1d7f784feb2ab279ce228be60672add5aab82aaf701762f4188f625efa8a15ee": "Signal Proposal: DXdao to commit to Ministry position in Dawn DAO organized by The DAOist", + "0x1ef0ceb197194b356d30b1acac18f4c6571563579f07e9bd2b122b68f3e10b48": "xDXD Buyback Order #166 for 10.17 WETH", + "0x1fe7d4a7baa0ccc75b3640d3be0abda448afcd6754f20b6cfe755a45b87e6aa4": "AllyQ Contributor Proposal 1/24/2022-3/18/2022 2/2", + "0x23ec0ba69003e9c8d0724a2b368c1d6d6a151e1f5a5673604f1097de28e966c6": "VanGrim (through Zanarkand AB) Contributor Proposal (12 May - 11 July 2022)", + "0x2497b482da5188cbd71cbc90dcceb1f3077142c55dab87f3107f4d3af31b9c1a": "CRV / WETH Epoch 16 - 17 SWPR farming campaign", + "0x269918338f757a7e4c56de96acf1e517282cab63f97485dc5ce38b8b6e374ede": "AllyQ Contributor Proposal 5/23/2022-7/15/2022 1/2", + "0x28e4d114cad1d9ef56d0e43953d44b7dee4786714b6d6429410e57bcf9f04d65": "AugustoL Contributor Work Proposal #4 Payout", + "0x2a8121413822a157f678e719ccdacbbf6a3faea752485c77685506aacc7feace": "Space Inch Development Work - April 2022", + "0x2afdb83c932c74bd5b39030a38c411c3afb8784a166dfb6bec0d2a4b7ee7212b": "xDXD Buyback Order #163 for 11.2 WETH", + "0x2f020d2fbb83b52923da248af446caa642de28af37d6f2a106e38bfdf050e162": "xDXD Buyback Order #194 for 13.99 WETH", + "0x328d33e198b9c4a882c647fdb1ea1469db89d631492260daecbc5bbde6956ea9": "xDXD Buyback Order #193 for 13.07 WETH", + "0x3453fe5a59e7c62ce400a8bba2c03d1b7f0792e513cc690ef3446bb2f9a17f2a": "xDXD Buyback Order #167 for 10.11 WETH", + "0x3495509fdd80bc85acbd43808c999fa67de371861579da4fad502f7d32b722ad": "Dino Crescimbeni - Contributor proposal [16/05/2022 - 15/08/2022]", + "0x34c95d7652e11caffc1dac7b885d0f2be28710b9012acbb651ce27bfefa45a04": "xDXD Buyback Order #189 for 11.97 WETH", + "0x36453048f17d2a6612da9380ee7526aba69976cf13637688b3beb6e701ff9cfb": "Melanie Contributor Proposal 2/1/2022-3/31/2022 - End", + "0x37feff80c278ccd50524730c81841678c7dc4710957626cfb25af517a22c8328": "xDXD Buyback Order #191 for 11.44 WETH", + "0x399c05f720af6db7dbb2ce7c539c3c48fdcefebbfeb2fc3a12ec65dba645249b": "Borisblock — Contributor Proposal [07/03/22 - 07/05/22] 2/2", + "0x3a8e9d0b5909b8dbaa1bc391c7f708dfa1432afa98a10e86a1197457d9037733": "SkyMine Labs - Contributor Proposal 5/22 to 6/22", + "0x3ad287fad8243f2ab939f0b5dee1b019ba8aeb80247f3719faf7ec87c6a7c153": "Nathan, beginning of new period 05/01/22 - 06/25/22", + "0x3c207c2cb2005382f4ee55b11949057df74e2914c3f66f172014736ea1a3207f": "0xKLOM.eth Contributor Proposal for 05/2022 to 06/2022 1/2", + "0x3c26e5af14f6c524ef8c8f32bb19fdbc86777acba84e70390cff89bb1e32e747": "xDXD Buyback Order #195 for 14.23 WETH", + "0x3d4de42fbdb25ede9391ab6c5e87985d524f7cd42f47c75fd75acb1e581b2cf1": "Wixzi Contributor Proposal [05/04/2022 - 18/06/2022]", + "0x3ea8a6cbf6c9f7c8087271473e098af2f16747f6e07408a1b5a6fab72580f7d4": "DXdao Discord Q1 2022 REP Boost for squidz", + "0x3fe2e55c745fba7e7f9ad5ee91e2bfd79ca13f4557cf2725247047b1c0a90849": "Madusha - Contributor Proposal [01/01/2022 - 01/03/2022] - Second Half", + "0x3fe35e33bfe91259ac39a0573d99f77042a695bbf5740c2c3c1d2549d3c3085f": "Space Inch Development Work in March 2022", + "0x41dc8a5d63f83d88227697b86954ae2d03891bc8996b592ffaa0897c4f9af82e": "xDXD Buyback Order #197 for 16.5 WETH", + "0x42984b06d80ce009f37621bad8bc85001497f6f3907562e9ff27f72f334a2c33": "DXdao Discord Q1 2022 REP Boost for TheThriller", + "0x42c0a5c0edd7b817c7d6e31e57c70ef3da70795ddf764fd4ac49b5dc7a6081e8": "xDXD Buyback Order #175 for 10.63 WETH", + "0x42d379b00f25079dd7964ebbc8c1fe6d44e03f1bd340aa68442c12df568cd909": "xDXD Buyback Order #185 for 11.62 WETH", + "0x4525007fb5948688f94c8db0644ca55ea5d3543ddedb8c8eeb3bfb954e982c60": "Space Inch Development Work in February 2022", + "0x45cdf73bb23f462aba74c8714b44784c7148bc953d59369dbe9c9fd6a65e5961": "xDXD Buyback Order #174 for 10.76 WETH", + "0x4a70402e1e43f472a7b58f52969390cd77cc7896cc8a9bb492f87303dc1c2e68": "Guerrap Contributor Proposal [14/03/2022 - 01/06/2022] [1/2]", + "0x4ad40975ba5bff960bdb7dfa1f6a7973ba84f4a70bd15e3b9313e48a075b2f2c": "xDXD Buyback Order #200 for 11.47 WETH", + "0x4b394e097059ecb4c3163c4e952f933ae8124072d19c3777b923087bef347fef": "Unwrap 65k WXDAI to Dai", + "0x4c48923b379c4910191f27c80dcd7148af588ca648f9304d3e7731f8f83f0b04": "(REDO) - Member Balancer to Supply xDXdao with 10,000 xGEN for Continued Governance Operations", + "0x4c87360f51d53256be331f459bdcebd659e2368d30ca9441c8b84cfe1bc38304": "Adam Azad - Contributor Proposal and DAI payout - Jan - Feb 2022", + "0x5031bca4a8f68ba19fdf6f2d302ba69b7eec3d5761c4f3420b391b035eda9ac6": "xDXD Buyback Order #184 for 11.18 WETH", + "0x5061ddc2fd7542b5119b3e6f6c651c17ef561f1b1e92d1023590b080ccc99784": "AGVE / XDAI Epoch 16 - 17 SWPR farming campaign", + "0x524dfaf6404411a9cab6635a99198ce127dcd1d398a0fd58f1432eb8802f225e": "xDXD Buyback Order #182 for 10.87 WETH", + "0x52a7e5f06b36a916e88e0e6c0adbc3fc8fb2b7e23970a0c5fcf81b41669a50b3": " Melanie Contributor Proposal 4/1/22 - 5/31/22", + "0x54e907e00b8897f6d67a8214bf735f84d655ca83f58783c2ec1639d4c64fd453": "Proposal: Carrot Awareness Activation During ETH week in Amsterdam (April 18-25, 2022)", + "0x55b61b9fea9a4f77fb6e4728437f1a8a306416f6cefe0067029eaa50efe90813": "Wayne’s Worker Proposal - Trial Period Late Jan 2022 to Early April 2022", + "0x55ff34c97949e067ed11379a5ebf2c032b146fd83b11b03d48e8685fde618761": "Wayne's Worker Proposal - Early April 2022 - Late May 2022 (signal)", + "0x5715b4637b8e32cf3f44dac8fbd78f38a87fc1563e5957e4eecbdd5723a03450": "Wayne - DXAmsterdam Contributor Stipend", + "0x57a0c98453a71eb8e745aa2bc1d3bf66a110cd2aeae9cbdfa62d750406b10766": "DXdao Discord Q1 2022 REP Boost for SpicySoup", + "0x57fd56a03e35d0b72b50bb594508352d7de0d5e3e42da36802916ac0573baf5e": "DXdao Discord Q1 2022 REP Boost for Tom-Fr", + "0x586578ee4810f82e5d9c7dbe43e430a56584924b9a0d99e23e3be1c4077a5c88": "xDXD Buyback Order #151 for 13.42 WETH", + "0x591e0bb6b2c5efa5b97f81d70784353ffc15a692ba58edca648bc147013c7511": "xDXD Buyback Order #156 for 13.93 WETH", + "0x594b2166099be1b5c99958b74c3fe46e5bfbf53020222b7134886d78287cfd7d": "xDXD Buyback Order #150 for 13.19 WETH", + "0x5cf0c076fc5cb1d7931cfbcb2988c9302494b5b967f87caa04cc01c4deee484a": "Carl B - DXamsterdam stipend", + "0x5e1617ca4b3b6d1ebed7c576eaaf428241e6a77c3407b578af82f4c71002c6bf": "Signal Proposal for DXdao Discord REP Boosts Q1 2022", + "0x610e201c8ac544589cd9d2743e3463344fe4d43065abe6726211eb59d53c4032": "SkyMine Labs - 1/22 to 2/22 Contribution Recap", + "0x615ab3d3e492672d555b01eecca68023ca5e8f1c014bcda6da5e4e4d79aea0df": "xDXD Buyback Order #173 for 10.75 WETH", + "0x62e0b3b82715547de7090f628f4c3bda4322226d664ef4e9f41a5cc4e8d7ab78": "December 2021", + "0x62f0b6b36f2071c93ed3b4b6cfcce354dadf9caf31fbfea64d95809195bc967f": "Diogo Ferreira Contributor Proposal [4/04/2022 - 15/04/2022]", + "0x62fb4da3c53ae8c70710acf49f9771a7f7b84ec36ce502a65dc2fd57f592bfc5": "DXColombia Contributor Stipend", + "0x651ae49a00ea204017b1dfb7ca7430bae1fad61a184ebbdb1b6ebc13b35f3ce0": "Worker proposal - 26/01/2022 to 25/03/2022 - luzzifoss", + "0x653fa2503a9d3fa394f234ec69e217270e12c0ace69880e4fb9f18509eb9303d": "DXdao Discord Q1 2022 REP Boost for Chiminiv9", + "0x6914b20e6c054d8787c6acb13ef1b61c5587a4912e2b7ced90f36b9aa09820e4": "xDXD Buyback Order #164 for 10.33 WETH", + "0x698c40183fd31e9df74d0a7d391c62b1475cd53b5cec730aa9090262876a79f0": "Caney Fork Feb/Mar 2022 Proposal (2/2) + DXAmsterdam stipend", + "0x6c2366402f1baa13661d68659ca4981c28c11078b67f9c6a10389a0d1e3fc922": "​ Borisblock — Contributor Proposal [07/03/22 - 07/05/22] 1/2", + "0x6cf3ce433d7d90f4f66486a9e09be8540bf0840e76a593b8e10f8a658897cf04": "xDXD Buyback Order #196 for 16.49 WETH", + "0x6da5d04d5cb90b4cf62cb609d6bf71524dcc0630e67063ee3746bf0c1e50977c": "xDXD Buyback Order #148 for 11.65 WETH", + "0x6f8961cbd277969e9d149d1220d26b678f5e7193dbdff9f136239c54c67c53b4": "VanGrim Contributor proposal 12 March - 11 May (2nd part)", + "0x728980d7a2086a78561c2b24b82a04957a2d31fb41e84373de77d85b423d93a2": "Jorge Lopes Contributor Proposal [18 April 2022 - 18 May 2022]", + "0x74cb0a9436ddd5810bb7dac8e537def758d2be703abcb6a93cc7125bf52c8266": "Caney Fork Proposal & Payout Dec '21 & Jan '22", + "0x753bfb5a08d34e80f87f31fa697f00f991d2e77e54d0509e49feb76539846e0b": "0xKLOM.eth Contributor Proposal for 03/2022 to 04/2022 1/2", + "0x775a93f01e24feb1707be6c9df8df89cb3e10fb2286c36c77409f0b4dfcdf668": "VanGrim (through Zanarkand AB) - DXAmsterdam Contributor Stipend", + "0x776ecd66631b9a0d97247c3300617450c56bbbe090746e3ddaab817a0f3cde4a": "xDXD Buyback Order #144 for 14.05 WETH", + "0x78da1d7655fc15a9d10eb4f6a434c9c2144fe44661296d3a6b7182e09eeb6e47": "xDXD Buyback Order #158 for 12.57 WETH", + "0x797e661cad50dfc6956c19e3e2538b3f36aed16e29d17f0a1d34cb90384ed9d0": "Leonardo Berteotti Contributor Proposal [21/02/2022 - 15/04/2022]", + "0x79a64e3beb29b46eb7c9dd192e13d8ac405b5a94e8b04392900497ba511ab2bc": "Dlabs Worker Proposal 23rd May 23rd June", + "0x7ac00c7b3e2b4c80084bba14ef50f75784a545f1b092a7247fb02ad46707632e": "xDXD Buyback Order #199 for 10.98 WETH", + "0x7c3b8569d7d196f5c2764bb4f7edf1e0c6fcdca10f1be153b8adb5dda4663609": "0xKLOM.eth Contributor Proposal for 03/2022 to 04/2022 2/2", + "0x7d706e5797f06b58d11fb7924856b55b0399fb4788447f23421cfdab16905f6e": "Violet Worker Proposal 31/03/2022-31/05/2022 1/2", + "0x7dd1a6275b358c040e113a069c88d3f44e4782ba1138cb8c30be2670e26d4b8a": "Tammy Vested DXD Proposal from periods 12.20-5.21", + "0x7e6b833973c06433dd53c04ed7664798fb11aae83881346d442c60279b087b71": "Carl B Contributor Proposal [2022-02-21 - 2022-05-09]", + "0x7fb7e060c4b6442e2cbd4fbd48bde7005c8553c9fa10f27d8d266793593fab6b": "SkyMine Labs - 3/22 to 4/22 Contribution Recap & Payment", + "0x817730e1c57f948c587a19b54177ab23ef7332a04c73986e1b66a06a5fc5983d": "xDXD Buyback Order #188 for 11.92 WETH", + "0x872704d2f1619dd5d83b9ca255348647b504322aac54bc4783712d6f6883020f": "Etherlabs AB - payment proposal - April 2022", + "0x8893d52df36f581cf2e30a17c5459380db55799d31fce85dd5e9b17e20a3251a": "Milton Contributor Proposal [04/04/2022 - 29/05/2022] (1/2)", + "0x88b8b7e41ea978cd9dad9fc9939ea56177813663d2d696ebe8a34bf147a1d17d": "MAI / XDAI Epoch 16 - 17 SWPR farming campaign", + "0x89fff26d1ddd066908be3072b78aad19225c79c78ab3753ce168113dd3be26a9": "xDXD Buyback Order #161 for 12.17 WETH", + "0x8bfde4ea88ff76a420a7cac5617cc500ac78c274a8c7787b5f5542562d0fb702": "xDXD Buyback Order #149 for 12.08 WETH", + "0x8e496a70764ca712c6444df8486024e5d88b98b97a0688c6ea2a325384e019c1": " Carl B Contributor Proposal [2022-02-21 - 2022-05-09]", + "0x9230fa1b8a5d6643c029a9259256e2d852c3a485b31694b9e0e33645ceeedbca": "AllyQ Contributor Proposal 3/28/2022-5/20/2022 2/2", + "0x929c071b90372dc4346cc7480290d00636dae64ba566562a8022afbcbd99477c": "xDXD Buyback Order #155 for 13.46 WETH", + "0x981f7ad155bb4d79fe963426b22d26bde961aa069b3afb6924968ac0325e1b08": "DAI payout (26/12/2021-25/01/2022) - luzzifoss", + "0x9897941fba1f7450d3c038868485ac4ca23b83cc695eb9326f9394e6a6ded43f": "Stake 255 GNO - Signal Proposal", + "0x991b4570a439520098f00d23755bce9e58a28d58ece5c54d2e4148b2d49fe5c8": "RICE / XDAI Epoch 16 - 17 SWPR farming campaign", + "0x9a9a8bd064b947fc9bb32f71b7813aee6fdbea0abe9cd620204f0d4b3731b979": "xDXD Buyback Order #147 for 11.83 WETH", + "0x9c05f686011ebb52a19955e2ffe63ba8ee0505dd29365b3a6a2490f2ab3ff714": "xDXD Buyback Order #146 for 13.99 WETH", + "0x9f5faacc7f41665b447e1423f3d79274a8434a7e7f2b4a2dd71b85d2146fb36c": "xDXD Buyback Order #171 for 9.79 WETH", + "0x9f66d49e0b800dca0de8d21044f7cdf67af8e12154d01b785253e29419a2eab2": "Nathan, proposal at end of period 3/5/22-4/30/22", + "0xa194a4b2ae0050d2396e030709e10b85daa13e6ba1d4f0642d2573e45c961e9d": "xDXD Buyback Order #154 for 13.66 WETH", + "0xa2be4abc07207c4a273cd977428078b3d81292522cd170d4e0f9c95091568427": "AllyQ Contributor Proposal 3/28/2022-5/20/2022 1/2", + "0xa59d47177919ce1121216ab29e6e1b15d0d1f5ebd238d8eda8c5ba54d6def171": "xDXD Buyback Order #190 for 11.36 WETH", + "0xa7523735a2f01afd953d4e7650c7323f2b3714d4b7b81133e471e734206f676a": "xDXD Buyback Order #162 for 11.9 WETH", + "0xa793418ec15a861296ddad02148bd28a6078e799ea605d299b7dd9c38656e5cb": "0xKLOM.eth Contributor Proposal for 01/2022 to 03/2022 2/2", + "0xa7eed37b53104a70aed9d73e811498ea0fee9a9e634230fd5745d94ebb3f7d4e": "xDXD Buyback Order #197 for 10.64 WETH", + "0xad91395f9ae6453c66819806237380cb9231812b2784ac727ad14aed5c348fe6": "xDXD Buyback Order #157 for 13.65 WETH", + "0xb0f303cda263824463740d66e310d06cf1a4f73bea67a6a8a51d95c37bcf532a": "​Borisblock — Contributor Proposal [07/05/22 - 07/07/22] 1/2", + "0xb399905e93fcc1ea604cc965a2622b39a62318706965aa13319784ad20a069f1": "Ross Neilson Worker Proposal 18/4/2022-18/6/2022 1/2", + "0xb7804c3f333e5012a66be5027f0ee73c9d765b54bb086f836ac20a905a46e20e": "xDXD Buyback Order #168 for 10.53 WETH", + "0xb7eb202304dbf0b1fa2fda84a1b972ac8ebc7c2dff79661de8de67c5e4ee2b2a": "Diogo - Contributor Proposal [16/04/2022 - 16/06/2022]", + "0xb7fc6d125eed2595e715c4f857a6512748d2bc96bd4a1adbce9ff21f209152ab": "xDXD Buyback Order #192 for 12.44 WETH", + "0xbac6b0fed6d4e10445d27f03ebc424082bf4c6c5cf127ac2f89cac04e99671e9": "Madusha - Contributor Proposal [01/03/2022 - 30/04/2022] - Second Half", + "0xbc80c062bb326a303dfc363083471eb8d6bf4146a8b6ba02328906ddb4ee16b5": "Adam Azad - REP Sync Proposal for January 1st to April 30 2022", + "0xc0cfd98a9317b65cd4b5496247b36bdf8990cf72b570e1b5d1f53c2ce7b5bdd2": "DXdao Discord Q1 2022 REP Boost for Cantillon", + "0xc51721575e515e3028ecb37ea148926a9ae9738f81c91564c435ffbc3437a009": "xDXD Buyback Order #183 for 11.72 WETH", + "0xcba3e751dece5607735455eed9e19271f1ae108ffbdbf8edc88d64d1281d75fc": "AllyQ Contributor Proposal 3/28/2022-5/20/2022 2/2", + "0xcbba59861ced43a022b2bf0bb5e26151b2e1088c7b0198d63b414732f6cf0aea": "Ross Neilson Worker Proposal 18/2/2022-18/4/2022 2/2", + "0xce7c3c0de28ba9b1d1c4180234200a4da01c4c1a900e39fd9671f1ff5cdaa71a": "Adam - Stipend for Devconnect Amsterdam", + "0xd066e809f19e7fcba92fd4c99001be124525484f5d37725c21d64479a248cd08": "Milan V Contributor Proposal [14/3/2022 - 14/5/2022] - Second Half", + "0xd2784e9d5a06ecf3db3cbf49147a5fb80d329d0d2fbb450274e77a7dabd08bb3": "xDXD Buyback Order #176 for 11.21 WETH", + "0xd39d6107149aafeb24a41a252a93a0a034609585059a82ffe395a99df5030bcd": "xDXD Buyback Order #152 for 13.76 WETH", + "0xd41c033b50c0d32cbcd5d8a15a9ad528de8732cc6542f0aff6100b61e5893dac": "Milton Contributor Proposal [07/02/2022 - 03/04/2022]", + "0xd59ed2baa46fc4b325a434e7c86922cee46801f2003b53f17b090cad1ea725d5": "Etherlabs AB - payment proposal - April 2022 (REP and vesting)", + "0xd91c3ef93c3fde9be42d2cf4d9d44aab1e98aff1c21a077fe3f47fe8e73c1e42": "xDXD Buyback Order #145 for 14.95 WETH", + "0xd98e432717bd3cef5046d3b5db806133b8ac79878cf83ccecd4b809ac1154f2f": "xDXD Buyback Order #143 for 14.99 WETH", + "0xd9fdc37c352bd29e87198f5463aa4c5ae3144887124ae245db2715f7cea14e8b": "xDXD Buyback Order #186 for 11.47 WETH", + "0xe03d013efeca08f6d0949861290bb2e8332d5dfe35f9516674ef5c208d84a91c": "Leonardo Berteotti Contributor Proposal [21/02/2022 - 15/04/2022] [END]", + "0xe3209990432c23293332367fbe0a399ede709e42c1c9640c72d949819c1185f0": "Wayne’s Worker Proposal - Trial Period Late Jan 2022 to Early April 2022", + "0xe4a0e623a08639960a2936b6eb629a24f48578e2f520cfe32b25af84f70826c3": "Milan V Contributor Proposal [14/3/2022 - 14/5/2022]", + "0xe71fa634850a4a10c9eb2610c39d680c7dd216cc76ce9b22d3ff6136c72fa9f4": "xDXD Buyback Order #160 for 12.21 WETH", + "0xe7eb6799d28cd42071dd73930b997d1979734d1cac61c83b0bd186136f03d5b8": "xDXD Buyback Order #177 for 10.86 WETH", + "0xeb093a056d71e391917e06a2751e0bd23cd7ef06092b1db4e4c8adf88642b8b7": "SkyMine Labs - Contributor Proposal 3/22 to 4/22", + "0xebdbce1b34db36d21ef282f356ae4778fbfcb5501b494ba505d3b6be312ba191": "HAUS / WETH Epoch 16 - 17 SWPR farming campaign", + "0xec1661e91a40e16976d06c8240559f21cc92cd58c127763bc74747f7192404a8": "Int_blue - Contributor Proposal - April - May (Trial Period)", + "0xec9f0b6961fff3c12f7fa80745997311de28135cbb3a2e3c0798eb9c82fffc64": "DXdao Discord Q1 2022 REP Boost for Tom-FR", + "0xed3af1bfdbb6e6fef16a925c7117d173c311ba8ad53bd7bfde53a09f01b02f68": "Caney Fork Worker Proposal (Feb payout)", + "0xed8b701a21d8df816253ee7718c233efe3dcbdd0a48ccd1f003fcd0ed9685498": "DXdao Discord Q1 2022 REP Boost for snufkin#7489", + "0xee7d2df08b9870157259690aac89380acec49fc4587178481338e31c97428f30": "Leonardo Berteotti Contributor Proposal [18/04/2022 - 10/06/2022]", + "0xeed6b0b0d4364a7186cfb30269e601fffecb57dbd4a9a63a435178ad46ef8692": "xDXD Buyback Order #172 for 10.29 WETH", + "0xf1e9f9b94b4998a2eccd70505d17c5f340965dbad018a3da7dc26463efba0724": "Melanie DXAmsterdam Contributor Stipend + Swag", + "0xf2a22d0253b8d8fe8c9a5141437c153d3f1219a46bb52db2ff616e917a0afbc4": "xDXD Buyback Order #143 for 12.65 WETH", + "0xf361b3de6f65d49f95f274330698297c1f0f4fbeac230a1affc3dd739bfc2bec": "Send 15.4 WETH to GP Relayer for xDXD buyback #4", + "0xf37c8f2d3b91c335f17b32267b37ad95d4fb76123fb65f32bef87947067276d1": "Etherlabs AB - payment proposal for Jan and Feb 2022 - stables", + "0xf386d0a218f489df4649b345c9e248369e82685b23dffb895f8a4bfe2d9f268b": "Zett Amsterdam Stipend, Copenhagen, Merch & Other Costs", + "0xf637871828ab1b20f7551cba7f81cc211502468f2273e77f619073c49d5ea6ef": "DAI payout (26/11/2021-26/12/2021) - luzzifoss", + "0xf64ef8e41d77cd00750b7cf0f82d1d5dd6a72ff9589c8c0667490e9318dd15eb": "Violet Worker Proposal 31/03/2022-31/05/2022 2/2 + Amsterdam stipend", + "0xf7a30ab1df563e6327e7a1114ea16161e1baf3aee6d07c62d8bccdc7b26f97a2": "Send 16.4 WETH to GP Relayer for xDXD buyback #11", + "0xf844a99ab8cb5556dc19b3dc76dc55ce88f3ed63f1aae6604318a5ced2e8e80c": "xDXD Buyback Order #178 for 11.06 WETH", + "0xfa21c90ad70a9f2f804246f3746b5aef5036864f29158c298efe49a28d6578f3": "Caney Fork Worker Proposal Apr/May 2022 (1/2)", + "0xfa8db9098d406b2efb145366102a314fc852a40d3c4361f4a30f15783bbf639a": "Violet Worker Proposal 31/01/2022-31/03/2022 1/2", + "0xfb27a478ec614dea90f5e0b4b4caa828fded254d0d589b120a119e0ca097ebad": "DAI payout Zett Period March 2022", + "0xfc921ce24f2458a725f2928d82e82888839af71a3cc15a8a29c374a3cae9102d": "Trial Worker Proposal - Adam Azad - January 13-27th, 2021", + "0xfcdb52fff5bed18bbce359b3bdeb5ffe0431c0e29f60af08b85aef969cf7fe25": "Melanie - ETHDenver Stipend Request", + "0xfd10e5ac9711008d583f7c8d5da9aec232be4f3ee9b64c715df95ad8340c6ba5": "xDXD Buyback Order #189 for 11.8 WETH", + "0xfe0d7e6f7c22c11e19a32d40f7360ce0251f2122d4a2a9a114bbd1d90853ff8d": "Melanie Worker Proposal 2/1/2022-3/31/2022", + "0xfe2765f0bbd28a9d00e6c231d12e19235b4f471b02fea652bc6b5d5280069a4f": "Nathan, contributor proposal for end of 01/08/22-3/4/22", + "0xfe8dbe253824d26a023cfbe75ccfd01a15defa01fd7f2712fb2a33a2bcb6b1bd": "DXdao Hackathon & Retreat Colombia October 2022", + "0xfeb2d79cd1e17a3e3f1b22b15a3b86d6284a1415dea8d120df0fb0ae1bb19f77": "xDXD Buyback Order #153 for 12.88 WETH", + "0xfeea9d463a997be8e640ecfdc7b8179e7424ebe8c0d8201c65c2019a7d7baf67": "Worker Proposal 06/21-07/21 - Kaden Zipfel Software", + "0x17dcc4df80b1dcde1c7943ecb9aacab9f6d369177f9dffdcc95be231511da1be": "Cross chain Member Balancer to Supply xDXdao with 35,000 xGEN for Continued Governance Operations", + "0x52c90416b88701ea09a44b0bd9bed5c7ea6db5cece2d28cb1456a7da668afc16": "Caney Fork Worker Proposal Feb/Mar 2022 (1/2)", + "0xa8036392ff2da5486d287473a8f1490fe74461e1838c098920dedca73ed0ecdf": "xDXD Buyback Order #179 for 11.01 WETH" } \ No newline at end of file diff --git a/src/configs/rinkeby/config.json b/src/configs/rinkeby/config.json index b2ede7e0f1..15460865ef 100644 --- a/src/configs/rinkeby/config.json +++ b/src/configs/rinkeby/config.json @@ -1,8 +1,8 @@ { "cache": { "fromBlock": 9234792, - "ipfsHash": "QmcQUUNkAy6V7PYH4wksHvhTmJbEiUGT6UyFik1FCAi8rE", - "toBlock": "10225316" + "ipfsHash": "QmTear8HaHRnNNTAu9RRMpdPdRxqKFmXTkqwGMgu1eKJFj", + "toBlock": "10525725" }, "contracts": { "avatar": "0x1A639b50D807ce7e61Dc9eeB091e6Cea8EcB1595", @@ -13,8 +13,8 @@ "utils": { "dxDaoNFT": "0xF7A2f72E8f6aeb884905a1fE515c1BB5752D342f", "dxdVestingFactory": "0xB5D42c8cA3B04479200E3782C41D99b46Cf8A6E1", - "multicall": "0x1FDD273e0B7E0d921Bda044Ebf64811428A79f15", - "guildRegistry": "0xA938DD7AEeF5F46BD9845882EAbA2ebF18e1b273" + "guildRegistry": "0xA938DD7AEeF5F46BD9845882EAbA2ebF18e1b273", + "multicall": "0x1FDD273e0B7E0d921Bda044Ebf64811428A79f15" }, "votingMachines": { "0xBa51391385A6bD5ECA0217d7e843B29227E72508": { diff --git a/src/configs/xdai/config.json b/src/configs/xdai/config.json index d3fafd1778..a61839cefe 100644 --- a/src/configs/xdai/config.json +++ b/src/configs/xdai/config.json @@ -1,8 +1,8 @@ { "cache": { "fromBlock": 13060713, - "ipfsHash": "QmfRWx3A2G3gL1sauTknmLUwiKP6NP1vWteEqPBXYV17uC", - "toBlock": "20806189" + "ipfsHash": "QmQgax9dCsQbWwovbLG6NA1A8Z1pfoh5kRq1asqYZ8ReTM", + "toBlock": "22297636" }, "contracts": { "avatar": "0xe716EC63C5673B3a4732D22909b38d779fa47c3F", diff --git a/src/contexts/index.ts b/src/contexts/index.ts index 4bb71cef4c..bc04b900b2 100644 --- a/src/contexts/index.ts +++ b/src/contexts/index.ts @@ -14,7 +14,6 @@ import CustomRpcService from '../services/CustomRpcService'; import ENSService from '../services/ENSService'; import TokenVestingService from '../services/TokenVestingService'; import SubgraphService from '../services/SubgraphService'; -import MessageLoggerService from '../services/MessageLoggerService'; import OrbitDBService from '../services/OrbitDBService'; import ProviderStore from '../stores/Provider'; @@ -62,7 +61,6 @@ export default class RootContext { ensService: ENSService; tokenVestingService: TokenVestingService; subgraphService: SubgraphService; - messageLoggerService: MessageLoggerService; orbitDBService: OrbitDBService; cacheService: CacheService; @@ -89,7 +87,6 @@ export default class RootContext { this.ensService = new ENSService(this); this.tokenVestingService = new TokenVestingService(this); this.subgraphService = new SubgraphService(this); - this.messageLoggerService = new MessageLoggerService(this); this.orbitDBService = new OrbitDBService(this); this.cacheService = new CacheService(this); } diff --git a/src/contracts/ERC20SnapshotRep.json b/src/contracts/ERC20SnapshotRep.json new file mode 100644 index 0000000000..0c9ed2433f --- /dev/null +++ b/src/contracts/ERC20SnapshotRep.json @@ -0,0 +1,459 @@ +{ + "_format": "hh-sol-artifact-1", + "contractName": "ERC20SnapshotRep", + "sourceName": "dxdao-contracts/contracts/utils/ERC20/ERC20SnapshotRep.sol", + "abi": [ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "Snapshot", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "snapshotId", + "type": "uint256" + } + ], + "name": "balanceOfAt", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "burn", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "subtractedValue", + "type": "uint256" + } + ], + "name": "decreaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "getCurrentSnapshotId", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "addedValue", + "type": "uint256" + } + ], + "name": "increaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "string", + "name": "symbol", + "type": "string" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "snapshot", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "snapshotId", + "type": "uint256" + } + ], + "name": "totalSupplyAt", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ] +} diff --git a/src/contracts/SnapshotERC20Guild.json b/src/contracts/SnapshotERC20Guild.json new file mode 100644 index 0000000000..c9bba32d29 --- /dev/null +++ b/src/contracts/SnapshotERC20Guild.json @@ -0,0 +1,1145 @@ +{ + "_format": "hh-sol-artifact-1", + "contractName": "SnapshotERC20Guild", + "sourceName": "dxdao-contracts/contracts/erc20guild/implementations/SnapshotERC20Guild.sol", + "abi": [ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "proposalId", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newState", + "type": "uint256" + } + ], + "name": "ProposalStateChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "voter", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "TokensLocked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "voter", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "TokensWithdrawn", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "proposalId", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "action", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "voter", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "votingPower", + "type": "uint256" + } + ], + "name": "VoteAdded", + "type": "event" + }, + { + "stateMutability": "payable", + "type": "fallback" + }, + { + "inputs": [], + "name": "ANY_SIGNATURE", + "outputs": [ + { + "internalType": "bytes4", + "name": "", + "type": "bytes4" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ERC20_APPROVE_SIGNATURE", + "outputs": [ + { + "internalType": "bytes4", + "name": "", + "type": "bytes4" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ERC20_TRANSFER_SIGNATURE", + "outputs": [ + { + "internalType": "bytes4", + "name": "", + "type": "bytes4" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "to", + "type": "address[]" + }, + { + "internalType": "bytes[]", + "name": "data", + "type": "bytes[]" + }, + { + "internalType": "uint256[]", + "name": "value", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "totalActions", + "type": "uint256" + }, + { + "internalType": "string", + "name": "title", + "type": "string" + }, + { + "internalType": "string", + "name": "contentHash", + "type": "string" + } + ], + "name": "createProposal", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "proposalId", + "type": "bytes32" + } + ], + "name": "endProposal", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "_data", + "type": "bytes" + } + ], + "name": "erc20TransferOrApproveDecode", + "outputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "getActiveProposalsNow", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getCurrentSnapshotId", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_hash", + "type": "bytes32" + } + ], + "name": "getEIP1271SignedHash", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "getFuncSignature", + "outputs": [ + { + "internalType": "bytes4", + "name": "", + "type": "bytes4" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getLockTime", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getMaxActiveProposals", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getMaxGasPrice", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getName", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getPermissionRegistry", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "proposalId", + "type": "bytes32" + } + ], + "name": "getProposal", + "outputs": [ + { + "internalType": "address", + "name": "creator", + "type": "address" + }, + { + "internalType": "uint256", + "name": "startTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "endTime", + "type": "uint256" + }, + { + "internalType": "address[]", + "name": "to", + "type": "address[]" + }, + { + "internalType": "bytes[]", + "name": "data", + "type": "bytes[]" + }, + { + "internalType": "uint256[]", + "name": "value", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "totalActions", + "type": "uint256" + }, + { + "internalType": "string", + "name": "title", + "type": "string" + }, + { + "internalType": "string", + "name": "contentHash", + "type": "string" + }, + { + "internalType": "enum ERC20Guild.ProposalState", + "name": "state", + "type": "uint8" + }, + { + "internalType": "uint256[]", + "name": "totalVotes", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "proposalId", + "type": "bytes32" + } + ], + "name": "getProposalSnapshotId", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getProposalTime", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "proposalId", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "voter", + "type": "address" + } + ], + "name": "getProposalVotesOfVoter", + "outputs": [ + { + "internalType": "uint256", + "name": "action", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "votingPower", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getProposalsIds", + "outputs": [ + { + "internalType": "bytes32[]", + "name": "", + "type": "bytes32[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getProposalsIdsLength", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "signedVoteHash", + "type": "bytes32" + } + ], + "name": "getSignedVote", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getTimeForExecution", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getToken", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getTokenVault", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getTotalLocked", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getTotalProposals", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getVoteGas", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "voter", + "type": "address" + } + ], + "name": "getVoterLockTimestamp", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getVotingPowerForProposalCreation", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getVotingPowerForProposalExecution", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "proposalId", + "type": "uint256" + } + ], + "name": "getVotingPowerForProposalExecution", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "voter", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "proposalId", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "action", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "votingPower", + "type": "uint256" + } + ], + "name": "hashVote", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_proposalTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_timeForExecution", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_votingPowerForProposalExecution", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_votingPowerForProposalCreation", + "type": "uint256" + }, + { + "internalType": "string", + "name": "_name", + "type": "string" + }, + { + "internalType": "uint256", + "name": "_voteGas", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_maxGasPrice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_maxActiveProposals", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_lockTime", + "type": "uint256" + }, + { + "internalType": "address", + "name": "_permissionRegistry", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "hash", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "signature", + "type": "bytes" + } + ], + "name": "isValidSignature", + "outputs": [ + { + "internalType": "bytes4", + "name": "magicValue", + "type": "bytes4" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenAmount", + "type": "uint256" + } + ], + "name": "lockTokens", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_proposalTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_timeForExecution", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_votingPowerForProposalExecution", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_votingPowerForProposalCreation", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_voteGas", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_maxGasPrice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_maxActiveProposals", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_lockTime", + "type": "uint256" + } + ], + "name": "setConfig", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_hash", + "type": "bytes32" + }, + { + "internalType": "bool", + "name": "isValid", + "type": "bool" + } + ], + "name": "setEIP1271SignedHash", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "asset", + "type": "address[]" + }, + { + "internalType": "address[]", + "name": "to", + "type": "address[]" + }, + { + "internalType": "bytes4[]", + "name": "functionSignature", + "type": "bytes4[]" + }, + { + "internalType": "uint256[]", + "name": "valueAllowed", + "type": "uint256[]" + }, + { + "internalType": "bool[]", + "name": "allowance", + "type": "bool[]" + } + ], + "name": "setPermission", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "permissionDelay", + "type": "uint256" + } + ], + "name": "setPermissionDelay", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "proposalId", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "action", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "votingPower", + "type": "uint256" + }, + { + "internalType": "address", + "name": "voter", + "type": "address" + }, + { + "internalType": "bytes", + "name": "signature", + "type": "bytes" + } + ], + "name": "setSignedVote", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32[]", + "name": "proposalIds", + "type": "bytes32[]" + }, + { + "internalType": "uint256[]", + "name": "actions", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "votingPowers", + "type": "uint256[]" + }, + { + "internalType": "address[]", + "name": "voters", + "type": "address[]" + }, + { + "internalType": "bytes[]", + "name": "signatures", + "type": "bytes[]" + } + ], + "name": "setSignedVotes", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "proposalId", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "action", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "votingPower", + "type": "uint256" + } + ], + "name": "setVote", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32[]", + "name": "proposalIds", + "type": "bytes32[]" + }, + { + "internalType": "uint256[]", + "name": "actions", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "votingPowers", + "type": "uint256[]" + } + ], + "name": "setVotes", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "snapshotId", + "type": "uint256" + } + ], + "name": "totalLockedAt", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "votingPowerOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "snapshotId", + "type": "uint256" + } + ], + "name": "votingPowerOfAt", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "accounts", + "type": "address[]" + } + ], + "name": "votingPowerOfMultiple", + "outputs": [ + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "accounts", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "snapshotIds", + "type": "uint256[]" + } + ], + "name": "votingPowerOfMultipleAt", + "outputs": [ + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenAmount", + "type": "uint256" + } + ], + "name": "withdrawTokens", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } + ] +} diff --git a/src/contracts/SnapshotRepERC20Guild.json b/src/contracts/SnapshotRepERC20Guild.json new file mode 100644 index 0000000000..6726d51dba --- /dev/null +++ b/src/contracts/SnapshotRepERC20Guild.json @@ -0,0 +1,1146 @@ +{ + "_format": "hh-sol-artifact-1", + "contractName": "SnapshotRepERC20Guild", + "sourceName": "dxdao-contracts/contracts/erc20guild/implementations/SnapshotRepERC20Guild.sol", + "abi": [ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "proposalId", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newState", + "type": "uint256" + } + ], + "name": "ProposalStateChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "voter", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "TokensLocked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "voter", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "TokensWithdrawn", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "proposalId", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "action", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "voter", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "votingPower", + "type": "uint256" + } + ], + "name": "VoteAdded", + "type": "event" + }, + { + "stateMutability": "payable", + "type": "fallback" + }, + { + "inputs": [], + "name": "ANY_SIGNATURE", + "outputs": [ + { + "internalType": "bytes4", + "name": "", + "type": "bytes4" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ERC20_APPROVE_SIGNATURE", + "outputs": [ + { + "internalType": "bytes4", + "name": "", + "type": "bytes4" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ERC20_TRANSFER_SIGNATURE", + "outputs": [ + { + "internalType": "bytes4", + "name": "", + "type": "bytes4" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "to", + "type": "address[]" + }, + { + "internalType": "bytes[]", + "name": "data", + "type": "bytes[]" + }, + { + "internalType": "uint256[]", + "name": "value", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "totalActions", + "type": "uint256" + }, + { + "internalType": "string", + "name": "title", + "type": "string" + }, + { + "internalType": "string", + "name": "contentHash", + "type": "string" + } + ], + "name": "createProposal", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "proposalId", + "type": "bytes32" + } + ], + "name": "endProposal", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "_data", + "type": "bytes" + } + ], + "name": "erc20TransferOrApproveDecode", + "outputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "getActiveProposalsNow", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_hash", + "type": "bytes32" + } + ], + "name": "getEIP1271SignedHash", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "getFuncSignature", + "outputs": [ + { + "internalType": "bytes4", + "name": "", + "type": "bytes4" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getLockTime", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getMaxActiveProposals", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getMaxGasPrice", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getName", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getPermissionRegistry", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "proposalId", + "type": "bytes32" + } + ], + "name": "getProposal", + "outputs": [ + { + "internalType": "address", + "name": "creator", + "type": "address" + }, + { + "internalType": "uint256", + "name": "startTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "endTime", + "type": "uint256" + }, + { + "internalType": "address[]", + "name": "to", + "type": "address[]" + }, + { + "internalType": "bytes[]", + "name": "data", + "type": "bytes[]" + }, + { + "internalType": "uint256[]", + "name": "value", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "totalActions", + "type": "uint256" + }, + { + "internalType": "string", + "name": "title", + "type": "string" + }, + { + "internalType": "string", + "name": "contentHash", + "type": "string" + }, + { + "internalType": "enum ERC20Guild.ProposalState", + "name": "state", + "type": "uint8" + }, + { + "internalType": "uint256[]", + "name": "totalVotes", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "proposalId", + "type": "bytes32" + } + ], + "name": "getProposalSnapshotId", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getProposalTime", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "proposalId", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "voter", + "type": "address" + } + ], + "name": "getProposalVotesOfVoter", + "outputs": [ + { + "internalType": "uint256", + "name": "action", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "votingPower", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getProposalsIds", + "outputs": [ + { + "internalType": "bytes32[]", + "name": "", + "type": "bytes32[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getProposalsIdsLength", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "signedVoteHash", + "type": "bytes32" + } + ], + "name": "getSignedVote", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getTimeForExecution", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getToken", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getTokenVault", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getTotalLocked", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getTotalProposals", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getVoteGas", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "voter", + "type": "address" + } + ], + "name": "getVoterLockTimestamp", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getVotingPowerForProposalCreation", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getVotingPowerForProposalExecution", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "voter", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "proposalId", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "action", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "votingPower", + "type": "uint256" + } + ], + "name": "hashVote", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_proposalTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_timeForExecution", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_votingPowerForProposalExecution", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_votingPowerForProposalCreation", + "type": "uint256" + }, + { + "internalType": "string", + "name": "_name", + "type": "string" + }, + { + "internalType": "uint256", + "name": "_voteGas", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_maxGasPrice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_maxActiveProposals", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_lockTime", + "type": "uint256" + }, + { + "internalType": "address", + "name": "_permissionRegistry", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "hash", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "signature", + "type": "bytes" + } + ], + "name": "isValidSignature", + "outputs": [ + { + "internalType": "bytes4", + "name": "magicValue", + "type": "bytes4" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenAmount", + "type": "uint256" + } + ], + "name": "lockTokens", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_proposalTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_timeForExecution", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_votingPowerForProposalExecution", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_votingPowerForProposalCreation", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_voteGas", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_maxGasPrice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_maxActiveProposals", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_lockTime", + "type": "uint256" + } + ], + "name": "setConfig", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_hash", + "type": "bytes32" + }, + { + "internalType": "bool", + "name": "isValid", + "type": "bool" + } + ], + "name": "setEIP1271SignedHash", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "asset", + "type": "address[]" + }, + { + "internalType": "address[]", + "name": "to", + "type": "address[]" + }, + { + "internalType": "bytes4[]", + "name": "functionSignature", + "type": "bytes4[]" + }, + { + "internalType": "uint256[]", + "name": "valueAllowed", + "type": "uint256[]" + }, + { + "internalType": "bool[]", + "name": "allowance", + "type": "bool[]" + } + ], + "name": "setPermission", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "permissionDelay", + "type": "uint256" + } + ], + "name": "setPermissionDelay", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "proposalId", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "action", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "votingPower", + "type": "uint256" + }, + { + "internalType": "address", + "name": "voter", + "type": "address" + }, + { + "internalType": "bytes", + "name": "signature", + "type": "bytes" + } + ], + "name": "setSignedVote", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32[]", + "name": "proposalIds", + "type": "bytes32[]" + }, + { + "internalType": "uint256[]", + "name": "actions", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "votingPowers", + "type": "uint256[]" + }, + { + "internalType": "address[]", + "name": "voters", + "type": "address[]" + }, + { + "internalType": "bytes[]", + "name": "signatures", + "type": "bytes[]" + } + ], + "name": "setSignedVotes", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "proposalId", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "action", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "votingPower", + "type": "uint256" + } + ], + "name": "setVote", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32[]", + "name": "proposalIds", + "type": "bytes32[]" + }, + { + "internalType": "uint256[]", + "name": "actions", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "votingPowers", + "type": "uint256[]" + } + ], + "name": "setVotes", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "votingPowerOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "snapshotId", + "type": "uint256" + } + ], + "name": "votingPowerOfAt", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "accounts", + "type": "address[]" + } + ], + "name": "votingPowerOfMultiple", + "outputs": [ + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "accounts", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "snapshotIds", + "type": "uint256[]" + } + ], + "name": "votingPowerOfMultipleAt", + "outputs": [ + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenAmount", + "type": "uint256" + } + ], + "name": "withdrawTokens", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } + ] +} diff --git a/src/hooks/Guilds/contracts/useContractInterface.ts b/src/hooks/Guilds/contracts/useContractInterface.ts new file mode 100644 index 0000000000..3353e5d9b6 --- /dev/null +++ b/src/hooks/Guilds/contracts/useContractInterface.ts @@ -0,0 +1,9 @@ +import { utils } from 'ethers'; + +const useContractInterface = (ABI: any) => { + let ERC20Contract = new utils.Interface(ABI); + + return ERC20Contract; +}; + +export default useContractInterface; diff --git a/src/hooks/Guilds/contracts/useDecodedCall.ts b/src/hooks/Guilds/contracts/useDecodedCall.ts index 92a90ca805..db2809e765 100644 --- a/src/hooks/Guilds/contracts/useDecodedCall.ts +++ b/src/hooks/Guilds/contracts/useDecodedCall.ts @@ -117,6 +117,7 @@ const decodeCall = ( ); return { + id: `action-${Math.random()}`, decodedCall, contract: contractInterface, }; @@ -143,5 +144,7 @@ export const useDecodedCall = (call: Call) => { const { chainId } = useWeb3React(); const { contracts } = useContractRegistry(); - return decodeCall(call, contracts, chainId); + return call + ? decodeCall(call, contracts, chainId) + : { decodedCall: null, contract: null }; }; diff --git a/src/hooks/Guilds/ether-swr/erc20/useERC20Info.ts b/src/hooks/Guilds/ether-swr/erc20/useERC20Info.ts index d5041fa784..428d5a50a9 100644 --- a/src/hooks/Guilds/ether-swr/erc20/useERC20Info.ts +++ b/src/hooks/Guilds/ether-swr/erc20/useERC20Info.ts @@ -1,11 +1,13 @@ import useEtherSWR from '../useEtherSWR'; import ERC20ABI from '../../../../abis/ERC20.json'; import { useMemo } from 'react'; +import { BigNumber } from 'ethers'; export type ERC20Info = { name: string; symbol: string; decimals: number; + totalSupply: BigNumber; }; export const useERC20Info = (contractAddress: string) => { @@ -15,6 +17,7 @@ export const useERC20Info = (contractAddress: string) => { [contractAddress, 'name'], [contractAddress, 'symbol'], [contractAddress, 'decimals'], + [contractAddress, 'totalSupply'], ] : [], { @@ -29,6 +32,7 @@ export const useERC20Info = (contractAddress: string) => { name: data[0], symbol: data[1], decimals: data[2], + totalSupply: data[3], }; }, [data]); diff --git a/src/hooks/Guilds/ether-swr/guild/useGuildConfig.ts b/src/hooks/Guilds/ether-swr/guild/useGuildConfig.ts index 54034bc285..158e3bdc40 100644 --- a/src/hooks/Guilds/ether-swr/guild/useGuildConfig.ts +++ b/src/hooks/Guilds/ether-swr/guild/useGuildConfig.ts @@ -1,7 +1,10 @@ import { BigNumber } from 'ethers'; import { useMemo } from 'react'; +import { SWRResponse } from 'swr'; import ERC20GuildContract from 'contracts/ERC20Guild.json'; import useEtherSWR from '../useEtherSWR'; +import useTotalLocked from './useTotalLocked'; +import useGuildToken from './useGuildToken'; type GuildConfig = { name: string; @@ -17,11 +20,12 @@ type GuildConfig = { totalLocked: BigNumber; }; -export const useGuildConfig = (guildAddress: string) => { +export const useGuildConfig = ( + guildAddress: string +): SWRResponse => { const { data, error, isValidating, mutate } = useEtherSWR( guildAddress ? [ - [guildAddress, 'getToken'], // Get the address of the ERC20Token used for voting [guildAddress, 'getPermissionRegistry'], // Get the address of the permission registry contract [guildAddress, 'getName'], // Get the name of the ERC20Guild [guildAddress, 'getProposalTime'], // Get the proposalTime (seconds) @@ -31,7 +35,6 @@ export const useGuildConfig = (guildAddress: string) => { [guildAddress, 'getVotingPowerForProposalExecution'], [guildAddress, 'getTokenVault'], [guildAddress, 'getLockTime'], - [guildAddress, 'getTotalLocked'], ] : [], { @@ -39,13 +42,14 @@ export const useGuildConfig = (guildAddress: string) => { refreshInterval: 0, } ); + const { data: token } = useGuildToken(guildAddress); + const { data: totalLocked } = useTotalLocked(guildAddress); // TODO: Move this into a SWR middleware - const transformedData: GuildConfig = useMemo(() => { + const transformedData = useMemo(() => { if (!data) return undefined; const [ - token, permissionRegistry, name, proposalTime, @@ -55,11 +59,9 @@ export const useGuildConfig = (guildAddress: string) => { votingPowerForProposalExecution, tokenVault, lockTime, - totalLocked, ] = data; return { - token, permissionRegistry, name, proposalTime: BigNumber.from(proposalTime), @@ -73,7 +75,6 @@ export const useGuildConfig = (guildAddress: string) => { ), tokenVault, lockTime: BigNumber.from(lockTime), - totalLocked: BigNumber.from(totalLocked), }; }, [data]); @@ -81,6 +82,8 @@ export const useGuildConfig = (guildAddress: string) => { error, isValidating, mutate, - data: transformedData, + data: transformedData + ? { ...transformedData, totalLocked, token } + : undefined, }; }; diff --git a/src/hooks/Guilds/ether-swr/guild/useGuildToken.ts b/src/hooks/Guilds/ether-swr/guild/useGuildToken.ts new file mode 100644 index 0000000000..fbc6617b1b --- /dev/null +++ b/src/hooks/Guilds/ether-swr/guild/useGuildToken.ts @@ -0,0 +1,11 @@ +import useEtherSWR from '../useEtherSWR'; +import ERC20Guild from 'contracts/ERC20Guild.json'; + +const useGuildToken = (guildAddress: string) => { + return useEtherSWR(guildAddress ? [guildAddress, 'getToken'] : [], { + ABIs: new Map([[guildAddress, ERC20Guild.abi]]), + refreshInterval: 0, + }); +}; + +export default useGuildToken; diff --git a/src/hooks/Guilds/ether-swr/guild/useSnapshotId.ts b/src/hooks/Guilds/ether-swr/guild/useSnapshotId.ts new file mode 100644 index 0000000000..0b8f20e520 --- /dev/null +++ b/src/hooks/Guilds/ether-swr/guild/useSnapshotId.ts @@ -0,0 +1,24 @@ +import { BigNumber } from 'ethers'; +import { SWRResponse } from 'swr'; +import useEtherSWR from '../useEtherSWR'; +import SnapshotERC20Guild from 'contracts/SnapshotERC20Guild.json'; + +interface UseSnapshotIdProps { + contractAddress: string; + proposalId: string; +} + +type UseSnapshotIdHook = (args: UseSnapshotIdProps) => SWRResponse; + +const useSnapshotId: UseSnapshotIdHook = ({ contractAddress, proposalId }) => { + return useEtherSWR( + proposalId && contractAddress + ? [contractAddress, 'getProposalSnapshotId', proposalId] + : [], + { + ABIs: new Map([[contractAddress, SnapshotERC20Guild.abi]]), + } + ); +}; + +export default useSnapshotId; diff --git a/src/hooks/Guilds/ether-swr/guild/useTotalLocked.ts b/src/hooks/Guilds/ether-swr/guild/useTotalLocked.ts new file mode 100644 index 0000000000..6f24e4d412 --- /dev/null +++ b/src/hooks/Guilds/ether-swr/guild/useTotalLocked.ts @@ -0,0 +1,51 @@ +import { useParams } from 'react-router-dom'; +import ERC20GuildContract from 'contracts/ERC20Guild.json'; +import useEtherSWR from '../useEtherSWR'; +import useTotalLockedAt from 'hooks/Guilds/ether-swr/guild/useTotalLockedAt'; +import useSnapshotId from 'hooks/Guilds/ether-swr/guild/useSnapshotId'; +import useGuildImplementationType from 'hooks/Guilds/guild/useGuildImplementationType'; +import useGuildToken from './useGuildToken'; +import useTotalSupplyAt from './useTotalSupplyAt'; + +const useTotalLocked = (guildAddress: string, snapshotId?: string) => { + // Hooks call + const { proposal_id: proposalId } = useParams<{ proposal_id?: string }>(); + + const { data: _snapshotId } = useSnapshotId({ + contractAddress: guildAddress, + proposalId, + }); + + const SNAPSHOT_ID = snapshotId ? snapshotId : _snapshotId?.toString(); + + const { isSnapshotGuild, isRepGuild, isSnapshotRepGuild } = + useGuildImplementationType(guildAddress); + + const totalLockedResponse = useEtherSWR( + guildAddress ? [guildAddress, 'getTotalLocked'] : [], + { + ABIs: new Map([[guildAddress, ERC20GuildContract.abi]]), + refreshInterval: 0, + } + ); + + const totalLockedAtProposalSnapshotResponse = useTotalLockedAt({ + contractAddress: guildAddress, + snapshotId: SNAPSHOT_ID, + }); + + const { data: guildTokenAddress } = useGuildToken(guildAddress); + + const totalSupplyAtSnapshotResponse = useTotalSupplyAt({ + contractAddress: guildTokenAddress, + snapshotId: SNAPSHOT_ID, + }); + + // Return response based on implementation type + if (isSnapshotGuild) return totalLockedAtProposalSnapshotResponse; + if (isSnapshotRepGuild) return totalSupplyAtSnapshotResponse; + if (isRepGuild) return totalLockedResponse; + return totalLockedResponse; +}; + +export default useTotalLocked; diff --git a/src/hooks/Guilds/ether-swr/guild/useTotalLockedAt.ts b/src/hooks/Guilds/ether-swr/guild/useTotalLockedAt.ts new file mode 100644 index 0000000000..4a732a1562 --- /dev/null +++ b/src/hooks/Guilds/ether-swr/guild/useTotalLockedAt.ts @@ -0,0 +1,32 @@ +import { BigNumber } from 'ethers'; +import { SWRResponse } from 'swr'; +import useEtherSWR from '../useEtherSWR'; +import SnapshotERC20Guild from 'contracts/SnapshotERC20Guild.json'; + +interface UseTotalLockedAtProps { + contractAddress: string; + snapshotId: string; +} + +type UseTotalLockedAtHook = ( + args: UseTotalLockedAtProps +) => SWRResponse; + +/** + * Get the total locked amount at snapshot + */ +const useTotalLockedAt: UseTotalLockedAtHook = ({ + contractAddress, + snapshotId, +}) => { + return useEtherSWR( + snapshotId && contractAddress + ? [contractAddress, 'totalLockedAt', snapshotId] + : [], + { + ABIs: new Map([[contractAddress, SnapshotERC20Guild.abi]]), + } + ); +}; + +export default useTotalLockedAt; diff --git a/src/hooks/Guilds/ether-swr/guild/useTotalSupplyAt.ts b/src/hooks/Guilds/ether-swr/guild/useTotalSupplyAt.ts new file mode 100644 index 0000000000..2f1f913aa6 --- /dev/null +++ b/src/hooks/Guilds/ether-swr/guild/useTotalSupplyAt.ts @@ -0,0 +1,32 @@ +import { BigNumber } from 'ethers'; +import { SWRResponse } from 'swr'; +import useEtherSWR from '../useEtherSWR'; +import ERC20SnapshotRep from 'contracts/ERC20SnapshotRep.json'; + +interface UseTotalSupplyAtProps { + contractAddress: string; + snapshotId: string; +} + +type UseTotalSupplyAtHook = ( + args: UseTotalSupplyAtProps +) => SWRResponse; + +/** + * Get the total supply amount at snapshot + */ +const useTotalSupplyAt: UseTotalSupplyAtHook = ({ + contractAddress, // tokenAddress, + snapshotId, +}) => { + return useEtherSWR( + snapshotId && contractAddress + ? [contractAddress, 'totalSupplyAt', snapshotId] + : [], + { + ABIs: new Map([[contractAddress, ERC20SnapshotRep.abi]]), + } + ); +}; + +export default useTotalSupplyAt; diff --git a/src/hooks/Guilds/ether-swr/guild/useVotingPowerOfAt.ts b/src/hooks/Guilds/ether-swr/guild/useVotingPowerOfAt.ts new file mode 100644 index 0000000000..bb979b1290 --- /dev/null +++ b/src/hooks/Guilds/ether-swr/guild/useVotingPowerOfAt.ts @@ -0,0 +1,31 @@ +import { BigNumber } from 'ethers'; +import { SWRResponse } from 'swr'; +import useEtherSWR from '../useEtherSWR'; +import SnapshotERC20Guild from 'contracts/SnapshotERC20Guild.json'; + +interface useVotingPowerOfAtProps { + contractAddress: string; + userAddress: string; + snapshotId: string; +} + +type useVotingPowerOfAtHook = ( + args: useVotingPowerOfAtProps +) => SWRResponse; + +/** + * Get the voting power of an account at snapshot id + */ +export const useVotingPowerOfAt: useVotingPowerOfAtHook = ({ + contractAddress, + userAddress, + snapshotId, +}) => + useEtherSWR( + contractAddress && snapshotId && userAddress + ? [contractAddress, 'votingPowerOf', userAddress, snapshotId] + : [], + { + ABIs: new Map([[contractAddress, SnapshotERC20Guild.abi]]), + } + ); diff --git a/src/hooks/Guilds/guild/useGuildImplementationType.ts b/src/hooks/Guilds/guild/useGuildImplementationType.ts index badd8406e4..4098514539 100644 --- a/src/hooks/Guilds/guild/useGuildImplementationType.ts +++ b/src/hooks/Guilds/guild/useGuildImplementationType.ts @@ -4,14 +4,47 @@ import useJsonRpcProvider from '../web3/useJsonRpcProvider'; import { GuildImplementationType } from '../../../types/types.guilds.d'; import deployedHashedBytecodes from '../../../bytecodes/config.json'; +const defaultImplementation = deployedHashedBytecodes.find( + ({ type }) => type === GuildImplementationType.IERC20Guild +) ?? { + type: GuildImplementationType.IERC20Guild, + features: [], + bytecode_hash: '', +}; + +interface ImplementationTypeConfig { + type: string; + features: string[]; + bytecode_hash: string; +} + +interface ImplementationTypeConfigReturn extends ImplementationTypeConfig { + isRepGuild: boolean; + isSnapshotGuild: boolean; + isSnapshotRepGuild: boolean; +} +const parseConfig = ( + config: ImplementationTypeConfig +): ImplementationTypeConfigReturn => { + return { + ...config, + isRepGuild: + config.features.includes('REP') && !config.features.includes('SNAPSHOT'), + isSnapshotGuild: + config.features.includes('SNAPSHOT') && !config.features.includes('REP'), + isSnapshotRepGuild: + config.features.includes('SNAPSHOT') && config.features.includes('REP'), + }; +}; + /** * @function useGuildImplementationType * @param {string} guildAddress * @returns {string} GuildImplementationType. 'SnapshotRepERC20Guild' | 'DXDGuild' | 'ERC20Guild' | 'IERC20Guild' */ -export default function useGuildImplementationType( +export default function useGuildImplementationTypeConfig( guildAddress: string -): GuildImplementationType { +): ImplementationTypeConfigReturn { const [guildBytecode, setGuildBytecode] = useState(''); const provider = useJsonRpcProvider(); @@ -23,15 +56,15 @@ export default function useGuildImplementationType( getBytecode(); }, [guildAddress, provider]); - const implementationType: GuildImplementationType = useMemo(() => { - if (!guildBytecode) return GuildImplementationType.IERC20Guild; + const implementationTypeConfig: ImplementationTypeConfig = useMemo(() => { + if (!guildBytecode) return defaultImplementation; const match = deployedHashedBytecodes.find( ({ bytecode_hash }) => guildBytecode === bytecode_hash ); - return match ? match.type : GuildImplementationType.IERC20Guild; // default to IERC20Guild - }, [guildBytecode]) as GuildImplementationType; + return match ? match : defaultImplementation; // default to IERC20Guild + }, [guildBytecode]); - return implementationType; + return parseConfig(implementationTypeConfig); } diff --git a/src/hooks/Guilds/guild/useProposalCalls.ts b/src/hooks/Guilds/guild/useProposalCalls.ts index 46b4441eed..768616f69c 100644 --- a/src/hooks/Guilds/guild/useProposalCalls.ts +++ b/src/hooks/Guilds/guild/useProposalCalls.ts @@ -1,3 +1,4 @@ +import { useTheme } from 'styled-components'; import { useWeb3React } from '@web3-react/core'; import { Call, Option } from 'components/Guilds/ActionsBuilder/types'; import { useMemo } from 'react'; @@ -12,6 +13,8 @@ const useProposalCalls = (guildId: string, proposalId: string) => { const { contracts } = useContractRegistry(); const { chainId } = useWeb3React(); + const theme = useTheme(); + const options: Option[] = useMemo(() => { if (!guildId || !proposalId || !proposal) return null; @@ -40,15 +43,16 @@ const useProposalCalls = (guildId: string, proposalId: string) => { } const encodedOptions: Option[] = splitCalls.map((calls, index) => ({ - index, + id: `option-${index}`, label: `Option ${index + 1}`, + color: theme?.colors?.votes?.[options.length], actions: calls.filter( call => call.data !== ZERO_HASH || !call.value?.isZero() ), })); return bulkDecodeCallsFromOptions(encodedOptions, contracts, chainId); - }, [proposal, proposalId, guildId, chainId, contracts]); + }, [theme, proposal, proposalId, guildId, chainId, contracts]); return { options, diff --git a/src/hooks/Guilds/guild/useTokenData.ts b/src/hooks/Guilds/guild/useTokenData.ts new file mode 100644 index 0000000000..72d012edaf --- /dev/null +++ b/src/hooks/Guilds/guild/useTokenData.ts @@ -0,0 +1,14 @@ +import { useParams } from 'react-router-dom'; +import { useGuildConfig } from '../ether-swr/guild/useGuildConfig'; +import { useERC20Info } from '../ether-swr/erc20/useERC20Info'; + +export const useTokenData = () => { + const { guild_id: guildId } = + useParams<{ chain_name?: string; guild_id?: string }>(); + const { data } = useGuildConfig(guildId); + const { data: tokenData } = useERC20Info(data?.token); + + return { + tokenData, + }; +}; diff --git a/src/hooks/Guilds/guild/useTotalSupply.ts b/src/hooks/Guilds/guild/useTotalSupply.ts new file mode 100644 index 0000000000..94af8143c6 --- /dev/null +++ b/src/hooks/Guilds/guild/useTotalSupply.ts @@ -0,0 +1,21 @@ +import { BigNumber } from 'ethers'; +import { useMemo } from 'react'; + +interface REPMintState { + toAddress: string; + amount: BigNumber; +} + +export const useTotalSupply = ({ decodedCall }) => { + const parsedData = useMemo(() => { + if (!decodedCall) return null; + return { + toAddress: decodedCall.args.to, + amount: decodedCall.args.amount, + }; + }, [decodedCall]); + + return { + parsedData, + }; +}; diff --git a/src/pages/Configuration.tsx b/src/pages/Configuration.tsx index 468fc4a162..48f630a56f 100644 --- a/src/pages/Configuration.tsx +++ b/src/pages/Configuration.tsx @@ -110,6 +110,7 @@ const ConfigPage = observer(() => { async function clearCache() { localStorage.clear(); caches.delete(`dxvote-cache`); + window.location.reload(); } return ( diff --git a/src/pages/ProposalSubmission/Custom.tsx b/src/pages/ProposalSubmission/Custom.tsx index 58ad10e984..b8c456af1d 100644 --- a/src/pages/ProposalSubmission/Custom.tsx +++ b/src/pages/ProposalSubmission/Custom.tsx @@ -86,6 +86,7 @@ const NewProposalPage = observer(() => { }, } = useContext(); + const networkName = configStore.getActiveChainName(); const schemes = daoStore.getAllSchemes(); const networkContracts = configStore.getNetworkContracts(); const schemeInLocalStorage = localStorage.getItem( @@ -720,7 +721,9 @@ const NewProposalPage = observer(() => { active={submitionState} /> ) : ( - Back to Proposals + + Back to Proposals + )} {submitionState > 1 ? ( diff --git a/src/pages/proposals/index.tsx b/src/pages/proposals/index.tsx index 600aa11ab7..b801a05801 100644 --- a/src/pages/proposals/index.tsx +++ b/src/pages/proposals/index.tsx @@ -21,6 +21,7 @@ import { formatNumberValue, PendingAction, isVoteNo, + VotingMachineProposalState, } from '../../utils'; import { FiFeather, @@ -71,6 +72,12 @@ const ProposalsPage = observer(() => { setSchemesFilter, } = useFilteredProposals(); + const allProposals = daoStore.getAllProposals(); + const activeProposalsCount = allProposals.filter( + proposal => + proposal.stateInVotingMachine > VotingMachineProposalState.Executed + ).length; + const history = useHistory(); return ( @@ -84,6 +91,12 @@ const ProposalsPage = observer(() => { + + {allProposals.length} Total Proposals + + + {activeProposalsCount} Active Proposals + diff --git a/src/services/CacheService.ts b/src/services/CacheService.ts index 61f15487e5..412437186e 100644 --- a/src/services/CacheService.ts +++ b/src/services/CacheService.ts @@ -1,5 +1,13 @@ import RootContext from '../contexts'; -import { getIPFSFile, NETWORK_NAMES } from '../utils'; +import { + batchPromisesOntarget, + getAppConfig, + getDefaultConfigHashes, + getIPFSFile, + getProposalTitles, + NETWORK_NAMES, + retryPromise, +} from '../utils'; import Web3 from 'web3'; import _ from 'lodash'; import { @@ -10,45 +18,22 @@ import { decodePermission, WalletSchemeProposalState, VotingMachineProposalState, - tryCacheUpdates, isWalletScheme, getEvents, getRawEvents, - sortEvents, executeMulticall, + sortNetworkCache, descriptionHashToIPFSHash, ipfsHashToDescriptionHash, getSchemeConfig, } from '../utils'; -import WalletScheme1_0JSON from '../contracts/WalletScheme1_0.json'; -import WalletScheme1_1JSON from '../contracts/WalletScheme1_1.json'; -import ContributionRewardJSON from '../contracts/ContributionReward.json'; -import TokenVestingJSON from '../contracts/TokenVesting.json'; + import { getContracts } from '../contracts'; +import { Contract } from 'ethers'; const Hash = require('ipfs-only-hash'); const jsonSort = require('json-keys-sort'); -const defaultConfigHashes = require('../configs/default.json'); - -const arbitrum = require('../configs/arbitrum/config.json'); -const arbitrumTestnet = require('../configs/arbitrumTestnet/config.json'); -const mainnet = require('../configs/mainnet/config.json'); -const xdai = require('../configs/xdai/config.json'); -const rinkeby = require('../configs/rinkeby/config.json'); -const localhost = require('../configs/localhost/config.json'); - -const proposalTitles = require('../configs/proposalTitles.json'); - -const appConfig: AppConfig = { - arbitrum, - arbitrumTestnet, - mainnet, - xdai, - rinkeby, - localhost, -}; - export default class UtilsService { context: RootContext; @@ -56,6 +41,84 @@ export default class UtilsService { this.context = context; } + async getCacheFromIPFS(ipfsHash: string): Promise { + let jsonCache = await this.context.ipfsService.getContentFromIPFS(ipfsHash); + + while (typeof jsonCache === 'string') { + console.log('Trying to get cache from ipfs again'); + await sleep(100); + jsonCache = await this.context.ipfsService.getContentFromIPFS(ipfsHash); + } + + return jsonCache; + } + + async getUpdatedCacheConfig( + networksConfig: { + [netwokId: number]: { + toBlock: number; + rpcUrl: string; + reset: boolean; + }; + }, + updateProposalTitles: boolean + ): Promise<{ + proposalTitles: { + [proposalId: string]: string; + }; + configHashes: { + [networkName: string]: string; + }; + configs: { + [networkName: string]: NetworkConfig; + }; + caches: { + [networkName: string]: DaoNetworkCache; + }; + }> { + const updatedCacheConfig = { + proposalTitles: getProposalTitles(), + configHashes: {}, + configs: {}, + caches: {}, + }; + + // Update the cache and config for each network + for (let i = 0; i < Object.keys(networksConfig).length; i++) { + const networkId = Number(Object.keys(networksConfig)[i]); + const networkName = NETWORK_NAMES[networkId]; + + const cacheForNetwork = await this.buildCacheForNetwork( + new Web3(networksConfig[networkId].rpcUrl), + networkId, + networksConfig[networkId].toBlock, + networksConfig[networkId].reset + ); + + // Update the appConfig file that stores the hashes of the dapp config and network caches + updatedCacheConfig.configHashes[networkName] = cacheForNetwork.configHash; + updatedCacheConfig.configs[networkName] = cacheForNetwork.config; + updatedCacheConfig.caches[networkName] = cacheForNetwork.cache; + + // Get proposal titles + if (updateProposalTitles) { + this.context.notificationStore.setGlobalLoading( + true, + `Getting proposal titles form ipfs` + ); + updatedCacheConfig.proposalTitles = Object.assign( + updatedCacheConfig.proposalTitles, + await this.updateProposalTitles( + cacheForNetwork.cache, + getProposalTitles() + ) + ); + } + } + + return updatedCacheConfig; + } + async buildCacheForNetwork( web3: Web3, chainId: number, @@ -69,7 +132,7 @@ export default class UtilsService { const networkName = NETWORK_NAMES[chainId]; // Get the network configuration - let networkConfig = appConfig[networkName]; + let networkConfig = getAppConfig()[networkName]; let networkCache: DaoNetworkCache; const emptyCache: DaoNetworkCache = { @@ -90,9 +153,12 @@ export default class UtilsService { }; // Set network cache and config objects + + // If network is localhost we use an empty cache to force the complete cache generation in each load if (networkName === 'localhost') { networkCache = emptyCache; } else { + // If the cache is not reset, we load the cache from the local storage if (resetCache) { networkConfig.cache.toBlock = networkConfig.cache.fromBlock; networkConfig.cache.ipfsHash = ''; @@ -100,54 +166,61 @@ export default class UtilsService { emptyCache.blockNumber = networkConfig.cache.fromBlock; networkCache = emptyCache; } else { + this.context.notificationStore.setGlobalLoading( + true, + `Getting configuration file from IPFS` + ); console.log( - `Getting config file from https://ipfs.io/ipfs/${defaultConfigHashes[networkName]}` + `Getting config file from https://ipfs.io/ipfs/${ + getDefaultConfigHashes()[networkName] + }` ); const networkConfigFileFetch = await getIPFSFile( - defaultConfigHashes[networkName], + getDefaultConfigHashes()[networkName], 5000 ); + this.context.notificationStore.setGlobalLoading( + true, + `Getting cache file from IPFS` + ); console.log( `Getting cache file from https://ipfs.io/ipfs/${networkConfigFileFetch.data.cache.ipfsHash}` ); - const networkCacheFetch = await getIPFSFile( - networkConfigFileFetch.data.cache.ipfsHash, - 60000 + const networkCacheFetch = await retryPromise( + getIPFSFile(networkConfigFileFetch.data.cache.ipfsHash, 60000) ); networkCache = networkCacheFetch.data; } } - // Set block range for the script to run, if cache to block is set that value is used, if not we use last block - const fromBlock = networkCache.blockNumber; - - if (Number(fromBlock) < toBlock) { + // Update the cache only if the toBlock is higher than the current networkCache block + if (Number(networkCache.blockNumber) + 1 < toBlock) { // The cache file is updated with the data that had before plus new data in the network cache file console.debug( 'Running cache script from block', - fromBlock, + networkCache.blockNumber + 1, 'to block', toBlock, 'in network', networkName ); networkCache = await this.getUpdatedCache( - this.context, networkCache, networkConfig.contracts, - fromBlock, toBlock, web3 ); } - // Write network cache file + // Sort json obj and parse it to string networkCache = await jsonSort.sortAsync(networkCache, true); const networkCacheString = JSON.stringify(networkCache, null, 2); - // Update appConfig file with the latest network config + // Update appConfig obj with the latest network config and networkCache hash networkConfig.cache.toBlock = toBlock; networkConfig.cache.ipfsHash = await Hash.of(networkCacheString); + + // Sort config obj and parse it to string networkConfig = await jsonSort.sortAsync(networkConfig, true); return { @@ -157,119 +230,44 @@ export default class UtilsService { }; } - async getUpdatedCacheConfig( - networksConfig: { - [netwokId: number]: { - toBlock: number; - rpcUrl: string; - reset: boolean; - }; - }, - updateProposalTitles: boolean - ): Promise<{ - proposalTitles: { - [proposalId: string]: string; - }; - configHashes: { - [networkName: string]: string; - }; - configs: { - [networkName: string]: NetworkConfig; - }; - caches: { - [networkName: string]: DaoNetworkCache; - }; - }> { - const updatedCacheConfig = { - proposalTitles: proposalTitles, - configHashes: {}, - configs: {}, - caches: {}, - }; - // Update the cache and config for each network - for (let i = 0; i < Object.keys(networksConfig).length; i++) { - const networkId = Number(Object.keys(networksConfig)[i]); - const networkName = NETWORK_NAMES[networkId]; - - const cacheForNetwork = await this.buildCacheForNetwork( - new Web3(networksConfig[networkId].rpcUrl), - networkId, - networksConfig[networkId].toBlock, - networksConfig[networkId].reset - ); - - // Update the appConfig file that stores the hashes of the dapp config and network caches - updatedCacheConfig.configHashes[networkName] = cacheForNetwork.configHash; - updatedCacheConfig.configs[networkName] = cacheForNetwork.config; - updatedCacheConfig.caches[networkName] = cacheForNetwork.cache; - - // Get proposal titles - if (updateProposalTitles) { - this.context.notificationStore.setGlobalLoading( - true, - `Getting proposal titles form ipfs` - ); - updatedCacheConfig.proposalTitles = Object.assign( - updatedCacheConfig.proposalTitles, - await this.getProposalTitlesFromIPFS( - cacheForNetwork.cache, - proposalTitles - ) - ); - } - } - - return updatedCacheConfig; - } - async getUpdatedCache( - context: RootContext, networkCache: DaoNetworkCache, - networkContractsConfig: NetworkContracts, - fromBlock: number, + networkContracts: NetworkContracts, toBlock: number, - web3: any + web3: Web3 ): Promise { - const notificationStore = context.notificationStore; + const fromBlock = networkCache.blockNumber + 1; + const networkWeb3Contracts = await getContracts(networkContracts, web3); console.debug(`[CACHE UPDATE] from ${fromBlock} to ${toBlock}`); - const networkWeb3Contracts = await getContracts( - networkContractsConfig, - web3 - ); - notificationStore.setGlobalLoading( + this.context.notificationStore.setGlobalLoading( true, `Collecting reputation and governance events in blocks ${fromBlock} - ${toBlock}` ); - networkCache = await tryCacheUpdates( + // The first promise round: + // - Reputation Events + // - Permission Registry + // - Vesting Contracts + + networkCache = await batchPromisesOntarget( [ this.updateReputationEvents( networkCache, networkWeb3Contracts.reputation, - fromBlock, - toBlock, - web3 - ), - this.updateVotingMachineEvents( - networkCache, - networkContractsConfig, - fromBlock, toBlock, web3 ), this.updatePermissionRegistry( networkCache, - networkContractsConfig, - fromBlock, + networkContracts, toBlock, web3 ), this.updateVestingContracts( networkCache, - networkContractsConfig, - fromBlock, + networkContracts, toBlock, web3 ), @@ -277,71 +275,64 @@ export default class UtilsService { networkCache ); - notificationStore.setGlobalLoading( - true, - `Updating scheme data in blocks ${fromBlock} - ${toBlock}` - ); - - networkCache = await tryCacheUpdates( + // The second promise round: + // - Voting Machine Events + // - Update schemes + networkCache = await batchPromisesOntarget( [ - this.updateSchemes( + this.updateVotingMachineEvents( networkCache, - networkContractsConfig, - fromBlock, + networkContracts, toBlock, web3 ), + this.updateSchemes(networkCache, networkContracts, toBlock, web3), ], - networkCache + networkCache, + 0, + 1000 ); - notificationStore.setGlobalLoading( + this.context.notificationStore.setGlobalLoading( true, `Collecting proposals in blocks ${fromBlock} - ${toBlock}` ); - networkCache = await tryCacheUpdates( - [ - this.updateProposals( - networkCache, - networkContractsConfig, - fromBlock, - toBlock, - web3 - ), - ], - networkCache + // The third promise round is just to update the proposals that needs the schemes udpated. + networkCache = await batchPromisesOntarget( + [this.updateProposals(networkCache, networkContracts, toBlock, web3)], + networkCache, + 0, + 1000, + 500 ); networkCache.blockNumber = Number(toBlock); console.log('Total Proposals', Object.keys(networkCache.proposals).length); - // Compare proposals data - // Object.keys(networkCache.proposals).map((proposalId) => { - // const mutableData = getProposalMutableData(networkCache, proposalId); - // const cacheData = networkCache.proposals[proposalId]; - // console.debug(proposalId, mutableData, cacheData); - // }) - - return networkCache; + return sortNetworkCache(networkCache); } - // Get all Mint and Burn reputation events to calculate rep by time off chain + // Get all Mint and Burn reputation events async updateReputationEvents( networkCache: DaoNetworkCache, - reputation: any, - fromBlock: number, + reputation: Contract, toBlock: number, - web3: any + web3: Web3 ): Promise { if (!networkCache.reputation.events) networkCache.reputation.events = []; - - let reputationEvents = sortEvents( - await getEvents(web3, reputation, fromBlock, toBlock, 'allEvents') + const fromBlock = networkCache.blockNumber + 1; + + let reputationEvents = await getEvents( + web3, + reputation, + fromBlock, + toBlock, + 'allEvents' ); - reputationEvents.map(reputationEvent => { + reputationEvents.forEach(reputationEvent => { switch (reputationEvent.event) { case 'Mint': networkCache.reputation.events.push({ @@ -382,15 +373,11 @@ export default class UtilsService { // Update all voting machines async updateVotingMachineEvents( networkCache: DaoNetworkCache, - networkContractsConfig: NetworkContracts, - fromBlock: number, + networkContracts: NetworkContracts, toBlock: number, - web3: any + web3: Web3 ): Promise { - const networkWeb3Contracts = await getContracts( - networkContractsConfig, - web3 - ); + const networkWeb3Contracts = await getContracts(networkContracts, web3); await Promise.all( Object.keys(networkWeb3Contracts.votingMachines).map( @@ -418,9 +405,8 @@ export default class UtilsService { networkCache = await this.updateVotingMachine( networkCache, - networkContractsConfig, + networkContracts, votingMachine, - fromBlock, toBlock, web3 ); @@ -430,24 +416,28 @@ export default class UtilsService { return networkCache; } - // Update all voting machine information, events, token and voting parameters used. + + // Update a voting machine information, events, token and voting parameters used. async updateVotingMachine( networkCache: DaoNetworkCache, - networkContractsConfig: NetworkContracts, - votingMachine: any, - fromBlock: number, + networkContracts: NetworkContracts, + votingMachine: Contract, toBlock: number, - web3: any + web3: Web3 ): Promise { - let newVotingMachineEvents = sortEvents( - await getEvents(web3, votingMachine, fromBlock, toBlock, 'allEvents') - ); - const avatarAddress = web3.utils.toChecksumAddress( - networkContractsConfig.avatar + const fromBlock = networkCache.blockNumber + 1; + + let newVotingMachineEvents = await getEvents( + web3, + votingMachine, + fromBlock, + toBlock, + 'allEvents' ); + const avatarAddress = web3.utils.toChecksumAddress(networkContracts.avatar); const votingMachineEventsInCache = networkCache.votingMachines[votingMachine._address].events; - newVotingMachineEvents.map(votingMachineEvent => { + newVotingMachineEvents.forEach(votingMachineEvent => { const proposalCreated = votingMachineEventsInCache.newProposal.findIndex( newProposalEvent => @@ -626,27 +616,23 @@ export default class UtilsService { // Gets all the events form the permission registry and stores the permissions set. async updatePermissionRegistry( networkCache: DaoNetworkCache, - networkContractsConfig: NetworkContracts, - fromBlock: number, + networkContracts: NetworkContracts, toBlock: number, - web3: any + web3: Web3 ): Promise { - const networkWeb3Contracts = await getContracts( - networkContractsConfig, - web3 - ); + const networkWeb3Contracts = await getContracts(networkContracts, web3); + const fromBlock = networkCache.blockNumber + 1; + if (networkWeb3Contracts.permissionRegistry._address !== ZERO_ADDRESS) { - let permissionRegistryEvents = sortEvents( - await getEvents( - web3, - networkWeb3Contracts.permissionRegistry, - fromBlock, - toBlock, - 'allEvents' - ) + let permissionRegistryEvents = await getEvents( + web3, + networkWeb3Contracts.permissionRegistry, + fromBlock, + toBlock, + 'allEvents' ); - permissionRegistryEvents.map(permissionRegistryEvent => { + permissionRegistryEvents.forEach(permissionRegistryEvent => { const eventValues = permissionRegistryEvent.returnValues; if (!networkCache.callPermissions[eventValues.asset]) @@ -684,25 +670,21 @@ export default class UtilsService { async updateVestingContracts( networkCache: DaoNetworkCache, - networkContractsConfig: NetworkContracts, - fromBlock: number, + networkContracts: NetworkContracts, toBlock: number, - web3: any + web3: Web3 ): Promise { - const networkWeb3Contracts = await getContracts( - networkContractsConfig, - web3 - ); + const networkWeb3Contracts = await getContracts(networkContracts, web3); + const fromBlock = networkCache.blockNumber + 1; + if (networkWeb3Contracts.vestingFactory) { try { - const vestingFactoryEvents = sortEvents( - await getEvents( - web3, - networkWeb3Contracts.vestingFactory, - fromBlock, - toBlock, - 'allEvents' - ) + const vestingFactoryEvents = await getEvents( + web3, + networkWeb3Contracts.vestingFactory, + fromBlock, + toBlock, + 'allEvents' ); console.debug( @@ -711,35 +693,65 @@ export default class UtilsService { ); for (let event of vestingFactoryEvents) { - const tokenVestingContract = await new web3.eth.Contract( - TokenVestingJSON.abi, - event.returnValues.vestingContractAddress - ); const callsToExecute = [ - [tokenVestingContract, 'beneficiary', []], - [tokenVestingContract, 'cliff', []], - [tokenVestingContract, 'duration', []], - [tokenVestingContract, 'owner', []], - [tokenVestingContract, 'start', []], - [tokenVestingContract, 'isOwner', []], - [tokenVestingContract, 'revocable', []], + [ + event.returnValues.vestingContractAddress, + 'beneficiary()', + [], + ['address'], + ], + [ + event.returnValues.vestingContractAddress, + 'cliff()', + [], + ['uint256'], + ], + [ + event.returnValues.vestingContractAddress, + 'duration()', + [], + ['uint256'], + ], + [ + event.returnValues.vestingContractAddress, + 'owner()', + [], + ['address'], + ], + [ + event.returnValues.vestingContractAddress, + 'start()', + [], + ['uint256'], + ], + [ + event.returnValues.vestingContractAddress, + 'isOwner()', + [], + ['bool'], + ], + [ + event.returnValues.vestingContractAddress, + 'revocable()', + [], + ['bool'], + ], ]; const callsResponse = await executeMulticall( - web3, networkWeb3Contracts.multicall, callsToExecute ); const tokenContractInfo = { address: event.returnValues.vestingContractAddress, - beneficiary: callsResponse.decodedReturnData[0], - cliff: callsResponse.decodedReturnData[1], - duration: callsResponse.decodedReturnData[2], - owner: callsResponse.decodedReturnData[3], - start: callsResponse.decodedReturnData[4], - isOwner: callsResponse.decodedReturnData[5], - revocable: callsResponse.decodedReturnData[6], + beneficiary: callsResponse.decodedReturnData[0][0], + cliff: callsResponse.decodedReturnData[1][0], + duration: callsResponse.decodedReturnData[2][0], + owner: callsResponse.decodedReturnData[3][0], + start: callsResponse.decodedReturnData[4][0], + isOwner: callsResponse.decodedReturnData[5][0], + revocable: callsResponse.decodedReturnData[6][0], }; networkCache.vestingContracts = [ @@ -758,1241 +770,117 @@ export default class UtilsService { // Update all the schemes information async updateSchemes( networkCache: DaoNetworkCache, - networkContractsConfig: NetworkContracts, - fromBlock: number, + networkContracts: NetworkContracts, toBlock: number, - web3: any + web3: Web3 ): Promise { - const networkWeb3Contracts = await getContracts( - networkContractsConfig, - web3 - ); + const networkWeb3Contracts = await getContracts(networkContracts, web3); + const fromBlock = networkCache.blockNumber + 1; // Get all the events from the Controller - let controllerEvents = sortEvents( - await getEvents( - web3, - networkWeb3Contracts.controller, - fromBlock, - toBlock, - 'allEvents' - ) + let controllerEvents = await getEvents( + web3, + networkWeb3Contracts.controller, + fromBlock, + toBlock, + 'allEvents' ); - // Go over all controller events and add update or remove schemes depending on the event - for ( - let controllerEventsIndex = 0; - controllerEventsIndex < controllerEvents.length; - controllerEventsIndex++ - ) { - const controllerEvent = controllerEvents[controllerEventsIndex]; - - const schemeAddress = controllerEvent.returnValues._scheme; - // Add or update the scheme information, - // register scheme is used to add a scheme or update the parametersHash of an existent one - if (controllerEvent.event === 'RegisterScheme') { - const schemeTypeData = getSchemeConfig( - networkContractsConfig, - schemeAddress - ); + // Process all the schemes that changed their registered state based on the events + await batchPromisesOntarget( + controllerEvents + .filter(controllerEvent => { + return ( + (controllerEvent.event === 'UnregisterScheme' || + controllerEvent.event === 'RegisterScheme') && + controllerEvent.returnValues._sender !== + controllerEvent.returnValues._scheme + ); + }) + .map(controllerEvent => { + return this.processScheme( + controllerEvent.returnValues._scheme, + networkCache, + networkContracts, + web3, + toBlock, + controllerEvent.event === 'UnregisterScheme' + ); + }), + networkCache, + 5 + ); - console.debug( - 'Register Scheme event for ', - schemeAddress, - schemeTypeData - ); - - let controllerAddress = networkWeb3Contracts.controller._address; - let schemeName = schemeTypeData.name; - let maxSecondsForExecution = bnum(0); - let maxRepPercentageChange = bnum(0); - let schemeType = schemeTypeData.type; - - let callsToExecute = [ - [ - networkWeb3Contracts.controller, - 'getSchemePermissions', - [schemeAddress, networkWeb3Contracts.avatar._address], - ], - [ - networkWeb3Contracts.controller, - 'getSchemeParameters', - [schemeAddress, networkWeb3Contracts.avatar._address], - ], - ]; - - if (schemeType === 'WalletScheme') { - const walletSchemeContract = await new web3.eth.Contract( - WalletScheme1_0JSON.abi, - schemeAddress - ); - const walletSchemeType = await walletSchemeContract.methods - .SCHEME_TYPE() - .call(); - - schemeType = walletSchemeType; - if (schemeType == 'Wallet Scheme v1') - schemeType = 'Wallet Scheme v1.0'; - - switch (schemeType) { - case 'Wallet Scheme v1.1': - const walletSchemeContract1_1 = await new web3.eth.Contract( - WalletScheme1_1JSON.abi, - schemeAddress - ); - callsToExecute.push([ - walletSchemeContract1_1, - 'votingMachine', - [], - ]); - callsToExecute.push([ - walletSchemeContract1_1, - 'doAvatarGenericCalls', - [], - ]); - callsToExecute.push([walletSchemeContract1_1, 'schemeName', []]); - callsToExecute.push([ - walletSchemeContract1_1, - 'maxSecondsForExecution', - [], - ]); - callsToExecute.push([ - walletSchemeContract1_1, - 'maxRepPercentageChange', - [], - ]); - break; - default: - callsToExecute.push([walletSchemeContract, 'votingMachine', []]); - callsToExecute.push([ - walletSchemeContract, - 'controllerAddress', - [], - ]); - callsToExecute.push([walletSchemeContract, 'schemeName', []]); - callsToExecute.push([ - walletSchemeContract, - 'maxSecondsForExecution', - [], - ]); - callsToExecute.push([ - walletSchemeContract, - 'maxRepPercentageChange', - [], - ]); - break; - } - } - - const callsResponse1 = await executeMulticall( - web3, - networkWeb3Contracts.multicall, - callsToExecute - ); - callsToExecute = []; - - const permissions = decodePermission( - callsResponse1.decodedReturnData[0] - ); - const paramsHash = - schemeTypeData.voteParams || callsResponse1.decodedReturnData[1]; - - const votingMachineAddress = - schemeTypeData.votingMachine || callsResponse1.decodedReturnData[2]; - - const votingMachine = - networkWeb3Contracts.votingMachines[votingMachineAddress].contract; - - if (schemeTypeData.type === 'WalletScheme') { - switch (schemeType) { - case 'Wallet Scheme v1.1': - controllerAddress = callsResponse1.decodedReturnData[3] - ? networkWeb3Contracts.controller._address - : ZERO_ADDRESS; - break; - default: - controllerAddress = callsResponse1.decodedReturnData[3]; - break; - } - schemeName = callsResponse1.decodedReturnData[4]; - maxSecondsForExecution = callsResponse1.decodedReturnData[5]; - maxRepPercentageChange = callsResponse1.decodedReturnData[6]; - } - - // Register the new voting parameters in the voting machine params - const votingParameters = await votingMachine.methods - .parameters(paramsHash) - .call(); - networkCache.votingMachines[votingMachine._address].votingParameters[ - paramsHash - ] = { - queuedVoteRequiredPercentage: - votingParameters.queuedVoteRequiredPercentage, - queuedVotePeriodLimit: votingParameters.queuedVotePeriodLimit, - boostedVotePeriodLimit: votingParameters.boostedVotePeriodLimit, - preBoostedVotePeriodLimit: votingParameters.preBoostedVotePeriodLimit, - thresholdConst: votingParameters.thresholdConst, - limitExponentValue: votingParameters.limitExponentValue, - quietEndingPeriod: votingParameters.quietEndingPeriod, - proposingRepReward: votingParameters.proposingRepReward, - votersReputationLossRatio: votingParameters.votersReputationLossRatio, - minimumDaoBounty: votingParameters.minimumDaoBounty, - daoBountyConst: votingParameters.daoBountyConst, - activationTime: votingParameters.activationTime, - }; - - // If the scheme not exist, register it - if (!networkCache.schemes[schemeAddress]) { - networkCache.schemes[schemeAddress] = { - address: schemeAddress, - registered: true, - controllerAddress, - name: schemeName, - type: schemeType, - votingMachine: votingMachineAddress, - paramsHash: paramsHash, - permissions, - boostedVoteRequiredPercentage: 0, - proposalIds: [], - boostedProposals: 0, - maxSecondsForExecution, - maxRepPercentageChange, - newProposalEvents: [], - }; - } else { - networkCache.schemes[schemeAddress].paramsHash = paramsHash; - networkCache.schemes[schemeAddress].permissions = permissions; - } - - // Mark scheme as not registered but save all previous data - } else if ( - controllerEvent.event === 'UnregisterScheme' && - // This condition is added to skip the first scheme added (that is the dao creator account) - controllerEvent.returnValues._sender !== schemeAddress - ) { - const schemeTypeData = getSchemeConfig( - networkContractsConfig, - schemeAddress - ); - const votingMachine = - networkWeb3Contracts.votingMachines[ - networkCache.schemes[schemeAddress].votingMachine - ].contract; - - console.debug('Unregister scheme event', schemeAddress, schemeTypeData); - let callsToExecute = [ - [ - votingMachine, - 'orgBoostedProposalsCnt', - [ - web3.utils.soliditySha3( - schemeAddress, - networkWeb3Contracts.avatar._address - ), - ], - ], - ]; - - if (isWalletScheme(networkCache.schemes[schemeAddress])) { - callsToExecute.push([ - await new web3.eth.Contract(WalletScheme1_0JSON.abi, schemeAddress), - 'maxSecondsForExecution', - [], - ]); - } - const callsResponse = await executeMulticall( - web3, - networkWeb3Contracts.multicall, - callsToExecute - ); - - const maxSecondsForExecution = isWalletScheme( - networkCache.schemes[schemeAddress] - ) - ? callsResponse.decodedReturnData[2] - : 0; - - // Update the scheme values a last time - networkCache.schemes[schemeAddress].boostedProposals = - callsResponse.decodedReturnData[0]; - networkCache.schemes[schemeAddress].maxSecondsForExecution = - maxSecondsForExecution; - networkCache.schemes[schemeAddress].registered = false; - } - } - // Update registered schemes - await Promise.all( - Object.keys(networkCache.schemes).map(async schemeAddress => { - if (networkCache.schemes[schemeAddress].registered) { - const votingMachine = - networkWeb3Contracts.votingMachines[ - networkCache.schemes[schemeAddress].votingMachine - ].contract; - - let callsToExecute = [ - [ - votingMachine, - 'orgBoostedProposalsCnt', - [ - web3.utils.soliditySha3( - schemeAddress, - networkWeb3Contracts.avatar._address - ), - ], - ], - ]; - - if (isWalletScheme(networkCache.schemes[schemeAddress])) { - callsToExecute.push([ - await new web3.eth.Contract( - WalletScheme1_0JSON.abi, - schemeAddress - ), - 'maxSecondsForExecution', - [], - ]); - callsToExecute.push([ - votingMachine, - 'boostedVoteRequiredPercentage', - [ - web3.utils.soliditySha3( - schemeAddress, - networkWeb3Contracts.avatar._address - ), - networkCache.schemes[schemeAddress].paramsHash, - ], - ]); - } - const callsResponse = await executeMulticall( - web3, - networkWeb3Contracts.multicall, - callsToExecute - ); - - const maxSecondsForExecution = isWalletScheme( - networkCache.schemes[schemeAddress] - ) - ? callsResponse.decodedReturnData[1] - : 0; - - const boostedVoteRequiredPercentage = isWalletScheme( - networkCache.schemes[schemeAddress] - ) - ? web3.eth.abi.decodeParameters( - ['uint256'], - callsResponse.returnData[2] - )['0'] - : 0; - - networkCache.schemes[schemeAddress].boostedProposals = - callsResponse.decodedReturnData[0]; - networkCache.schemes[schemeAddress].maxSecondsForExecution = - maxSecondsForExecution; - networkCache.schemes[schemeAddress].boostedVoteRequiredPercentage = - boostedVoteRequiredPercentage; - } - }) - ); + // Process all the registered schemes + await batchPromisesOntarget( + Object.keys(networkCache.schemes) + .filter(schemeAddress => { + return networkCache.schemes[schemeAddress].registered; + }) + .map(schemeAddress => { + return this.processScheme( + schemeAddress, + networkCache, + networkContracts, + web3, + toBlock + ); + }), + networkCache, + 5 + ); return networkCache; } - // Update all the proposals information + // Update the proposals information async updateProposals( networkCache: DaoNetworkCache, - networkContractsConfig: NetworkContracts, - fromBlock: number, + networkContracts: NetworkContracts, toBlock: number, - web3: any + web3: Web3 ): Promise { - const networkWeb3Contracts = await getContracts( - networkContractsConfig, - web3 - ); - const avatarAddress = networkWeb3Contracts.avatar._address; - const avatarAddressEncoded = web3.eth.abi.encodeParameter( - 'address', - avatarAddress - ); - - // Get new proposals - await Promise.all( - Object.keys(networkCache.schemes).map(async schemeAddress => { - const schemeTypeData = getSchemeConfig( - networkContractsConfig, - schemeAddress - ); - const votingMachine = - networkWeb3Contracts.votingMachines[ - networkCache.schemes[schemeAddress].votingMachine - ].contract; - - let schemeEvents = []; - for (let i = 0; i < schemeTypeData.newProposalTopics.length; i++) { - schemeEvents = schemeEvents.concat( - await getRawEvents( - web3, - schemeAddress, - fromBlock, - toBlock, - schemeTypeData.newProposalTopics[i] - ) - ); - } - - let schemeEventsBatchs = []; - let schemeEventsBatchsIndex = 0; - for (var i = 0; i < schemeEvents.length; i += 50) - schemeEventsBatchs.push(schemeEvents.slice(i, i + 50)); - - while (schemeEventsBatchsIndex < schemeEventsBatchs.length) { - try { - console.debug( - `Getting proposals of scheme ${schemeTypeData.name}: ${schemeAddress}, batch: ${schemeEventsBatchsIndex}` - ); - await Promise.all( - schemeEventsBatchs[schemeEventsBatchsIndex].map( - async schemeEvent => { - const proposalId = - schemeEvent.topics[1] === avatarAddressEncoded - ? web3.eth.abi.decodeParameter( - 'bytes32', - schemeEvent.topics[2] - ) - : web3.eth.abi.decodeParameter( - 'bytes32', - schemeEvent.topics[1] - ); - // Get all the proposal information from the scheme and voting machine - let callsToExecute = [ - [votingMachine, 'proposals', [proposalId]], - [votingMachine, 'voteStatus', [proposalId, 1]], - [votingMachine, 'voteStatus', [proposalId, 2]], - [votingMachine, 'proposalStatus', [proposalId]], - [votingMachine, 'getProposalTimes', [proposalId]], - ]; - - if (schemeTypeData.type === 'WalletScheme') { - callsToExecute.push([ - await new web3.eth.Contract( - WalletScheme1_0JSON.abi, - schemeAddress - ), - 'getOrganizationProposal', - [proposalId], - ]); - } else if (schemeTypeData.type === 'ContributionReward') { - callsToExecute.push([ - await new web3.eth.Contract( - ContributionRewardJSON.abi, - schemeAddress - ), - 'getRedeemedPeriods', - [proposalId, networkWeb3Contracts.avatar._address, 0], - ]); - callsToExecute.push([ - await new web3.eth.Contract( - ContributionRewardJSON.abi, - schemeAddress - ), - 'getRedeemedPeriods', - [proposalId, networkWeb3Contracts.avatar._address, 1], - ]); - callsToExecute.push([ - await new web3.eth.Contract( - ContributionRewardJSON.abi, - schemeAddress - ), - 'getRedeemedPeriods', - [proposalId, networkWeb3Contracts.avatar._address, 2], - ]); - callsToExecute.push([ - await new web3.eth.Contract( - ContributionRewardJSON.abi, - schemeAddress - ), - 'getRedeemedPeriods', - [proposalId, networkWeb3Contracts.avatar._address, 3], - ]); - } - - const callsResponse = await executeMulticall( - web3, - networkWeb3Contracts.multicall, - callsToExecute - ); - - const votingMachineProposalInfo = - web3.eth.abi.decodeParameters( - [ - { type: 'bytes32', name: 'organizationId' }, - { type: 'address', name: 'callbacks' }, - { type: 'uint256', name: 'state' }, - { type: 'uint256', name: 'winningVote' }, - { type: 'address', name: 'proposer' }, - { - type: 'uint256', - name: 'currentBoostedVotePeriodLimit', - }, - { type: 'bytes32', name: 'paramsHash' }, - { type: 'uint256', name: 'daoBountyRemain' }, - { type: 'uint256', name: 'daoBounty' }, - { type: 'uint256', name: 'totalStakes' }, - { type: 'uint256', name: 'confidenceThreshold' }, - { - type: 'uint256', - name: 'secondsFromTimeOutTillExecuteBoosted', - }, - ], - callsResponse.returnData[0] - ); - const positiveVotes = callsResponse.returnData[1]; - const negativeVotes = callsResponse.returnData[2]; - - const proposalStatusWithVotes = web3.eth.abi.decodeParameters( - ['uint256', 'uint256', 'uint256', 'uint256'], - callsResponse.returnData[3] - ); - const proposalTimes = callsResponse.decodedReturnData[4]; - - let schemeProposalInfo = { - to: [], - callData: [], - value: [], - state: WalletSchemeProposalState.Submitted, - title: '', - descriptionHash: '', - submittedTime: 0, - }; - let decodedProposer; - let creationLogDecoded; - - if (schemeTypeData.type === 'WalletScheme') { - schemeProposalInfo = web3.eth.abi.decodeParameters( - [ - { type: 'address[]', name: 'to' }, - { type: 'bytes[]', name: 'callData' }, - { type: 'uint256[]', name: 'value' }, - { type: 'uint256', name: 'state' }, - { type: 'string', name: 'title' }, - { type: 'string', name: 'descriptionHash' }, - { type: 'uint256', name: 'submittedTime' }, - ], - callsResponse.returnData[5] - ); - } else { - if (schemeTypeData.type === 'GenericMulticall') { - const executionEvent = await web3.eth.getPastLogs({ - fromBlock: schemeEvent.blockNumber, - address: schemeAddress, - topics: [ - '0x253ad9614c337848bbe7dc3b18b439d139ef5787282b5a517ba7296513d1f533', - avatarAddressEncoded, - proposalId, - ], - }); - if (executionEvent.length > 0) - schemeProposalInfo.state = - WalletSchemeProposalState.ExecutionSucceded; - else - schemeProposalInfo.state = - WalletSchemeProposalState.Submitted; - } else if (schemeTypeData.type === 'ContributionReward') { - if ( - callsResponse.decodedReturnData[5] > 0 || - callsResponse.decodedReturnData[6] > 0 || - callsResponse.decodedReturnData[7] > 0 || - callsResponse.decodedReturnData[8] > 0 - ) { - schemeProposalInfo.state = - WalletSchemeProposalState.ExecutionSucceded; - } else if ( - votingMachineProposalInfo.state === '1' || - votingMachineProposalInfo.state === '2' - ) { - schemeProposalInfo.state = - WalletSchemeProposalState.Rejected; - } else { - schemeProposalInfo.state = - WalletSchemeProposalState.Submitted; - } - } - - const transactionReceipt = - await web3.eth.getTransactionReceipt( - schemeEvent.transactionHash - ); - try { - schemeTypeData.newProposalTopics.map( - (newProposalTopic, i) => { - transactionReceipt.logs.map(log => { - if ( - log.topics[0] === - '0x75b4ff136cc5de5957574c797de3334eb1c141271922b825eb071e0487ba2c5c' - ) { - decodedProposer = web3.eth.abi.decodeParameters( - [ - { type: 'uint256', name: '_numOfChoices' }, - { type: 'address', name: '_proposer' }, - { type: 'bytes32', name: '_paramsHash' }, - ], - log.data - )._proposer; - } - if ( - !creationLogDecoded && - log.topics[0] === newProposalTopic[0] - ) { - creationLogDecoded = - web3.eth.abi.decodeParameters( - schemeTypeData.creationLogEncoding[i], - log.data - ); - if ( - creationLogDecoded._descriptionHash.length > - 0 && - creationLogDecoded._descriptionHash !== - ZERO_HASH - ) { - schemeProposalInfo.descriptionHash = - ipfsHashToDescriptionHash( - creationLogDecoded._descriptionHash - ); - } - } - }); - } - ); - } catch (error) { - console.error( - 'Error in getting proposal data from creation event', - error - ); - } - - if (schemeTypeData.type === 'SchemeRegistrar') { - schemeProposalInfo.to = [schemeTypeData.contractToCall]; - schemeProposalInfo.value = [0]; - - if (creationLogDecoded._parametersHash) { - schemeProposalInfo.callData = [ - web3.eth.abi.encodeFunctionCall( - { - name: 'registerScheme', - type: 'function', - inputs: [ - { type: 'address', name: '_scheme' }, - { type: 'bytes32', name: '_paramsHash' }, - { type: 'bytes4', name: '_permissions' }, - { type: 'address', name: '_avatar' }, - ], - }, - [ - creationLogDecoded['_scheme '], - creationLogDecoded._parametersHash, - creationLogDecoded._permissions, - avatarAddress, - ] - ), - ]; - } else { - schemeProposalInfo.callData = [ - web3.eth.abi.encodeFunctionCall( - { - name: 'unregisterScheme', - type: 'function', - inputs: [ - { type: 'address', name: '_scheme' }, - { type: 'address', name: '_avatar' }, - ], - }, - [creationLogDecoded['_scheme '], avatarAddress] - ), - ]; - } - } else if (schemeTypeData.type === 'ContributionReward') { - if (creationLogDecoded._reputationChange > 0) { - schemeProposalInfo.to.push( - schemeTypeData.contractToCall - ); - schemeProposalInfo.value.push(0); - schemeProposalInfo.callData.push( - web3.eth.abi.encodeFunctionCall( - { - name: 'mintReputation', - type: 'function', - inputs: [ - { type: 'uint256', name: '_amount' }, - { type: 'address', name: '_to' }, - { type: 'address', name: '_avatar' }, - ], - }, - [ - creationLogDecoded._reputationChange, - creationLogDecoded._beneficiary, - avatarAddress, - ] - ) - ); - } else if (creationLogDecoded._reputationChange < 0) { - schemeProposalInfo.to.push( - schemeTypeData.contractToCall - ); - schemeProposalInfo.value.push(0); - - // Remove the negative sign in the number - if (creationLogDecoded._reputationChange[0] == '-') - creationLogDecoded._reputationChange = - creationLogDecoded._reputationChange.substring(1); - - schemeProposalInfo.callData.push( - web3.eth.abi.encodeFunctionCall( - { - name: 'burnReputation', - type: 'function', - inputs: [ - { type: 'uint256', name: '_amount' }, - { type: 'address', name: '_from' }, - { type: 'address', name: '_avatar' }, - ], - }, - [ - creationLogDecoded._reputationChange, - creationLogDecoded._beneficiary, - avatarAddress, - ] - ) - ); - } - - if (creationLogDecoded._rewards[0] > 0) { - schemeProposalInfo.to.push( - schemeTypeData.contractToCall - ); - schemeProposalInfo.value.push(0); - schemeProposalInfo.callData.push( - web3.eth.abi.encodeFunctionCall( - { - name: 'mintTokens', - type: 'function', - inputs: [ - { type: 'uint256', name: '_amount' }, - { type: 'address', name: '_beneficiary' }, - { type: 'address', name: '_avatar' }, - ], - }, - [ - creationLogDecoded._rewards[0], - creationLogDecoded._beneficiary, - avatarAddress, - ] - ) - ); - } - - if (creationLogDecoded._rewards[1] > 0) { - schemeProposalInfo.to.push( - schemeTypeData.contractToCall - ); - schemeProposalInfo.value.push(0); - schemeProposalInfo.callData.push( - web3.eth.abi.encodeFunctionCall( - { - name: 'sendEther', - type: 'function', - inputs: [ - { type: 'uint256', name: '_amountInWei' }, - { type: 'address', name: '_to' }, - { type: 'address', name: '_avatar' }, - ], - }, - [ - creationLogDecoded._rewards[1], - creationLogDecoded._beneficiary, - avatarAddress, - ] - ) - ); - } - - if (creationLogDecoded._rewards[2] > 0) { - schemeProposalInfo.to.push( - schemeTypeData.contractToCall - ); - schemeProposalInfo.value.push(0); - schemeProposalInfo.callData.push( - web3.eth.abi.encodeFunctionCall( - { - name: 'externalTokenTransfer', - type: 'function', - inputs: [ - { type: 'address', name: '_externalToken' }, - { type: 'address', name: '_to' }, - { type: 'uint256', name: '_value' }, - { type: 'address', name: '_avatar' }, - ], - }, - [ - creationLogDecoded._externalToken, - creationLogDecoded._beneficiary, - creationLogDecoded._rewards[2], - avatarAddress, - ] - ) - ); - } - } else if (schemeTypeData.type === 'GenericScheme') { - schemeProposalInfo.to = [ - networkWeb3Contracts.controller._address, - ]; - schemeProposalInfo.value = [0]; - schemeProposalInfo.callData = [ - web3.eth.abi.encodeFunctionCall( - { - name: 'genericCall', - type: 'function', - inputs: [ - { type: 'address', name: '_contract' }, - { type: 'bytes', name: '_data' }, - { type: 'address', name: '_avatar' }, - { type: 'uint256', name: '_value' }, - ], - }, - [ - schemeTypeData.contractToCall, - creationLogDecoded._data, - avatarAddress, - creationLogDecoded._value, - ] - ), - ]; - } else if (schemeTypeData.type === 'GenericMulticall') { - for ( - let callIndex = 0; - callIndex < creationLogDecoded._contractsToCall.length; - callIndex++ - ) { - schemeProposalInfo.to.push( - networkWeb3Contracts.controller._address - ); - schemeProposalInfo.value.push(0); - schemeProposalInfo.callData.push( - web3.eth.abi.encodeFunctionCall( - { - name: 'genericCall', - type: 'function', - inputs: [ - { type: 'address', name: '_contract' }, - { type: 'bytes', name: '_data' }, - { type: 'address', name: '_avatar' }, - { type: 'uint256', name: '_value' }, - ], - }, - [ - creationLogDecoded._contractsToCall[callIndex], - creationLogDecoded._callsData[callIndex], - avatarAddress, - creationLogDecoded._values[callIndex], - ] - ) - ); - } - } - } - - // Register the new voting parameters in the voting machine params - if ( - !networkCache.votingMachines[votingMachine._address] - .votingParameters[votingMachineProposalInfo.paramsHash] - ) { - const votingParameters = await votingMachine.methods - .parameters(votingMachineProposalInfo.paramsHash) - .call(); - networkCache.votingMachines[ - votingMachine._address - ].votingParameters[votingMachineProposalInfo.paramsHash] = { - queuedVoteRequiredPercentage: - votingParameters.queuedVoteRequiredPercentage, - queuedVotePeriodLimit: - votingParameters.queuedVotePeriodLimit, - boostedVotePeriodLimit: - votingParameters.boostedVotePeriodLimit, - preBoostedVotePeriodLimit: - votingParameters.preBoostedVotePeriodLimit, - thresholdConst: votingParameters.thresholdConst, - limitExponentValue: votingParameters.limitExponentValue, - quietEndingPeriod: votingParameters.quietEndingPeriod, - proposingRepReward: votingParameters.proposingRepReward, - votersReputationLossRatio: - votingParameters.votersReputationLossRatio, - minimumDaoBounty: votingParameters.minimumDaoBounty, - daoBountyConst: votingParameters.daoBountyConst, - activationTime: votingParameters.activationTime, - }; - } - - networkCache.proposals[proposalId] = { - id: proposalId, - scheme: schemeAddress, - to: schemeProposalInfo.to, - title: schemeProposalInfo.title || '', - callData: schemeProposalInfo.callData, - values: schemeProposalInfo.value.map(value => bnum(value)), - stateInScheme: Number(schemeProposalInfo.state), - stateInVotingMachine: Number( - votingMachineProposalInfo.state - ), - descriptionHash: schemeProposalInfo.descriptionHash, - creationEvent: { - event: schemeEvent.event, - signature: schemeEvent.signature, - address: schemeEvent.address, - tx: schemeEvent.transactionHash, - blockNumber: schemeEvent.blockNumber, - timestamp: schemeEvent.timestamp, - transactionIndex: schemeEvent.transactionIndex, - logIndex: schemeEvent.logIndex, - }, - winningVote: votingMachineProposalInfo.winningVote, - proposer: decodedProposer - ? decodedProposer - : votingMachineProposalInfo.proposer, - currentBoostedVotePeriodLimit: - votingMachineProposalInfo.currentBoostedVotePeriodLimit, - paramsHash: votingMachineProposalInfo.paramsHash, - daoBountyRemain: bnum( - votingMachineProposalInfo.daoBountyRemain - ), - daoBounty: bnum(votingMachineProposalInfo.daoBounty), - confidenceThreshold: - votingMachineProposalInfo.confidenceThreshold, - secondsFromTimeOutTillExecuteBoosted: - votingMachineProposalInfo.secondsFromTimeOutTillExecuteBoosted, - submittedTime: bnum(proposalTimes[0]), - boostedPhaseTime: bnum(proposalTimes[1]), - preBoostedPhaseTime: bnum(proposalTimes[2]), - daoRedeemItsWinnings: - votingMachineProposalInfo.daoRedeemItsWinnings, - shouldBoost: false, - positiveVotes: bnum(positiveVotes), - negativeVotes: bnum(negativeVotes), - positiveStakes: bnum(proposalStatusWithVotes[2]), - negativeStakes: bnum(proposalStatusWithVotes[3]), - }; - - networkCache.schemes[schemeAddress].proposalIds.push( - proposalId - ); - networkCache.schemes[schemeAddress].newProposalEvents.push({ - proposalId: proposalId, - event: schemeEvent.event, - signature: schemeEvent.signature, - address: schemeEvent.address, - tx: schemeEvent.transactionHash, - blockNumber: schemeEvent.blockNumber, - timestamp: schemeEvent.timestamp, - transactionIndex: schemeEvent.transactionIndex, - logIndex: schemeEvent.logIndex, - }); - - if (schemeProposalInfo.descriptionHash.length > 1) { - networkCache.ipfsHashes.push({ - hash: descriptionHashToIPFSHash( - schemeProposalInfo.descriptionHash - ), - type: 'proposal', - name: proposalId, - }); - } - } - ) - ); - - schemeEventsBatchsIndex++; - } catch (error) { - console.error( - 'Error in getting proposal info of schemeEventsBatchs index', - schemeEventsBatchsIndex, - error - ); - } - } - }) - ); + const fromBlock = networkCache.blockNumber + 1; // Update existent active proposals - // @ts-ignore - const activeProposals = []; - Object.keys(networkCache.proposals).map(proposalId => { - if ( - networkCache.proposals[proposalId].stateInVotingMachine > - VotingMachineProposalState.Executed || - networkCache.proposals[proposalId].stateInScheme === - WalletSchemeProposalState.Submitted - ) - activeProposals.push(networkCache.proposals[proposalId]); - }); - - let activeProposalsBatch = []; - let activeProposalsBatchIndex = 0; - for (var i = 0; i < activeProposals.length; i += 5) - activeProposalsBatch.push(activeProposals.slice(i, i + 5)); - - while (activeProposalsBatchIndex < activeProposalsBatch.length) { - await Promise.all( - activeProposalsBatch[activeProposalsBatchIndex].map(async proposal => { - let retry = true; - while (retry) { - try { - const schemeAddress = networkCache.proposals[proposal.id].scheme; - const schemeTypeData = getSchemeConfig( - networkContractsConfig, - schemeAddress - ); - const votingMachine = - networkWeb3Contracts.votingMachines[ - networkCache.schemes[schemeAddress].votingMachine - ].contract; - - // Get all the proposal information from the scheme and voting machine - let callsToExecute = [ - [votingMachine, 'proposals', [proposal.id]], - [votingMachine, 'voteStatus', [proposal.id, 1]], - [votingMachine, 'voteStatus', [proposal.id, 2]], - [votingMachine, 'proposalStatus', [proposal.id]], - [votingMachine, 'getProposalTimes', [proposal.id]], - [votingMachine, 'shouldBoost', [proposal.id]], - ]; - - if (schemeTypeData.type === 'WalletScheme') { - callsToExecute.push([ - await new web3.eth.Contract( - WalletScheme1_0JSON.abi, - schemeAddress - ), - 'getOrganizationProposal', - [proposal.id], - ]); - } else if ( - schemeTypeData.type === 'ContributionReward' && - networkCache.proposals[proposal.id].stateInVotingMachine === - VotingMachineProposalState.Executed && - networkCache.proposals[proposal.id].stateInScheme === - WalletSchemeProposalState.Submitted - ) { - callsToExecute.push([ - await new web3.eth.Contract( - ContributionRewardJSON.abi, - schemeAddress - ), - 'getRedeemedPeriods', - [proposal.id, networkWeb3Contracts.avatar._address, 0], - ]); - callsToExecute.push([ - await new web3.eth.Contract( - ContributionRewardJSON.abi, - schemeAddress - ), - 'getRedeemedPeriods', - [proposal.id, networkWeb3Contracts.avatar._address, 1], - ]); - callsToExecute.push([ - await new web3.eth.Contract( - ContributionRewardJSON.abi, - schemeAddress - ), - 'getRedeemedPeriods', - [proposal.id, networkWeb3Contracts.avatar._address, 2], - ]); - callsToExecute.push([ - await new web3.eth.Contract( - ContributionRewardJSON.abi, - schemeAddress - ), - 'getRedeemedPeriods', - [proposal.id, networkWeb3Contracts.avatar._address, 3], - ]); - } - - const callsResponse = await executeMulticall( - web3, - networkWeb3Contracts.multicall, - callsToExecute - ); - - const votingMachineProposalInfo = web3.eth.abi.decodeParameters( - [ - { type: 'bytes32', name: 'organizationId' }, - { type: 'address', name: 'callbacks' }, - { type: 'uint256', name: 'state' }, - { type: 'uint256', name: 'winningVote' }, - { type: 'address', name: 'proposer' }, - { type: 'uint256', name: 'currentBoostedVotePeriodLimit' }, - { type: 'bytes32', name: 'paramsHash' }, - { type: 'uint256', name: 'daoBountyRemain' }, - { type: 'uint256', name: 'daoBounty' }, - { type: 'uint256', name: 'totalStakes' }, - { type: 'uint256', name: 'confidenceThreshold' }, - { - type: 'uint256', - name: 'secondsFromTimeOutTillExecuteBoosted', - }, - ], - callsResponse.returnData[0] - ); - const positiveVotes = callsResponse.returnData[1]; - const negativeVotes = callsResponse.returnData[2]; - - const proposalStatusWithVotes = web3.eth.abi.decodeParameters( - ['uint256', 'uint256', 'uint256', 'uint256'], - callsResponse.returnData[3] - ); - const proposalTimes = callsResponse.decodedReturnData[4]; - const proposalShouldBoost = callsResponse.decodedReturnData[5]; - - if (schemeTypeData.type === 'WalletScheme') { - networkCache.proposals[proposal.id].stateInScheme = Number( - web3.eth.abi.decodeParameters( - [ - { type: 'address[]', name: 'to' }, - { type: 'bytes[]', name: 'callData' }, - { type: 'uint256[]', name: 'value' }, - { type: 'uint256', name: 'state' }, - { type: 'string', name: 'title' }, - { type: 'string', name: 'descriptionHash' }, - { type: 'uint256', name: 'submittedTime' }, - ], - callsResponse.returnData[6] - ).state - ); - } else if ( - schemeTypeData.type === 'ContributionReward' && - networkCache.proposals[proposal.id].stateInVotingMachine === - VotingMachineProposalState.Executed && - networkCache.proposals[proposal.id].stateInScheme === - WalletSchemeProposalState.Submitted - ) { - if (schemeTypeData.type === 'ContributionReward') { - if ( - callsResponse.decodedReturnData[6] > 0 || - callsResponse.decodedReturnData[7] > 0 || - callsResponse.decodedReturnData[8] > 0 || - callsResponse.decodedReturnData[9] > 0 - ) { - networkCache.proposals[proposal.id].stateInScheme = - WalletSchemeProposalState.ExecutionSucceded; - } else if ( - votingMachineProposalInfo.state === '1' || - votingMachineProposalInfo.state === '2' - ) { - networkCache.proposals[proposal.id].stateInScheme = - WalletSchemeProposalState.Rejected; - } - } - } else if (schemeTypeData.type === 'GenericMulticall') { - const executionEvent = await web3.eth.getPastLogs({ - fromBlock: - networkCache.proposals[proposal.id].creationEvent - .blockNumber, - address: schemeAddress, - topics: [ - '0x6bc0cb9e9967b59a69ace442598e1df4368d38661bd5c0800fbcbc9fe855fbbe', - avatarAddressEncoded, - proposal.id, - ], - }); - if (executionEvent.length > 0) - networkCache.proposals[proposal.id].stateInScheme = - WalletSchemeProposalState.ExecutionSucceded; - else - networkCache.proposals[proposal.id].stateInScheme = - WalletSchemeProposalState.Submitted; - } else if ( - networkCache.proposals[proposal.id].stateInVotingMachine === - VotingMachineProposalState.Executed - ) { - networkCache.proposals[proposal.id].stateInScheme = - WalletSchemeProposalState.ExecutionSucceded; - } - - networkCache.proposals[proposal.id].stateInVotingMachine = Number( - votingMachineProposalInfo.state - ); - networkCache.proposals[proposal.id].winningVote = - votingMachineProposalInfo.winningVote; - networkCache.proposals[ - proposal.id - ].currentBoostedVotePeriodLimit = - votingMachineProposalInfo.currentBoostedVotePeriodLimit; - networkCache.proposals[proposal.id].daoBountyRemain = bnum( - votingMachineProposalInfo.daoBountyRemain - ); - networkCache.proposals[proposal.id].daoBounty = bnum( - votingMachineProposalInfo.daoBounty - ); - networkCache.proposals[proposal.id].confidenceThreshold = - votingMachineProposalInfo.confidenceThreshold; - networkCache.proposals[ - proposal.id - ].secondsFromTimeOutTillExecuteBoosted = - votingMachineProposalInfo.secondsFromTimeOutTillExecuteBoosted; - networkCache.proposals[proposal.id].boostedPhaseTime = bnum( - proposalTimes[1] - ); - networkCache.proposals[proposal.id].preBoostedPhaseTime = bnum( - proposalTimes[2] - ); - networkCache.proposals[proposal.id].daoRedeemItsWinnings = - votingMachineProposalInfo.daoRedeemItsWinnings; - networkCache.proposals[proposal.id].shouldBoost = - proposalShouldBoost; - networkCache.proposals[proposal.id].positiveVotes = - bnum(positiveVotes); - networkCache.proposals[proposal.id].negativeVotes = - bnum(negativeVotes); - networkCache.proposals[proposal.id].positiveStakes = bnum( - proposalStatusWithVotes[2] - ); - networkCache.proposals[proposal.id].negativeStakes = bnum( - proposalStatusWithVotes[3] - ); - - retry = false; - } catch (e) { - console.error( - 'Error on updating proposal (trying again)', - proposal - ); - console.error(e); - retry = true; - } - } + await batchPromisesOntarget( + Object.keys(networkCache.proposals) + .filter(proposalId => { + const proposal = networkCache.proposals[proposalId]; + const scheme = networkCache.schemes[proposal.scheme]; + return ( + // Only update proposals for registered schemes + scheme.registered && + // This condition check that the proposal already existed in the current block range. + // If not, it means that the proposal was created in the current block range when processing the scheme, + // So there is no need to process it again. + proposal.creationEvent.blockNumber < fromBlock && + // This condition check that the proposal is active + (proposal.stateInVotingMachine > + VotingMachineProposalState.Executed || + proposal.stateInScheme === WalletSchemeProposalState.Submitted) + ); }) - ); - - activeProposalsBatchIndex++; - } - - // Sort cache data, so the IPFS hash is consistent - Object.keys(networkCache.schemes).forEach(schemeId => { - networkCache.schemes[schemeId].proposalIds.sort(); - networkCache.schemes[schemeId].newProposalEvents.sort((a, b) => - a.proposalId.localeCompare(b.proposalId) - ); - }); - networkCache.proposals = Object.keys(networkCache.proposals) - .sort() - .reduce((obj, key) => { - obj[key] = networkCache.proposals[key]; - return obj; - }, {}); - networkCache.ipfsHashes = _.uniqBy(networkCache.ipfsHashes, 'name'); - networkCache.ipfsHashes.sort((a, b) => a.name.localeCompare(b.name)); + .map(proposalId => { + return this.processProposal( + proposalId, + networkCache, + networkContracts, + web3, + fromBlock, + toBlock + ); + }), + networkCache, + 5, + 1000, + 500 + ); return networkCache; } - async getProposalTitlesFromIPFS( + async updateProposalTitles( networkCache: DaoNetworkCache, proposalTitles: Record ) { @@ -2073,4 +961,968 @@ export default class UtilsService { return proposalTitles; } + + // Process a scheme in the networkCache + // If the scheme does not exist in the cache it gets the immutable + mutable data + // The scheme already exists it gets the mutable data only + // At the end it process all the new proposals added in the scheme to the toBlock + async processScheme( + schemeAddress: string, + networkCache: DaoNetworkCache, + networkContracts: NetworkContracts, + web3: Web3, + toBlock: number, + removeScheme: boolean = false + ): Promise { + const networkWeb3Contracts = await getContracts(networkContracts, web3); + const isNewScheme = !networkCache.schemes[schemeAddress]; + const schemeTypeData = getSchemeConfig(networkContracts, schemeAddress); + console.debug( + 'Processing Scheme', + schemeAddress, + schemeTypeData, + removeScheme + ); + + let controllerAddress = networkWeb3Contracts.controller._address; + let schemeName = schemeTypeData.name; + let maxSecondsForExecution = 0; + let maxRepPercentageChange = 0; + let schemeType = schemeTypeData.type; + const isWalletScheme = schemeType === 'WalletScheme'; + + let callsToExecute = [ + [ + controllerAddress, + 'getSchemePermissions(address,address)', + [schemeAddress, networkWeb3Contracts.avatar._address], + ['bytes4'], + ], + [ + controllerAddress, + 'getSchemeParameters(address,address)', + [schemeAddress, networkWeb3Contracts.avatar._address], + ['bytes32'], + ], + ]; + + if (isWalletScheme) { + callsToExecute.push([ + schemeAddress, + 'maxSecondsForExecution()', + [], + ['uint256'], + ]); + callsToExecute.push([ + schemeAddress, + 'maxRepPercentageChange()', + [], + ['uint256'], + ]); + + if (isNewScheme) { + schemeType = ( + await executeMulticall(networkWeb3Contracts.multicall, [ + [schemeAddress, 'SCHEME_TYPE()', [], ['string']], + ]) + ).decodedReturnData[0][0]; + if (schemeType === 'Wallet Scheme v1') + schemeType = 'Wallet Scheme v1.0'; + + callsToExecute.push([ + schemeAddress, + 'votingMachine()', + [], + ['address'], + ]); + callsToExecute.push([schemeAddress, 'schemeName()', [], ['string']]); + + switch (schemeType) { + case 'Wallet Scheme v1.0': + callsToExecute.push([ + schemeAddress, + 'controllerAddress()', + [], + ['address'], + ]); + break; + default: + callsToExecute.push([ + schemeAddress, + 'doAvatarGenericCalls()', + [], + ['bool'], + ]); + break; + } + } + } + + const callsResponse1 = await executeMulticall( + networkWeb3Contracts.multicall, + callsToExecute + ); + + const permissions = decodePermission( + callsResponse1.decodedReturnData[0][0] + ); + const paramsHash = + schemeTypeData.voteParams || callsResponse1.decodedReturnData[1][0]; + + const votingMachineAddress = !isNewScheme + ? networkCache.schemes[schemeAddress].votingMachine + : isWalletScheme + ? callsResponse1.decodedReturnData[4][0] + : schemeTypeData.votingMachine; + + if (isWalletScheme) { + maxSecondsForExecution = callsResponse1.decodedReturnData[2][0]; + maxRepPercentageChange = callsResponse1.decodedReturnData[3][0]; + + if (isNewScheme) { + schemeName = callsResponse1.decodedReturnData[5][0]; + + switch (schemeType) { + case 'Wallet Scheme v1.0': + controllerAddress = callsResponse1.decodedReturnData[6][0]; + break; + default: + controllerAddress = callsResponse1.decodedReturnData[6][0] + ? networkWeb3Contracts.controller._address + : ZERO_ADDRESS; + break; + } + } + } + + callsToExecute = [ + [ + votingMachineAddress, + 'orgBoostedProposalsCnt(bytes32)', + [ + web3.utils.soliditySha3( + schemeAddress, + networkWeb3Contracts.avatar._address + ), + ], + ['uint256'], + ], + ]; + + // Register the new voting parameters in the voting machine params + if ( + !networkCache.votingMachines[votingMachineAddress].votingParameters[ + paramsHash + ] + ) { + callsToExecute.push([ + votingMachineAddress, + 'parameters(bytes32)', + [paramsHash], + [ + 'uint256', + 'uint256', + 'uint256', + 'uint256', + 'uint256', + 'uint256', + 'uint256', + 'uint256', + 'uint256', + 'uint256', + 'uint256', + 'uint256', + 'address', + ], + ]); + } + + if (isWalletScheme) { + callsToExecute.push([ + votingMachineAddress, + 'boostedVoteRequiredPercentage(bytes32,bytes32)', + [ + web3.utils.soliditySha3( + schemeAddress, + networkWeb3Contracts.avatar._address + ), + paramsHash, + ], + ['uint256'], + ]); + } + + const callsResponse2 = await executeMulticall( + networkWeb3Contracts.multicall, + callsToExecute + ); + + const boostedProposals = callsResponse2.decodedReturnData[0][0]; + + if ( + !networkCache.votingMachines[votingMachineAddress].votingParameters[ + paramsHash + ] + ) { + networkCache.votingMachines[votingMachineAddress].votingParameters[ + paramsHash + ] = { + queuedVoteRequiredPercentage: callsResponse2.decodedReturnData[1][0], + queuedVotePeriodLimit: callsResponse2.decodedReturnData[1][1], + boostedVotePeriodLimit: callsResponse2.decodedReturnData[1][2], + preBoostedVotePeriodLimit: callsResponse2.decodedReturnData[1][3], + thresholdConst: callsResponse2.decodedReturnData[1][4], + limitExponentValue: callsResponse2.decodedReturnData[1][5], + quietEndingPeriod: callsResponse2.decodedReturnData[1][6], + proposingRepReward: callsResponse2.decodedReturnData[1][7], + votersReputationLossRatio: callsResponse2.decodedReturnData[1][8], + minimumDaoBounty: callsResponse2.decodedReturnData[1][9], + daoBountyConst: callsResponse2.decodedReturnData[1][10], + activationTime: callsResponse2.decodedReturnData[1][11], + }; + } + + const boostedVoteRequiredPercentage = isWalletScheme + ? callsResponse2.decodedReturnData[callsToExecute.length - 1][0] + : 0; + + if (isNewScheme) { + networkCache.schemes[schemeAddress] = { + address: schemeAddress, + registered: true, + controllerAddress, + name: schemeName, + type: schemeType, + votingMachine: votingMachineAddress, + paramsHash: paramsHash, + permissions, + boostedVoteRequiredPercentage, + proposalIds: [], + boostedProposals: boostedProposals, + maxSecondsForExecution, + maxRepPercentageChange, + newProposalEvents: [], + }; + } else { + networkCache.schemes[schemeAddress].boostedProposals = boostedProposals; + networkCache.schemes[schemeAddress].maxSecondsForExecution = + maxSecondsForExecution; + networkCache.schemes[schemeAddress].maxRepPercentageChange = + maxRepPercentageChange; + networkCache.schemes[schemeAddress].boostedVoteRequiredPercentage = + boostedVoteRequiredPercentage; + networkCache.schemes[schemeAddress].paramsHash = paramsHash; + networkCache.schemes[schemeAddress].permissions = permissions; + networkCache.schemes[schemeAddress].registered = !removeScheme; + } + + // Get the new proposals submitted in the scheme and process it + const avatarAddressEncoded = web3.eth.abi.encodeParameter( + 'address', + networkCache.address + ); + const fromBlock = networkCache.blockNumber + 1; + + let schemeEvents = await getRawEvents( + web3, + schemeAddress, + fromBlock, + toBlock, + schemeTypeData.newProposalTopics + ); + + await batchPromisesOntarget( + schemeEvents.map(schemeEvent => { + const proposalId: string = + schemeEvent.topics[1] === avatarAddressEncoded + ? schemeEvent.topics[2] + : schemeEvent.topics[1]; + + schemeEvent.tx = schemeEvent.transactionHash; + + return this.processProposal( + proposalId, + networkCache, + networkContracts, + web3, + fromBlock, + toBlock, + schemeEvent + ); + }), + networkCache, + 50 + ); + + return networkCache; + } + + // Process a proposal in the networkCache + // If the proposal does not exist in the cache it gets the immutable + mutable data + // The proposal already exists it gets the mutable data only + async processProposal( + proposalId: string, + networkCache: DaoNetworkCache, + networkContracts: NetworkContracts, + web3: Web3, + fromBlock: number, + toBlock: number, + creationEvent?: BlockchainEvent + ): Promise { + const newProposal = !networkCache.proposals[proposalId]; + const schemeAddress = newProposal + ? creationEvent.address + : networkCache.proposals[proposalId].scheme; + const schemeOfProposal = networkCache.schemes[schemeAddress]; + const avatarAddress = networkCache.address; + const schemeTypeData = getSchemeConfig(networkContracts, schemeAddress); + const networkWeb3Contracts = await getContracts(networkContracts, web3); + const avatarAddressEncoded = web3.eth.abi.encodeParameter( + 'address', + avatarAddress + ); + + // These first calls target mainly the voting machine where most of the proposal information is mutable + let callsToExecute = [ + [ + networkCache.schemes[schemeAddress].votingMachine, + 'proposals(bytes32)', + [proposalId], + [ + { type: 'bytes32', name: 'organizationId' }, + { type: 'address', name: 'callbacks' }, + { type: 'uint256', name: 'state' }, + { type: 'uint256', name: 'winningVote' }, + { type: 'address', name: 'proposer' }, + { + type: 'uint256', + name: 'currentBoostedVotePeriodLimit', + }, + { type: 'bytes32', name: 'paramsHash' }, + { type: 'uint256', name: 'daoBountyRemain' }, + { type: 'uint256', name: 'daoBounty' }, + { type: 'uint256', name: 'totalStakes' }, + { type: 'uint256', name: 'confidenceThreshold' }, + { + type: 'uint256', + name: 'secondsFromTimeOutTillExecuteBoosted', + }, + ], + ], + [ + networkCache.schemes[schemeAddress].votingMachine, + 'voteStatus(bytes32,uint256)', + [proposalId, 1], + ['uint256'], + ], + [ + networkCache.schemes[schemeAddress].votingMachine, + 'voteStatus(bytes32,uint256)', + [proposalId, 2], + ['uint256'], + ], + [ + networkCache.schemes[schemeAddress].votingMachine, + 'proposalStatus(bytes32)', + [proposalId], + ['uint256', 'uint256', 'uint256', 'uint256'], + ], + [ + networkCache.schemes[schemeAddress].votingMachine, + 'getProposalTimes(bytes32)', + [proposalId], + ['uint256', 'uint256', 'uint256'], + ], + [ + networkCache.schemes[schemeAddress].votingMachine, + 'shouldBoost(bytes32)', + [proposalId], + ['bool'], + ], + ]; + + // The next calls added target ContributionReward and WalletScheme to get immutable and mutable data + if (schemeTypeData.type === 'ContributionReward') { + callsToExecute.push([ + schemeAddress, + 'getRedeemedPeriods(bytes32,address,uint256)', + [proposalId, schemeAddress, 0], + ['uint256'], + ]); + callsToExecute.push([ + schemeAddress, + 'getRedeemedPeriods(bytes32,address,uint256)', + [proposalId, avatarAddress, 1], + ['uint256'], + ]); + callsToExecute.push([ + schemeAddress, + 'getRedeemedPeriods(bytes32,address,uint256)', + [proposalId, avatarAddress, 2], + ['uint256'], + ]); + callsToExecute.push([ + schemeAddress, + 'getRedeemedPeriods(bytes32,address,uint256)', + [proposalId, avatarAddress, 3], + ['uint256'], + ]); + } else if (isWalletScheme(schemeOfProposal)) { + callsToExecute.push([ + schemeAddress, + 'getOrganizationProposal(bytes32)', + [proposalId], + [ + { type: 'address[]', name: 'to' }, + { type: 'bytes[]', name: 'callData' }, + { type: 'uint256[]', name: 'value' }, + { type: 'uint256', name: 'state' }, + { type: 'string', name: 'title' }, + { type: 'string', name: 'descriptionHash' }, + { type: 'uint256', name: 'submittedTime' }, + ], + ]); + } + + const callsResponse = await executeMulticall( + networkWeb3Contracts.multicall, + callsToExecute + ); + + const proposalTimes = callsResponse.decodedReturnData[4]; + + let schemeProposalInfo = { + to: [], + callData: [], + value: [], + state: WalletSchemeProposalState.Submitted, + title: '', + descriptionHash: '', + submittedTime: 0, + }; + let decodedProposer; + let creationLogDecoded; + + if (isWalletScheme(schemeOfProposal)) { + schemeProposalInfo.state = callsResponse.decodedReturnData[6].state; + + if (newProposal) { + schemeProposalInfo.to = callsResponse.decodedReturnData[6].to; + schemeProposalInfo.callData = + callsResponse.decodedReturnData[6].callData; + schemeProposalInfo.value = callsResponse.decodedReturnData[6].value; + schemeProposalInfo.title = callsResponse.decodedReturnData[6].title; + schemeProposalInfo.descriptionHash = + callsResponse.decodedReturnData[6].descriptionHash; + schemeProposalInfo.submittedTime = + callsResponse.decodedReturnData[6].submittedTime; + } + } else { + // Here we get the events triggered in the GenericMulticall to get their final state + // When the proposal is executed by the voting machine we get if the proposal was rejected + // If the proposal was executed by teh voting machine and not rejected we get the ProposalEnded + // to know if it was executed by the WalletScheme + if (schemeOfProposal.type === 'GenericMulticall') { + // event ProposalExecutedByVotingMachine(address indexed _avatar,bytes32 indexed _proposalId,int256 _param) + const votingMachineExecutionEvent = + schemeProposalInfo.state === WalletSchemeProposalState.Submitted + ? await getRawEvents( + web3, + schemeAddress, + fromBlock, + toBlock, + [ + '0x25d4c89430c1f10c60c292556941e3e624ec1ec04972a5da46cee1b352429cbe', + avatarAddressEncoded, + proposalId, + ], + 10000000 + ) + : []; + if ( + votingMachineExecutionEvent.length > 0 && + votingMachineExecutionEvent[0].data !== + '0x0000000000000000000000000000000000000000000000000000000000000001' + ) + schemeProposalInfo.state = WalletSchemeProposalState.Rejected; + + if ( + callsResponse.decodedReturnData[0].state === + VotingMachineProposalState.Executed && + schemeProposalInfo.state === WalletSchemeProposalState.Submitted + ) { + // event ProposalDeleted(address indexed _avatar, bytes32 indexed _proposalId) + const executionEvent = await getRawEvents( + web3, + schemeAddress, + fromBlock, + toBlock, + [ + '0x253ad9614c337848bbe7dc3b18b439d139ef5787282b5a517ba7296513d1f533', + avatarAddressEncoded, + proposalId, + ], + 10000000 + ); + if (executionEvent.length > 0) { + schemeProposalInfo.state = + WalletSchemeProposalState.ExecutionSucceded; + } + } + + // If any of the values of the redeemPeriods of the contribution reward is higher than zero it means that it executed the reward + } else if (schemeOfProposal.type === 'ContributionReward') { + if ( + callsResponse.decodedReturnData[0].winningVote === '2' && + callsResponse.decodedReturnData[0].state === '2' + ) { + schemeProposalInfo.state = WalletSchemeProposalState.Rejected; + } else if ( + callsResponse.decodedReturnData[6][0] > 0 || + callsResponse.decodedReturnData[7][0] > 0 || + callsResponse.decodedReturnData[8][0] > 0 || + callsResponse.decodedReturnData[9][0] > 0 + ) { + schemeProposalInfo.state = + WalletSchemeProposalState.ExecutionSucceded; + } else { + schemeProposalInfo.state = WalletSchemeProposalState.Submitted; + } + } + } + + // If the proposal is processed with a creation event it means that it has to be added to the cache + // We will get the immutable data stored on the contracts by decoding the creation event logs + if (newProposal) { + if (creationEvent && !isWalletScheme(schemeOfProposal)) { + const transactionReceipt = await web3.eth.getTransactionReceipt( + creationEvent.tx + ); + try { + // Decode the creation event data to get the num of choices, paramsHash and proposer + schemeTypeData.newProposalTopics.forEach((newProposalTopic, i) => { + transactionReceipt.logs.forEach(log => { + if ( + log.topics[0] === + '0x75b4ff136cc5de5957574c797de3334eb1c141271922b825eb071e0487ba2c5c' + ) { + decodedProposer = web3.eth.abi.decodeParameters( + [ + { type: 'uint256', name: '_numOfChoices' }, + { type: 'address', name: '_proposer' }, + { type: 'bytes32', name: '_paramsHash' }, + ], + log.data + )._proposer; + } + if ( + !creationLogDecoded && + log.topics[0] === newProposalTopic[0] + ) { + creationLogDecoded = web3.eth.abi.decodeParameters( + schemeTypeData.creationLogEncoding[i], + log.data + ); + if ( + creationLogDecoded._descriptionHash.length > 0 && + creationLogDecoded._descriptionHash !== ZERO_HASH + ) { + schemeProposalInfo.descriptionHash = + ipfsHashToDescriptionHash( + creationLogDecoded._descriptionHash + ); + } + } + }); + }); + } catch (error) { + console.error( + 'Error in getting proposal data from creation event', + error + ); + } + } + + // Try to decode as much as we can from the creation event, decoding creation logs. + // Depending the type of the scheme we have to decode different data and parse it to decoded calls + if (schemeTypeData.type === 'SchemeRegistrar') { + schemeProposalInfo.to = [schemeTypeData.contractToCall]; + schemeProposalInfo.value = [0]; + + if (creationLogDecoded._parametersHash) { + schemeProposalInfo.callData = [ + web3.eth.abi.encodeFunctionCall( + { + name: 'registerScheme', + type: 'function', + inputs: [ + { type: 'address', name: '_scheme' }, + { type: 'bytes32', name: '_paramsHash' }, + { type: 'bytes4', name: '_permissions' }, + { type: 'address', name: '_avatar' }, + ], + }, + [ + creationLogDecoded['_scheme '], + creationLogDecoded._parametersHash, + creationLogDecoded._permissions, + avatarAddress, + ] + ), + ]; + } else { + schemeProposalInfo.callData = [ + web3.eth.abi.encodeFunctionCall( + { + name: 'unregisterScheme', + type: 'function', + inputs: [ + { type: 'address', name: '_scheme' }, + { type: 'address', name: '_avatar' }, + ], + }, + [creationLogDecoded['_scheme '], avatarAddress] + ), + ]; + } + } else if (schemeTypeData.type === 'ContributionReward') { + if (creationLogDecoded._reputationChange > 0) { + schemeProposalInfo.to.push(schemeTypeData.contractToCall); + schemeProposalInfo.value.push(0); + schemeProposalInfo.callData.push( + web3.eth.abi.encodeFunctionCall( + { + name: 'mintReputation', + type: 'function', + inputs: [ + { type: 'uint256', name: '_amount' }, + { type: 'address', name: '_to' }, + { type: 'address', name: '_avatar' }, + ], + }, + [ + creationLogDecoded._reputationChange, + creationLogDecoded._beneficiary, + avatarAddress, + ] + ) + ); + } else if (creationLogDecoded._reputationChange < 0) { + schemeProposalInfo.to.push(schemeTypeData.contractToCall); + schemeProposalInfo.value.push(0); + + // Remove the negative sign in the number + if (creationLogDecoded._reputationChange[0] === '-') + creationLogDecoded._reputationChange = + creationLogDecoded._reputationChange.substring(1); + + schemeProposalInfo.callData.push( + web3.eth.abi.encodeFunctionCall( + { + name: 'burnReputation', + type: 'function', + inputs: [ + { type: 'uint256', name: '_amount' }, + { type: 'address', name: '_from' }, + { type: 'address', name: '_avatar' }, + ], + }, + [ + creationLogDecoded._reputationChange, + creationLogDecoded._beneficiary, + avatarAddress, + ] + ) + ); + } + + if (creationLogDecoded._rewards[0] > 0) { + schemeProposalInfo.to.push(schemeTypeData.contractToCall); + schemeProposalInfo.value.push(0); + schemeProposalInfo.callData.push( + web3.eth.abi.encodeFunctionCall( + { + name: 'mintTokens', + type: 'function', + inputs: [ + { type: 'uint256', name: '_amount' }, + { type: 'address', name: '_beneficiary' }, + { type: 'address', name: '_avatar' }, + ], + }, + [ + creationLogDecoded._rewards[0], + creationLogDecoded._beneficiary, + avatarAddress, + ] + ) + ); + } + + if (creationLogDecoded._rewards[1] > 0) { + schemeProposalInfo.to.push(schemeTypeData.contractToCall); + schemeProposalInfo.value.push(0); + schemeProposalInfo.callData.push( + web3.eth.abi.encodeFunctionCall( + { + name: 'sendEther', + type: 'function', + inputs: [ + { type: 'uint256', name: '_amountInWei' }, + { type: 'address', name: '_to' }, + { type: 'address', name: '_avatar' }, + ], + }, + [ + creationLogDecoded._rewards[1], + creationLogDecoded._beneficiary, + avatarAddress, + ] + ) + ); + } + + if (creationLogDecoded._rewards[2] > 0) { + schemeProposalInfo.to.push(schemeTypeData.contractToCall); + schemeProposalInfo.value.push(0); + schemeProposalInfo.callData.push( + web3.eth.abi.encodeFunctionCall( + { + name: 'externalTokenTransfer', + type: 'function', + inputs: [ + { type: 'address', name: '_externalToken' }, + { type: 'address', name: '_to' }, + { type: 'uint256', name: '_value' }, + { type: 'address', name: '_avatar' }, + ], + }, + [ + creationLogDecoded._externalToken, + creationLogDecoded._beneficiary, + creationLogDecoded._rewards[2], + avatarAddress, + ] + ) + ); + } + } else if (schemeTypeData.type === 'GenericScheme') { + schemeProposalInfo.to = [networkWeb3Contracts.controller._address]; + schemeProposalInfo.value = [0]; + schemeProposalInfo.callData = [ + web3.eth.abi.encodeFunctionCall( + { + name: 'genericCall', + type: 'function', + inputs: [ + { type: 'address', name: '_contract' }, + { type: 'bytes', name: '_data' }, + { type: 'address', name: '_avatar' }, + { type: 'uint256', name: '_value' }, + ], + }, + [ + schemeTypeData.contractToCall, + creationLogDecoded._data, + avatarAddress, + creationLogDecoded._value, + ] + ), + ]; + } else if (schemeTypeData.type === 'GenericMulticall') { + for ( + let callIndex = 0; + callIndex < creationLogDecoded._contractsToCall.length; + callIndex++ + ) { + schemeProposalInfo.to.push(networkWeb3Contracts.controller._address); + schemeProposalInfo.value.push(0); + schemeProposalInfo.callData.push( + web3.eth.abi.encodeFunctionCall( + { + name: 'genericCall', + type: 'function', + inputs: [ + { type: 'address', name: '_contract' }, + { type: 'bytes', name: '_data' }, + { type: 'address', name: '_avatar' }, + { type: 'uint256', name: '_value' }, + ], + }, + [ + creationLogDecoded._contractsToCall[callIndex], + creationLogDecoded._callsData[callIndex], + avatarAddress, + creationLogDecoded._values[callIndex], + ] + ) + ); + } + } + + // Register the new voting parameters in the voting machine params if they dont exist in the cache + if ( + !networkCache.votingMachines[ + networkCache.schemes[schemeAddress].votingMachine + ].votingParameters[callsResponse.decodedReturnData[0].paramsHash] + ) { + const votingParameters = ( + await executeMulticall(networkWeb3Contracts.multicall, [ + [ + networkCache.schemes[schemeAddress].votingMachine, + 'parameters(bytes32)', + [callsResponse.decodedReturnData[0].paramsHash], + [ + 'uint256', + 'uint256', + 'uint256', + 'uint256', + 'uint256', + 'uint256', + 'uint256', + 'uint256', + 'uint256', + 'uint256', + 'uint256', + 'uint256', + 'address', + ], + ], + ]) + ).decodedReturnData[0]; + networkCache.votingMachines[ + networkCache.schemes[schemeAddress].votingMachine + ].votingParameters[callsResponse.decodedReturnData[0].paramsHash] = { + queuedVoteRequiredPercentage: votingParameters[0], + queuedVotePeriodLimit: votingParameters[1], + boostedVotePeriodLimit: votingParameters[2], + preBoostedVotePeriodLimit: votingParameters[3], + thresholdConst: votingParameters[4], + limitExponentValue: votingParameters[5], + quietEndingPeriod: votingParameters[6], + proposingRepReward: votingParameters[7], + votersReputationLossRatio: votingParameters[8], + minimumDaoBounty: votingParameters[9], + daoBountyConst: votingParameters[10], + activationTime: votingParameters[11], + }; + } + + networkCache.proposals[proposalId] = { + id: proposalId, + scheme: schemeAddress, + to: schemeProposalInfo.to, + title: schemeProposalInfo.title || '', + callData: schemeProposalInfo.callData, + values: schemeProposalInfo.value.map(value => bnum(value)), + stateInScheme: Number(schemeProposalInfo.state), + stateInVotingMachine: Number(callsResponse.decodedReturnData[0].state), + descriptionHash: schemeProposalInfo.descriptionHash, + creationEvent: { + event: creationEvent.event, + signature: creationEvent.signature, + address: creationEvent.address, + tx: creationEvent.tx, + blockNumber: creationEvent.blockNumber, + timestamp: creationEvent.timestamp, + transactionIndex: creationEvent.transactionIndex, + logIndex: creationEvent.logIndex, + }, + winningVote: callsResponse.decodedReturnData[0].winningVote, + proposer: decodedProposer + ? decodedProposer + : callsResponse.decodedReturnData[0].proposer, + currentBoostedVotePeriodLimit: + callsResponse.decodedReturnData[0].currentBoostedVotePeriodLimit, + paramsHash: callsResponse.decodedReturnData[0].paramsHash, + daoBountyRemain: bnum( + callsResponse.decodedReturnData[0].daoBountyRemain + ), + daoBounty: bnum(callsResponse.decodedReturnData[0].daoBounty), + confidenceThreshold: + callsResponse.decodedReturnData[0].confidenceThreshold, + secondsFromTimeOutTillExecuteBoosted: + callsResponse.decodedReturnData[0] + .secondsFromTimeOutTillExecuteBoosted, + submittedTime: bnum(proposalTimes[0]), + boostedPhaseTime: bnum(proposalTimes[1]), + preBoostedPhaseTime: bnum(proposalTimes[2]), + daoRedeemItsWinnings: + callsResponse.decodedReturnData[0].daoRedeemItsWinnings, + shouldBoost: callsResponse.decodedReturnData[5][0], + positiveVotes: bnum(callsResponse.decodedReturnData[1][0]), + negativeVotes: bnum(callsResponse.decodedReturnData[2][0]), + positiveStakes: bnum(callsResponse.decodedReturnData[3][2]), + negativeStakes: bnum(callsResponse.decodedReturnData[3][3]), + }; + + networkCache.schemes[schemeAddress].proposalIds.push(proposalId); + networkCache.schemes[schemeAddress].newProposalEvents.push({ + proposalId: proposalId, + event: creationEvent.event, + signature: creationEvent.signature, + address: creationEvent.address, + tx: creationEvent.tx, + blockNumber: creationEvent.blockNumber, + timestamp: creationEvent.timestamp, + transactionIndex: creationEvent.transactionIndex, + logIndex: creationEvent.logIndex, + }); + + if (schemeProposalInfo.descriptionHash.length > 1) { + networkCache.ipfsHashes.push({ + hash: descriptionHashToIPFSHash(schemeProposalInfo.descriptionHash), + type: 'proposal', + name: proposalId, + }); + } + + // Is the proposal is not new we only assign the values that are mutable + } else { + networkCache.proposals[proposalId].stateInScheme = Number( + schemeProposalInfo.state + ); + networkCache.proposals[proposalId].stateInVotingMachine = Number( + callsResponse.decodedReturnData[0].state + ); + networkCache.proposals[proposalId].winningVote = + callsResponse.decodedReturnData[0].winningVote; + networkCache.proposals[proposalId].currentBoostedVotePeriodLimit = + callsResponse.decodedReturnData[0].currentBoostedVotePeriodLimit; + networkCache.proposals[proposalId].daoBountyRemain = bnum( + callsResponse.decodedReturnData[0].daoBountyRemain + ); + networkCache.proposals[proposalId].daoBounty = bnum( + callsResponse.decodedReturnData[0].daoBounty + ); + networkCache.proposals[proposalId].confidenceThreshold = + callsResponse.decodedReturnData[0].confidenceThreshold; + networkCache.proposals[proposalId].secondsFromTimeOutTillExecuteBoosted = + callsResponse.decodedReturnData[0].secondsFromTimeOutTillExecuteBoosted; + networkCache.proposals[proposalId].boostedPhaseTime = bnum( + proposalTimes[1] + ); + networkCache.proposals[proposalId].preBoostedPhaseTime = bnum( + proposalTimes[2] + ); + networkCache.proposals[proposalId].daoRedeemItsWinnings = + callsResponse.decodedReturnData[0].daoRedeemItsWinnings; + networkCache.proposals[proposalId].shouldBoost = + callsResponse.decodedReturnData[5][0]; + networkCache.proposals[proposalId].positiveVotes = bnum( + callsResponse.decodedReturnData[1][0] + ); + networkCache.proposals[proposalId].negativeVotes = bnum( + callsResponse.decodedReturnData[2][0] + ); + networkCache.proposals[proposalId].positiveStakes = bnum( + callsResponse.decodedReturnData[3][2] + ); + networkCache.proposals[proposalId].negativeStakes = bnum( + callsResponse.decodedReturnData[3][3] + ); + } + + return networkCache; + } } diff --git a/src/services/IPFSService.ts b/src/services/IPFSService.ts index d5bcff1bd1..20f4d59409 100644 --- a/src/services/IPFSService.ts +++ b/src/services/IPFSService.ts @@ -51,28 +51,34 @@ export default class IPFSService { async getContentFromIPFS(hash: string) { const response = await Promise.any([ axios.request({ - url: 'https://ipfs.io/ipfs/' + hash, + url: 'https://dxgov.mypinata.cloud/ipfs/' + hash, method: 'GET', + timeout: 60000, }), axios.request({ - url: 'https://gateway.ipfs.io/ipfs/' + hash, + url: 'https://ipfs.io/ipfs/' + hash, method: 'GET', + timeout: 60000, }), axios.request({ - url: 'https://cloudflare-ipfs.com/ipfs/' + hash, + url: 'https://gateway.ipfs.io/ipfs/' + hash, method: 'GET', + timeout: 60000, }), axios.request({ - url: 'https://gateway.pinata.cloud/ipfs/' + hash, + url: 'https://cloudflare-ipfs.com/ipfs/' + hash, method: 'GET', + timeout: 60000, }), axios.request({ url: 'https://dweb.link/ipfs/' + hash, method: 'GET', + timeout: 60000, }), axios.request({ url: 'https://infura-ipfs.io/ipfs/' + hash, method: 'GET', + timeout: 60000, }), ]); return response.data; @@ -95,8 +101,10 @@ export default class IPFSService { localStorage.setItem('dxvote-newProposal-hash', hash); if (pinataService.auth) { - const pinataPin = await this.pin(hash); + const pinataPin = await pinataService.pin(hash); console.debug('[PINATA PIN]', pinataPin.toString()); + } else { + console.debug('[PINATA PIN] NOT AUTHENTICATED'); } const ipfsPin = await this.pin(hash); console.debug('[IPFS PIN]', ipfsPin); @@ -115,7 +123,7 @@ export default class IPFSService { const hash = await this.add(content); if (this.context.pinataService.auth) { - const pinataPin = await this.pin(hash); + const pinataPin = await this.context.pinataService.pin(hash); console.debug('[PINATA PIN]', pinataPin.toString()); } const ipfsPin = await this.pin(hash); diff --git a/src/services/MessageLoggerService.ts b/src/services/MessageLoggerService.ts deleted file mode 100644 index 5343151725..0000000000 --- a/src/services/MessageLoggerService.ts +++ /dev/null @@ -1,109 +0,0 @@ -import Common, { Chain, Hardfork } from '@ethereumjs/common'; -import { FeeMarketEIP1559Transaction } from '@ethereumjs/tx'; -import RootContext from '../contexts'; -import { arrayBufferHex } from 'utils'; -import { ethers, utils } from 'ethers'; -import { JsonRpcProvider } from '@ethersproject/providers'; - -export default class MessageLoggerService { - context: RootContext; - - messageLoggerAddress: string = '0xA490faF0DC4F26101a15bAc6ECad55b59db014a7'; - messageLoggerABI: ethers.utils.Interface = new ethers.utils.Interface([ - 'event Message(bytes32 indexed topic, string message, address sender)', - 'function broadcast(bytes32 topic, string message)', - ]); - fromBlock: number = 9904867; - - constructor(context: RootContext) { - this.context = context; - } - - async broadcast( - topic: string, - message: string, - rinkebyWeb3: JsonRpcProvider - ) { - const { account } = this.context.providerStore.getActiveWeb3React(); - const common = new Common({ - chain: Chain.Rinkeby, - hardfork: Hardfork.London, - }); - - // Step 1: Create the TX to send in rinkeby with the signature of the vote. - let txData = { - from: account, - data: this.messageLoggerABI.encodeFunctionData('broadcast', [ - topic, - message, - ]), - nonce: await rinkebyWeb3.getTransactionCount(account), - gasLimit: 50000, - maxPriorityFeePerGas: 1000000000, - maxFeePerGas: 1000000000, - to: this.messageLoggerAddress, - value: 0, - type: '0x02', - chainId: '0x04', - DEFAULT_CHAIN: 'rinkeby', - }; - - const tx = FeeMarketEIP1559Transaction.fromTxData(txData, { common }); - const unsignedTx = tx.getMessageToSign(false); - console.log('Unsigned Rinkeby tx:', tx); - - // Step 2: Sign the transaction with the vote signature to be shared in rinkeby executing a tx in rinkeby network - let signature = await this.context.providerStore.sign( - this.context.providerStore.getActiveWeb3React(), - utils.keccak256('0x' + arrayBufferHex(unsignedTx)) - ); - - // Step 3: Send the raw transaction signed to the rinkeby network - if (signature.result) { - signature = signature.result.substr(2); - const r = '0x' + signature.substr(0, 64); - const s = '0x' + signature.substr(64, 64); - const v = signature.substr(128) == '1c' ? '0x1' : '0x0'; - console.log('Rinkeby tx object:', txData); - console.log(`Rinkeby tx signature: ${v}${r}${s}`); - - const signedTx = FeeMarketEIP1559Transaction.fromTxData({ - ...txData, - v, - r, - s, - }); - const from = signedTx.getSenderAddress().toString(); - console.log( - `Signed Rinkeby tx hex: ${signedTx - .serialize() - .toString('hex')}\n Rinkeby tx Signer: ${from}` - ); - - rinkebyWeb3.send('eth_sendRawTransaction', [ - '0x' + signedTx.serialize().toString('hex'), - ]); - } - } - - async getMessages(topic: string, rinkebyWeb3: JsonRpcProvider) { - const messageLogger = new ethers.Contract( - this.messageLoggerAddress, - this.messageLoggerABI, - rinkebyWeb3 - ); - - const filter = messageLogger.filters.Message(topic); - try { - const events = await messageLogger.queryFilter( - filter, - this.fromBlock, - await rinkebyWeb3.getBlockNumber() - ); - return events; - } catch (error) { - console.error('Error fetching message logger events', error); - return []; - } - } -} diff --git a/src/services/PinataService.ts b/src/services/PinataService.ts index 3624d98321..9a09b2ca77 100644 --- a/src/services/PinataService.ts +++ b/src/services/PinataService.ts @@ -10,7 +10,7 @@ export default class PinataService { this.context = context; } defaultApiKey = - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySW5mb3JtYXRpb24iOnsiaWQiOiI4ZTNlZjUzNi0wZWQ5LTQ4YzAtOTFlYS1kNzUwYjk0Nzk4ZDMiLCJlbWFpbCI6Im1lQHJvc3NuZWlsc29uLmRldiIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJwaW5fcG9saWN5Ijp7InJlZ2lvbnMiOlt7ImlkIjoiRlJBMSIsImRlc2lyZWRSZXBsaWNhdGlvbkNvdW50IjoxfV0sInZlcnNpb24iOjF9LCJtZmFfZW5hYmxlZCI6ZmFsc2V9LCJhdXRoZW50aWNhdGlvblR5cGUiOiJzY29wZWRLZXkiLCJzY29wZWRLZXlLZXkiOiJlNmM5ZDA4ZGY3ODY0YWRhZWIyMyIsInNjb3BlZEtleVNlY3JldCI6ImRmYmY4ZmNiNGQ0YjQxNWViODgyZDM1YjgyMDFlMDVjNjk1MjBkZDllOTg2MzgxZTY3YTI1YTk2N2YyOWQxOGQiLCJpYXQiOjE2Mzk1OTg2MTl9.tkenai9BlBubfnPJmIXz9DkjJg12aCyk3BAtAc-TU1A'; + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySW5mb3JtYXRpb24iOnsiaWQiOiI4MTJmMzIwZC1iOTA1LTQwOTgtYmViZC1jMjMwNzhlNDNmM2MiLCJlbWFpbCI6ImZsdWlkZHJvcDU2NDgyM0BnbWFpbC5jb20iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwicGluX3BvbGljeSI6eyJyZWdpb25zIjpbeyJpZCI6Ik5ZQzEiLCJkZXNpcmVkUmVwbGljYXRpb25Db3VudCI6MX1dLCJ2ZXJzaW9uIjoxfSwibWZhX2VuYWJsZWQiOmZhbHNlfSwiYXV0aGVudGljYXRpb25UeXBlIjoic2NvcGVkS2V5Iiwic2NvcGVkS2V5S2V5IjoiYmFhMjllYjUxZWYzZWQyMDY4MWEiLCJzY29wZWRLZXlTZWNyZXQiOiI0OTIyYzQ2MThhYWZlNzZmNzhiNWQzNzU0NzY4MjBiNTk1MWM5MjdkZjFiNzY3ZGI3OWUzMGY5OTI3MDBmYTc5IiwiaWF0IjoxNjUyMTg4MjAyfQ.c8CpCVxvdknULzW6dJALyWgHD_DMq5167Nlb1KkXNRI'; async updatePinList() { // const pinList = await this.getPinList(); diff --git a/src/stores/BlockchainStore.ts b/src/stores/BlockchainStore.ts index 1b5436a905..74de7ae043 100644 --- a/src/stores/BlockchainStore.ts +++ b/src/stores/BlockchainStore.ts @@ -4,6 +4,7 @@ import { Web3ReactContextInterface } from '@web3-react/core/dist/types'; import { isChainIdSupported } from '../provider/connectors'; import { CacheLoadError } from '../utils/errors'; import { bnum } from 'utils'; +import { getProposalTitles } from '../utils/cache'; const targetCacheVersion = 1; @@ -37,7 +38,6 @@ export default class BlockchainStore { const { providerStore, configStore, - ipfsService, daoStore, notificationStore, cacheService, @@ -112,9 +112,10 @@ export default class BlockchainStore { true, 'Fetching cached data from IPFS' ); - networkCache = daoStore.parseCache( - await ipfsService.getContentFromIPFS(newestCacheIpfsHash) + const ipfsCache = await cacheService.getCacheFromIPFS( + newestCacheIpfsHash ); + networkCache = daoStore.parseCache(ipfsCache); networkCache.baseCacheIpfsHash = newestCacheIpfsHash; } @@ -127,15 +128,12 @@ export default class BlockchainStore { chainId ); - const fromBlock = lastCheckedBlockNumber + 1; const toBlock = blockNumber; const networkContracts = configStore.getNetworkContracts(); networkCache = await cacheService.getUpdatedCache( - this.context, networkCache, networkContracts, - fromBlock, toBlock, library ); @@ -144,9 +142,9 @@ export default class BlockchainStore { true, `Getting proposal titles form ipfs` ); - const proposalTitles = await cacheService.getProposalTitlesFromIPFS( + const proposalTitles = await cacheService.updateProposalTitles( networkCache, - configStore.getProposalTitlesInBuild() + getProposalTitles() ); Object.keys(networkCache.proposals).map(proposalId => { diff --git a/src/stores/ConfigStore.ts b/src/stores/ConfigStore.ts index 4e1fc74d53..d2ab5c4d2d 100644 --- a/src/stores/ConfigStore.ts +++ b/src/stores/ConfigStore.ts @@ -10,26 +10,7 @@ import { DEFAULT_CHAIN_ID, } from '../utils'; import { ZERO_ADDRESS, ANY_ADDRESS, ANY_FUNC_SIGNATURE } from '../utils'; - -const arbitrum = require('../configs/arbitrum/config.json'); -const arbitrumTestnet = require('../configs/arbitrumTestnet/config.json'); -const mainnet = require('../configs/mainnet/config.json'); -const xdai = require('../configs/xdai/config.json'); -const rinkeby = require('../configs/rinkeby/config.json'); -const localhost = require('../configs/localhost/config.json'); -const proposalTitles = require('../configs/proposalTitles.json'); - -const defaultAppConfigs = { - arbitrum, - arbitrumTestnet, - mainnet, - xdai, - rinkeby, - localhost, -}; - -// Use the same content outside src folder in defaultConfigHashes.json or override -const defaultCacheConfig = require('../configs/default.json'); +import { getDefaultConfigHashes, getAppConfig } from '../utils/cache'; export default class ConfigStore { darkMode: boolean; @@ -51,14 +32,14 @@ export default class ConfigStore { } reset() { - this.networkConfig = defaultAppConfigs[this.getActiveChainName()]; + this.networkConfig = getAppConfig()[this.getActiveChainName()]; this.networkConfigLoaded = false; } async loadNetworkConfig() { const { ensService, ipfsService } = this.context; - this.networkConfig = defaultAppConfigs[this.getActiveChainName()]; + this.networkConfig = getAppConfig()[this.getActiveChainName()]; const isTestingEnv = !window?.location?.href?.includes('dxvote.eth'); if (this.getActiveChainName() !== 'localhost' && !this.networkConfigLoaded) @@ -76,7 +57,7 @@ export default class ConfigStore { ); const configRefs = isTestingEnv - ? defaultCacheConfig + ? getDefaultConfigHashes() : await ipfsService.getContentFromIPFS(metadataHash); const configContentHash = configRefs[this.getActiveChainName()]; @@ -105,10 +86,6 @@ export default class ConfigStore { return this.networkConfig; } - getProposalTitlesInBuild() { - return proposalTitles; - } - getActiveChainName() { return NETWORK_NAMES[ this.context?.providerStore.getActiveWeb3React().chainId || @@ -124,6 +101,7 @@ export default class ConfigStore { } getLocalConfig() { + const defaultAppConfigs = getAppConfig(); const defaultConfig = { etherscan: '', pinata: '', @@ -153,6 +131,7 @@ export default class ConfigStore { } resetLocalConfig() { + const defaultAppConfigs = getAppConfig(); localStorage.setItem( 'dxvote-config', JSON.stringify({ diff --git a/src/stores/DaoStore.ts b/src/stores/DaoStore.ts index 4a953584d5..3c9b254e93 100644 --- a/src/stores/DaoStore.ts +++ b/src/stores/DaoStore.ts @@ -59,7 +59,7 @@ export default class DaoStore { ].value = bnum( unparsedCache.callPermissions[asset][from][to][ functionSignature - ].value + ].value || 0 ); } ); diff --git a/src/utils/cache.ts b/src/utils/cache.ts index 162f2501da..2785a2500c 100644 --- a/src/utils/cache.ts +++ b/src/utils/cache.ts @@ -1,8 +1,62 @@ +import { Contract } from 'ethers'; import _ from 'lodash'; import { MAX_BLOCKS_PER_EVENTS_FETCH } from './constants'; import { sleep } from './helpers'; const Web3 = require('web3'); +const web3 = new Web3(); + +const arbitrum = require('../configs/arbitrum/config.json'); +const arbitrumTestnet = require('../configs/arbitrumTestnet/config.json'); +const mainnet = require('../configs/mainnet/config.json'); +const xdai = require('../configs/xdai/config.json'); +const rinkeby = require('../configs/rinkeby/config.json'); +const localhost = require('../configs/localhost/config.json'); + +const proposalTitles = require('../configs/proposalTitles.json'); + +const defaultConfigHashes = require('../configs/default.json'); + +export const getDefaultConfigHashes = (): Record => { + return defaultConfigHashes; +}; + +export const getProposalTitles = (): Record => { + return proposalTitles; +}; + +export const getAppConfig = (): AppConfig => { + return { + arbitrum, + arbitrumTestnet, + mainnet, + xdai, + rinkeby, + localhost, + }; +}; + +// Sort cache data, so the IPFS hash is consistent +export const sortNetworkCache = ( + networkCache: DaoNetworkCache +): DaoNetworkCache => { + Object.keys(networkCache.schemes).forEach(schemeId => { + networkCache.schemes[schemeId].proposalIds.sort(); + networkCache.schemes[schemeId].newProposalEvents.sort((a, b) => + a.proposalId.localeCompare(b.proposalId) + ); + }); + networkCache.proposals = Object.keys(networkCache.proposals) + .sort() + .reduce((obj, key) => { + obj[key] = networkCache.proposals[key]; + return obj; + }, {}); + networkCache.ipfsHashes = _.uniqBy(networkCache.ipfsHashes, 'name'); + networkCache.ipfsHashes.sort((a, b) => a.name.localeCompare(b.name)); + + return networkCache; +}; export const getEvents = async function ( web3, @@ -33,6 +87,7 @@ export const getEvents = async function ( } catch (error) { if ((error as Error).message.indexOf('Relay attempts exhausted') > -1) { const blocksToLower = Math.max(Math.trunc((to - from) / 2), 10000); + console.error('Relay attempts exhausted'); console.debug('Lowering toBlock', blocksToLower, 'blocks'); to = to - blocksToLower; await sleep(5000); @@ -41,8 +96,11 @@ export const getEvents = async function ( 'You cannot query logs for more than 100000 blocks at once.' ) > -1 ) { - maxBlocksPerFetch = 100000; + maxBlocksPerFetch = Number((maxBlocksPerFetch / 4).toFixed(0)); to = from + 100000; + console.error( + 'You cannot query logs for more than 100000 blocks at once.' + ); console.debug('Lowering toBlock to', to); } else if ( (error as Error).message.indexOf('Relay attempts exhausted') === -1 && @@ -55,7 +113,7 @@ export const getEvents = async function ( } } } - return events; + return sortEvents(events); }; export const getRawEvents = async function ( @@ -73,7 +131,13 @@ export const getRawEvents = async function ( from = fromBlock; while (from < to) { console.debug( - `Fetching logs of ${contractAddress} from blocks ${from} -> ${to}` + `Fetching logs of ${contractAddress} from blocks ${from} -> ${to}`, + { + address: contractAddress, + fromBlock: from, + toBlock: to, + topics: topicsToGet, + } ); try { let eventsFetched = await web3.eth.getPastLogs({ @@ -87,74 +151,53 @@ export const getRawEvents = async function ( from = to; to = Math.min(from + maxBlocksPerFetch, toBlock); } catch (error) { - if ( - (error as Error).message.indexOf( - 'You cannot query logs for more than 100000 blocks at once.' - ) > -1 - ) { + if ((error as Error).message.indexOf('Relay attempts exhausted') > -1) { const blocksToLower = Math.max(Math.trunc((to - from) / 2), 10000); + console.error('Relay attempts exhausted'); console.debug('Lowering toBlock', blocksToLower, 'blocks'); to = to - blocksToLower; await sleep(5000); + } else if ( + (error as Error).message.indexOf( + 'You cannot query logs for more than 100000 blocks at once.' + ) > -1 + ) { + maxBlocksPerFetch = Number((maxBlocksPerFetch / 4).toFixed(0)); + to = from + 100000; + console.error( + 'You cannot query logs for more than 100000 blocks at once.' + ); + console.debug('Lowering toBlock to', to); } else if ( (error as Error).message.indexOf('Relay attempts exhausted') === -1 && Math.trunc((to - from) / 2) > 10000 ) { - console.error('Error fetching blocks:', (error as Error).message); + console.error('Error fetching blocks:', error); const blocksToLower = Math.max(Math.trunc((to - from) / 2), 10000); console.debug('Lowering toBlock', blocksToLower, 'blocks'); to = to - blocksToLower; } } } - return events; + return sortEvents(events); }; export const getTimestampOfEvents = async function (web3, events) { - //// TODO: See how can we batch requests can be implemented - // async function batchRequest(blocks) { - // const batch = new web3.BatchRequest(); - // let requests = []; - // for (let i = 0; i < blocks.length; i++) { - // const request = new Promise((resolve, reject) => { - // batch.add(web3.eth.getBlock.request(blocks[i], (err, data) => { - // console.log(1) - // if (err) return reject(err); - // resolve(data); - // })); - // }); - // requests.push(request); - // } - // batch.execute(); - // console.log(batch) - // await Promise.all(requests); - // return batch; - // }; - - let blocksToFetch = []; - let timestamps = []; - events.map(event => { - if (blocksToFetch.indexOf(event.blockNumber) < 0) - blocksToFetch.push(event.blockNumber); - }); - const totalLength = blocksToFetch.length; - while (blocksToFetch.length > 0 && totalLength > timestamps.length) { - // timestamps = (await batchRequest(blocksToFetch)).map((blockResult) => { - // return blockResult.timestamp; - // }); - const blocksToFetchBatch = blocksToFetch.splice(0, 500); - await Promise.all( - blocksToFetchBatch.map(async block => { - const blockInfo = await web3.eth.getBlock(block); - for (let i = 0; i < events.length; i++) { - if (events[i].blockNumber === blockInfo.number) - events[i].timestamp = blockInfo.timestamp; - if (blockInfo.l2BlockNumber) - events[i].blockNumber = Number(blockInfo.l2BlockNumber); - } - }) - ); - } + await batchPromises( + events.map(async (event, i) => { + const blockInfo = await web3.eth.getBlock(event.blockNumber); + if (!blockInfo) console.log(event.blockNumber); + for (let i = 0; i < events.length; i++) { + if (events[i].blockNumber === blockInfo.number) + events[i].timestamp = blockInfo.timestamp; + if (blockInfo.l2BlockNumber) + events[i].blockNumber = Number(blockInfo.l2BlockNumber); + } + }), + 100, + 1000, + 500 + ); return events; }; @@ -168,27 +211,30 @@ export const sortEvents = function (events) { ); }; -export const executeMulticall = async function (web3, multicall, calls) { +export const executeMulticall = async function ( + multicall: Contract, + calls: any[] +) { const rawCalls = calls.map(call => { + const functionParams = [...call[1].matchAll(/\(([^)]+)\)/g)] + .flat()[1] + ?.split(','); return [ - call[0]._address, - web3.eth.abi.encodeFunctionCall( - call[0]._jsonInterface.find(method => method.name === call[1]), - call[2] - ), + call[0], + web3.eth.abi.encodeFunctionSignature(call[1]) + + (functionParams + ? web3.eth.abi.encodeParameters(functionParams, call[2]).substring(2) + : ''), ]; }); - const { returnData } = await multicall.methods.aggregate(rawCalls).call(); return { returnData, decodedReturnData: returnData.map((callResult, i) => { - return web3.eth.abi.decodeParameters( - calls[i][0]._jsonInterface.find(method => method.name === calls[i][1]) - .outputs, - callResult - )['0']; + return calls[i][3].length > 0 + ? web3.eth.abi.decodeParameters(calls[i][3], callResult) + : ''; }), }; }; @@ -230,7 +276,7 @@ export const getSchemeConfig = function (networkContracts, schemeAddress) { newProposalTopics: [ [ Web3.utils.soliditySha3('ProposalStateChange(bytes32,uint256)'), - null, + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000001', ], ], @@ -254,3 +300,64 @@ export async function tryCacheUpdates(promises, networkCache) { } return networkCache; } + +export async function batchPromisesOntarget( + promises, + targetObject, + maxPromisesPerTry = 0, + maxTries = 10, + sleepTimeBetweenRetries = 100 +) { + let promisesBatch = maxPromisesPerTry === 0 ? [promises] : []; + let promisesBatchIndex = 0; + + if (maxPromisesPerTry > 0) + for (var i = 0; i < promises.length; i += maxPromisesPerTry) + promisesBatch.push(promises.slice(i, i + maxPromisesPerTry)); + + while (promisesBatchIndex < promisesBatch.length && maxTries > 0) { + try { + (await Promise.all(promisesBatch[promisesBatchIndex])).map( + targetObjectUpdated => { + targetObject = Object.assign(targetObject, targetObjectUpdated); + } + ); + promisesBatchIndex++; + } catch (e) { + console.error(e); + maxTries--; + await sleep(sleepTimeBetweenRetries); + if (maxTries === 0) + console.error('[BATCH PROMISES] (max errors reached)'); + } + } + return targetObject; +} + +export async function batchPromises( + promises, + maxPromisesPerTry = 0, + maxTries = 10, + sleepTimeBetweenRetries = 100 +) { + let promisesBatch = maxPromisesPerTry === 0 ? [promises] : []; + let promisesBatchIndex = 0; + + if (maxPromisesPerTry > 0) + for (var i = 0; i < promises.length; i += maxPromisesPerTry) + promisesBatch.push(promises.slice(i, i + maxPromisesPerTry)); + + while (promisesBatchIndex < promisesBatch.length && maxTries > 0) { + try { + await Promise.all(promisesBatch[promisesBatchIndex]); + promisesBatchIndex++; + } catch (e) { + console.error(e); + maxTries--; + await sleep(sleepTimeBetweenRetries); + if (maxTries === 0) + console.error('[BATCH PROMISES] (max errors reached)'); + } + } + return; +} diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 9bfa25902f..ca1f59fcc7 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -39,7 +39,7 @@ export const NETWORKS: ChainConfig[] = id: 1, name: 'mainnet', displayName: 'Ethereum Mainnet', - defaultRpc: POKT_NETWORK_URLS['1'], + defaultRpc: `https://eth-mainnet.alchemyapi.io/v2/${defaultAlchemyKey}`, nativeAsset: { name: 'Ethereum', symbol: 'ETH', @@ -116,7 +116,7 @@ export const NETWORKS: ChainConfig[] = id: 1, name: 'mainnet', displayName: 'Ethereum Mainnet', - defaultRpc: POKT_NETWORK_URLS['1'], + defaultRpc: `https://eth-mainnet.alchemyapi.io/v2/${defaultAlchemyKey}`, nativeAsset: { name: 'Ethereum', symbol: 'ETH', diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts index 1c857b62b3..acb9daf43b 100644 --- a/src/utils/helpers.ts +++ b/src/utils/helpers.ts @@ -87,3 +87,28 @@ export const appendEthAPIKey = ( export function escapeRegExp(string: string): string { return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string } + +export async function retryPromise( + promise: Promise, + timeToWait: number = 0, + maxTries: number = 10 +): Promise { + let toReturn; + while (!toReturn && maxTries > 0) { + try { + toReturn = await promise; + if (!toReturn) { + await sleep(timeToWait); + maxTries--; + } + } catch (error) { + console.warn(error); + await sleep(timeToWait); + maxTries--; + } finally { + if (maxTries === 0) console.error('[RETRY PROMISE] (max errors reached)'); + else maxTries = 0; + } + } + return toReturn; +} diff --git a/src/utils/proposals.ts b/src/utils/proposals.ts index 0d2f65668e..57988b3ff3 100644 --- a/src/utils/proposals.ts +++ b/src/utils/proposals.ts @@ -200,29 +200,7 @@ export const decodeProposalStatus = function ( pendingAction: PendingAction.None, }; case VotingMachineProposalState.Executed: - if ( - proposal.stateInScheme === WalletSchemeProposalState.Rejected && - schemeType === 'ContributionReward' - ) - return { - status: 'Passed', - boostTime: boostedPhaseTime, - finishTime: proposalStateChangeEvents.find( - event => Number(event.state) === VotingMachineProposalState.Executed - ) - ? bnum( - proposalStateChangeEvents.find( - event => - Number(event.state) === VotingMachineProposalState.Executed - ).timestamp - ) - : bnum(0), - pendingAction: PendingAction.Redeem, - }; - else if ( - proposal.winningVote == '2' || - proposal.stateInScheme === WalletSchemeProposalState.Rejected - ) + if (proposal.stateInScheme === WalletSchemeProposalState.Rejected) return { status: 'Proposal Rejected', boostTime: boostedPhaseTime, @@ -236,11 +214,7 @@ export const decodeProposalStatus = function ( ).timestamp ) : bnum(0), - pendingAction: - schemeType === 'ContributionReward' && - proposal.stateInVotingMachine < 3 - ? PendingAction.Redeem - : PendingAction.None, + pendingAction: PendingAction.None, }; else if ( proposal.stateInScheme === WalletSchemeProposalState.ExecutionSucceded diff --git a/yarn.lock b/yarn.lock index 801e022b56..5a7f7c2e91 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1489,6 +1489,45 @@ debug "^3.1.0" lodash.once "^4.1.1" +"@dnd-kit/accessibility@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@dnd-kit/accessibility/-/accessibility-3.0.0.tgz#b56e3750414fd907b7d6972b3116aa8f96d07fde" + integrity sha512-QwaQ1IJHQHMMuAGOOYHQSx7h7vMZPfO97aDts8t5N/MY7n2QTDSnW+kF7uRQ1tVBkr6vJ+BqHWG5dlgGvwVjow== + dependencies: + tslib "^2.0.0" + +"@dnd-kit/core@^5.0.3": + version "5.0.3" + resolved "https://registry.yarnpkg.com/@dnd-kit/core/-/core-5.0.3.tgz#028d7e543e9fd3756f9cb857b34f818d73020810" + integrity sha512-IribcBLsaPqHdYLpc5xG0TqwYIaD+65offdEYxoKh38WDjzRxUjDmrt7hj9oa/ooUKL0epux20u+mBTd92i/zw== + dependencies: + "@dnd-kit/accessibility" "^3.0.0" + "@dnd-kit/utilities" "^3.1.0" + tslib "^2.0.0" + +"@dnd-kit/modifiers@^5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@dnd-kit/modifiers/-/modifiers-5.0.0.tgz#c2ba0bfdb6bed994affcfc4c86834795b7a7f029" + integrity sha512-ZVOfa5dKmvAPBxjjnI4QTm8fHV4/gMdS6k0zY5Z8p/p9HkL/3QEHn2fYgqM2zVDpxf5maqrZ4MvqXILSPNQPMQ== + dependencies: + "@dnd-kit/utilities" "^3.1.0" + tslib "^2.0.0" + +"@dnd-kit/sortable@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@dnd-kit/sortable/-/sortable-6.0.1.tgz#08d046efa85c546fd21979836b007d6fea1001f9" + integrity sha512-FGpRHBcflpwWpSP8bMJ4zNcDywDXssZDJBwGYuHd1RqUU/+JX43pEU0u0OHw06LabEZMOLBbyWoIaZJ0abCOEA== + dependencies: + "@dnd-kit/utilities" "^3.1.0" + tslib "^2.0.0" + +"@dnd-kit/utilities@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@dnd-kit/utilities/-/utilities-3.1.0.tgz#330ef24303da05f85ff20bb8f74b385c96caee58" + integrity sha512-2JBdIgjJ7xjMRpKagdMuYKWJdDoVVw9Wc6lfMrLjrq8t4QlMEjtsab5JVUlzWvHgANn7w3Y3VhalFfbp4dQrKg== + dependencies: + tslib "^2.0.0" + "@electron/get@^1.13.0": version "1.14.1" resolved "https://registry.yarnpkg.com/@electron/get/-/get-1.14.1.tgz#16ba75f02dffb74c23965e72d617adc721d27f40" @@ -3142,15 +3181,15 @@ find-up "^4.1.0" fs-extra "^8.1.0" -"@openzeppelin/contracts-upgradeable@4.3.2": - version "4.3.2" - resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.3.2.tgz#92df481362e366c388fc02133cf793029c744cea" - integrity sha512-i/pOaOtcqDk4UqsrOv735uYyTbn6dvfiuVu5hstsgV6c4ZKUtu88/31zT2BzkCg+3JfcwOfgg2TtRKVKKZIGkQ== +"@openzeppelin/contracts-upgradeable@4.4.0": + version "4.4.0" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.4.0.tgz#85161d87c840c5bce2b6ed0c727b407e774852ae" + integrity sha512-hIEyWJHu7bDTv6ckxOaV+K3+7mVzhjtyvp3QSaz56Rk5PscXtPAbkiNTb3yz6UJCWHPWpxVyULVgZ6RubuFEZg== -"@openzeppelin/contracts@4.3.2": - version "4.3.2" - resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.3.2.tgz#ff80affd6d352dbe1bbc5b4e1833c41afd6283b6" - integrity sha512-AybF1cesONZStg5kWf6ao9OlqTZuPqddvprc0ky7lrUVOjXeKpmQ2Y9FK+6ygxasb+4aic4O5pneFBfwVsRRRg== +"@openzeppelin/contracts@4.4.0": + version "4.4.0" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.4.0.tgz#4a1df71f736c31230bbbd634dfb006a756b51e6b" + integrity sha512-dlKiZmDvJnGRLHojrDoFZJmsQVeltVeoiRN7RK+cf2FmkhASDEblE0RiaYdxPNsUZa6mRG8393b9bfyp+V5IAw== "@openzeppelin/test-helpers@^0.5.15": version "0.5.15" @@ -3238,11 +3277,6 @@ resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA= -"@realitio/realitio-contracts@>=0.0.0": - version "2.2.9" - resolved "https://registry.yarnpkg.com/@realitio/realitio-contracts/-/realitio-contracts-2.2.9.tgz#4274dd0ceca6d1b64a13553fe458312c920680d7" - integrity sha512-Ka4oMm6a7+W4kVcDjPa4Ov0pstd4nGk+2/rt8ShL6COwLFIgeybJmoZYBSCqLDrqfYk9N1glCjpBbU0EojTRTg== - "@resolver-engine/core@^0.2.1": version "0.2.1" resolved "https://registry.yarnpkg.com/@resolver-engine/core/-/core-0.2.1.tgz#0d71803f6d3b8cb2e9ed481a1bf0ca5f5256d0c0" @@ -9054,15 +9088,14 @@ duplexify@^3.4.2, duplexify@^3.6.0: readable-stream "^2.0.0" stream-shift "^1.0.0" -"dxdao-contracts@https://github.com/DXGovernance/dxdao-contracts.git#develop": - version "0.0.1" - resolved "https://github.com/DXGovernance/dxdao-contracts.git#4361fd7270d275f8d046471b4bbfa3b1062d15ab" +"dxdao-contracts@https://github.com/DXGovernance/dxdao-contracts.git#12ea7f4e1ea070699d148d8c4ced4aaa88f08627": + version "1.0.0" + resolved "https://github.com/DXGovernance/dxdao-contracts.git#12ea7f4e1ea070699d148d8c4ced4aaa88f08627" dependencies: "@maticnetwork/eth-decoder" "^0.0.4" - "@openzeppelin/contracts" "4.3.2" - "@openzeppelin/contracts-upgradeable" "4.3.2" + "@openzeppelin/contracts" "4.4.0" + "@openzeppelin/contracts-upgradeable" "4.4.0" "@openzeppelin/test-helpers" "^0.5.15" - "@realitio/realitio-contracts" ">=0.0.0" "@truffle/hdwallet-provider" "^1.4.0" arb-ethers-web3-bridge "^0.7.3" chai "^4.2.0"