Skip to content

Commit

Permalink
[WIP] test/bonding: Test GovernorCountingOverridable
Browse files Browse the repository at this point in the history
  • Loading branch information
victorges committed Jul 18, 2023
1 parent ea54d0d commit 8e13381
Show file tree
Hide file tree
Showing 4 changed files with 353 additions and 2 deletions.
71 changes: 71 additions & 0 deletions contracts/test/mocks/GovernorCountingOverridableTestable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.9;

import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/governance/GovernorUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/governance/extensions/GovernorVotesUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/governance/extensions/GovernorVotesQuorumFractionUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/governance/extensions/GovernorSettingsUpgradeable.sol";

import "../../treasury/GovernorCountingOverridable.sol";

/**
* @dev This is a concrete contract to test the GovernorCountingOverridable extension. It implements the minimum
* necessary to get a working Governor to test the extension.
*/
contract GovernorCountingOverridableTestable is
Initializable,
GovernorUpgradeable,
GovernorSettingsUpgradeable,
GovernorVotesUpgradeable,
GovernorCountingOverridable
{
// use non-standard values for these to test if it's really used
uint256 constant QUOTA = 420000; // 42%
uint256 constant QUORUM = 370000; // 37%

IVotes internal iVotes; // 🍎

function initialize(IVotes _votes) public initializer {
iVotes = _votes;

__Governor_init("GovernorCountingOverridableConcrete");
__GovernorSettings_init(
0, /* no voting delay */
100, /* 100 blocks voting period */
0 /* no minimum proposal threshold */
);

__GovernorVotes_init(iVotes);
__GovernorCountingOverridable_init();
}

function votes() public view override returns (IVotes) {
return iVotes;
}

function quorum(uint256 timepoint) public view virtual override returns (uint256) {
return MathUtils.percOf(iVotes.getPastTotalSupply(timepoint), QUORUM);
}

function quota() public pure override returns (uint256) {
return QUOTA;
}

/**
* @dev Expose internal function for testing.
*/
function quorumReached(uint256 proposalId) public view returns (bool) {
uint256 timepoint = proposalSnapshot(proposalId);
return super._quorumReached(timepoint);
}

function proposalThreshold()
public
view
override(GovernorUpgradeable, GovernorSettingsUpgradeable)
returns (uint256)
{
return super.proposalThreshold();
}
}
76 changes: 76 additions & 0 deletions contracts/test/mocks/VotesMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20BurnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20SnapshotUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20VotesUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";

import { IVotes } from "../../treasury/GovernorCountingOverridable.sol";
import "../../bonding/libraries/SortedArrays.sol";

/**
* @dev Minimum implementation of an IVotes interface to test the GovernorCountingOverridable extension.
*/
contract VotesMock is
Initializable,
ERC20Upgradeable,
ERC20BurnableUpgradeable,
OwnableUpgradeable,
ERC20VotesUpgradeable,
IVotes
{
mapping(address => uint256[]) private _delegateChangingTimes;
mapping(address => mapping(uint256 => address)) private _delegatedAtTime;

function initialize() public initializer {
__ERC20_init("VotesMock", "VTCK");
__ERC20Burnable_init();
__Ownable_init();
__ERC20Votes_init();
}

function delegatedAt(address _account, uint256 _timepoint) external view returns (address) {
uint256[] storage rounds = _delegateChangingTimes[_account];
if (rounds.length == 0 || _timepoint < rounds[0]) {
return address(0);
}

uint256 prevRound = SortedArrays.findLowerBound(rounds, _timepoint);
return _delegatedAtTime[_account][prevRound];
}

function _delegate(address _delegator, address _to) internal override {
super._delegate(_delegator, _to);

uint256 currTime = clock();

uint256[] storage rounds = _delegateChangingTimes[_delegator];
SortedArrays.pushSorted(rounds, currTime);
_delegatedAtTime[_delegator][currTime] = _to;
}

function mint(address to, uint256 amount) public onlyOwner {
_mint(to, amount);
}

// The following functions are overrides required by Solidity.

function _afterTokenTransfer(
address from,
address to,
uint256 amount
) internal override(ERC20Upgradeable, ERC20VotesUpgradeable) {
super._afterTokenTransfer(from, to, amount);
}

function _mint(address to, uint256 amount) internal override(ERC20Upgradeable, ERC20VotesUpgradeable) {
super._mint(to, amount);
}

function _burn(address account, uint256 amount) internal override(ERC20Upgradeable, ERC20VotesUpgradeable) {
super._burn(account, amount);
}
}
205 changes: 205 additions & 0 deletions test/unit/GovernorCountingOverridable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
import Fixture from "./helpers/Fixture"
// import {functionSig} from "../../utils/helpers"
import {assert} from "chai"
import {ethers, web3} from "hardhat"
import chai from "chai"
import {solidity} from "ethereum-waffle"
// import {BigNumber} from "ethers"

chai.use(solidity)
// const {expect} = chai

const ProposalState = {
Pending: 0,
Active: 1,
Canceled: 2,
Defeated: 3,
Succeeded: 4,
Queued: 5,
Expired: 6,
Executed: 7
}

describe.only("BondingCheckpointsVotes", () => {
let signers
let fixture

let votes
let governor

let proposer
let proposalId
let voters

const initVoter = async ({
signer,
amount = ethers.utils.parseEther("1"),
delegateAddress = signer.address
}) => {
await votes.mint(signer.address, amount)
await votes.connect(signer).delegate(delegateAddress)
return signer
}

before(async () => {
signers = await ethers.getSigners()
proposer = signers[0]
voters = signers.slice(1, 11)

fixture = new Fixture(web3)
await fixture.deploy()

// setup votes token

const votesFac = await ethers.getContractFactory("VotesMock")
votes = await votesFac.deploy()
await votes.initialize()

await initVoter({signer: proposer})
for (const i = 1; i <= voters.length; i++) {
await initVoter({
signer: voters[i - 1],
amount: ethers.utils.parseEther("1").mul(i)
})
}

// setup governor

const governorFac = await ethers.getContractFactory(
"GovernorCountingOverridableTestable"
)
governor = await governorFac.deploy()
await governor.initialize(votes.address)

await votes.mint(governor.address, ethers.utils.parseEther("100"))

// setup proposal

const tx = await governor.propose(
[votes.address],
[0],
[
votes.interface.encodeFunctionData("transfer", [
proposer.address,
ethers.utils.parseEther("100")
])
],
"Steal all the money"
)

const filter = governor.filters.ProposalCreated()
const events = await governor.queryFilter(
filter,
tx.blockNumber,
tx.blockNumber
)
proposalId = events[0].args[0]
})

beforeEach(async () => {
await fixture.setUp()
})

afterEach(async () => {
await fixture.tearDown()
})

it("it should use the block number as clock", async () => {
assert.equal(
await governor.clock(),
await ethers.provider.getBlockNumber()
)
})

describe("COUNTING_MODE", () => {
it("should include bravo support and all vote types on quorum count", async () => {
assert.equal(
await governor.COUNTING_MODE(),
"support=bravo&quorum=for,abstain,against"
)
})
})

describe("hasVoted", () => {
it("should return false for users that haven't voted", async () => {
for (let i = 0; i < 10; i++) {
assert.isFalse(
await governor.hasVoted(proposalId, signers[i].address)
)
}
})

it("should return true after voting", async () => {
await governor.connect(voters[0]).castVote(proposalId, 1)

assert.isTrue(
await governor.hasVoted(proposalId, voters[0].address)
)
})
})

describe("proposalVotes", () => {
it("should return the sum of all votes made of each type", async () => {
// against, for abstain, as per bravo ordering
const tally = [0, 0, 0]

const checkTally = async () => {
const ether = ethers.utils.parseEther("1")
const expected = tally.map(c => ether.mul(c).toString())

const votes = await governor
.proposalVotes(proposalId)
.then(v => v.map(v => v.toString()))

assert.deepEqual(votes, expected)
}

for (let i = 1; i <= 10; i++) {
await checkTally()

// Each voter has a voting power of {i} VTCK. This renders a voting power sum of:
// - 25 against (1 + 3 + 5 + 7 + 9)
// - 24 for (2 + 4 + 8 + 10)
// - 6 abstain (6)
const voteType = i % 2 ? 0 : i % 3 ? 1 : 2

await governor
.connect(voters[i - 1])
.castVote(proposalId, voteType)
tally[voteType] += i
}

// sanity check
assert.deepEqual(tally, [25, 24, 6])
await checkTally()

await fixture.rpc.wait(100)

assert.equal(
await governor.state(proposalId),
ProposalState.Succeeded // funds were stolen!
)
})
})

describe("_quorumReached", () => {
it("should return false if less than the quorum has voted", async () => {
// this results in a 41.8% participation, just below the quorum of 42%
const voterIdxs = [1, 2, 3, 4, 5, 8]

for (const i of voterIdxs) {
assert.isFalse(await governor.quorumReached(proposalId))

await governor.connect(voters[i - 1]).castVote(proposalId, 0)
}
assert.isFalse(await governor.quorumReached(proposalId))

await fixture.rpc.wait(100)

assert.equal(
await governor.state(proposalId), // calls _quorumReached internally
ProposalState.Expired // not enough participation for the coup!
)
})
})
})
3 changes: 1 addition & 2 deletions test/unit/helpers/expectCheckpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ export default async function expectCheckpoints(
tx: ethers.providers.TransactionReceipt,
...checkpoints: Checkpoint[]
) {
const filter =
await fixture.bondingCheckpoints.filters.CheckpointBondingState()
const filter = fixture.bondingCheckpoints.filters.CheckpointBondingState()
const events = await fixture.bondingCheckpoints.queryFilter(
filter,
tx.blockNumber,
Expand Down

0 comments on commit 8e13381

Please sign in to comment.