governance implementaion
Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
This commit is contained in:
parent
55b463dc6d
commit
0f23d7bd3d
@ -17,6 +17,7 @@ gas_reports = [
|
|||||||
"GhostDistributor",
|
"GhostDistributor",
|
||||||
"GhostBondingCalculator",
|
"GhostBondingCalculator",
|
||||||
"GhostTreasury",
|
"GhostTreasury",
|
||||||
|
"GhostGovernorExposed",
|
||||||
]
|
]
|
||||||
remappings = [
|
remappings = [
|
||||||
"@openzeppelin-contracts/=dependencies/@openzeppelin-contracts-5.0.2/",
|
"@openzeppelin-contracts/=dependencies/@openzeppelin-contracts-5.0.2/",
|
||||||
|
|||||||
@ -13,6 +13,8 @@ contract Ghost is IGHST, ERC20, ERC20Permit, ERC20Votes {
|
|||||||
address public override stnk;
|
address public override stnk;
|
||||||
address private _initializer;
|
address private _initializer;
|
||||||
|
|
||||||
|
error GhostDelegationIsBuiltIn();
|
||||||
|
|
||||||
constructor(address _stnk, string memory name, string memory symbol)
|
constructor(address _stnk, string memory name, string memory symbol)
|
||||||
ERC20(name, symbol)
|
ERC20(name, symbol)
|
||||||
ERC20Permit(name)
|
ERC20Permit(name)
|
||||||
@ -37,6 +39,21 @@ contract Ghost is IGHST, ERC20, ERC20Permit, ERC20Votes {
|
|||||||
_burn(_from, _amount);
|
_burn(_from, _amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function delegate(address) public override pure {
|
||||||
|
revert GhostDelegationIsBuiltIn();
|
||||||
|
}
|
||||||
|
|
||||||
|
function delegateBySig(
|
||||||
|
address,
|
||||||
|
uint256,
|
||||||
|
uint256,
|
||||||
|
uint8,
|
||||||
|
bytes32,
|
||||||
|
bytes32
|
||||||
|
) public override pure {
|
||||||
|
revert GhostDelegationIsBuiltIn();
|
||||||
|
}
|
||||||
|
|
||||||
function index() public view override returns (uint256) {
|
function index() public view override returns (uint256) {
|
||||||
return ISTNK(stnk).index();
|
return ISTNK(stnk).index();
|
||||||
}
|
}
|
||||||
@ -49,11 +66,15 @@ contract Ghost is IGHST, ERC20, ERC20Permit, ERC20Votes {
|
|||||||
return _amount * 1e18 / index();
|
return _amount * 1e18 / index();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function nonces(address owner) public view override(ERC20Permit, Nonces) returns (uint256) {
|
||||||
|
return super.nonces(owner);
|
||||||
|
}
|
||||||
|
|
||||||
|
function delegates(address account) public pure override returns (address) {
|
||||||
|
return account;
|
||||||
|
}
|
||||||
|
|
||||||
function _update(address from, address to, uint256 amount) internal override(ERC20, ERC20Votes) {
|
function _update(address from, address to, uint256 amount) internal override(ERC20, ERC20Votes) {
|
||||||
super._update(from, to, amount);
|
super._update(from, to, amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
function nonces(address owner) public view virtual override(ERC20Permit, Nonces) returns (uint256) {
|
|
||||||
return super.nonces(owner);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,118 +2,184 @@
|
|||||||
pragma solidity ^0.8.20;
|
pragma solidity ^0.8.20;
|
||||||
|
|
||||||
import {IGovernor, Governor} from "@openzeppelin-contracts/governance/Governor.sol";
|
import {IGovernor, Governor} from "@openzeppelin-contracts/governance/Governor.sol";
|
||||||
import {GovernorCountingSimple} from "@openzeppelin-contracts/governance/extensions/GovernorCountingSimple.sol";
|
import {GovernorPreventLateQuorum} from "@openzeppelin-contracts/governance/extensions/GovernorPreventLateQuorum.sol";
|
||||||
|
import {GovernorSettings} from "@openzeppelin-contracts/governance/extensions/GovernorSettings.sol";
|
||||||
|
import {GovernorStorage} from "@openzeppelin-contracts/governance/extensions/GovernorStorage.sol";
|
||||||
import {GovernorVotes} from "@openzeppelin-contracts/governance/extensions/GovernorVotes.sol";
|
import {GovernorVotes} from "@openzeppelin-contracts/governance/extensions/GovernorVotes.sol";
|
||||||
import {GovernorVotesQuorumFraction} from "@openzeppelin-contracts/governance/extensions/GovernorVotesQuorumFraction.sol";
|
import {GovernorVotesQuorumFraction} from "@openzeppelin-contracts/governance/extensions/GovernorVotesQuorumFraction.sol";
|
||||||
import {GovernorTimelockControl} from "@openzeppelin-contracts/governance/extensions/GovernorTimelockControl.sol";
|
|
||||||
import {TimelockController} from "@openzeppelin-contracts/governance/TimelockController.sol";
|
|
||||||
import {IVotes} from "@openzeppelin-contracts/governance/utils/IVotes.sol";
|
import {IVotes} from "@openzeppelin-contracts/governance/utils/IVotes.sol";
|
||||||
import {IERC165} from "@openzeppelin-contracts/interfaces/IERC165.sol";
|
import {ReentrancyGuard} from "@openzeppelin-contracts/utils/ReentrancyGuard.sol";
|
||||||
|
|
||||||
|
import "@openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol";
|
||||||
|
import "@openzeppelin-contracts/token/ERC20/IERC20.sol";
|
||||||
|
|
||||||
|
import {GovernorGhostCounting} from "./GovernorGhostCounting.sol";
|
||||||
|
import {Babylonian} from "../libraries/FixedPoint.sol";
|
||||||
|
|
||||||
contract GhostGovernor is
|
contract GhostGovernor is
|
||||||
Governor,
|
GovernorGhostCounting,
|
||||||
GovernorCountingSimple,
|
GovernorPreventLateQuorum,
|
||||||
|
GovernorSettings,
|
||||||
|
GovernorStorage,
|
||||||
GovernorVotes,
|
GovernorVotes,
|
||||||
GovernorVotesQuorumFraction,
|
GovernorVotesQuorumFraction,
|
||||||
GovernorTimelockControl
|
ReentrancyGuard
|
||||||
{
|
{
|
||||||
uint256 private immutable _innerVotingDelay;
|
using SafeERC20 for IERC20;
|
||||||
uint256 private immutable _innerVotingPeriod;
|
|
||||||
|
uint256 public activeProposedLock;
|
||||||
|
uint256 private _lastProposalId;
|
||||||
|
mapping(uint256 => uint256) public lockedAmounts;
|
||||||
|
|
||||||
|
error ProposerNotEnoughVotes(uint256 proposalVotes, uint256 accumulatedVotes);
|
||||||
|
error InsufficientFundsToExtend(uint256 votes, uint256 neededVotes);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
IVotes _ghst,
|
IVotes _ghst,
|
||||||
TimelockController _timelock,
|
uint48 _initialVoteExtension,
|
||||||
uint256 _fraction,
|
uint48 _initialVotingDelay,
|
||||||
uint256 _innerVotingDelayValue,
|
uint32 _initialVotingPeriod,
|
||||||
uint256 _innerVotingPeriodValue
|
uint256 _initialProposalThreshold,
|
||||||
|
uint256 _quorumFraction
|
||||||
)
|
)
|
||||||
Governor("GhostGovernor")
|
Governor("GhostGovernorV1")
|
||||||
GovernorVotes(_ghst)
|
GovernorVotes(_ghst)
|
||||||
GovernorVotesQuorumFraction(_fraction)
|
GovernorPreventLateQuorum(_initialVoteExtension)
|
||||||
GovernorTimelockControl(_timelock)
|
GovernorSettings(_initialVotingDelay, _initialVotingPeriod, _initialProposalThreshold)
|
||||||
{
|
GovernorVotesQuorumFraction(_quorumFraction)
|
||||||
_innerVotingDelay = _innerVotingDelayValue;
|
{}
|
||||||
_innerVotingPeriod = _innerVotingPeriodValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
function votingDelay() public view override returns (uint256) {
|
function proposalThreshold()
|
||||||
return _innerVotingDelay;
|
|
||||||
}
|
|
||||||
|
|
||||||
function votingPeriod() public view override returns (uint256) {
|
|
||||||
return _innerVotingPeriod;
|
|
||||||
}
|
|
||||||
|
|
||||||
function proposalThreshold() public pure override returns (uint256) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
function state(uint256 proposalId)
|
|
||||||
public
|
|
||||||
view
|
|
||||||
override(Governor, GovernorTimelockControl)
|
|
||||||
returns (ProposalState)
|
|
||||||
{
|
|
||||||
return super.state(proposalId);
|
|
||||||
}
|
|
||||||
|
|
||||||
function proposalNeedsQueuing(uint256 proposalId)
|
|
||||||
public
|
public
|
||||||
view
|
view
|
||||||
virtual
|
virtual
|
||||||
override(Governor, GovernorTimelockControl)
|
override(Governor, GovernorSettings)
|
||||||
returns (bool)
|
returns (uint256)
|
||||||
{
|
{
|
||||||
return super.proposalNeedsQueuing(proposalId);
|
return super.proposalThreshold();
|
||||||
}
|
}
|
||||||
|
|
||||||
function _queueOperations(
|
function proposalDeadline(uint256 proposalId)
|
||||||
|
public
|
||||||
|
view
|
||||||
|
virtual
|
||||||
|
override(Governor, GovernorPreventLateQuorum)
|
||||||
|
returns (uint256)
|
||||||
|
{
|
||||||
|
return super.proposalDeadline(proposalId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function releaseLocked(uint256 proposalId) public nonReentrant returns (uint256) {
|
||||||
|
uint256 releaseAmount = lockedAmounts[proposalId];
|
||||||
|
address proposer = proposalProposer(proposalId);
|
||||||
|
|
||||||
|
if (releaseAmount > 0 && clock() > proposalDeadline(proposalId)) {
|
||||||
|
lockedAmounts[proposalId] = 0;
|
||||||
|
IERC20(address(token())).transfer(proposer, releaseAmount);
|
||||||
|
return releaseAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function state(uint256 proposalId) public view virtual override returns (ProposalState) {
|
||||||
|
ProposalState result = super.state(proposalId);
|
||||||
|
|
||||||
|
if (result == ProposalState.Active) {
|
||||||
|
(uint256 againstVotes, uint256 forVotes,) = proposalVotes(proposalId);
|
||||||
|
uint256 halfTotalVotes = token().getPastTotalSupply(proposalSnapshot(proposalId)) / 2;
|
||||||
|
if (forVotes > halfTotalVotes) return ProposalState.Succeeded;
|
||||||
|
if (againstVotes > halfTotalVotes) return ProposalState.Defeated;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function propose(
|
||||||
|
address[] memory targets,
|
||||||
|
uint256[] memory values,
|
||||||
|
bytes[] memory calldatas,
|
||||||
|
string memory description
|
||||||
|
) public virtual override returns (uint256) {
|
||||||
|
address proposer = _msgSender();
|
||||||
|
|
||||||
|
// check description restriction
|
||||||
|
if (!_isValidDescriptionForProposer(proposer, description)) {
|
||||||
|
revert GovernorRestrictedProposer(proposer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check proposal threshold
|
||||||
|
uint256 proposerVotes = getVotes(proposer, clock() - 1);
|
||||||
|
uint256 votesThreshold = proposalThreshold();
|
||||||
|
if (proposerVotes < votesThreshold) {
|
||||||
|
revert GovernorInsufficientProposerVotes(proposer, proposerVotes, votesThreshold);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (proposerVotes <= activeProposedLock) {
|
||||||
|
revert ProposerNotEnoughVotes(proposerVotes, activeProposedLock);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint256 proposalId = _lastProposalId;
|
||||||
|
if (proposalId != 0) {
|
||||||
|
bytes32 currentProposalState = _encodeStateBitmap(state(proposalId));
|
||||||
|
bytes32 terminalStatesBitmap =
|
||||||
|
_encodeStateBitmap(ProposalState.Canceled) |
|
||||||
|
_encodeStateBitmap(ProposalState.Expired) |
|
||||||
|
_encodeStateBitmap(ProposalState.Executed);
|
||||||
|
|
||||||
|
if (currentProposalState & terminalStatesBitmap == 0) {
|
||||||
|
(
|
||||||
|
address[] memory currentTargets,
|
||||||
|
uint256[] memory currentValues,
|
||||||
|
bytes[] memory currentCalldatas,
|
||||||
|
bytes32 descriptionHash
|
||||||
|
) = proposalDetails(proposalId);
|
||||||
|
_cancel(currentTargets, currentValues, currentCalldatas, descriptionHash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
proposalId = _propose(targets, values, calldatas, description, proposer);
|
||||||
|
|
||||||
|
lockedAmounts[proposalId] += proposerVotes;
|
||||||
|
activeProposedLock = proposerVotes;
|
||||||
|
_lastProposalId = proposalId;
|
||||||
|
IERC20(address(token())).safeTransferFrom(proposer, address(this), proposerVotes);
|
||||||
|
|
||||||
|
return proposalId;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _propose(
|
||||||
|
address[] memory targets,
|
||||||
|
uint256[] memory values,
|
||||||
|
bytes[] memory calldatas,
|
||||||
|
string memory description,
|
||||||
|
address proposer
|
||||||
|
) internal virtual override(Governor, GovernorStorage) returns (uint256) {
|
||||||
|
return super._propose(targets, values, calldatas, description, proposer);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _castVote(
|
||||||
uint256 proposalId,
|
uint256 proposalId,
|
||||||
address[] memory targets,
|
address account,
|
||||||
uint256[] memory values,
|
uint8 support,
|
||||||
bytes[] memory calldatas,
|
string memory reason,
|
||||||
bytes32 descriptionHash
|
bytes memory params
|
||||||
) internal override(Governor, GovernorTimelockControl) returns (uint48) {
|
) internal virtual override(Governor, GovernorPreventLateQuorum) returns (uint256) {
|
||||||
return super._queueOperations(
|
return super._castVote(proposalId, account, support, reason, params);
|
||||||
proposalId,
|
|
||||||
targets,
|
|
||||||
values,
|
|
||||||
calldatas,
|
|
||||||
descriptionHash
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function _executeOperations(
|
function _voteSucceeded(uint256 proposalId)
|
||||||
uint256 proposalId,
|
|
||||||
address[] memory targets,
|
|
||||||
uint256[] memory values,
|
|
||||||
bytes[] memory calldatas,
|
|
||||||
bytes32 descriptionHash
|
|
||||||
) internal override(Governor, GovernorTimelockControl) {
|
|
||||||
super._executeOperations(
|
|
||||||
proposalId,
|
|
||||||
targets,
|
|
||||||
values,
|
|
||||||
calldatas,
|
|
||||||
descriptionHash
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function _cancel(
|
|
||||||
address[] memory targets,
|
|
||||||
uint256[] memory values,
|
|
||||||
bytes[] memory calldatas,
|
|
||||||
bytes32 descriptionHash
|
|
||||||
) internal override(Governor, GovernorTimelockControl) returns (uint256) {
|
|
||||||
return super._cancel(targets, values, calldatas, descriptionHash);
|
|
||||||
}
|
|
||||||
|
|
||||||
function _executor()
|
|
||||||
internal
|
internal
|
||||||
view
|
view
|
||||||
override(Governor, GovernorTimelockControl)
|
virtual
|
||||||
returns (address)
|
override(Governor, GovernorGhostCounting)
|
||||||
|
returns (bool)
|
||||||
{
|
{
|
||||||
return super._executor();
|
uint256 totalVotes = token().getPastTotalSupply(proposalSnapshot(proposalId));
|
||||||
|
(uint256 againstVotes, uint256 forVotes,) = proposalVotes(proposalId);
|
||||||
|
uint256 rhs = 3 * (5 * againstVotes + forVotes) * Babylonian.sqrt(totalVotes);
|
||||||
|
uint256 lhs = (forVotes + againstVotes);
|
||||||
|
lhs = lhs * Babylonian.sqrt(80 * lhs + totalVotes);
|
||||||
|
return lhs > rhs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
76
src/governance/GovernorGhostCounting.sol
Normal file
76
src/governance/GovernorGhostCounting.sol
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
pragma solidity ^0.8.20;
|
||||||
|
|
||||||
|
import {Governor} from "@openzeppelin-contracts/governance/Governor.sol";
|
||||||
|
|
||||||
|
abstract contract GovernorGhostCounting is Governor {
|
||||||
|
enum VoteType {
|
||||||
|
Against,
|
||||||
|
For,
|
||||||
|
Abstain
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ProposalVote {
|
||||||
|
uint256 againstVotes;
|
||||||
|
uint256 forVotes;
|
||||||
|
uint256 abstainVotes;
|
||||||
|
mapping(address voter => uint8) votes;
|
||||||
|
}
|
||||||
|
|
||||||
|
mapping(uint256 proposalId => ProposalVote) private _proposalVotes;
|
||||||
|
|
||||||
|
// solhint-disable-next-line func-name-mixedcase
|
||||||
|
function COUNTING_MODE() public pure virtual override returns (string memory) {
|
||||||
|
return "support=bravo&quorum=for";
|
||||||
|
}
|
||||||
|
|
||||||
|
function voteOf(uint256 proposalId, address account) public view virtual returns (uint8) {
|
||||||
|
return _proposalVotes[proposalId].votes[account];
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasVoted(uint256 proposalId, address account) public view virtual override returns (bool) {
|
||||||
|
return _proposalVotes[proposalId].votes[account] > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function proposalVotes(
|
||||||
|
uint256 proposalId
|
||||||
|
) public view virtual returns (uint256 againstVotes, uint256 forVotes, uint256 abstainVotes) {
|
||||||
|
ProposalVote storage proposalVote = _proposalVotes[proposalId];
|
||||||
|
return (proposalVote.againstVotes, proposalVote.forVotes, proposalVote.abstainVotes);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _quorumReached(uint256 proposalId) internal view virtual override returns (bool) {
|
||||||
|
ProposalVote storage proposalVote = _proposalVotes[proposalId];
|
||||||
|
uint256 totalVotes = proposalVote.forVotes + proposalVote.againstVotes;
|
||||||
|
return quorum(proposalSnapshot(proposalId)) <= totalVotes;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _voteSucceeded(uint256 proposalId) internal view virtual override returns (bool) {
|
||||||
|
ProposalVote storage proposalVote = _proposalVotes[proposalId];
|
||||||
|
return proposalVote.forVotes > proposalVote.againstVotes;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _countVote(
|
||||||
|
uint256 proposalId,
|
||||||
|
address account,
|
||||||
|
uint8 support,
|
||||||
|
uint256 weight,
|
||||||
|
bytes memory // params
|
||||||
|
) internal virtual override {
|
||||||
|
ProposalVote storage proposalVote = _proposalVotes[proposalId];
|
||||||
|
|
||||||
|
if (proposalVote.votes[account] > 0) {
|
||||||
|
revert GovernorAlreadyCastVote(account);
|
||||||
|
}
|
||||||
|
proposalVote.votes[account] = support + 1;
|
||||||
|
|
||||||
|
if (support == uint8(VoteType.Against)) {
|
||||||
|
proposalVote.againstVotes += weight;
|
||||||
|
} else if (support == uint8(VoteType.For)) {
|
||||||
|
proposalVote.forVotes += weight;
|
||||||
|
} else {
|
||||||
|
revert GovernorInvalidVoteType();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
559
test/governance/GhostGovernor.t.sol
Normal file
559
test/governance/GhostGovernor.t.sol
Normal file
@ -0,0 +1,559 @@
|
|||||||
|
pragma solidity 0.8.20;
|
||||||
|
|
||||||
|
import {Test} from "forge-std/Test.sol";
|
||||||
|
import {Strings} from "@openzeppelin-contracts/utils/Strings.sol";
|
||||||
|
|
||||||
|
import "../../src/governance/GhostGovernor.sol";
|
||||||
|
import "../../src/GhstERC20.sol";
|
||||||
|
import "../../src/StinkyERC20.sol";
|
||||||
|
|
||||||
|
contract GhostGovernorExposed is GhostGovernor {
|
||||||
|
constructor(
|
||||||
|
IVotes _ghst,
|
||||||
|
uint48 _initialVoteExtension,
|
||||||
|
uint48 _initialVotingDelay,
|
||||||
|
uint32 _initialVotingPeriod,
|
||||||
|
uint256 _initialProposalThreshold,
|
||||||
|
uint256 _quorumFraction
|
||||||
|
) GhostGovernor(
|
||||||
|
_ghst,
|
||||||
|
_initialVoteExtension,
|
||||||
|
_initialVotingDelay,
|
||||||
|
_initialVotingPeriod,
|
||||||
|
_initialProposalThreshold,
|
||||||
|
_quorumFraction
|
||||||
|
) {}
|
||||||
|
|
||||||
|
function isValidDescriptionForProposer(
|
||||||
|
address proposer,
|
||||||
|
string memory description
|
||||||
|
) public view returns (bool) {
|
||||||
|
return super._isValidDescriptionForProposer(proposer, description);
|
||||||
|
}
|
||||||
|
|
||||||
|
function quorumReached(uint256 proposalId) public view returns (bool) {
|
||||||
|
return super._quorumReached(proposalId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function voteSucceeded(uint256 proposalId) public view returns (bool) {
|
||||||
|
return super._voteSucceeded(proposalId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contract GhostGovernorTest is Test {
|
||||||
|
using Strings for address;
|
||||||
|
|
||||||
|
uint48 public constant VOTE_EXTENSION = 69;
|
||||||
|
uint48 public constant VOTING_DELAY = 420;
|
||||||
|
uint32 public constant VOTING_PERIOD = 1337;
|
||||||
|
uint256 public constant PROPOSAL_THRESHOLD = 69 * 1e18;
|
||||||
|
uint256 public constant QUORUM_FRACTION = 20; // percent
|
||||||
|
|
||||||
|
address constant init = 0x0000000000000000000000000000000000000001;
|
||||||
|
address constant alice = 0x0000000000000000000000000000000000000002;
|
||||||
|
address constant bob = 0x0000000000000000000000000000000000000003;
|
||||||
|
address constant carol = 0x0000000000000000000000000000000000000004;
|
||||||
|
address constant dave = 0x0000000000000000000000000000000000000005;
|
||||||
|
address constant eve = 0x0000000000000000000000000000000000000006;
|
||||||
|
|
||||||
|
GhostGovernorExposed public governor;
|
||||||
|
Stinky public stnk;
|
||||||
|
Ghost public ghst;
|
||||||
|
|
||||||
|
function setUp() public {
|
||||||
|
vm.startPrank(init);
|
||||||
|
stnk = new Stinky(10819917194513808e56, "Stinky Test Name", "STNKTST");
|
||||||
|
ghst = new Ghost(address(stnk), "Ghost Test Name", "GHSTTST");
|
||||||
|
ghst.initialize(init);
|
||||||
|
governor = new GhostGovernorExposed(
|
||||||
|
ghst,
|
||||||
|
VOTE_EXTENSION,
|
||||||
|
VOTING_DELAY,
|
||||||
|
VOTING_PERIOD,
|
||||||
|
PROPOSAL_THRESHOLD,
|
||||||
|
QUORUM_FRACTION
|
||||||
|
);
|
||||||
|
vm.stopPrank();
|
||||||
|
|
||||||
|
vm.prank(alice);
|
||||||
|
ghst.approve(address(governor), type(uint256).max);
|
||||||
|
|
||||||
|
vm.prank(bob);
|
||||||
|
ghst.approve(address(governor), type(uint256).max);
|
||||||
|
|
||||||
|
vm.prank(carol);
|
||||||
|
ghst.approve(address(governor), type(uint256).max);
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_correctGovernanceName() public view {
|
||||||
|
assertEq(governor.name(), "GhostGovernorV1");
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_correctGovernanceVersion() public view {
|
||||||
|
assertEq(governor.version(), "1");
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_correctGovernanceConfig() public view {
|
||||||
|
assertEq(governor.COUNTING_MODE(), "support=bravo&quorum=for");
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_correctGovernanceProposalThreshold() public view {
|
||||||
|
assertEq(governor.proposalThreshold(), PROPOSAL_THRESHOLD);
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_correctGovernanceVotingDelay() public view {
|
||||||
|
assertEq(governor.votingDelay(), VOTING_DELAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_correctGovernanceVotingPeriod() public view {
|
||||||
|
assertEq(governor.votingPeriod(), VOTING_PERIOD);
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_correctGovernanceVoteExtension() public view {
|
||||||
|
assertEq(governor.lateQuorumVoteExtension(), VOTE_EXTENSION);
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_correctGovernanceQuorumFraction() public view {
|
||||||
|
assertEq(governor.quorumNumerator(), QUORUM_FRACTION);
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_validDescriptionForProposer(
|
||||||
|
string memory description,
|
||||||
|
address proposer,
|
||||||
|
bool includeProposer
|
||||||
|
) public view {
|
||||||
|
string memory finalDescription = description;
|
||||||
|
|
||||||
|
if (includeProposer) {
|
||||||
|
finalDescription = string.concat(
|
||||||
|
description,
|
||||||
|
"#proposer=",
|
||||||
|
Strings.toHexString(proposer)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isValid = governor.isValidDescriptionForProposer(proposer, finalDescription);
|
||||||
|
assertTrue(isValid);
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_invalidDescriptionForProposer(
|
||||||
|
string memory description,
|
||||||
|
address commitProposer,
|
||||||
|
address actualProposer
|
||||||
|
) public view {
|
||||||
|
vm.assume(commitProposer != actualProposer);
|
||||||
|
|
||||||
|
string memory wrongDescription = string.concat(
|
||||||
|
description,
|
||||||
|
"#proposer=",
|
||||||
|
Strings.toHexString(commitProposer)
|
||||||
|
);
|
||||||
|
|
||||||
|
bool isValid = governor.isValidDescriptionForProposer(actualProposer, wrongDescription);
|
||||||
|
assertFalse(isValid);
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_succeededOnAbsoluteMajority() public {
|
||||||
|
vm.startPrank(init);
|
||||||
|
ghst.mint(alice, PROPOSAL_THRESHOLD);
|
||||||
|
ghst.mint(bob, 555 * 1e17);
|
||||||
|
ghst.mint(carol, 345 * 1e17 + 1);
|
||||||
|
ghst.mint(dave, 21 * 1e18);
|
||||||
|
vm.stopPrank();
|
||||||
|
|
||||||
|
vm.roll(block.number + 1);
|
||||||
|
|
||||||
|
(uint256 proposalId,,,,) = _proposeDummy(alice, 69);
|
||||||
|
_waitForActive(proposalId);
|
||||||
|
|
||||||
|
assertEq(uint8(governor.state(proposalId)), uint8(IGovernor.ProposalState.Active));
|
||||||
|
_castVoteWrapper(proposalId, bob, 1, true, true);
|
||||||
|
assertEq(uint8(governor.state(proposalId)), uint8(IGovernor.ProposalState.Active));
|
||||||
|
_castVoteWrapper(proposalId, carol, 1, true, true);
|
||||||
|
assertEq(uint8(governor.state(proposalId)), uint8(IGovernor.ProposalState.Succeeded));
|
||||||
|
|
||||||
|
vm.expectRevert();
|
||||||
|
vm.prank(dave);
|
||||||
|
governor.castVote(proposalId, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_defeatedOnAbsoluteMajority() public {
|
||||||
|
vm.startPrank(init);
|
||||||
|
ghst.mint(alice, PROPOSAL_THRESHOLD);
|
||||||
|
ghst.mint(bob, 555 * 1e17);
|
||||||
|
ghst.mint(carol, 345 * 1e17 + 1);
|
||||||
|
ghst.mint(dave, 21 * 1e18);
|
||||||
|
vm.stopPrank();
|
||||||
|
|
||||||
|
vm.roll(block.number + 1);
|
||||||
|
|
||||||
|
(uint256 proposalId,,,,) = _proposeDummy(alice, 69);
|
||||||
|
_waitForActive(proposalId);
|
||||||
|
|
||||||
|
assertEq(uint8(governor.state(proposalId)), uint8(IGovernor.ProposalState.Active));
|
||||||
|
_castVoteWrapper(proposalId, bob, 0, true, false);
|
||||||
|
assertEq(uint8(governor.state(proposalId)), uint8(IGovernor.ProposalState.Active));
|
||||||
|
_castVoteWrapper(proposalId, carol, 0, true, false);
|
||||||
|
assertEq(uint8(governor.state(proposalId)), uint8(IGovernor.ProposalState.Defeated));
|
||||||
|
|
||||||
|
vm.expectRevert();
|
||||||
|
vm.prank(dave);
|
||||||
|
governor.castVote(proposalId, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_onlyOneActiveProposal() public {
|
||||||
|
uint256 amount = PROPOSAL_THRESHOLD;
|
||||||
|
vm.startPrank(init);
|
||||||
|
ghst.mint(alice, amount);
|
||||||
|
ghst.mint(bob, 2 * amount);
|
||||||
|
ghst.mint(carol, 3 * amount);
|
||||||
|
vm.stopPrank();
|
||||||
|
|
||||||
|
vm.roll(block.number + 1);
|
||||||
|
|
||||||
|
(uint256 proposalId,,,,) = _proposeDummy(alice, 69);
|
||||||
|
_waitForActive(proposalId);
|
||||||
|
|
||||||
|
vm.prank(alice);
|
||||||
|
assertEq(governor.releaseLocked(proposalId), 0);
|
||||||
|
assertEq(governor.lockedAmounts(proposalId), amount);
|
||||||
|
assertEq(ghst.balanceOf(alice), 0);
|
||||||
|
|
||||||
|
_castVoteWrapper(proposalId, alice, 1, false, false);
|
||||||
|
(uint256 againstVotes, uint256 forVotes, uint256 abstainVotes) = governor.proposalVotes(proposalId);
|
||||||
|
assertEq(againstVotes, 0);
|
||||||
|
assertEq(forVotes, 0);
|
||||||
|
assertEq(abstainVotes, 0);
|
||||||
|
|
||||||
|
_castVoteWrapper(proposalId, bob, 1, true, true);
|
||||||
|
(againstVotes, forVotes, abstainVotes) = governor.proposalVotes(proposalId);
|
||||||
|
assertEq(againstVotes, 0);
|
||||||
|
assertEq(forVotes, 2 * amount);
|
||||||
|
assertEq(abstainVotes, 0);
|
||||||
|
|
||||||
|
assertEq(ghst.balanceOf(alice), 0);
|
||||||
|
assertEq(ghst.balanceOf(bob), 2 * amount);
|
||||||
|
assertEq(ghst.balanceOf(carol), 3 * amount);
|
||||||
|
|
||||||
|
_waitForSucceed(proposalId);
|
||||||
|
|
||||||
|
vm.prank(alice);
|
||||||
|
assertEq(governor.releaseLocked(proposalId), amount);
|
||||||
|
assertEq(governor.lockedAmounts(proposalId), 0);
|
||||||
|
assertEq(ghst.balanceOf(alice), amount);
|
||||||
|
|
||||||
|
vm.prank(alice);
|
||||||
|
assertEq(governor.releaseLocked(proposalId), 0);
|
||||||
|
assertEq(governor.lockedAmounts(proposalId), 0);
|
||||||
|
assertEq(ghst.balanceOf(alice), amount);
|
||||||
|
|
||||||
|
assertEq(ghst.balanceOf(alice), amount);
|
||||||
|
assertEq(ghst.balanceOf(bob), 2 * amount);
|
||||||
|
assertEq(ghst.balanceOf(carol), 3 * amount);
|
||||||
|
|
||||||
|
(proposalId,,,,) = _proposeDummy(bob, 420);
|
||||||
|
_waitForActive(proposalId);
|
||||||
|
|
||||||
|
_castVoteWrapper(proposalId, alice, 1, false, true);
|
||||||
|
_castVoteWrapper(proposalId, carol, 1, true, true);
|
||||||
|
|
||||||
|
vm.roll(block.number + 3);
|
||||||
|
|
||||||
|
(uint256 newProposalId,,,,) = _proposeDummy(carol, 1337);
|
||||||
|
assertEq(uint8(governor.state(proposalId)), uint8(IGovernor.ProposalState.Canceled));
|
||||||
|
assertEq(uint8(governor.state(newProposalId)), uint8(IGovernor.ProposalState.Pending));
|
||||||
|
|
||||||
|
assertEq(ghst.balanceOf(alice), amount);
|
||||||
|
assertEq(ghst.balanceOf(bob), 0);
|
||||||
|
assertEq(ghst.balanceOf(carol), 0);
|
||||||
|
|
||||||
|
vm.prank(bob);
|
||||||
|
assertEq(governor.releaseLocked(proposalId), 0);
|
||||||
|
assertEq(governor.lockedAmounts(proposalId), 2 * amount);
|
||||||
|
assertEq(ghst.balanceOf(bob), 0);
|
||||||
|
|
||||||
|
vm.roll(governor.proposalDeadline(proposalId) + 1);
|
||||||
|
|
||||||
|
vm.prank(bob);
|
||||||
|
assertEq(governor.releaseLocked(proposalId), 2 * amount);
|
||||||
|
assertEq(governor.lockedAmounts(proposalId), 0);
|
||||||
|
assertEq(ghst.balanceOf(bob), 2 * amount);
|
||||||
|
|
||||||
|
vm.prank(carol);
|
||||||
|
assertEq(governor.releaseLocked(newProposalId), 0);
|
||||||
|
assertEq(governor.lockedAmounts(newProposalId), 3 * amount);
|
||||||
|
assertEq(ghst.balanceOf(carol), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_proposalCountWorks() public {
|
||||||
|
assertEq(governor.proposalCount(), 0);
|
||||||
|
|
||||||
|
uint256 i = 1;
|
||||||
|
for (; i < 69; ) {
|
||||||
|
{
|
||||||
|
uint256 amount = governor.activeProposedLock() + PROPOSAL_THRESHOLD;
|
||||||
|
|
||||||
|
if (amount + ghst.totalSupply() > type(uint208).max) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
vm.prank(init);
|
||||||
|
ghst.mint(alice, amount);
|
||||||
|
vm.roll(block.number + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
(
|
||||||
|
uint256 proposalId,
|
||||||
|
address[] memory targets,
|
||||||
|
uint256[] memory values,
|
||||||
|
bytes[] memory calldatas,
|
||||||
|
string memory description
|
||||||
|
) = _proposeDummy(alice, i);
|
||||||
|
|
||||||
|
{
|
||||||
|
(
|
||||||
|
address[] memory thisTargets,
|
||||||
|
uint256[] memory thisValues,
|
||||||
|
bytes[] memory thisCalldatas,
|
||||||
|
bytes32 thisDescriptionHash
|
||||||
|
) = governor.proposalDetails(proposalId);
|
||||||
|
|
||||||
|
assertEq(thisTargets.length, targets.length);
|
||||||
|
assertEq(thisValues.length, values.length);
|
||||||
|
assertEq(thisCalldatas.length, calldatas.length);
|
||||||
|
|
||||||
|
assertEq(thisTargets[0], targets[0]);
|
||||||
|
assertEq(thisValues[0], values[0]);
|
||||||
|
assertEq(thisCalldatas[0], calldatas[0]);
|
||||||
|
assertEq(thisDescriptionHash, keccak256(bytes(description)));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
(
|
||||||
|
uint256 otherProposalId,
|
||||||
|
address[] memory otherTargets,
|
||||||
|
uint256[] memory otherValues,
|
||||||
|
bytes[] memory otherCalldatas,
|
||||||
|
bytes32 otherDescriptionHash
|
||||||
|
) = governor.proposalDetailsAt(i - 1);
|
||||||
|
|
||||||
|
assertEq(otherTargets.length, targets.length);
|
||||||
|
assertEq(otherValues.length, values.length);
|
||||||
|
assertEq(otherCalldatas.length, calldatas.length);
|
||||||
|
|
||||||
|
assertEq(otherProposalId, governor.hashProposal(targets, values, calldatas, keccak256(bytes(description))));
|
||||||
|
assertEq(otherTargets[0], targets[0]);
|
||||||
|
assertEq(otherValues[0], values[0]);
|
||||||
|
assertEq(otherCalldatas[0], calldatas[0]);
|
||||||
|
assertEq(otherDescriptionHash, keccak256(bytes(description)));
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEq(governor.proposalCount(), i);
|
||||||
|
unchecked { ++i; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_preventLateQuorumWorks() public {
|
||||||
|
vm.startPrank(init);
|
||||||
|
ghst.mint(alice, PROPOSAL_THRESHOLD);
|
||||||
|
ghst.mint(bob, 50 * 1e18);
|
||||||
|
ghst.mint(carol, 100 * 1e18);
|
||||||
|
ghst.mint(dave, 100 * 1e18);
|
||||||
|
ghst.mint(eve, 500 * 1e18);
|
||||||
|
vm.stopPrank();
|
||||||
|
|
||||||
|
vm.roll(block.number + 1);
|
||||||
|
(uint256 proposalId,,,,) = _proposeDummy(alice, 69);
|
||||||
|
|
||||||
|
assertEq(uint8(governor.state(proposalId)), uint8(IGovernor.ProposalState.Pending));
|
||||||
|
vm.roll(block.number + VOTING_DELAY + 1);
|
||||||
|
assertEq(uint8(governor.state(proposalId)), uint8(IGovernor.ProposalState.Active));
|
||||||
|
|
||||||
|
assertEq(governor.voteOf(proposalId, bob), 0);
|
||||||
|
assertEq(governor.voteOf(proposalId, carol), 0);
|
||||||
|
assertEq(governor.voteOf(proposalId, dave), 0);
|
||||||
|
assertEq(governor.voteOf(proposalId, eve), 0);
|
||||||
|
|
||||||
|
vm.roll(governor.proposalDeadline(proposalId));
|
||||||
|
_castVoteWrapper(proposalId, bob, 1, false, false);
|
||||||
|
vm.roll(governor.proposalDeadline(proposalId));
|
||||||
|
_castVoteWrapper(proposalId, carol, 0, false, false);
|
||||||
|
vm.roll(governor.proposalDeadline(proposalId));
|
||||||
|
_castVoteWrapper(proposalId, dave, 1, true, false);
|
||||||
|
vm.roll(governor.proposalDeadline(proposalId));
|
||||||
|
_castVoteWrapper(proposalId, eve, 1, true, true);
|
||||||
|
|
||||||
|
vm.prank(alice);
|
||||||
|
governor.execute(proposalId);
|
||||||
|
assertEq(uint8(governor.state(proposalId)), uint8(IGovernor.ProposalState.Executed));
|
||||||
|
|
||||||
|
assertEq(governor.voteOf(proposalId, bob), 2);
|
||||||
|
assertEq(governor.voteOf(proposalId, carol), 1);
|
||||||
|
assertEq(governor.voteOf(proposalId, dave), 2);
|
||||||
|
assertEq(governor.voteOf(proposalId, eve), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_quorumNumeratorWorks() public {
|
||||||
|
uint256 numerator = governor.quorumNumerator();
|
||||||
|
uint256 denominator = governor.quorumDenominator();
|
||||||
|
uint256 currentBlock = block.number;
|
||||||
|
|
||||||
|
vm.startPrank(init);
|
||||||
|
ghst.mint(alice, 1 * 1e18);
|
||||||
|
ghst.mint(bob, 69 * 1e18);
|
||||||
|
ghst.mint(carol, 420 * 1e18);
|
||||||
|
ghst.mint(dave, 1337 * 1e18);
|
||||||
|
ghst.mint(eve, 69420 * 1e18);
|
||||||
|
vm.stopPrank();
|
||||||
|
|
||||||
|
vm.roll(currentBlock + 2);
|
||||||
|
assertEq(governor.quorum(currentBlock), 71247 * 1e18 * numerator / denominator);
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_votingPowerTransfer() public {
|
||||||
|
vm.startPrank(init);
|
||||||
|
ghst.mint(alice, 35 * 1e18);
|
||||||
|
ghst.mint(bob, 34 * 1e18);
|
||||||
|
vm.stopPrank();
|
||||||
|
|
||||||
|
vm.roll(block.number + 1);
|
||||||
|
|
||||||
|
vm.expectRevert();
|
||||||
|
vm.prank(alice);
|
||||||
|
ghst.delegate(alice);
|
||||||
|
|
||||||
|
vm.expectRevert();
|
||||||
|
vm.prank(bob);
|
||||||
|
ghst.delegate(bob);
|
||||||
|
|
||||||
|
_assertVotesEqualToBalance();
|
||||||
|
|
||||||
|
vm.prank(alice);
|
||||||
|
ghst.transfer(bob, 420);
|
||||||
|
_assertVotesEqualToBalance();
|
||||||
|
|
||||||
|
uint256 aliceBalance = ghst.balanceOf(alice);
|
||||||
|
vm.prank(alice);
|
||||||
|
ghst.transfer(bob, aliceBalance);
|
||||||
|
_assertVotesEqualToBalance();
|
||||||
|
|
||||||
|
vm.prank(bob);
|
||||||
|
ghst.transfer(carol, 1337);
|
||||||
|
_assertVotesEqualToBalance();
|
||||||
|
|
||||||
|
uint256 bobBalance = ghst.balanceOf(bob);
|
||||||
|
vm.prank(bob);
|
||||||
|
ghst.transfer(carol, bobBalance);
|
||||||
|
_assertVotesEqualToBalance();
|
||||||
|
|
||||||
|
vm.expectRevert();
|
||||||
|
vm.prank(carol);
|
||||||
|
ghst.delegate(carol);
|
||||||
|
|
||||||
|
assertEq(ghst.getVotes(alice), 0);
|
||||||
|
assertEq(ghst.getVotes(bob), 0);
|
||||||
|
assertEq(ghst.getVotes(carol), 69 * 1e18);
|
||||||
|
|
||||||
|
assertEq(ghst.balanceOf(alice), 0);
|
||||||
|
assertEq(ghst.balanceOf(bob), 0);
|
||||||
|
assertEq(ghst.balanceOf(carol), 69 * 1e18);
|
||||||
|
|
||||||
|
vm.prank(init);
|
||||||
|
ghst.burn(carol, 69 * 1e18);
|
||||||
|
|
||||||
|
assertEq(ghst.getVotes(carol), 0);
|
||||||
|
assertEq(ghst.balanceOf(carol), 0);
|
||||||
|
assertEq(ghst.totalSupply(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_changesOfTotalSupplyHasNoAffectOnPastTotalSupply() public {
|
||||||
|
vm.prank(init);
|
||||||
|
ghst.mint(alice, 100);
|
||||||
|
|
||||||
|
vm.roll(block.number + 1);
|
||||||
|
assertEq(ghst.getPastTotalSupply(block.number - 1), 100);
|
||||||
|
assertEq(ghst.totalSupply(), 100);
|
||||||
|
|
||||||
|
vm.prank(init);
|
||||||
|
ghst.burn(alice, 50);
|
||||||
|
|
||||||
|
vm.roll(block.number + 1);
|
||||||
|
assertEq(ghst.getPastTotalSupply(block.number - 2), 100);
|
||||||
|
assertEq(ghst.getPastTotalSupply(block.number - 1), 50);
|
||||||
|
assertEq(ghst.totalSupply(), 50);
|
||||||
|
|
||||||
|
vm.prank(init);
|
||||||
|
ghst.mint(bob, 150);
|
||||||
|
|
||||||
|
vm.roll(block.number + 1);
|
||||||
|
assertEq(ghst.getPastTotalSupply(block.number - 3), 100);
|
||||||
|
assertEq(ghst.getPastTotalSupply(block.number - 2), 50);
|
||||||
|
assertEq(ghst.getPastTotalSupply(block.number - 1), 200);
|
||||||
|
assertEq(ghst.totalSupply(), 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _proposeDummy(address who, uint256 index)
|
||||||
|
private
|
||||||
|
returns (
|
||||||
|
uint256 proposalId,
|
||||||
|
address[] memory targets,
|
||||||
|
uint256[] memory values,
|
||||||
|
bytes[] memory calldatas,
|
||||||
|
string memory description
|
||||||
|
)
|
||||||
|
{
|
||||||
|
targets = new address[](1);
|
||||||
|
targets[0] = address(uint160(index + 1));
|
||||||
|
|
||||||
|
values = new uint256[](1);
|
||||||
|
values[0] = 0;
|
||||||
|
|
||||||
|
calldatas = new bytes[](1);
|
||||||
|
calldatas[0] = abi.encodeWithSignature("setVotingDelay(uint48)", 0);
|
||||||
|
|
||||||
|
description = string.concat("Proposal #", Strings.toString(index));
|
||||||
|
|
||||||
|
vm.prank(who);
|
||||||
|
proposalId = governor.propose(
|
||||||
|
targets,
|
||||||
|
values,
|
||||||
|
calldatas,
|
||||||
|
description
|
||||||
|
);
|
||||||
|
|
||||||
|
vm.roll(block.number + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _castVoteWrapper(
|
||||||
|
uint256 proposalId,
|
||||||
|
address who,
|
||||||
|
uint8 vote,
|
||||||
|
bool isQuorumReached,
|
||||||
|
bool isVoteSucceeded
|
||||||
|
) private {
|
||||||
|
assertEq(governor.hasVoted(proposalId, who), false);
|
||||||
|
vm.prank(who);
|
||||||
|
governor.castVote(proposalId, vote);
|
||||||
|
assertEq(governor.hasVoted(proposalId, who), true);
|
||||||
|
assertEq(governor.quorumReached(proposalId), isQuorumReached);
|
||||||
|
assertEq(governor.voteSucceeded(proposalId), isVoteSucceeded);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _assertVotesEqualToBalance() private view {
|
||||||
|
assertEq(ghst.getVotes(alice), ghst.balanceOf(alice));
|
||||||
|
assertEq(ghst.getVotes(bob), ghst.balanceOf(bob));
|
||||||
|
assertEq(ghst.getVotes(carol), ghst.balanceOf(carol));
|
||||||
|
}
|
||||||
|
|
||||||
|
function _waitForActive(uint256 proposalId) private {
|
||||||
|
assertEq(uint8(governor.state(proposalId)), uint8(IGovernor.ProposalState.Pending));
|
||||||
|
vm.roll(block.number + VOTING_DELAY + 1);
|
||||||
|
assertEq(uint8(governor.state(proposalId)), uint8(IGovernor.ProposalState.Active));
|
||||||
|
}
|
||||||
|
|
||||||
|
function _waitForSucceed(uint256 proposalId) private {
|
||||||
|
assertEq(uint8(governor.state(proposalId)), uint8(IGovernor.ProposalState.Active));
|
||||||
|
vm.roll(governor.proposalDeadline(proposalId) + 1);
|
||||||
|
assertEq(uint8(governor.state(proposalId)), uint8(IGovernor.ProposalState.Succeeded));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -39,12 +39,11 @@ abstract contract ERC20VotesTest is Test {
|
|||||||
|
|
||||||
function test_votes_delegate() external {
|
function test_votes_delegate() external {
|
||||||
// case 1: first delegation without balance
|
// case 1: first delegation without balance
|
||||||
assertEq(tokenVotes.delegates(address(this)), address(0));
|
assertEq(tokenVotes.delegates(address(this)), address(this));
|
||||||
vm.expectEmit(true, true, true, false, address(tokenVotes));
|
|
||||||
emit DelegateChanged(address(this), address(0), aliceVotes);
|
|
||||||
|
|
||||||
|
vm.expectRevert();
|
||||||
tokenVotes.delegate(aliceVotes);
|
tokenVotes.delegate(aliceVotes);
|
||||||
assertEq(tokenVotes.delegates(address(this)), aliceVotes);
|
assertEq(tokenVotes.delegates(address(this)), address(this));
|
||||||
// no votes in aliceVotes
|
// no votes in aliceVotes
|
||||||
assertEq(tokenVotes.getVotes(aliceVotes), 0);
|
assertEq(tokenVotes.getVotes(aliceVotes), 0);
|
||||||
// no Checkpoint generated
|
// no Checkpoint generated
|
||||||
@ -52,50 +51,43 @@ abstract contract ERC20VotesTest is Test {
|
|||||||
|
|
||||||
// case 2: first delegate with balance
|
// case 2: first delegate with balance
|
||||||
_mintVotesTokens(aliceVotes, amountVotes);
|
_mintVotesTokens(aliceVotes, amountVotes);
|
||||||
assertEq(tokenVotes.delegates(aliceVotes), address(0));
|
assertEq(tokenVotes.delegates(aliceVotes), address(aliceVotes));
|
||||||
|
|
||||||
vm.expectEmit(true, true, true, false, address(tokenVotes));
|
|
||||||
emit DelegateChanged(aliceVotes, address(0), bobVotes);
|
|
||||||
vm.expectEmit(true, false, false, true, address(tokenVotes));
|
|
||||||
emit DelegateVotesChanged(bobVotes, 0, amountVotes);
|
|
||||||
|
|
||||||
|
vm.expectRevert();
|
||||||
vm.prank(aliceVotes);
|
vm.prank(aliceVotes);
|
||||||
tokenVotes.delegate(bobVotes);
|
tokenVotes.delegate(bobVotes);
|
||||||
assertEq(tokenVotes.delegates(aliceVotes), bobVotes);
|
|
||||||
// amountVotes votes in bobVotes
|
assertEq(tokenVotes.delegates(aliceVotes), aliceVotes);
|
||||||
assertEq(tokenVotes.getVotes(bobVotes), amountVotes);
|
assertEq(tokenVotes.getVotes(aliceVotes), amountVotes);
|
||||||
|
assertEq(tokenVotes.getVotes(bobVotes), 0);
|
||||||
|
|
||||||
// 1 Checkpoint generated
|
// 1 Checkpoint generated
|
||||||
assertEq(tokenVotes.numCheckpoints(bobVotes), 1);
|
assertEq(tokenVotes.numCheckpoints(aliceVotes), 1);
|
||||||
Checkpoints.Checkpoint208 memory ckpt = tokenVotes.checkpoints(bobVotes, 0);
|
Checkpoints.Checkpoint208 memory ckpt = tokenVotes.checkpoints(aliceVotes, 0);
|
||||||
assertEq(ckpt._key, 1);
|
assertEq(ckpt._key, 1);
|
||||||
assertEq(ckpt._value, amountVotes);
|
assertEq(ckpt._value, amountVotes);
|
||||||
|
|
||||||
// case 3: delegate with balance not first
|
// case 3: delegate with balance not first
|
||||||
vm.roll(2);
|
vm.roll(2);
|
||||||
vm.expectEmit(true, true, true, false, address(tokenVotes));
|
|
||||||
emit DelegateChanged(aliceVotes, bobVotes, charlieVotes);
|
|
||||||
vm.expectEmit(true, false, false, true, address(tokenVotes));
|
|
||||||
emit DelegateVotesChanged(bobVotes, amountVotes, 0);
|
|
||||||
vm.expectEmit(true, false, false, true, address(tokenVotes));
|
|
||||||
emit DelegateVotesChanged(charlieVotes, 0, amountVotes);
|
|
||||||
|
|
||||||
|
vm.expectRevert();
|
||||||
vm.prank(aliceVotes);
|
vm.prank(aliceVotes);
|
||||||
tokenVotes.delegate(charlieVotes);
|
tokenVotes.delegate(charlieVotes);
|
||||||
assertEq(tokenVotes.delegates(aliceVotes), charlieVotes);
|
|
||||||
// amountVotes votes in charlieVotes
|
assertEq(tokenVotes.delegates(aliceVotes), aliceVotes);
|
||||||
assertEq(tokenVotes.getVotes(charlieVotes), amountVotes);
|
assertEq(tokenVotes.delegates(bobVotes), bobVotes);
|
||||||
// 1 Checkpoint generated
|
assertEq(tokenVotes.delegates(charlieVotes), charlieVotes);
|
||||||
assertEq(tokenVotes.numCheckpoints(charlieVotes), 1);
|
|
||||||
ckpt = tokenVotes.checkpoints(charlieVotes, 0);
|
assertEq(tokenVotes.getVotes(aliceVotes), amountVotes);
|
||||||
assertEq(ckpt._key, 2);
|
|
||||||
assertEq(ckpt._value, amountVotes);
|
|
||||||
// 0 votes in bobVotes
|
|
||||||
assertEq(tokenVotes.getVotes(bobVotes), 0);
|
assertEq(tokenVotes.getVotes(bobVotes), 0);
|
||||||
|
assertEq(tokenVotes.getVotes(charlieVotes), 0);
|
||||||
|
|
||||||
// 1 Checkpoint generated
|
// 1 Checkpoint generated
|
||||||
assertEq(tokenVotes.numCheckpoints(bobVotes), 2);
|
assertEq(tokenVotes.numCheckpoints(aliceVotes), 1);
|
||||||
ckpt = tokenVotes.checkpoints(bobVotes, 1);
|
ckpt = tokenVotes.checkpoints(aliceVotes, 0);
|
||||||
assertEq(ckpt._key, 2);
|
assertEq(ckpt._key, 1);
|
||||||
assertEq(ckpt._value, 0);
|
assertEq(ckpt._value, amountVotes);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function test_votes_mintAndBurnAndMaxSupply() external {
|
function test_votes_mintAndBurnAndMaxSupply() external {
|
||||||
@ -139,56 +131,73 @@ abstract contract ERC20VotesTest is Test {
|
|||||||
|
|
||||||
function test_votes_afterTokenTransfer() external {
|
function test_votes_afterTokenTransfer() external {
|
||||||
_mintVotesTokens(address(this), 100);
|
_mintVotesTokens(address(this), 100);
|
||||||
|
vm.expectRevert();
|
||||||
tokenVotes.delegate(aliceVotes);
|
tokenVotes.delegate(aliceVotes);
|
||||||
|
|
||||||
assertEq(tokenVotes.delegates(address(this)), aliceVotes);
|
assertEq(tokenVotes.delegates(address(this)), address(this));
|
||||||
assertEq(tokenVotes.numCheckpoints(aliceVotes), 1);
|
assertEq(tokenVotes.numCheckpoints(aliceVotes), 0);
|
||||||
|
assertEq(tokenVotes.numCheckpoints(address(this)), 1);
|
||||||
|
|
||||||
// test for {transfer}
|
// test for {transfer}
|
||||||
// case 1: 'to' has no delegatee
|
// case 1: 'to' has no delegatee
|
||||||
vm.roll(2);
|
vm.roll(2);
|
||||||
vm.expectEmit(true, false, false, true, address(tokenVotes));
|
vm.expectEmit(true, false, false, true, address(tokenVotes));
|
||||||
emit DelegateVotesChanged(aliceVotes, 100, 100 - 1);
|
emit DelegateVotesChanged(address(this), 100, 100 - 1);
|
||||||
|
|
||||||
vm.prank(address(this));
|
vm.prank(address(this));
|
||||||
tokenVotes.transfer(bobVotes, 1);
|
tokenVotes.transfer(bobVotes, 1);
|
||||||
assertEq(tokenVotes.getVotes(aliceVotes), 100 - 1);
|
assertEq(tokenVotes.getVotes(address(this)), 100 - 1);
|
||||||
|
|
||||||
// case 2: 'to' has a delegatee
|
// case 2: 'to' has a delegatee
|
||||||
_mintVotesTokens(charlieVotes, 100);
|
_mintVotesTokens(charlieVotes, 100);
|
||||||
|
vm.expectRevert();
|
||||||
vm.prank(charlieVotes);
|
vm.prank(charlieVotes);
|
||||||
tokenVotes.delegate(eveVotes);
|
tokenVotes.delegate(eveVotes);
|
||||||
|
|
||||||
vm.roll(3);
|
vm.roll(3);
|
||||||
vm.expectEmit(true, false, false, true, address(tokenVotes));
|
vm.expectEmit(true, false, false, true, address(tokenVotes));
|
||||||
emit DelegateVotesChanged(aliceVotes, 99, 99 - 1);
|
emit DelegateVotesChanged(charlieVotes, 100, 100 - 1);
|
||||||
vm.expectEmit(true, false, false, true, address(tokenVotes));
|
vm.expectEmit(true, false, false, true, address(tokenVotes));
|
||||||
emit DelegateVotesChanged(eveVotes, 100, 100 + 1);
|
emit DelegateVotesChanged(eveVotes, 0, 1);
|
||||||
|
|
||||||
tokenVotes.transfer(charlieVotes, 1);
|
vm.prank(charlieVotes);
|
||||||
assertEq(tokenVotes.getVotes(aliceVotes), 99 - 1);
|
tokenVotes.transfer(eveVotes, 1);
|
||||||
assertEq(tokenVotes.getVotes(eveVotes), 100 + 1);
|
assertEq(tokenVotes.getVotes(charlieVotes), 100 - 1);
|
||||||
|
assertEq(tokenVotes.getVotes(eveVotes), 1);
|
||||||
|
|
||||||
// test for {transferFrom}
|
// test for {transferFrom}
|
||||||
// case 3: 'to' has no delegatee
|
// case 3: 'to' has no delegatee
|
||||||
vm.roll(4);
|
vm.roll(4);
|
||||||
assertEq(tokenVotes.delegates(bobVotes), address(0));
|
assertEq(tokenVotes.delegates(aliceVotes), aliceVotes);
|
||||||
|
assertEq(tokenVotes.delegates(bobVotes), bobVotes);
|
||||||
|
assertEq(tokenVotes.delegates(charlieVotes), charlieVotes);
|
||||||
|
assertEq(tokenVotes.delegates(eveVotes), eveVotes);
|
||||||
|
|
||||||
tokenVotes.approve(aliceVotes, 100);
|
tokenVotes.approve(aliceVotes, 100);
|
||||||
|
assertEq(tokenVotes.delegates(aliceVotes), aliceVotes);
|
||||||
|
assertEq(tokenVotes.delegates(bobVotes), bobVotes);
|
||||||
|
assertEq(tokenVotes.delegates(charlieVotes), charlieVotes);
|
||||||
|
assertEq(tokenVotes.delegates(eveVotes), eveVotes);
|
||||||
|
|
||||||
vm.startPrank(aliceVotes);
|
vm.startPrank(aliceVotes);
|
||||||
vm.expectEmit(true, false, false, true, address(tokenVotes));
|
vm.expectEmit(true, false, false, true, address(tokenVotes));
|
||||||
emit DelegateVotesChanged(aliceVotes, 98, 98 - 1);
|
emit DelegateVotesChanged(address(this), 99, 99 - 1);
|
||||||
|
vm.expectEmit(true, false, false, true, address(tokenVotes));
|
||||||
|
emit DelegateVotesChanged(bobVotes, 1, 2);
|
||||||
|
|
||||||
tokenVotes.transferFrom(address(this), bobVotes, 1);
|
tokenVotes.transferFrom(address(this), bobVotes, 1);
|
||||||
|
|
||||||
// case 4: 'to' has a delegatee
|
// case 4: 'to' has a delegatee
|
||||||
vm.roll(5);
|
vm.roll(5);
|
||||||
assertEq(tokenVotes.delegates(charlieVotes), eveVotes);
|
assertEq(tokenVotes.delegates(aliceVotes), aliceVotes);
|
||||||
|
assertEq(tokenVotes.delegates(bobVotes), bobVotes);
|
||||||
|
assertEq(tokenVotes.delegates(charlieVotes), charlieVotes);
|
||||||
|
assertEq(tokenVotes.delegates(eveVotes), eveVotes);
|
||||||
|
|
||||||
vm.expectEmit(true, false, false, true, address(tokenVotes));
|
vm.expectEmit(true, false, false, true, address(tokenVotes));
|
||||||
emit DelegateVotesChanged(aliceVotes, 97, 97 - 1);
|
emit DelegateVotesChanged(address(this), 98, 98 - 1);
|
||||||
vm.expectEmit(true, false, false, true, address(tokenVotes));
|
vm.expectEmit(true, false, false, true, address(tokenVotes));
|
||||||
emit DelegateVotesChanged(eveVotes, 101, 101 + 1);
|
emit DelegateVotesChanged(charlieVotes, 99, 99 + 1);
|
||||||
|
|
||||||
tokenVotes.transferFrom(address(this), charlieVotes, 1);
|
tokenVotes.transferFrom(address(this), charlieVotes, 1);
|
||||||
}
|
}
|
||||||
@ -212,6 +221,7 @@ abstract contract ERC20VotesTest is Test {
|
|||||||
// 11 23 4
|
// 11 23 4
|
||||||
// 13 31 5
|
// 13 31 5
|
||||||
|
|
||||||
|
vm.expectRevert();
|
||||||
tokenVotes.delegate(aliceVotes);
|
tokenVotes.delegate(aliceVotes);
|
||||||
vm.roll(2);
|
vm.roll(2);
|
||||||
_mintVotesTokens(address(this), 10);
|
_mintVotesTokens(address(this), 10);
|
||||||
@ -228,33 +238,33 @@ abstract contract ERC20VotesTest is Test {
|
|||||||
vm.roll(20);
|
vm.roll(20);
|
||||||
|
|
||||||
// check {getPastVotes} && {getPastTotalSupply}
|
// check {getPastVotes} && {getPastTotalSupply}
|
||||||
assertEq(tokenVotes.numCheckpoints(aliceVotes), 6);
|
assertEq(tokenVotes.numCheckpoints(address(this)), 6);
|
||||||
|
|
||||||
assertEq(tokenVotes.getPastVotes(aliceVotes, 1), 0);
|
assertEq(tokenVotes.getPastVotes(address(this), 1), 0);
|
||||||
assertEq(tokenVotes.getPastTotalSupply(1), 0);
|
assertEq(tokenVotes.getPastTotalSupply(1), 0);
|
||||||
|
|
||||||
assertEq(tokenVotes.getPastVotes(aliceVotes, 2), 10);
|
assertEq(tokenVotes.getPastVotes(address(this), 2), 10);
|
||||||
assertEq(tokenVotes.getPastTotalSupply(2), 10);
|
assertEq(tokenVotes.getPastTotalSupply(2), 10);
|
||||||
|
|
||||||
assertEq(tokenVotes.getPastVotes(aliceVotes, 4), 15);
|
assertEq(tokenVotes.getPastVotes(address(this), 4), 15);
|
||||||
assertEq(tokenVotes.getPastTotalSupply(4), 15);
|
assertEq(tokenVotes.getPastTotalSupply(4), 15);
|
||||||
|
|
||||||
assertEq(tokenVotes.getPastVotes(aliceVotes, 6), 19);
|
assertEq(tokenVotes.getPastVotes(address(this), 6), 19);
|
||||||
assertEq(tokenVotes.getPastTotalSupply(6), 19);
|
assertEq(tokenVotes.getPastTotalSupply(6), 19);
|
||||||
|
|
||||||
assertEq(tokenVotes.getPastVotes(aliceVotes, 9), 19);
|
assertEq(tokenVotes.getPastVotes(address(this), 9), 19);
|
||||||
assertEq(tokenVotes.getPastTotalSupply(9), 19);
|
assertEq(tokenVotes.getPastTotalSupply(9), 19);
|
||||||
|
|
||||||
assertEq(tokenVotes.getPastVotes(aliceVotes, 10), 20);
|
assertEq(tokenVotes.getPastVotes(address(this), 10), 20);
|
||||||
assertEq(tokenVotes.getPastTotalSupply(10), 20);
|
assertEq(tokenVotes.getPastTotalSupply(10), 20);
|
||||||
|
|
||||||
assertEq(tokenVotes.getPastVotes(aliceVotes, 12), 23);
|
assertEq(tokenVotes.getPastVotes(address(this), 12), 23);
|
||||||
assertEq(tokenVotes.getPastTotalSupply(12), 23);
|
assertEq(tokenVotes.getPastTotalSupply(12), 23);
|
||||||
|
|
||||||
assertEq(tokenVotes.getPastVotes(aliceVotes, 13), 31);
|
assertEq(tokenVotes.getPastVotes(address(this), 13), 31);
|
||||||
assertEq(tokenVotes.getPastTotalSupply(13), 31);
|
assertEq(tokenVotes.getPastTotalSupply(13), 31);
|
||||||
|
|
||||||
assertEq(tokenVotes.getPastVotes(aliceVotes, 19), 31);
|
assertEq(tokenVotes.getPastVotes(address(this), 19), 31);
|
||||||
assertEq(tokenVotes.getPastTotalSupply(19), 31);
|
assertEq(tokenVotes.getPastTotalSupply(19), 31);
|
||||||
|
|
||||||
// revert if block not mined
|
// revert if block not mined
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user