// 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; } }