@gooddollar/goodprotocol
Version:
GoodDollar Protocol
343 lines (294 loc) • 11.2 kB
text/typescript
import { ethers, upgrades } from "hardhat";
import { loadFixture } from "@nomicfoundation/hardhat-network-helpers";
import { expect } from "chai";
import { GReputation, CompoundVotingMachine } from "../../types";
import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/dist/src/signer-with-address";
import { createDAO } from "../helpers";
const BN = ethers.BigNumber;
export const NULL_ADDRESS = "0x0000000000000000000000000000000000000000";
let grep: GReputation, grepWithOwner: GReputation, identity, gd, bounty;
let signers: SignerWithAddress[], founder, repOwner, rep1, rep2, rep3;
const encodeParameters = (types, values) =>
ethers.utils.defaultAbiCoder.encode(types, values);
const advanceBlocks = async (blocks: number) => {
let ps = [];
for (let i = 0; i < blocks; i++) {
ps.push(ethers.provider.send("evm_mine", []));
if (i % 5000 === 0) {
await Promise.all(ps);
ps = [];
}
}
};
async function increaseTime(seconds) {
await ethers.provider.send("evm_increaseTime", [seconds]);
await advanceBlocks(1);
}
async function setTime(seconds) {
await ethers.provider.send("evm_setTime", [new Date(seconds * 1000)]);
}
const states = [
"Pending",
"Active",
"ActiveTimelock",
"Canceled",
"Defeated",
"Succeeded",
"Expired",
"Executed"
];
describe("CompoundVotingMachine#States", () => {
let gov: CompoundVotingMachine,
root: SignerWithAddress,
acct: SignerWithAddress;
let trivialProposal, targets, values, signatures, callDatas;
let proposalBlock,
proposalId,
voteDelay,
votePeriod,
queuePeriod,
gracePeriod;
before(async () => {
[root, acct, ...signers] = await ethers.getSigners();
let {
setSchemes,
avatar,
reputation,
setDAOAddress,
nameService,
votingMachine
} = await loadFixture(createDAO);
grep = (await ethers.getContractAt(
"GReputation",
reputation
)) as GReputation;
gov = votingMachine;
//this will give root minter permissions
await setDAOAddress("GDAO_CLAIMERS", root.address);
//set voting machiine as scheme with permissions
await setSchemes([gov.address]);
await grep.mint(root.address, ethers.BigNumber.from("1000000"));
await grep.mint(acct.address, ethers.BigNumber.from("500000"));
targets = [grep.address, grep.address];
values = ["0", "0"];
signatures = ["balanceOf(address)", ""];
callDatas = [
encodeParameters(["address"], [acct.address]),
grep.interface.encodeFunctionData("balanceOf", [acct.address])
];
await gov["propose(address[],uint256[],string[],bytes[],string)"](
targets,
values,
signatures,
callDatas,
"do nothing"
);
proposalBlock = +(await ethers.provider.getBlockNumber());
proposalId = await gov.latestProposalIds(root.address);
trivialProposal = await gov.proposals(proposalId);
voteDelay = await gov.votingDelay().then(_ => _.toNumber());
votePeriod = await gov.votingPeriod().then(_ => _.toNumber());
queuePeriod = await gov.queuePeriod().then(_ => _.toNumber());
gracePeriod = await gov.gracePeriod().then(_ => _.toNumber());
//disable guardian
await gov.renounceGuardian()
});
it("Invalid for proposal not found", async () => {
await expect(gov.state(5)).to.revertedWith(
/CompoundVotingMachine::state: invalid proposal id/
);
});
it("Pending", async () => {
expect(states[await gov.state(trivialProposal.id)]).to.equal("Pending");
});
it("Active", async () => {
await ethers.provider.send("evm_mine", []);
await ethers.provider.send("evm_mine", []);
expect(states[await gov.state(trivialProposal.id)]).to.equal("Active");
});
it("Canceled", async () => {
let actor = signers[4];
await grep.delegateTo(actor.address);
await gov
.connect(actor)
["propose(address[],uint256[],string[],bytes[],string)"](
targets,
values,
signatures,
callDatas,
"do nothing"
);
let newProposalId = await gov.proposalCount();
// send away the delegates
await grep.undelegate();
await gov.cancel(newProposalId);
expect(states[await gov.state(newProposalId)]).to.equal("Canceled");
});
it("Defeated", async () => {
// travel to end block
await advanceBlocks(votePeriod + 2);
expect(states[await gov.state(trivialProposal.id)]).to.equal("Defeated");
});
describe("queue period", async () => {
let proposalId;
before(async () => {
await advanceBlocks(1);
await gov["propose(address[],uint256[],string[],bytes[],string)"](
targets,
values,
signatures,
callDatas,
"do nothing"
);
proposalId = await gov.latestProposalIds(root.address);
await advanceBlocks(1);
});
it("ActiveTimelock in queue period", async () => {
await gov.castVote(proposalId, true);
await advanceBlocks(1);
expect((await gov.proposals(proposalId)).eta).to.gt(0);
await increaseTime(queuePeriod / 2);
expect(states[await gov.state(proposalId)]).to.equal("ActiveTimelock"); //active while in queue period
});
it("succeeded after queue period", async () => {
await increaseTime(queuePeriod / 2);
expect(states[await gov.state(proposalId)]).to.equal("Succeeded");
});
it("Still succeeded in grace period", async () => {
await increaseTime(gracePeriod - 10);
expect(states[await gov.state(proposalId)]).to.equal("Succeeded");
});
it("Cant vote in grace period", async () => {
let proposalId = await gov.latestProposalIds(root.address);
await expect(
gov.connect(acct).castVote(proposalId, false)
).to.be.revertedWith(
/CompoundVotingMachine::_castVote: voting is closed/
);
});
it("Expired after grace period", async () => {
await increaseTime(10);
expect(states[await gov.state(proposalId)]).to.equal("Expired");
await expect(gov.execute(proposalId)).to.be.revertedWith(
/CompoundVotingMachine::execute: proposal can only be executed if it is succeeded/
);
});
});
//
it("ActiveTimelock in queue period then defeated", async () => {
let actor = signers[0];
await grep.mint(actor.address, ethers.BigNumber.from("1000000"));
await advanceBlocks(1);
await gov
.connect(actor)
["propose(address[],uint256[],string[],bytes[],string)"](
targets,
values,
signatures,
callDatas,
"do nothing"
);
let proposalId = await gov.latestProposalIds(actor.address);
await advanceBlocks(1);
await gov.connect(actor).castVote(proposalId, false);
expect((await gov.proposals(proposalId)).eta).to.gt(0);
await increaseTime(queuePeriod / 2);
expect(states[await gov.state(proposalId)]).to.equal("ActiveTimelock"); //active while in queue period
await increaseTime(queuePeriod / 2);
expect(states[await gov.state(proposalId)]).to.equal("Defeated");
await increaseTime(gracePeriod);
expect(states[await gov.state(proposalId)]).to.equal("Defeated"); //still defeated after expiration
});
it("Executed", async () => {
let actor = signers[1];
await grep.mint(actor.address, ethers.BigNumber.from("1000000"));
await advanceBlocks(1);
await gov
.connect(actor)
["propose(address[],uint256[],string[],bytes[],string)"](
targets,
values,
signatures,
callDatas,
"do nothing"
);
let proposalId = await gov.latestProposalIds(actor.address);
await advanceBlocks(1);
await gov.connect(actor).castVote(proposalId, true);
expect((await gov.proposals(proposalId)).eta).to.gt(0);
await increaseTime(queuePeriod);
expect(states[await gov.state(proposalId)]).to.equal("Succeeded");
let eta = (await gov.proposals(proposalId)).eta;
await increaseTime(gracePeriod / 2);
expect(states[await gov.state(proposalId)]).to.equal("Succeeded"); //still succeeded
await gov.execute(proposalId);
expect(states[await gov.state(proposalId)]).to.equal("Executed");
// still executed even though would be expired
await increaseTime(gracePeriod);
expect(states[await gov.state(proposalId)]).to.equal("Executed");
});
it("can not cancel executed proposal", async () => {
let actor = signers[1];
let proposalId = await gov.latestProposalIds(actor.address);
await expect(gov.cancel(proposalId)).to.be.revertedWith(
/cannot cancel executed proposal/
);
});
it("Game changer extends eta", async () => {
let gameChangerPeriod = await gov
.gameChangerPeriod()
.then(_ => _.toNumber());
await advanceBlocks(1);
await gov["propose(address[],uint256[],string[],bytes[],string)"](
targets,
values,
signatures,
callDatas,
"do nothing"
);
let proposalId = await gov.latestProposalIds(root.address);
await advanceBlocks(1);
await gov.connect(acct).castVote(proposalId, false);
let firstEta = (await gov.proposals(proposalId)).eta;
expect(firstEta).to.gt(0);
await increaseTime(queuePeriod / 2);
expect(states[await gov.state(proposalId)]).to.equal("ActiveTimelock"); //active while in queue period
await increaseTime(queuePeriod / 2 - 10); //almost to end of queue period
await gov.castVote(proposalId, true);
let secondEta = (await gov.proposals(proposalId)).eta;
expect(firstEta).to.lt(secondEta); //eta should now end in atleast gameChangrePeriod
// expect(secondEta.sub(firstEta)).to.eq(gameChangerPeriod.sub(1));
await increaseTime(gameChangerPeriod - 10); //almost to end of gameChangerPeriod
expect(states[await gov.state(proposalId)]).to.equal("ActiveTimelock"); //should be still active after almost 24hours
await increaseTime(10);
expect(states[await gov.state(proposalId)]).to.equal("Succeeded");
await increaseTime(gracePeriod);
});
it("can be executed after short activetimelock period when passed with absolute majority", async () => {
let fastQueuePeriod = await gov.fastQueuePeriod().then(_ => _.toNumber());
await advanceBlocks(1);
await gov
.connect(signers[0])
["propose(address[],uint256[],string[],bytes[],string)"](
targets,
values,
signatures,
callDatas,
"do nothing"
);
let proposalId = await gov.proposalCount();
await advanceBlocks(1);
//vote with > 50%
await gov.connect(root).castVote(proposalId, true);
await gov.connect(signers[0]).castVote(proposalId, true);
await gov.connect(signers[1]).castVote(proposalId, true);
let proposal = await gov.proposals(proposalId);
expect(proposal.forVotes).gt((await grep.totalSupply()).div(2)); //verify absolute majority
await increaseTime(fastQueuePeriod / 2);
expect(states[await gov.state(proposalId)]).to.equal("ActiveTimelock"); //active while in queue period
await increaseTime(fastQueuePeriod / 2);
await advanceBlocks(1);
expect(states[await gov.state(proposalId)]).to.equal("Succeeded");
await expect(gov.execute(proposalId)).to.not.reverted;
});
});