Skip to content

Commit

Permalink
Add governance delay
Browse files Browse the repository at this point in the history
  • Loading branch information
neokry committed Dec 12, 2023
1 parent e81cfce commit 0980654
Show file tree
Hide file tree
Showing 9 changed files with 204 additions and 93 deletions.
27 changes: 10 additions & 17 deletions src/deployers/L2MigrationDeployer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pragma solidity 0.8.16;

import { IManager } from "../manager/IManager.sol";
import { IToken } from "../token/IToken.sol";
import { IGovernor } from "../governance/governor/IGovernor.sol";
import { IPropertyIPFSMetadataRenderer } from "../token/metadata/interfaces/IPropertyIPFSMetadataRenderer.sol";
import { MerkleReserveMinter } from "../minters/MerkleReserveMinter.sol";
import { TokenTypesV2 } from "../token/types/TokenTypesV2.sol";
Expand Down Expand Up @@ -68,11 +69,7 @@ contract L2MigrationDeployer {
/// CONSTRUCTOR ///
/// ///

constructor(
address _manager,
address _merkleMinter,
address _crossDomainMessenger
) {
constructor(address _manager, address _merkleMinter, address _crossDomainMessenger) {
manager = _manager;
merkleMinter = _merkleMinter;
crossDomainMessenger = _crossDomainMessenger;
Expand All @@ -89,19 +86,24 @@ contract L2MigrationDeployer {
/// @param _auctionParams The auction settings
/// @param _govParams The governance settings
/// @param _minterParams The minter settings
/// @param _delayedGovernanceAmount The amount of time to delay governance by
function deploy(
IManager.FounderParams[] calldata _founderParams,
IManager.TokenParams calldata _tokenParams,
IManager.AuctionParams calldata _auctionParams,
IManager.GovParams calldata _govParams,
MerkleReserveMinter.MerkleMinterSettings calldata _minterParams
MerkleReserveMinter.MerkleMinterSettings calldata _minterParams,
uint256 _delayedGovernanceAmount
) external returns (address token) {
if (_getTokenFromSender() != address(0)) {
revert DAO_ALREADY_DEPLOYED();
}

// Deploy the DAO
(address _token, , , , ) = IManager(manager).deploy(_founderParams, _tokenParams, _auctionParams, _govParams);
(address _token, , , , address _governor) = IManager(manager).deploy(_founderParams, _tokenParams, _auctionParams, _govParams);

// Set the governance expiration
IGovernor(_governor).updateDelayedGovernanceExpirationTimestamp(block.timestamp + _delayedGovernanceAmount);

// Setup minter settings to use the redeem minter
TokenTypesV2.MinterParams[] memory minters = new TokenTypesV2.MinterParams[](1);
Expand Down Expand Up @@ -208,16 +210,7 @@ contract L2MigrationDeployer {
return crossDomainDeployerToToken[_xMsgSender()];
}

function _getDAOAddressesFromSender()
private
returns (
address token,
address metadata,
address auction,
address treasury,
address governor
)
{
function _getDAOAddressesFromSender() private returns (address token, address metadata, address auction, address treasury, address governor) {
address _token = _getTokenFromSender();

// Revert if no token has been deployed
Expand Down
58 changes: 37 additions & 21 deletions src/governance/governor/Governor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { EIP712 } from "../../lib/utils/EIP712.sol";
import { SafeCast } from "../../lib/utils/SafeCast.sol";

import { GovernorStorageV1 } from "./storage/GovernorStorageV1.sol";
import { GovernorStorageV2 } from "./storage/GovernorStorageV2.sol";
import { Token } from "../../token/Token.sol";
import { Treasury } from "../treasury/Treasury.sol";
import { IManager } from "../../manager/IManager.sol";
Expand All @@ -21,7 +22,7 @@ import { VersionedContract } from "../../VersionedContract.sol";
/// Modified from:
/// - OpenZeppelin Contracts v4.7.3 (governance/extensions/GovernorTimelockControl.sol)
/// - NounsDAOLogicV1.sol commit 2cbe6c7 - licensed under the BSD-3-Clause license.
contract Governor is IGovernor, VersionedContract, UUPS, Ownable, EIP712, ProposalHasher, GovernorStorageV1 {
contract Governor is IGovernor, VersionedContract, UUPS, Ownable, EIP712, ProposalHasher, GovernorStorageV1, GovernorStorageV2 {
/// ///
/// IMMUTABLES ///
/// ///
Expand Down Expand Up @@ -53,6 +54,9 @@ contract Governor is IGovernor, VersionedContract, UUPS, Ownable, EIP712, Propos
/// @notice The maximum voting period setting
uint256 public immutable MAX_VOTING_PERIOD = 24 weeks;

/// @notice The maximum delayed governance expiration setting
uint256 public immutable MAX_DELAYED_GOVERNANCE_EXPIRATION = 30 days;

/// @notice The basis points for 100%
uint256 private immutable BPS_PER_100_PERCENT = 10_000;

Expand Down Expand Up @@ -137,6 +141,11 @@ contract Governor is IGovernor, VersionedContract, UUPS, Ownable, EIP712, Propos
bytes[] memory _calldatas,
string memory _description
) external returns (bytes32) {
// Ensure governance is not delayed or all reserved tokens have been minted
if (block.timestamp < delayedGovernanceExpirationTimestamp && settings.token.remainingTokensInReserve() > 0) {
revert WAITING_FOR_TOKENS_TO_CLAIM_OR_EXPIRATION();
}

// Get the current proposal threshold
uint256 currentProposalThreshold = proposalThreshold();

Expand Down Expand Up @@ -209,11 +218,7 @@ contract Governor is IGovernor, VersionedContract, UUPS, Ownable, EIP712, Propos
/// @param _proposalId The proposal id
/// @param _support The support value (0 = Against, 1 = For, 2 = Abstain)
/// @param _reason The vote reason
function castVoteWithReason(
bytes32 _proposalId,
uint256 _support,
string memory _reason
) external returns (uint256) {
function castVoteWithReason(bytes32 _proposalId, uint256 _support, string memory _reason) external returns (uint256) {
return _castVote(_proposalId, msg.sender, _support, _reason);
}

Expand Down Expand Up @@ -265,12 +270,7 @@ contract Governor is IGovernor, VersionedContract, UUPS, Ownable, EIP712, Propos
/// @param _proposalId The proposal id
/// @param _voter The voter address
/// @param _support The vote choice
function _castVote(
bytes32 _proposalId,
address _voter,
uint256 _support,
string memory _reason
) internal returns (uint256) {
function _castVote(bytes32 _proposalId, address _voter, uint256 _support, string memory _reason) internal returns (uint256) {
// Ensure voting is active
if (state(_proposalId) != ProposalState.Active) revert VOTING_NOT_STARTED();

Expand Down Expand Up @@ -518,15 +518,7 @@ contract Governor is IGovernor, VersionedContract, UUPS, Ownable, EIP712, Propos

/// @notice The vote counts for a proposal
/// @param _proposalId The proposal id
function proposalVotes(bytes32 _proposalId)
external
view
returns (
uint256,
uint256,
uint256
)
{
function proposalVotes(bytes32 _proposalId) external view returns (uint256, uint256, uint256) {
Proposal memory proposal = proposals[_proposalId];

return (proposal.againstVotes, proposal.forVotes, proposal.abstainVotes);
Expand Down Expand Up @@ -629,6 +621,30 @@ contract Governor is IGovernor, VersionedContract, UUPS, Ownable, EIP712, Propos
settings.quorumThresholdBps = uint16(_newQuorumVotesBps);
}

/// @notice Updates the delayed governance expiration timestamp
/// @param _newDelayedTimestamp The new delayed governance expiration timestamp
function updateDelayedGovernanceExpirationTimestamp(uint256 _newDelayedTimestamp) external {
// We want the founder to be able to set a governance delay if they are using a minter contract like MerkleReserveMinter
if (msg.sender != settings.token.owner()) {
revert ONLY_TOKEN_OWNER();
}

// Ensure the new timestamp is not too far in the future
if (_newDelayedTimestamp > block.timestamp + MAX_DELAYED_GOVERNANCE_EXPIRATION) {
revert INVALID_DELAYED_GOVERNANCE_EXPIRATION();
}

// Delay should only be set if no tokens have been minted to prevent active DAOs from accidentally or malicously enabling this funcationality
// Delay is only availible for DAOs that have reserved tokens
if (settings.token.totalSupply() > 0 || settings.token.reservedUntilTokenId() == 0) {
revert CANNOT_DELAY_GOVERNANCE();
}

emit DelayedGovernanceExpirationTimestampUpdated(delayedGovernanceExpirationTimestamp, _newDelayedTimestamp);

delayedGovernanceExpirationTimestamp = _newDelayedTimestamp;
}

/// @notice Updates the vetoer
/// @param _newVetoer The new vetoer address
function updateVetoer(address _newVetoer) external onlyOwner {
Expand Down
33 changes: 20 additions & 13 deletions src/governance/governor/IGovernor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ interface IGovernor is IUUPS, IOwnable, IEIP712, GovernorTypesV1 {
//// @notice Emitted when the governor's vetoer is updated
event VetoerUpdated(address prevVetoer, address newVetoer);

/// @notice Emitted when the governor's delay is updated
event DelayedGovernanceExpirationTimestampUpdated(uint256 prevTimestamp, uint256 newTimestamp);

/// ///
/// ERRORS ///
/// ///
Expand All @@ -69,6 +72,8 @@ interface IGovernor is IUUPS, IOwnable, IEIP712, GovernorTypesV1 {

error INVALID_VOTING_PERIOD();

error INVALID_DELAYED_GOVERNANCE_EXPIRATION();

/// @dev Reverts if a proposal already exists
/// @param proposalId The proposal id
error PROPOSAL_EXISTS(bytes32 proposalId);
Expand Down Expand Up @@ -110,6 +115,15 @@ interface IGovernor is IUUPS, IOwnable, IEIP712, GovernorTypesV1 {
/// @dev Reverts if a vote was attempted to be casted incorrectly
error INVALID_VOTE();

/// @dev Reverts if a proposal was attempted to be created before expiration or all tokens have been claimed
error WAITING_FOR_TOKENS_TO_CLAIM_OR_EXPIRATION();

/// @dev Reverts if governance cannot be delayed
error CANNOT_DELAY_GOVERNANCE();

/// @dev Reverts if the caller was not the token owner
error ONLY_TOKEN_OWNER();

/// @dev Reverts if the caller was not the contract manager
error ONLY_MANAGER();

Expand Down Expand Up @@ -156,11 +170,7 @@ interface IGovernor is IUUPS, IOwnable, IEIP712, GovernorTypesV1 {
/// @param proposalId The proposal id
/// @param support The support value (0 = Against, 1 = For, 2 = Abstain)
/// @param reason The vote reason
function castVoteWithReason(
bytes32 proposalId,
uint256 support,
string memory reason
) external returns (uint256);
function castVoteWithReason(bytes32 proposalId, uint256 support, string memory reason) external returns (uint256);

/// @notice Casts a signed vote
/// @param voter The voter address
Expand Down Expand Up @@ -235,14 +245,7 @@ interface IGovernor is IUUPS, IOwnable, IEIP712, GovernorTypesV1 {

/// @notice The vote counts for a proposal
/// @param proposalId The proposal id
function proposalVotes(bytes32 proposalId)
external
view
returns (
uint256 againstVotes,
uint256 forVotes,
uint256 abstainVotes
);
function proposalVotes(bytes32 proposalId) external view returns (uint256 againstVotes, uint256 forVotes, uint256 abstainVotes);

/// @notice The timestamp valid to execute a proposal
/// @param proposalId The proposal id
Expand Down Expand Up @@ -285,6 +288,10 @@ interface IGovernor is IUUPS, IOwnable, IEIP712, GovernorTypesV1 {
/// @param newQuorumVotesBps The new quorum votes basis points
function updateQuorumThresholdBps(uint256 newQuorumVotesBps) external;

/// @notice Updates the delayed governance expiration timestamp
/// @param _newDelayedTimestamp The new delayed governance expiration timestamp
function updateDelayedGovernanceExpirationTimestamp(uint256 _newDelayedTimestamp) external;

/// @notice Updates the vetoer
/// @param newVetoer The new vetoer addresss
function updateVetoer(address newVetoer) external;
Expand Down
10 changes: 10 additions & 0 deletions src/governance/governor/storage/GovernorStorageV2.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;

/// @title GovernorStorageV2
/// @author Neokry
/// @notice The Governor storage contract
contract GovernorStorageV2 {
/// @notice The delayed governance expiration timestamp
uint256 public delayedGovernanceExpirationTimestamp;
}
3 changes: 3 additions & 0 deletions src/token/IToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,9 @@ interface IToken is IUUPS, IERC721Votes, TokenTypesV1, TokenTypesV2 {
/// @param tokenId The ERC-721 token id
function getScheduledRecipient(uint256 tokenId) external view returns (Founder memory);

/// @notice The total number of tokens that can be claimed from the reserve
function remainingTokensInReserve() external view returns (uint256);

/// @notice The total supply of tokens
function totalSupply() external view returns (uint256);

Expand Down
9 changes: 9 additions & 0 deletions src/token/Token.sol
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,15 @@ contract Token is IToken, VersionedContract, UUPS, Ownable, ReentrancyGuard, ERC
return settings.totalSupply;
}

/// @notice The total number of tokens that can be claimed from the reserve
function remainingTokensInReserve() external view returns (uint256) {
// total supply - total minted from auctions and airdrops
uint256 totalMintedFromReserve = settings.totalSupply - settings.mintCount;

// reservedUntilTokenId is also the total number of tokens in the reserve since tokens 0 -> reservedUntilTokenId - 1 are reserved
return reservedUntilTokenId - totalMintedFromReserve;
}

/// @notice The address of the auction house
function auction() external view returns (address) {
return settings.auction;
Expand Down
Loading

0 comments on commit 0980654

Please sign in to comment.