From b243f73ed1030db0215238d8298b4671a53700cb Mon Sep 17 00:00:00 2001 From: Milton Tulli Date: Tue, 7 Mar 2023 19:20:08 -0300 Subject: [PATCH 1/2] Create dao v2 test --- test/dao/gov2.0.test.js | 254 ++++++++++++++++++++++++++++++++++++++++ test/helpers/index.js | 49 ++++---- 2 files changed, 282 insertions(+), 21 deletions(-) create mode 100644 test/dao/gov2.0.test.js diff --git a/test/dao/gov2.0.test.js b/test/dao/gov2.0.test.js new file mode 100644 index 00000000..6773ad65 --- /dev/null +++ b/test/dao/gov2.0.test.js @@ -0,0 +1,254 @@ +import { expect } from "chai"; +import * as helpers from "../helpers"; +const BigNumber = require("bignumber.js"); +const bn = n => new BigNumber(n); + +const PermissionRegistry = artifacts.require("./PermissionRegistry.sol"); +const AvatarScheme = artifacts.require("./AvatarScheme.sol"); +const ERC20Mock = artifacts.require("./ERC20Mock.sol"); + +/** + * Representation of voting power at dao level. + * Very verbose test to explain actions and expectations. + */ + +contract("GOV2.0", function (accounts) { + const constants = helpers.constants; + let votingMachineToken, + votingPower, + precision, + holders, + controller, + avatar, + reputation, + votingMachine, + permissionRegistry, + dxdInfluence, + mintApproveStake, + defaultParamsHash, + masterAvatarScheme, + dxDao; + + const members = [ + { address: accounts[1], amount: { rep: 100, dxd: 10 } }, + { address: accounts[2], amount: { rep: 300, dxd: 390 } }, + { address: accounts[3], amount: { rep: 400, dxd: 400 } }, + ]; + + before(async function () { + votingMachineToken = await ERC20Mock.new("DXDao", "DXD", 1000, accounts[0]); + // Util to deploy dao + dxDao = await helpers.deployDaoV2({ + repTokenWeight: 100, // 100% rep for initial vp deploy + stakeTokenWeight: 0, // 0% of dxd weight for initial voting power deploy + owner: accounts[0], + votingMachineToken: votingMachineToken.address, + repHolders: [{ address: accounts[0], amount: { dxd: 0, rep: 1000 } }], + }); + votingPower = dxDao.votingPowerToken; + controller = dxDao.controller; + avatar = dxDao.avatar; + reputation = dxDao.reputation; + votingMachine = dxDao.votingMachine; + dxdInfluence = dxDao.dxdInfluence; + defaultParamsHash = dxDao.defaultParamsHash; + holders = dxDao.holders; + precision = bn(await votingPower.precision()); + mintApproveStake = dxDao.mintApproveStake; + + permissionRegistry = await PermissionRegistry.new(avatar.address, 10); + await permissionRegistry.initialize(); + + masterAvatarScheme = await AvatarScheme.new(); + + await masterAvatarScheme.initialize( + avatar.address, + votingMachine.address, + controller.address, + permissionRegistry.address, + votingPower.address, + "Master Scheme", + 100 + ); + await controller.registerScheme( + masterAvatarScheme.address, + defaultParamsHash, + true, + true, + true + ); + + // set permissions to execute votingPower.setComposition() + await permissionRegistry.setETHPermission( + avatar.address, + votingPower.address, + web3.eth.abi.encodeFunctionSignature("setComposition(uint256,uint256)"), + 0, + true + ); + }); + + it("Starts with initial holders", async () => { + const user1 = holders[0].address; + const user0VotingPower = bn(await votingPower.balanceOf(user1)).toNumber(); + expect(user0VotingPower).equal(precision.mul(100).toNumber()); + }); + + it("Mint rep for other members", async () => { + // 1. create proposal to mint rep + const burnRepCall = web3.eth.abi.encodeFunctionCall( + controller.abi.find(x => x.name === "burnReputation"), + [800, holders[0].address] + ); + + const mintCalls = members.map(member => { + return web3.eth.abi.encodeFunctionCall( + controller.abi.find(x => x.name === "mintReputation"), + [member.amount.rep, member.address] + ); + }); + + const tx = await masterAvatarScheme.proposeCalls( + [ + controller.address, + controller.address, + controller.address, + controller.address, + ], // To + [burnRepCall, ...mintCalls], // data + [0, 0, 0, 0], + 2, // options + "Mint rep for members", // title + constants.SOME_HASH // description hash + ); + + const proposalId = await helpers.getValueFromLogs(tx, "proposalId"); + + // 2. Vote/execute proposal + await votingMachine.vote(proposalId, constants.YES_OPTION, 0, { + from: holders[0].address, + }); + + // Assertions --->> + // O: Member 0 should have 100 reputation + const member0Rep = bn(await reputation.balanceOf(members[0].address)); + expect(member0Rep.toNumber()).equal(100); + + // O: Member 0 should have 10% voting power (based on distribution) + const member0VotingPower = bn( + await votingPower.balanceOf(members[0].address) + ); + expect(member0VotingPower.toString()).equal(precision.mul(10).toString()); + + // O: Member 1 should have 300 reputation + const member1Rep = bn(await reputation.balanceOf(members[1].address)); + expect(member1Rep.toNumber()).equal(300); + + // O: Member 1 should have 30% voting power (based on 1000REP distribution) + const member1VotingPower = bn( + await votingPower.balanceOf(members[1].address) + ); + expect(member1VotingPower.toString()).equal(precision.mul(30).toString()); + }); + + it("Dxdao can update weights composition", async () => { + // 1. Create a proposal to update tokens weights in votingPower contract (owned by avatar) + const updateCompositionCall = web3.eth.abi.encodeFunctionCall( + votingPower.abi.find(x => x.name === "setComposition"), + [ + 70, // REP weight + 30, // DXDInfluence weight + ] + ); + + const tx = await masterAvatarScheme.proposeCalls( + [votingPower.address], // to + [updateCompositionCall], // data + [0], // value + 2, // options + "Update votingPower weights composition", // title + constants.SOME_HASH // description hash + ); + + const proposalId = await helpers.getValueFromLogs(tx, "proposalId"); + + // 2. Vote for proposal to update composition + await votingMachine.vote(proposalId, constants.YES_OPTION, 0, { + from: members[1].address, + }); + + await votingMachine.vote(proposalId, constants.YES_OPTION, 0, { + from: members[2].address, + }); + + const snapshotId = bn(await votingPower.getCurrentSnapshotId()).toNumber(); + + // Verify that values are updated: get reputation weight in internal storage + const settingsReputationWeight = bn( + await votingPower.weights(reputation.address, snapshotId) + ).toNumber(); + + // Get actual reputation value that will be applied to votingPower formula. + const actualReputationWeightToBeApplied = bn( + await votingPower.getWeightOf(reputation.address) + ).toNumber(); + + // Config should be 70% to rep token - this is contract storage config. (previous call was executed) + expect(settingsReputationWeight).equal(70); + + // Reputation weight should be 100% since tokens locked (zero by this time) in dxdInfluence contract < _minStakingTokensLocked (set to 100 on init) + // This is the value will be consider when reading .balanceOf(address) + expect(actualReputationWeightToBeApplied).equal(100); + + // Verify that voting power hasn't been affected comparing to previous test + const member0VotingPower = bn( + await votingPower.balanceOf(members[0].address) + ); + expect(member0VotingPower.toString()).equal(precision.mul(10).toString()); + }); + + it("Stake dxd and get new voting power", async () => { + // At this point the total supply of influence is zero, since no token has been staked yet. + + /** + * Let's keep in mind this values: + * const members = [ + { amount: { rep: 100, dxd: 100 } }, + { amount: { rep: 300, dxd: 300 } }, + { amount: { rep: 400, dxd: 400 } }, + ]; + */ + + // 1. Stake tokens for all members + await mintApproveStake(members[0].address, members[0].amount.dxd); // 10 + await mintApproveStake(members[1].address, members[1].amount.dxd); // 390 + await mintApproveStake(members[2].address, members[2].amount.dxd); // 400 + await mintApproveStake(holders[0].address, 200); // 200 + + // 2. Verify that reputation/influence are updated now that influence amount of tokens locked (staked) is over _minStakingTokensLocked + // This are not only sotred values. Also values that computes inside .balance() getter. + const reputationWeight = bn( + await votingPower.getWeightOf(reputation.address) + ).toNumber(); + + const influenceWeight = bn( + await votingPower.getWeightOf(dxdInfluence.address) + ).toNumber(); + + // Check correct values. + expect(reputationWeight).equal(70); + expect(influenceWeight).equal(30); + + /** + * At this point test user has 10% REP and 1% dxd (based on total supply) + * Voting power config weights is 70%rep and 30% dxd + * + * So: 0.7(10rep) + 0.3(1) = 7.3% voting power + */ + const testUser = members[0]; + + let member0VotingPower = bn(await votingPower.balanceOf(testUser.address)); + expect(member0VotingPower.toString()).equal(precision.mul(7.3).toString()); + }); +}); + diff --git a/test/helpers/index.js b/test/helpers/index.js index d59497d2..fd38beb7 100644 --- a/test/helpers/index.js +++ b/test/helpers/index.js @@ -102,22 +102,25 @@ export const deployDaoV2 = async function (config) { config ); + // contract instances const reputation = await DAOReputation.new(); - const dxdStake = await DXDStake.new(); const dxdInfluence = await DXDInfluence.new(); - const dxd = await ERC20Mock.new( "DXD Token", "DXD", web3.utils.toWei("10000", "ether"), deployConfig.owner ); - const votingPowerToken = await VotingPower.new({ from: deployConfig.owner }); + const controller = await DAOController.new(); + const avatar = await DAOAvatar.new(); + const votingMachine = await VotingMachine.new( + deployConfig.votingMachineToken + ); + // Initialize contracts await reputation.initialize("Reputation", "REP", votingPowerToken.address); - await dxdStake.initialize( dxd.address, dxdInfluence.address, @@ -129,7 +132,6 @@ export const deployDaoV2 = async function (config) { from: deployConfig.owner, } ); - await dxdInfluence.initialize( dxdStake.address, votingPowerToken.address, @@ -140,7 +142,6 @@ export const deployDaoV2 = async function (config) { from: deployConfig.owner, } ); - await votingPowerToken.initialize( "Voting Power Token", "VPT", @@ -151,27 +152,30 @@ export const deployDaoV2 = async function (config) { deployConfig.minStakingTokensLocked ); - const controller = await DAOController.new(); - const avatar = await DAOAvatar.new(); await avatar.initialize(controller.address); + const mintApproveStake = async (account, amount) => { + await dxd.mint(account, amount); + await dxd.approve(dxdStake.address, amount, { from: account }); + await dxdStake.stake(amount, 100, { + from: account, + }); + }; + + // Mint rep / stake dxd for initial holders for (let i = 0; i < deployConfig.repHolders.length; i++) { const addressI = deployConfig.repHolders[i].address; - const amountI = deployConfig.repHolders[i].amount; - await reputation.mint(addressI, amountI); - await dxd.mint(addressI, amountI); - await dxd.approve(dxdStake.address, amountI, { - from: deployConfig.repHolders[i].address, - }); - await dxdStake.stake(amountI, 25, { - from: deployConfig.repHolders[i].address, - }); + const repAmount = deployConfig.repHolders[i].amount.rep; + const dxdAmount = deployConfig.repHolders[i].amount.dxd; + await reputation.mint(addressI, repAmount); + if (dxdAmount) { + await mintApproveStake(addressI, dxdAmount); + } } - await reputation.transferOwnership(controller.address); - const votingMachine = await VotingMachine.new( - deployConfig.votingMachineToken - ); + await reputation.transferOwnership(controller.address); + // Transfer votingPower ownership to avatar + await votingPowerToken.transferOwnership(avatar.address); const defaultParamsHash = await setDefaultParameters(votingMachine); @@ -190,6 +194,9 @@ export const deployDaoV2 = async function (config) { dxdInfluence, votingPowerToken, defaultParamsHash, + holders: deployConfig.repHolders, + mintApproveStake, + deployConfig, }; }; From 782b1fcee90f63fad83d342f2912f21341bdb5e1 Mon Sep 17 00:00:00 2001 From: Milton Tulli Date: Tue, 7 Mar 2023 19:24:04 -0300 Subject: [PATCH 2/2] Fix tests with update in deployDaov2 --- test/dao/DAOController.js | 6 +++--- test/dao/dxdao.js | 6 +++--- test/dao/schemes/AvatarScheme.js | 6 +++--- test/dao/schemes/WalletScheme.js | 6 +++--- test/dao/votingMachines/VotingMachine.js | 8 ++++---- test/erc20guild/implementations/DXDGuild.js | 6 +++--- test/utils/PermissionRegistry.js | 6 +++--- 7 files changed, 22 insertions(+), 22 deletions(-) diff --git a/test/dao/DAOController.js b/test/dao/DAOController.js index fe2317b2..046377ee 100644 --- a/test/dao/DAOController.js +++ b/test/dao/DAOController.js @@ -11,9 +11,9 @@ contract("DAOController", function (accounts) { beforeEach(async function () { actionMock = await ActionMock.new(); repHolders = [ - { address: accounts[0], amount: 20000 }, - { address: accounts[1], amount: 10000 }, - { address: accounts[2], amount: 70000 }, + { address: accounts[0], amount: { dxd: 20000, rep: 20000 } }, + { address: accounts[1], amount: { dxd: 10000, rep: 10000 } }, + { address: accounts[2], amount: { dxd: 70000, rep: 70000 } }, ]; const org = await helpers.deployDaoV2({ diff --git a/test/dao/dxdao.js b/test/dao/dxdao.js index 7fdb6580..7197859d 100644 --- a/test/dao/dxdao.js +++ b/test/dao/dxdao.js @@ -28,9 +28,9 @@ contract("DXdao", function (accounts) { owner: accounts[0], votingMachineToken: votingMachineToken.address, repHolders: [ - { address: accounts[0], amount: 20 }, - { address: accounts[1], amount: 10 }, - { address: accounts[2], amount: 70 }, + { address: accounts[0], amount: { dxd: 20, rep: 20 } }, + { address: accounts[1], amount: { dxd: 10, rep: 10 } }, + { address: accounts[2], amount: { dxd: 70, rep: 70 } }, ], }); diff --git a/test/dao/schemes/AvatarScheme.js b/test/dao/schemes/AvatarScheme.js index a7eec7c8..a0d3875e 100644 --- a/test/dao/schemes/AvatarScheme.js +++ b/test/dao/schemes/AvatarScheme.js @@ -33,9 +33,9 @@ contract("AvatarScheme", function (accounts) { owner: accounts[0], votingMachineToken: standardTokenMock.address, repHolders: [ - { address: accounts[0], amount: 20000 }, - { address: accounts[1], amount: 10000 }, - { address: accounts[2], amount: 70000 }, + { address: accounts[0], amount: { dxd: 20000, rep: 20000 } }, + { address: accounts[1], amount: { dxd: 10000, rep: 10000 } }, + { address: accounts[2], amount: { dxd: 70000, rep: 70000 } }, ], }); diff --git a/test/dao/schemes/WalletScheme.js b/test/dao/schemes/WalletScheme.js index f973a725..b2d21336 100644 --- a/test/dao/schemes/WalletScheme.js +++ b/test/dao/schemes/WalletScheme.js @@ -34,9 +34,9 @@ contract("WalletScheme", function (accounts) { owner: accounts[0], votingMachineToken: standardTokenMock.address, repHolders: [ - { address: accounts[0], amount: 20000 }, - { address: accounts[1], amount: 10000 }, - { address: accounts[2], amount: 70000 }, + { address: accounts[0], amount: { dxd: 20000, rep: 20000 } }, + { address: accounts[1], amount: { dxd: 10000, rep: 10000 } }, + { address: accounts[2], amount: { dxd: 70000, rep: 70000 } }, ], }); diff --git a/test/dao/votingMachines/VotingMachine.js b/test/dao/votingMachines/VotingMachine.js index 4262f60a..b5ccf49a 100644 --- a/test/dao/votingMachines/VotingMachine.js +++ b/test/dao/votingMachines/VotingMachine.js @@ -60,10 +60,10 @@ contract("VotingMachine", function (accounts) { owner: accounts[0], votingMachineToken: stakingToken.address, repHolders: [ - { address: accounts[0], amount: 10000 }, - { address: accounts[1], amount: 10000 }, - { address: accounts[2], amount: 10000 }, - { address: accounts[3], amount: 70000 }, + { address: accounts[0], amount: { dxd: 10000, rep: 10000 } }, + { address: accounts[1], amount: { dxd: 10000, rep: 10000 } }, + { address: accounts[2], amount: { dxd: 10000, rep: 10000 } }, + { address: accounts[3], amount: { dxd: 70000, rep: 70000 } }, ], }); diff --git a/test/erc20guild/implementations/DXDGuild.js b/test/erc20guild/implementations/DXDGuild.js index 7b213f7c..8de52876 100644 --- a/test/erc20guild/implementations/DXDGuild.js +++ b/test/erc20guild/implementations/DXDGuild.js @@ -49,9 +49,9 @@ contract("DXDGuild", function (accounts) { owner: accounts[0], votingMachineToken: votingMachineToken.address, repHolders: [ - { address: accounts[0], amount: 20 }, - { address: accounts[1], amount: 10 }, - { address: dxdGuild.address, amount: 70 }, + { address: accounts[0], amount: { dxd: 20, rep: 20 } }, + { address: accounts[1], amount: { dxd: 10, rep: 10 } }, + { address: dxdGuild.address, amount: { dxd: 70, rep: 70 } }, ], }); diff --git a/test/utils/PermissionRegistry.js b/test/utils/PermissionRegistry.js index 40384f62..626e6b14 100644 --- a/test/utils/PermissionRegistry.js +++ b/test/utils/PermissionRegistry.js @@ -29,9 +29,9 @@ contract("PermissionRegistry", function (accounts) { owner: accounts[0], votingMachineToken: votingMachineToken.address, repHolders: [ - { address: accounts[0], amount: 20000 }, - { address: accounts[1], amount: 10000 }, - { address: accounts[2], amount: 70000 }, + { address: accounts[0], amount: { dxd: 20000, rep: 20000 } }, + { address: accounts[1], amount: { dxd: 10000, rep: 10000 } }, + { address: accounts[2], amount: { dxd: 70000, rep: 70000 } }, ], });