mirror of
https://github.com/Instadapp/dsa-governance.git
synced 2024-07-29 22:27:52 +00:00
389 lines
18 KiB
Solidity
389 lines
18 KiB
Solidity
pragma solidity ^0.7.0;
|
|
pragma experimental ABIEncoderV2;
|
|
|
|
import {
|
|
GovernorBravoDelegateStorageV1,
|
|
GovernorBravoEvents,
|
|
TimelockInterface,
|
|
TokenInterface
|
|
} from "./GovernorBravoInterfaces.sol";
|
|
import { SafeMath } from "./SafeMath.sol";
|
|
|
|
contract InstaGovernorBravoDelegate is GovernorBravoDelegateStorageV1, GovernorBravoEvents {
|
|
/// @notice The name of this contract
|
|
string public constant name = "INST Governor Bravo";
|
|
|
|
/// @notice The minimum setable proposal threshold
|
|
uint public constant MIN_PROPOSAL_THRESHOLD = 500000e18; // 500,000
|
|
|
|
/// @notice The maximum setable proposal threshold
|
|
uint public constant MAX_PROPOSAL_THRESHOLD = 50000000e18; // 5,000,000
|
|
|
|
/// @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
|
|
uint public constant quorumVotes = 4000000e18; // 4,000,000
|
|
|
|
/// @notice The maximum number of actions that can be included in a proposal
|
|
uint public constant proposalMaxOperations = 30; // 30 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;
|
|
// Store timelock with value pendingAdmin
|
|
emit NewTimelock(address(timelock), pendingAdmin);
|
|
timelock = TimelockInterface(pendingAdmin);
|
|
|
|
// Clear the pending value
|
|
pendingAdmin = address(0);
|
|
|
|
emit NewAdmin(oldAdmin, admin);
|
|
emit NewPendingAdmin(oldPendingAdmin, pendingAdmin);
|
|
}
|
|
|
|
/**
|
|
* @notice Accepts transfer of admin rights on timelock contract. msg.sender must be admin of this contract
|
|
* @dev Admin function for pending admin to accept role and update admin on timelock contract
|
|
*/
|
|
function _acceptAdminOnTimelock() external {
|
|
require(msg.sender == admin, "GovernorBravo:_acceptAdminOnTimelock: pending admin only");
|
|
timelock.acceptAdmin();
|
|
}
|
|
|
|
|
|
function getChainIdInternal() internal pure returns (uint) {
|
|
uint chainId;
|
|
assembly { chainId := chainid() }
|
|
return chainId;
|
|
}
|
|
|
|
} |