const { expect } = require('chai') const { ethers } = require('hardhat') const { BigNumber } = require('@ethersproject/bignumber') const { propose } = require('../../scripts/helper/propose_proposal.js') const testcases = require('@ethersproject/testcases') const seedbase = require('../../resources/hdnode.json') const accountList = require('../../resources/accounts.json') describe('V2 governance tests', () => { ///// ON-CHAIN CONSTANTS let proxy_address = '0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce' let quorumVotes ///////////////////////////// CONTRACTS let GovernanceContract let TornToken //////////////////// IMPERSONATED let tornadoMultisig //////////////////////////////// MOCK let MockProposalFactory /////// GOV PARAMS const ProposalState = { Pending: 0, Active: 1, Defeated: 2, Timelocked: 3, AwaitingExecution: 4, Executed: 5, Expired: 6, } ///// ACCOUNTS let dore let whale let signerArray = [] let whales = [] //////////////////////////////////// TESTING & UTILITY let randN = Math.floor(Math.random() * 1023) let testseed = seedbase[randN].seed let minewait = async (time) => { await ethers.provider.send('evm_increaseTime', [time]) await ethers.provider.send('evm_mine', []) } let sendr = async (method, params) => { return await ethers.provider.send(method, params) } let clog = (...x) => { console.log(x) } let pE = (x) => { return ethers.utils.parseEther(`${x}`) } let rand = (l, u) => { return testcases.randomNumber(testseed, l, u) } let snapshotIdArray = [] ///////////////////////////////////////////////////////////////////////////7 before(async function () { signerArray = await ethers.getSigners() dore = signerArray[0] MockProposalFactory = await ethers.getContractFactory('MockProposal') GovernanceContract = await ethers.getContractAt('GovernanceGasUpgrade', proxy_address) TornToken = await ethers.getContractAt( '@openzeppelin/contracts/token/ERC20/IERC20.sol:IERC20', '0x77777FeDdddFfC19Ff86DB637967013e6C6A116C', ) quorumVotes = await GovernanceContract.QUORUM_VOTES() }) describe('#imitation block', () => { it('Should successfully imitate tornado multisig', async function () { await sendr('hardhat_impersonateAccount', ['0xb04E030140b30C27bcdfaafFFA98C57d80eDa7B4']) tornadoMultisig = await ethers.getSigner('0xb04E030140b30C27bcdfaafFFA98C57d80eDa7B4') }) it('Should successfully imitate whale', async function () { await sendr('hardhat_impersonateAccount', ['0xA2b2fBCaC668d86265C45f62dA80aAf3Fd1dEde3']) whale = await ethers.getSigner('0xA2b2fBCaC668d86265C45f62dA80aAf3Fd1dEde3') GovernanceContract = await GovernanceContract.connect(whale) let balance = await TornToken.balanceOf(whale.address) TornToken = await TornToken.connect(whale) await TornToken.approve(GovernanceContract.address, ethers.utils.parseEther('8000000000')) await expect(GovernanceContract.lockWithApproval(balance)).to.not.be.reverted expect((await GovernanceContract.lockedBalance(whale.address)).toString()).to.equal(balance.toString()) snapshotIdArray[0] = await sendr('evm_snapshot', []) }) }) describe('#mock rewards + proposal distribution with multiple accounts', () => { let addrArray = [] let signerArmy = [] let delegatedSignerArmy = [] let votingAddressArray = [] const numberOfVoters = 80 const numberOfDelegators = 30 it('Should create empty address array', () => { for (let i = 0; i < 10; i++) { votingAddressArray[i] = new Array(numberOfDelegators / 10 + 1) } }) it('Should impersonate and fund 80 accounts', async function () { ////////// WRITE WHALE ADDRESSES AND PREPARE FOR TRANSFERS addrArray = [ '0x6cC5F688a315f3dC28A7781717a9A798a59fDA7b', '0xF977814e90dA44bFA03b6295A0616a897441aceC', '0xA2b2fBCaC668d86265C45f62dA80aAf3Fd1dEde3', '0x055AD5E56c11c0eF55818155c69ed9BA2f4b3e90', ] for (let i = 0; i < 4; i++) { await sendr('hardhat_impersonateAccount', [addrArray[i]]) whales[i] = await ethers.getSigner(addrArray[i]) } for (let i = 1; i < 4; i++) { //last test really unnecessary const torn = await TornToken.connect(whales[i]) const whaleBalance = await torn.balanceOf(whales[i].address) await torn.approve(addrArray[0], whaleBalance) await expect(() => torn.transfer(addrArray[0], whaleBalance)).to.changeTokenBalance( torn, whales[0], whaleBalance, ) } const whale0Balance = await TornToken.balanceOf(whales[0].address) const toTransfer = whale0Balance.sub(pE(10000)).div(numberOfVoters * 3) let torn0 = await TornToken.connect(whales[0]) const oldBalance = await TornToken.balanceOf(await GovernanceContract.userVault()) let lockedSum = BigNumber.from(0) ////////// TRANSFER TO 50 ACCOUNTS + DELEGATION TO 10 for (let i = 0; i < numberOfVoters; i++) { /// PREPARE ACCOUNTS const accAddress = accountList[i + 7].checksumAddress await sendr('hardhat_impersonateAccount', [accAddress]) signerArmy[i] = await ethers.getSigner(accAddress) const tx = { to: signerArmy[i].address, value: pE(1) } await signerArray[0].sendTransaction(tx) /// FILL WITH GAS FOR LATER await expect(() => torn0.transfer(signerArmy[i].address, toTransfer)).to.changeTokenBalance( torn0, signerArmy[i], toTransfer, ) let torn = await torn0.connect(signerArmy[i]) /// APPROVE TO GOVERNANCE FOR LOCK await expect(torn.approve(GovernanceContract.address, toTransfer)).to.not.be.reverted const gov = await GovernanceContract.connect(signerArmy[i]) ///// LOCK if (i > numberOfVoters / 2) { await expect(() => gov.lockWithApproval(toTransfer.div(i))).to.changeTokenBalance( torn, signerArmy[i], BigNumber.from(0).sub(toTransfer.div(i)), ) lockedSum = lockedSum.add(toTransfer.div(i)) } else { await expect(() => gov.lockWithApproval(toTransfer)).to.changeTokenBalance( torn, signerArmy[i], BigNumber.from(0).sub(toTransfer), ) lockedSum = lockedSum.add(toTransfer) } if (i > numberOfVoters - numberOfDelegators - 1) { delegatedSignerArmy[i - (numberOfVoters - numberOfDelegators)] = signerArmy[i] } if (i < 10) { votingAddressArray[i][0] = signerArmy[i].address } const restBalance = await torn.balanceOf(signerArmy[i].address) await torn.transfer(whale.address, restBalance) } for (let i = 0; i < numberOfDelegators; i++) { const gov = await GovernanceContract.connect(delegatedSignerArmy[i]) /// DELEGATE TO 10 FIRST SIGNERS await expect(gov.delegate(signerArmy[i % 10].address)).to.emit(gov, 'Delegated') votingAddressArray[i % 10][Math.floor(i / 10) + 1] = delegatedSignerArmy[i].address } const TornVault = await GovernanceContract.userVault() expect(await TornToken.balanceOf(TornVault)).to.equal(lockedSum.add(oldBalance)) const gov = await GovernanceContract.connect(whales[0]) await expect(torn0.approve(GovernanceContract.address, pE(10000))).to.not.be.reverted await expect(() => gov.lockWithApproval(toTransfer)).to.changeTokenBalance( torn0, whales[0], BigNumber.from(0).sub(toTransfer), ) snapshotIdArray[1] = await sendr('evm_snapshot', []) }) it('Test multiple accounts proposal', async function () { let checkIfQuorumFulfilled = async function (proposalId) { const proposalData = await GovernanceContract.proposals(proposalId) const allVotes = proposalData[4].add(proposalData[5]) return allVotes.gte(quorumVotes) } const ProposalContract = await MockProposalFactory.deploy() clog( 'Torn balance of governance contract: ', (await TornToken.balanceOf(GovernanceContract.address)).toString(), ) ////////////// STANDARD PROPOSAL ARGS TEST ////////////////////// let response, id, state ;[response, id, state] = await propose([whales[0], ProposalContract, 'LotteryUpgrade']) const { events } = await response.wait() const args = events.find(({ event }) => event == 'ProposalCreated').args expect(args.id).to.be.equal(id) expect(args.target).to.be.equal(ProposalContract.address) expect(args.description).to.be.equal('LotteryUpgrade') expect(state).to.be.equal(ProposalState.Pending) ////////////////////////INCREMENT TO VOTING TIME//////////////////////// await minewait((await GovernanceContract.VOTING_DELAY()).add(1).toNumber()) /////////////////// PREPARE MULTISIG AND COMPENSATIONS let multiGov = await GovernanceContract.connect(tornadoMultisig) await dore.sendTransaction({ to: tornadoMultisig.address, value: pE(1) }) await expect(multiGov.setGasCompensations(pE(500))).to.not.be.reverted ///////////////////////////// VOTE //////////////////////////// const overrides = { gasPrice: BigNumber.from(5), } let signerArmyBalanceInitial = [] let signerArmyBalanceDiff = [] let gasUsedArray = [] snapshotIdArray[2] = await sendr('evm_snapshot', []) for (let i = 0; i < 10; i++) { let gov = await GovernanceContract.connect(signerArmy[i]) let randN = rand(i * 5, i * 6) randN = randN % 2 let response signerArmyBalanceInitial[i] = await signerArmy[i].getBalance() if (randN > 0) { response = await gov.castDelegatedVote(votingAddressArray[i], id, true, overrides) } else { response = await gov.castDelegatedVote(votingAddressArray[i], id, false, overrides) } signerArmyBalanceDiff[i] = !(await checkIfQuorumFulfilled(id)) ? signerArmyBalanceInitial[i].sub(await signerArmy[i].getBalance()) : signerArmyBalanceDiff[i - 1] const receipt = await response.wait() gasUsedArray[i] = receipt.cumulativeGasUsed } for (let i = 10; i < numberOfVoters - numberOfDelegators; i++) { let gov = await GovernanceContract.connect(signerArmy[i]) let randN = rand(i * 5, i * 6) randN = randN % 2 let response signerArmyBalanceInitial[i] = await signerArmy[i].getBalance() if (randN > 0) { response = await gov.castVote(id, true, overrides) } else { response = await gov.castVote(id, false, overrides) } signerArmyBalanceDiff[i] = !(await checkIfQuorumFulfilled(id)) ? signerArmyBalanceInitial[i].sub(await signerArmy[i].getBalance()) : signerArmyBalanceDiff[i - 1] const receipt = await response.wait() gasUsedArray[i] = receipt.cumulativeGasUsed } //////////////////////////////// GET STATE /////////////////////////////// state = await GovernanceContract.state(id) expect(state).to.be.equal(ProposalState.Active) ///////////////////////////// VOTER INFO /////////////////////////////////// // (uncomment for more data) /* for (i = 0; i < numberOfVoters; i+=5) { const j = BigNumber.from(i); console.log( `Voter ${i} sqrt: `, ((await GovernanceLottery.lotteryUserData(id,j))[0]).toString(), `Voter ${i+1} sqrt: `, ((await GovernanceLottery.lotteryUserData(id,j.add(1)))[0]).toString(), `Voter ${i+2} sqrt: `, ((await GovernanceLottery.lotteryUserData(id,j.add(2)))[0]).toString(), `Voter ${i+3} sqrt: `, ((await GovernanceLottery.lotteryUserData(id,j.add(3)))[0]).toString(), `Voter ${i+4} sqrt: `, ((await GovernanceLottery.lotteryUserData(id,j.add(4)))[0]).toString(), "\n", ) } for (i = 0; i < numberOfVoters; i+=5) { console.log( `Voter ${i} ether used: `, gasUsedArray[i], `Voter ${i+1} ether used: `, gasUsedArray[i+1], `Voter ${i+2} ether used: `, gasUsedArray[i+2], `Voter ${i+3} ether used: `, gasUsedArray[i+3], `Voter ${i+4} ether used: `, gasUsedArray[i+4], "\n", ) } */ await sendr('evm_revert', [snapshotIdArray[2]]) ///////////////////////////////// VOTE WITHOUT COMPENSATION ////////////////////////////////////// let gasUsedWithoutCompensation = [] await multiGov.setGasCompensations(pE(100000)) for (let i = 0; i < 10; i++) { let gov = await GovernanceContract.connect(signerArmy[i]) let randN = rand(i * 5, i * 6) randN = randN % 2 let response if (randN > 0) { response = await gov.castDelegatedVote(votingAddressArray[i], id, true, overrides) } else { response = await gov.castDelegatedVote(votingAddressArray[i], id, false, overrides) } const receipt = await response.wait() gasUsedWithoutCompensation[i] = receipt.cumulativeGasUsed } for (let i = 10; i < numberOfVoters - numberOfDelegators; i++) { let gov = await GovernanceContract.connect(signerArmy[i]) let randN = rand(i * 5, i * 6) randN = randN % 2 let response if (randN > 0) { response = await gov.castVote(id, true, overrides) } else { response = await gov.castVote(id, false, overrides) } const receipt = await response.wait() gasUsedWithoutCompensation[i] = receipt.cumulativeGasUsed } await multiGov.setGasCompensations(pE(100)) //////////////////////////////// GET STATE /////////////////////////////// state = await GovernanceContract.state(id) expect(state).to.be.equal(ProposalState.Active) ///////////////////////////// VOTING GAS INFO /////////////////////////////////// let gasUsedSumNoComp = BigNumber.from(0) let gasUsedSum = BigNumber.from(0) let gasSumDiff = BigNumber.from(0) let gasUsedSumNoCompDel = BigNumber.from(0) let gasUsedSumDel = BigNumber.from(0) let gasSumDiffDel = BigNumber.from(0) for (let i = 0; i < 10; i++) { gasUsedSumDel = gasUsedSumDel.add(gasUsedArray[i]) gasUsedSumNoCompDel = gasUsedSumNoCompDel.add(gasUsedWithoutCompensation[i]) gasSumDiffDel = gasSumDiffDel.add(signerArmyBalanceDiff[i]) } for (let i = 10; i < numberOfVoters - numberOfDelegators; i++) { gasUsedSum = gasUsedSum.add(gasUsedArray[i]) gasUsedSumNoComp = gasUsedSumNoComp.add(gasUsedWithoutCompensation[i]) gasSumDiff = gasSumDiff.add(signerArmyBalanceDiff[i]) } const gasUsedAverageNoCompDel = gasUsedSumNoCompDel.div(10) const gasUsedAverageDel = gasUsedSumDel.div(10) const gasSumAverageDiffDel = gasSumDiffDel.div(10) const gasUsedAverageNoComp = gasUsedSumNoComp.div(numberOfVoters - 10) const gasUsedAverage = gasUsedSum.div(numberOfVoters - 10) const gasSumAverageDiff = gasSumDiff.div(numberOfVoters - 10) console.log( '\n', '----------------------------CAST VOTE INFO------------------------', '\n', 'Gas use average: ', gasUsedAverage.toString(), '\n', 'Gas use without compensation average: ', gasUsedAverageNoComp.toString(), '\n', 'Gas diff average: ', gasSumAverageDiff.toString(), '\n', 'Gas compensated in average: ', gasUsedAverage.sub(gasSumAverageDiff).toString(), '\n', '--------------------------------------------------------------------', '\n', ) console.log( '\n', '----------------------------CAST DELEGATED VOTE INFO------------------------', '\n', 'Gas use average: ', gasUsedAverageDel.toString(), '\n', 'Gas use without compensation average: ', gasUsedAverageNoCompDel.toString(), '\n', 'Gas diff average: ', gasSumAverageDiffDel.toString(), '\n', 'Gas compensated in average: ', gasUsedAverageDel.sub(gasSumAverageDiffDel).toString(), '\n', '--------------------------------------------------------------------', '\n', ) /////////////////////////////// INCREMENT AGAIN ////////////////////////////////// await minewait( ( await GovernanceContract.VOTING_PERIOD() ) .add(await GovernanceContract.EXECUTION_DELAY()) .add(10000) .toNumber(), ) ////////////// EXECUTE if (BigNumber.from(await GovernanceContract.state(id)).eq(ProposalState.Defeated)) { await expect(GovernanceContract.execute(id)).to.be.reverted } else { await expect(GovernanceContract.execute(id)).to.not.be.reverted } }) }) after(async function () { await ethers.provider.send('hardhat_reset', [ { forking: { jsonRpcUrl: `https://eth-mainnet.alchemyapi.io/v2/${process.env.ALCHEMY_KEY}`, blockNumber: process.env.use_latest_block == 'true' ? undefined : 14042331, }, }, ]) }) })