Skip to content

Commit

Permalink
add test for DaoGovernor and improved the DaoGovernor contract code
Browse files Browse the repository at this point in the history
  • Loading branch information
thurendous committed Sep 13, 2024
1 parent 29811eb commit 0eec478
Show file tree
Hide file tree
Showing 3 changed files with 188 additions and 5 deletions.
18 changes: 13 additions & 5 deletions src/MyGovernor.sol → src/DaoGovernor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

pragma solidity 0.8.24;

import {Governor} from "@openzeppelin/contracts/governance/Governor.sol";
import {Governor, IGovernor} from "@openzeppelin/contracts/governance/Governor.sol";
import {GovernorSettings} from "@openzeppelin/contracts/governance/extensions/GovernorSettings.sol";
import {GovernorCountingSimple} from "@openzeppelin/contracts/governance/extensions/GovernorCountingSimple.sol";
import {GovernorVotes, IVotes} from "@openzeppelin/contracts/governance/extensions/GovernorVotes.sol";
Expand All @@ -15,19 +15,27 @@ import {
TimelockController
} from "@openzeppelin/contracts/governance/extensions/GovernorTimelockControl.sol";

contract MyGovernor is
contract DaoGovernor is
Governor,
GovernorSettings,
GovernorCountingSimple,
GovernorVotes,
GovernorVotesQuorumFraction,
GovernorTimelockControl
{
// TODO: should decide first, second, third arguments before deploying the contract
// TODO: should decide the quorum fraction before deploying the contract.
constructor(IVotes _token, TimelockController _timelock)
Governor("MyGovernor")
GovernorSettings(1 days, 1 weeks, 0)
Governor("DaoGovernor")
// fist argument: delay since proposal is created until voting starts.
// second argument: duration of voting.
// third argument: minimum number of votes required to create a proposal.
GovernorSettings(1 days, 1 weeks, 1e18)
// governance token
GovernorVotes(_token)
GovernorVotesQuorumFraction(10)
// quorum: minimum number of votes required to pass a proposal.
GovernorVotesQuorumFraction(1) // 1% of the total supply must vote for a proposal to be passed.
// timelock: timelock contract to execute the proposal.
GovernorTimelockControl(_timelock)
{}

Expand Down
4 changes: 4 additions & 0 deletions src/Timelock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ contract Timelock is TimelockController {
// minDelay is the minimum time allowed before executing a transaction
// proposers is an array of addresses that are allowed to propose
// executors is an array of addresses that are allowed to execute
// TODO: we need to remove the admin from the contract eventually
// need to grant the role of minter of govToken to the timelock contract
// grant zero address to the timelock contract if you want to allow anyone to execute
// grant the role of proposer to the daoGovernor contract

constructor(uint256 minDelay, address[] memory proposers, address[] memory executors, address admin)
TimelockController(minDelay, proposers, executors, admin)
Expand Down
171 changes: 171 additions & 0 deletions test/integration/DAOGovernorTest.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.8.24;

import {Test, console} from "forge-std/Test.sol";
import {DaoGovernor, IGovernor} from "../../src/DaoGovernor.sol";
import {Timelock} from "../../src/Timelock.sol";
import {GovToken} from "../../src/GovToken.sol";
import {ERC20UpgradeableTokenV1} from "src/ERC20UpgradeableTokenV1.sol";
import {VotingPowerExchange} from "src/VotingPowerExchange.sol";
import {DeployContracts, DeploymentResult} from "script/DeployContracts.s.sol";

contract DAOGovernorTest is Test {
// instances
DaoGovernor public daoGovernor;
GovToken public govToken;
ERC20UpgradeableTokenV1 public utilityToken;
VotingPowerExchange public votingPowerExchange;
DeployContracts public dc;

Timelock public timelock;
GovToken public token;
ERC20UpgradeableTokenV1 public erc20;

DeploymentResult result;

uint256 public default_anvil_key2 = 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d;
// user for testing exchange
address public participant = makeAddr("levelUpper2");
address public participant2;

// admin roles
address admin;
address pauser;
address minter;
address burner;
address manager;
address exchanger;

// users for testing
address public user = makeAddr("user");
address public executor = makeAddr("executor");

// values
uint256 public constant MIN_DELAY = 6 hours; // 6 hours - after a vote passes.
uint256 public constant VOTING_DELAY = 1 days; // How many time till a proposal vote becomes active
uint256 public constant VOTING_PERIOD = 1 weeks; // How long a proposal vote is active

address[] proposers;
address[] executors;

// for the proposal
address[] targets;
uint256[] values;
bytes[] calldatas;

function setUp() public {
//////////////////////////////
//// using the deploy contracts script to deploy the contracts
dc = new DeployContracts();
result = dc.run();

utilityToken = ERC20UpgradeableTokenV1(result.utilityToken);
govToken = GovToken(result.govToken);
votingPowerExchange = VotingPowerExchange(result.exchange);
admin = result.admin;
pauser = result.pauser;
minter = result.minter;
burner = result.burner;
manager = result.manager;
exchanger = result.exchanger;
participant2 = vm.addr(dc.DEFAULT_ANVIL_KEY2());

vm.startPrank(minter);
govToken.mint(participant2, 6 * 1e18);
govToken.mint(user, 4 * 1e18);
utilityToken.mint(participant2, 10_000 * 1e18);
vm.stopPrank();

// delegate the voting power to the participant2 and user themselves
vm.prank(participant2);
govToken.delegate(participant2);
vm.prank(user);
govToken.delegate(user);
//////////////////////////////

// deploy the timelock
timelock = new Timelock(MIN_DELAY, proposers, executors, admin); // empty array means anyone can propose and anyone can execute

daoGovernor = new DaoGovernor(govToken, timelock);

bytes32 proposerRole = timelock.PROPOSER_ROLE(); // keccak256("PROPOSER_ROLE")
bytes32 executorRole = timelock.EXECUTOR_ROLE(); // keccak256("EXECUTOR_ROLE")
bytes32 adminRole = timelock.DEFAULT_ADMIN_ROLE(); // keccak256("DEFAULT_ADMIN_ROLE")

vm.startPrank(admin);
// only the governor can propose
timelock.grantRole(proposerRole, address(daoGovernor));
// anybody can execute
timelock.grantRole(executorRole, address(0)); // anybody can execute the proposal, this should not be done in production
timelock.revokeRole(adminRole, admin); // user is the admin and we do not need a single pointo of failure

govToken.grantRole(govToken.MINTER_ROLE(), address(timelock));
vm.stopPrank();
}

function testCannotMintTokenWithoutDaoGovernance() public {
vm.prank(participant2);
vm.expectRevert();
govToken.mint(participant2, 1e18);
vm.stopPrank();
}

function testGovernorCanGetProposedAndMintUtilityToken() public {
address ourAddress = makeAddr("ourAddress");
string memory description = "mint 1000e18 to our address";
bytes memory encodeFunctionData = abi.encodeWithSignature("mint(address,uint256)", ourAddress, 1000e18);
targets.push(address(govToken)); // this is the calling address
values.push(0); // this means no value is sent to the target address
calldatas.push(encodeFunctionData); // this is the data of the function call

// check the balance and the voting power of the participant2
console.log(govToken.balanceOf(user));
console.log(govToken.getVotes(user));
// 1. propose to the dao
// at least 1 block later(12 seconds)
vm.warp(block.timestamp + 15);
vm.roll(block.number + 1);

vm.startPrank(user);
uint256 proposalId = daoGovernor.propose(targets, values, calldatas, description);
vm.stopPrank();

// view the state
uint256 state = uint256(daoGovernor.state(proposalId));
console.log("state1", state);

// time lasts
vm.warp(block.timestamp + VOTING_DELAY + 1);
vm.roll(block.number + 1 + 1);

uint256 state2 = uint256(daoGovernor.state(proposalId));
console.log("state2", state2);

// 2. vote
string memory reason = "your proposal is always right!";
vm.prank(participant2);
daoGovernor.castVoteWithReason(proposalId, 1, reason);

// 3. queue
// time lasts
vm.warp(block.timestamp + VOTING_PERIOD + 1);
vm.roll(block.number + 50400 + 1);
uint256 state3 = uint256(daoGovernor.state(proposalId));
console.log("state3", state3);

// 4. queue the tx
bytes32 descriptionHash = keccak256(abi.encodePacked(description));
daoGovernor.queue(targets, values, calldatas, descriptionHash);

// 5. execute
vm.warp(block.timestamp + MIN_DELAY + 1);
vm.roll(block.number + MIN_DELAY / 12 + 1);
daoGovernor.execute(targets, values, calldatas, descriptionHash);

console.log("token balance of our address: ", govToken.balanceOf(ourAddress));
assertEq(govToken.balanceOf(ourAddress), 1000e18);

assertEq(uint256(daoGovernor.state(proposalId)), uint256(IGovernor.ProposalState.Executed));
}
}

0 comments on commit 0eec478

Please sign in to comment.