2021-03-22 07:45:44 +00:00
pragma solidity ^ 0 . 7 . 0 ;
pragma experimental ABIEncoderV2 ;
import {
GovernorBravoDelegateStorageV1 ,
GovernorBravoEvents ,
TimelockInterface ,
TokenInterface
} from " ./GovernorBravoInterfaces.sol " ;
import { SafeMath } from " ./SafeMath.sol " ;
2021-03-28 22:57:43 +00:00
contract InstaGovernorBravoDelegate is GovernorBravoDelegateStorageV1 , GovernorBravoEvents {
2021-03-22 07:45:44 +00:00
/// @notice The name of this contract
string public constant name = " DSL Governor Bravo " ;
/// @notice The minimum setable proposal threshold
2021-03-28 23:22:49 +00:00
uint public constant MIN_PROPOSAL_THRESHOLD = 500000 e18 ; // 500,000
2021-03-22 07:45:44 +00:00
/// @notice The maximum setable proposal threshold
2021-03-28 23:22:49 +00:00
uint public constant MAX_PROPOSAL_THRESHOLD = 50000000 e18 ; // 5,000,000
2021-03-22 07:45:44 +00:00
/// @notice The minimum setable voting period
uint public constant MIN_VOTING_PERIOD = 5760 ; // About 24 hours
/// @notice The max setable voting period
uint public constant MAX_VOTING_PERIOD = 80640 ; // About 2 weeks
/// @notice The min setable voting delay
uint public constant MIN_VOTING_DELAY = 1 ;
/// @notice The max setable voting delay
uint public constant MAX_VOTING_DELAY = 40320 ; // About 1 week
/// @notice The number of votes in support of a proposal required in order for a quorum to be reached and for a vote to succeed
2021-03-28 23:22:49 +00:00
uint public constant quorumVotes = 4000000 e18 ; // 4,000,000
2021-03-22 07:45:44 +00:00
/// @notice The maximum number of actions that can be included in a proposal
uint public constant proposalMaxOperations = 10 ; // 10 actions
/// @notice The EIP-712 typehash for the contract's domain
bytes32 public constant DOMAIN_TYPEHASH = keccak256 ( " EIP712Domain(string name,uint256 chainId,address verifyingContract) " ) ;
/// @notice The EIP-712 typehash for the ballot struct used by the contract
bytes32 public constant BALLOT_TYPEHASH = keccak256 ( " Ballot(uint256 proposalId,uint8 support) " ) ;
/**
* @ notice Used to initialize the contract during delegator contructor
* @ param timelock_ The address of the Timelock
* @ param token_ The address of the DSL Governance Token
* @ param votingPeriod_ The initial voting period
* @ param votingDelay_ The initial voting delay
* @ param proposalThreshold_ The initial proposal threshold
* /
function initialize ( address timelock_ , address token_ , uint votingPeriod_ , uint votingDelay_ , uint proposalThreshold_ ) public {
require ( address ( timelock ) == address ( 0 ) , " GovernorBravo::initialize: can only initialize once " ) ;
require ( msg . sender == admin , " GovernorBravo::initialize: admin only " ) ;
require ( timelock_ != address ( 0 ) , " GovernorBravo::initialize: invalid timelock address " ) ;
require ( token_ != address ( 0 ) , " GovernorBravo::initialize: invalid comp address " ) ;
require ( votingPeriod_ >= MIN_VOTING_PERIOD && votingPeriod_ <= MAX_VOTING_PERIOD , " GovernorBravo::initialize: invalid voting period " ) ;
require ( votingDelay_ >= MIN_VOTING_DELAY && votingDelay_ <= MAX_VOTING_DELAY , " GovernorBravo::initialize: invalid voting delay " ) ;
require ( proposalThreshold_ >= MIN_PROPOSAL_THRESHOLD && proposalThreshold_ <= MAX_PROPOSAL_THRESHOLD , " GovernorBravo::initialize: invalid proposal threshold " ) ;
timelock = TimelockInterface ( timelock_ ) ;
token = TokenInterface ( token_ ) ;
votingPeriod = votingPeriod_ ;
votingDelay = votingDelay_ ;
proposalThreshold = proposalThreshold_ ;
}
/**
* @ notice Function used to propose a new proposal . Sender must have delegates above the proposal threshold
* @ param targets Target addresses for proposal calls
* @ param values Eth values for proposal calls
* @ param signatures Function signatures for proposal calls
* @ param calldatas Calldatas for proposal calls
* @ param description String description of the proposal
* @ return Proposal id of new proposal
* /
function propose ( address [ ] memory targets , uint [ ] memory values , string [ ] memory signatures , bytes [ ] memory calldatas , string memory description ) public returns ( uint ) {
require ( token . getPriorVotes ( msg . sender , SafeMath . sub ( block . number , 1 ) ) > proposalThreshold , " GovernorBravo::propose: proposer votes below proposal threshold " ) ;
require ( targets . length == values . length && targets . length == signatures . length && targets . length == calldatas . length , " GovernorBravo::propose: proposal function information arity mismatch " ) ;
require ( targets . length != 0 , " GovernorBravo::propose: must provide actions " ) ;
require ( targets . length <= proposalMaxOperations , " GovernorBravo::propose: too many actions " ) ;
uint latestProposalId = latestProposalIds [ msg . sender ] ;
if ( latestProposalId != 0 ) {
ProposalState proposersLatestProposalState = state ( latestProposalId ) ;
require ( proposersLatestProposalState != ProposalState . Active , " GovernorBravo::propose: one live proposal per proposer, found an already active proposal " ) ;
require ( proposersLatestProposalState != ProposalState . Pending , " GovernorBravo::propose: one live proposal per proposer, found an already pending proposal " ) ;
}
uint startBlock = SafeMath . add ( block . number , votingDelay ) ;
uint endBlock = SafeMath . add ( startBlock , votingPeriod ) ;
proposalCount ++ ;
Proposal storage newProposal = proposals [ proposalCount ] ;
newProposal . id = proposalCount ;
newProposal . proposer = msg . sender ;
newProposal . targets = targets ;
newProposal . values = values ;
newProposal . signatures = signatures ;
newProposal . calldatas = calldatas ;
newProposal . startBlock = startBlock ;
newProposal . endBlock = endBlock ;
latestProposalIds [ newProposal . proposer ] = proposalCount ;
emit ProposalCreated ( proposalCount , msg . sender , targets , values , signatures , calldatas , startBlock , endBlock , description ) ;
return proposalCount ;
}
/**
* @ notice Queues a proposal of state succeeded
* @ param proposalId The id of the proposal to queue
* /
function queue ( uint proposalId ) external {
require ( state ( proposalId ) == ProposalState . Succeeded , " GovernorBravo::queue: proposal can only be queued if it is succeeded " ) ;
Proposal storage proposal = proposals [ proposalId ] ;
uint eta = SafeMath . add ( block . timestamp , timelock . delay ( ) ) ;
for ( uint i = 0 ; i < proposal . targets . length ; i ++ ) {
queueOrRevertInternal ( proposal . targets [ i ] , proposal . values [ i ] , proposal . signatures [ i ] , proposal . calldatas [ i ] , eta ) ;
}
proposal . eta = eta ;
emit ProposalQueued ( proposalId , eta ) ;
}
function queueOrRevertInternal ( address target , uint value , string memory signature , bytes memory data , uint eta ) internal {
require ( ! timelock . queuedTransactions ( keccak256 ( abi . encode ( target , value , signature , data , eta ) ) ) , " GovernorBravo::queueOrRevertInternal: identical proposal action already queued at eta " ) ;
timelock . queueTransaction ( target , value , signature , data , eta ) ;
}
/**
* @ notice Executes a queued proposal if eta has passed
* @ param proposalId The id of the proposal to execute
* /
function execute ( uint proposalId ) external payable {
require ( state ( proposalId ) == ProposalState . Queued , " GovernorBravo::execute: proposal can only be executed if it is queued " ) ;
Proposal storage proposal = proposals [ proposalId ] ;
proposal . executed = true ;
for ( uint i = 0 ; i < proposal . targets . length ; i ++ ) {
timelock . executeTransaction { value : proposal . values [ i ] } ( proposal . targets [ i ] , proposal . values [ i ] , proposal . signatures [ i ] , proposal . calldatas [ i ] , proposal . eta ) ;
}
emit ProposalExecuted ( proposalId ) ;
}
/**
* @ notice Cancels a proposal only if sender is the proposer , or proposer delegates dropped below proposal threshold
* @ param proposalId The id of the proposal to cancel
* /
function cancel ( uint proposalId ) external {
require ( state ( proposalId ) != ProposalState . Executed , " GovernorBravo::cancel: cannot cancel executed proposal " ) ;
Proposal storage proposal = proposals [ proposalId ] ;
require ( msg . sender == proposal . proposer || token . getPriorVotes ( proposal . proposer , SafeMath . sub ( block . number , 1 ) ) < proposalThreshold , " GovernorBravo::cancel: proposer above threshold " ) ;
proposal . canceled = true ;
for ( uint i = 0 ; i < proposal . targets . length ; i ++ ) {
timelock . cancelTransaction ( proposal . targets [ i ] , proposal . values [ i ] , proposal . signatures [ i ] , proposal . calldatas [ i ] , proposal . eta ) ;
}
emit ProposalCanceled ( proposalId ) ;
}
/**
* @ notice Gets actions of a proposal
* @ param proposalId the id of the proposal
* @ return targets
* @ return values
* @ return signatures
* @ return calldatas
* /
function getActions ( uint proposalId ) external view returns ( address [ ] memory targets , uint [ ] memory values , string [ ] memory signatures , bytes [ ] memory calldatas ) {
Proposal storage p = proposals [ proposalId ] ;
return ( p . targets , p . values , p . signatures , p . calldatas ) ;
}
/**
* @ notice Gets the receipt for a voter on a given proposal
* @ param proposalId the id of proposal
* @ param voter The address of the voter
* @ return The voting receipt
* /
function getReceipt ( uint proposalId , address voter ) external view returns ( Receipt memory ) {
return proposals [ proposalId ] . receipts [ voter ] ;
}
/**
* @ notice Gets the state of a proposal
* @ param proposalId The id of the proposal
* @ return Proposal state
* /
function state ( uint proposalId ) public view returns ( ProposalState ) {
require ( proposalCount >= proposalId , " GovernorBravo::state: invalid proposal id " ) ;
Proposal storage proposal = proposals [ proposalId ] ;
if ( proposal . canceled ) {
return ProposalState . Canceled ;
} else if ( block . number <= proposal . startBlock ) {
return ProposalState . Pending ;
} else if ( block . number <= proposal . endBlock ) {
return ProposalState . Active ;
} else if ( proposal . forVotes <= proposal . againstVotes || proposal . forVotes < quorumVotes ) {
return ProposalState . Defeated ;
} else if ( proposal . eta == 0 ) {
return ProposalState . Succeeded ;
} else if ( proposal . executed ) {
return ProposalState . Executed ;
} else if ( block . timestamp >= SafeMath . add ( proposal . eta , timelock . GRACE_PERIOD ( ) ) ) {
return ProposalState . Expired ;
} else {
return ProposalState . Queued ;
}
}
/**
* @ notice Cast a vote for a proposal
* @ param proposalId The id of the proposal to vote on
* @ param support The support value for the vote . 0 = against , 1 = for , 2 = abstain
* /
function castVote ( uint proposalId , uint8 support ) external {
emit VoteCast ( msg . sender , proposalId , support , castVoteInternal ( msg . sender , proposalId , support ) , " " ) ;
}
/**
* @ notice Cast a vote for a proposal with a reason
* @ param proposalId The id of the proposal to vote on
* @ param support The support value for the vote . 0 = against , 1 = for , 2 = abstain
* @ param reason The reason given for the vote by the voter
* /
function castVoteWithReason ( uint proposalId , uint8 support , string calldata reason ) external {
emit VoteCast ( msg . sender , proposalId , support , castVoteInternal ( msg . sender , proposalId , support ) , reason ) ;
}
/**
* @ notice Cast a vote for a proposal by signature
* @ dev External function that accepts EIP - 712 signatures for voting on proposals .
* /
function castVoteBySig ( uint proposalId , uint8 support , uint8 v , bytes32 r , bytes32 s ) external {
bytes32 domainSeparator = keccak256 ( abi . encode ( DOMAIN_TYPEHASH , keccak256 ( bytes ( name ) ) , getChainIdInternal ( ) , address ( this ) ) ) ;
bytes32 structHash = keccak256 ( abi . encode ( BALLOT_TYPEHASH , proposalId , support ) ) ;
bytes32 digest = keccak256 ( abi . encodePacked ( " \x19 \x01 " , domainSeparator , structHash ) ) ;
address signatory = ecrecover ( digest , v , r , s ) ;
require ( signatory != address ( 0 ) , " GovernorBravo::castVoteBySig: invalid signature " ) ;
emit VoteCast ( signatory , proposalId , support , castVoteInternal ( signatory , proposalId , support ) , " " ) ;
}
/**
* @ notice Internal function that caries out voting logic
* @ param voter The voter that is casting their vote
* @ param proposalId The id of the proposal to vote on
* @ param support The support value for the vote . 0 = against , 1 = for , 2 = abstain
* @ return The number of votes cast
* /
function castVoteInternal ( address voter , uint proposalId , uint8 support ) internal returns ( uint96 ) {
require ( state ( proposalId ) == ProposalState . Active , " GovernorBravo::castVoteInternal: voting is closed " ) ;
require ( support <= 2 , " GovernorBravo::castVoteInternal: invalid vote type " ) ;
Proposal storage proposal = proposals [ proposalId ] ;
Receipt storage receipt = proposal . receipts [ voter ] ;
require ( receipt . hasVoted == false , " GovernorBravo::castVoteInternal: voter already voted " ) ;
uint96 votes = token . getPriorVotes ( voter , proposal . startBlock ) ;
if ( support == 0 ) {
proposal . againstVotes = SafeMath . add ( proposal . againstVotes , votes ) ;
} else if ( support == 1 ) {
proposal . forVotes = SafeMath . add ( proposal . forVotes , votes ) ;
} else if ( support == 2 ) {
proposal . abstainVotes = SafeMath . add ( proposal . abstainVotes , votes ) ;
}
receipt . hasVoted = true ;
receipt . support = support ;
receipt . votes = votes ;
return votes ;
}
/**
* @ notice Admin function for setting the voting delay
* @ param newVotingDelay new voting delay , in blocks
* /
function _setVotingDelay ( uint newVotingDelay ) external {
require ( msg . sender == admin , " GovernorBravo::_setVotingDelay: admin only " ) ;
require ( newVotingDelay >= MIN_VOTING_DELAY && newVotingDelay <= MAX_VOTING_DELAY , " GovernorBravo::_setVotingDelay: invalid voting delay " ) ;
uint oldVotingDelay = votingDelay ;
votingDelay = newVotingDelay ;
emit VotingDelaySet ( oldVotingDelay , votingDelay ) ;
}
/**
* @ notice Admin function for setting the voting period
* @ param newVotingPeriod new voting period , in blocks
* /
function _setVotingPeriod ( uint newVotingPeriod ) external {
require ( msg . sender == admin , " GovernorBravo::_setVotingPeriod: admin only " ) ;
require ( newVotingPeriod >= MIN_VOTING_PERIOD && newVotingPeriod <= MAX_VOTING_PERIOD , " GovernorBravo::_setVotingPeriod: invalid voting period " ) ;
uint oldVotingPeriod = votingPeriod ;
votingPeriod = newVotingPeriod ;
emit VotingPeriodSet ( oldVotingPeriod , votingPeriod ) ;
}
/**
* @ notice Admin function for setting the proposal threshold
* @ dev newProposalThreshold must be greater than the hardcoded min
* @ param newProposalThreshold new proposal threshold
* /
function _setProposalThreshold ( uint newProposalThreshold ) external {
require ( msg . sender == admin , " GovernorBravo::_setProposalThreshold: admin only " ) ;
require ( newProposalThreshold >= MIN_PROPOSAL_THRESHOLD && newProposalThreshold <= MAX_PROPOSAL_THRESHOLD , " GovernorBravo::_setProposalThreshold: invalid proposal threshold " ) ;
uint oldProposalThreshold = proposalThreshold ;
proposalThreshold = newProposalThreshold ;
emit ProposalThresholdSet ( oldProposalThreshold , proposalThreshold ) ;
}
/**
* @ notice Initiate the GovernorBravo contract
* @ dev Admin only . Accepts timelock admin
* /
function _initiate ( ) external {
require ( msg . sender == admin , " GovernorBravo::_initiate: admin only " ) ;
require ( proposalCount == 0 , " GovernorBravo::_initiate: can only initiate once " ) ;
timelock . acceptAdmin ( ) ;
}
/**
* @ notice Begins transfer of admin rights . The newPendingAdmin must call ` _acceptAdmin ` to finalize the transfer .
* @ dev Admin function to begin change of admin . The newPendingAdmin must call ` _acceptAdmin ` to finalize the transfer .
* @ param newPendingAdmin New pending admin .
* /
function _setPendingAdmin ( address newPendingAdmin ) external {
// Check caller = admin
require ( msg . sender == admin , " GovernorBravo:_setPendingAdmin: admin only " ) ;
// Save current value, if any, for inclusion in log
address oldPendingAdmin = pendingAdmin ;
// Store pendingAdmin with value newPendingAdmin
pendingAdmin = newPendingAdmin ;
// Emit NewPendingAdmin(oldPendingAdmin, newPendingAdmin)
emit NewPendingAdmin ( oldPendingAdmin , newPendingAdmin ) ;
}
/**
* @ notice Accepts transfer of admin rights . msg . sender must be pendingAdmin
* @ dev Admin function for pending admin to accept role and update admin
* /
function _acceptAdmin ( ) external {
// Check caller is pendingAdmin and pendingAdmin ≠ address(0)
require ( msg . sender == pendingAdmin && msg . sender != address ( 0 ) , " GovernorBravo:_acceptAdmin: pending admin only " ) ;
// Save current values for inclusion in log
address oldAdmin = admin ;
address oldPendingAdmin = pendingAdmin ;
// Store admin with value pendingAdmin
admin = pendingAdmin ;
// Clear the pending value
pendingAdmin = address ( 0 ) ;
emit NewAdmin ( oldAdmin , admin ) ;
emit NewPendingAdmin ( oldPendingAdmin , pendingAdmin ) ;
}
function getChainIdInternal ( ) internal pure returns ( uint ) {
uint chainId ;
assembly { chainId := chainid ( ) }
return chainId ;
}
}