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 = 7200; // About 24 hours, 12s per block /// @notice The max setable voting period uint public constant MAX_VOTING_PERIOD = 100800; // About 2 weeks, 12s per block /// @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 = 50400; // About 1 week, 12s per block /// @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; } }