190 lines
7.0 KiB
Solidity
190 lines
7.0 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.20;
|
|
|
|
import {Governor} from "@openzeppelin-contracts/governance/Governor.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 {GovernorVotesQuorumFraction} from "@openzeppelin-contracts/governance/extensions/GovernorVotesQuorumFraction.sol";
|
|
import {IVotes} from "@openzeppelin-contracts/governance/utils/IVotes.sol";
|
|
import {ReentrancyGuard} from "@openzeppelin-contracts/utils/ReentrancyGuard.sol";
|
|
|
|
import {SafeERC20} from "@openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol";
|
|
import {IERC20} from "@openzeppelin-contracts/token/ERC20/IERC20.sol";
|
|
|
|
import {GovernorGhostCounting} from "./GovernorGhostCounting.sol";
|
|
import {Babylonian} from "../libraries/FixedPoint.sol";
|
|
|
|
contract GhostGovernor is
|
|
GovernorGhostCounting,
|
|
GovernorPreventLateQuorum,
|
|
GovernorSettings,
|
|
GovernorStorage,
|
|
GovernorVotes,
|
|
GovernorVotesQuorumFraction,
|
|
ReentrancyGuard
|
|
{
|
|
using SafeERC20 for IERC20;
|
|
|
|
uint256 public activeProposedLock;
|
|
uint256 private _lastProposalId;
|
|
mapping(uint256 => uint256) public lockedAmounts;
|
|
|
|
error ProposerNotEnoughVotes(uint256 proposalVotes, uint256 accumulatedVotes);
|
|
error InsufficientFundsToExtend(uint256 votes, uint256 neededVotes);
|
|
|
|
constructor(
|
|
IVotes _ghst,
|
|
uint48 _initialVoteExtension,
|
|
uint48 _initialVotingDelay,
|
|
uint32 _initialVotingPeriod,
|
|
uint256 _initialProposalThreshold,
|
|
uint256 _quorumFraction
|
|
)
|
|
Governor("GhostGovernorV1")
|
|
GovernorVotes(_ghst)
|
|
GovernorPreventLateQuorum(_initialVoteExtension)
|
|
GovernorSettings(_initialVotingDelay, _initialVotingPeriod, _initialProposalThreshold)
|
|
GovernorVotesQuorumFraction(_quorumFraction)
|
|
{}
|
|
|
|
function proposalThreshold()
|
|
public
|
|
view
|
|
virtual
|
|
override(Governor, GovernorSettings)
|
|
returns (uint256)
|
|
{
|
|
return super.proposalThreshold();
|
|
}
|
|
|
|
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);
|
|
|
|
bytes32 currentProposalState = _encodeStateBitmap(state(proposalId));
|
|
bytes32 activePendingMask =
|
|
_encodeStateBitmap(ProposalState.Pending) |
|
|
_encodeStateBitmap(ProposalState.Active);
|
|
|
|
if (releaseAmount > 0 && (currentProposalState & activePendingMask == 0)) {
|
|
lockedAmounts[proposalId] = 0;
|
|
IERC20(address(token())).safeTransfer(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);
|
|
}
|
|
|
|
uint256 proposalId = _lastProposalId;
|
|
if (proposalId != 0) {
|
|
bytes32 currentProposalState = _encodeStateBitmap(state(proposalId));
|
|
bytes32 activePendingMask =
|
|
_encodeStateBitmap(ProposalState.Pending) |
|
|
_encodeStateBitmap(ProposalState.Active);
|
|
|
|
if (currentProposalState & activePendingMask != 0) {
|
|
if (proposerVotes <= activeProposedLock) {
|
|
revert ProposerNotEnoughVotes(proposerVotes, activeProposedLock);
|
|
}
|
|
(
|
|
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);
|
|
_countVote(proposalId, proposer, 1, proposerVotes, "");
|
|
|
|
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,
|
|
address account,
|
|
uint8 support,
|
|
string memory reason,
|
|
bytes memory params
|
|
) internal virtual override(Governor, GovernorPreventLateQuorum) returns (uint256) {
|
|
return super._castVote(proposalId, account, support, reason, params);
|
|
}
|
|
|
|
function _voteSucceeded(uint256 proposalId)
|
|
internal
|
|
view
|
|
virtual
|
|
override(Governor, GovernorGhostCounting)
|
|
returns (bool)
|
|
{
|
|
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;
|
|
}
|
|
}
|