Contract Name:
GovernorAlpha
Contract Source Code:
File 1 of 1 : GovernorAlpha
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
contract GovernorAlpha {
/// @dev The name of this contract
string public constant name = "Whiteswap Governor Alpha";
/// @dev The number of votes in support of a proposal required in order for a quorum to be reached and for a vote to succeed
function quorumVotes() public pure returns (uint) { return 40_000_000e18; } // 4% of WSE
/// @dev The number of votes required in order for a voter to become a proposer
function proposalThreshold() public pure returns (uint) { return 5_000_000e18; } // 0.5% of WSE
/// @dev The maximum number of actions that can be included in a proposal
function proposalMaxOperations() public pure returns (uint) { return 10; } // 10 actions
/// @dev The delay before voting on a proposal may take place, once proposed
function votingDelay() public pure returns (uint) { return 1; } // 1 block
/// @dev The duration of voting on a proposal, in unix time
function votingPeriod() public pure returns (uint) { return 604_800; } // 7 days in unix time
/// @dev The address of the Whiteswap Protocol Timelock
TimelockInterface public timelock;
/// @dev The address of the Whiteswap governance token
WSEInterface public wse;
/// @dev The total number of proposals
uint public proposalCount;
struct Proposal {
// @dev WSEque id for looking up a proposal
uint id;
// @dev Creator of the proposal
address proposer;
// @dev The timestamp that the proposal will be available for execution, set once the vote succeeds
uint eta;
// @dev the ordered list of target addresses for calls to be made
address[] targets;
// @dev The ordered list of values (i.e. msg.value) to be passed to the calls to be made
uint[] values;
// @dev The ordered list of function signatures to be called
string[] signatures;
// @dev The ordered list of calldata to be passed to each call
bytes[] calldatas;
// @dev The block at which voting begins: holders must delegate their votes prior to this block
uint startBlock;
// @dev The timestamp at which voting ends: votes must be cast prior to this timestamp
uint endTimestamp;
// @dev Current number of votes in favor of this proposal
uint forVotes;
// @dev Current number of votes in opposition to this proposal
uint againstVotes;
// @dev Flag marking whether the proposal has been canceled
bool canceled;
// @dev Flag marking whether the proposal has been executed
bool executed;
// @dev Receipts of ballots for the entire set of voters
mapping (address => Receipt) receipts;
}
/// @dev Ballot receipt record for a voter
struct Receipt {
// @dev Whether or not a vote has been cast
bool hasVoted;
// @dev Whether or not the voter supports the proposal
bool support;
// @dev The number of votes the voter had, which were cast
uint96 votes;
}
/// @dev Possible states that a proposal may be in
enum ProposalState {
Pending,
Active,
Canceled,
Defeated,
Succeeded,
Queued,
Expired,
Executed
}
/// @dev The official record of all proposals ever proposed
mapping (uint => Proposal) public proposals;
/// @dev The latest proposal for each proposer
mapping (address => uint) public latestProposalIds;
/// @dev The EIP-712 typehash for the contract's domain
bytes32 public constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)");
/// @dev The EIP-712 typehash for the ballot struct used by the contract
bytes32 public constant BALLOT_TYPEHASH = keccak256("Ballot(uint256 proposalId,bool support)");
/// @dev An event emitted when a new proposal is created
event ProposalCreated(uint id, address proposer, address[] targets, uint[] values, string[] signatures, bytes[] calldatas, uint startBlock, uint endTimestamp, string description);
/// @dev An event emitted when a vote has been cast on a proposal
event VoteCast(address voter, uint proposalId, bool support, uint votes);
/// @dev An event emitted when a proposal has been canceled
event ProposalCanceled(uint id);
/// @dev An event emitted when a proposal has been queued in the Timelock
event ProposalQueued(uint id, uint eta);
/// @dev An event emitted when a proposal has been executed in the Timelock
event ProposalExecuted(uint id);
constructor(address timelock_, address wse_) public {
timelock = TimelockInterface(timelock_);
wse = WSEInterface(wse_);
}
function propose(address[] memory targets, uint[] memory values, string[] memory signatures, bytes[] memory calldatas, string memory description) public returns (uint) {
require(wse.getPriorVotes(msg.sender, sub256(block.number, 1)) > proposalThreshold(), "GovernorAlpha::propose: proposer votes below proposal threshold");
require(targets.length == values.length && targets.length == signatures.length && targets.length == calldatas.length, "GovernorAlpha::propose: proposal function information arity mismatch");
require(targets.length != 0, "GovernorAlpha::propose: must provide actions");
require(targets.length <= proposalMaxOperations(), "GovernorAlpha::propose: too many actions");
uint latestProposalId = latestProposalIds[msg.sender];
if (latestProposalId != 0) {
ProposalState proposersLatestProposalState = state(latestProposalId);
require(proposersLatestProposalState != ProposalState.Active, "GovernorAlpha::propose: one live proposal per proposer, found an already active proposal");
require(proposersLatestProposalState != ProposalState.Pending, "GovernorAlpha::propose: one live proposal per proposer, found an already pending proposal");
}
uint startBlock = add256(block.number, votingDelay());
uint endTimestamp = add256(block.timestamp, votingPeriod());
proposalCount++;
Proposal memory newProposal = Proposal({
id: proposalCount,
proposer: msg.sender,
eta: 0,
targets: targets,
values: values,
signatures: signatures,
calldatas: calldatas,
startBlock: startBlock,
endTimestamp: endTimestamp,
forVotes: 0,
againstVotes: 0,
canceled: false,
executed: false
});
proposals[newProposal.id] = newProposal;
latestProposalIds[newProposal.proposer] = newProposal.id;
emit ProposalCreated(newProposal.id, msg.sender, targets, values, signatures, calldatas, startBlock, endTimestamp, description);
return newProposal.id;
}
function queue(uint proposalId) public {
require(state(proposalId) == ProposalState.Succeeded, "GovernorAlpha::queue: proposal can only be queued if it is succeeded");
Proposal storage proposal = proposals[proposalId];
uint eta = add256(block.timestamp, timelock.delay());
for (uint i = 0; i < proposal.targets.length; i++) {
_queueOrRevert(proposal.targets[i], proposal.values[i], proposal.signatures[i], proposal.calldatas[i], eta);
}
proposal.eta = eta;
emit ProposalQueued(proposalId, eta);
}
function _queueOrRevert(address target, uint value, string memory signature, bytes memory data, uint eta) internal {
require(!timelock.queuedTransactions(keccak256(abi.encode(target, value, signature, data, eta))), "GovernorAlpha::_queueOrRevert: proposal action already queued at eta");
timelock.queueTransaction(target, value, signature, data, eta);
}
function execute(uint proposalId) public payable {
require(state(proposalId) == ProposalState.Queued, "GovernorAlpha::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);
}
function cancel(uint proposalId) public {
ProposalState state = state(proposalId);
require(state != ProposalState.Executed, "GovernorAlpha::cancel: cannot cancel executed proposal");
Proposal storage proposal = proposals[proposalId];
require(wse.getPriorVotes(proposal.proposer, sub256(block.number, 1)) < proposalThreshold(), "GovernorAlpha::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);
}
function getActions(uint proposalId) public 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);
}
function getReceipt(uint proposalId, address voter) public view returns (Receipt memory) {
return proposals[proposalId].receipts[voter];
}
function state(uint proposalId) public view returns (ProposalState) {
require(proposalCount >= proposalId && proposalId > 0, "GovernorAlpha::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.timestamp <= proposal.endTimestamp) {
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 >= add256(proposal.eta, timelock.GRACE_PERIOD())) {
return ProposalState.Expired;
} else {
return ProposalState.Queued;
}
}
function castVote(uint proposalId, bool support) public {
return _castVote(msg.sender, proposalId, support);
}
function castVoteBySig(uint proposalId, bool support, uint8 v, bytes32 r, bytes32 s) public {
bytes32 domainSeparator = keccak256(abi.encode(DOMAIN_TYPEHASH, keccak256(bytes(name)), getChainId(), 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), "GovernorAlpha::castVoteBySig: invalid signature");
return _castVote(signatory, proposalId, support);
}
function _castVote(address voter, uint proposalId, bool support) internal {
require(state(proposalId) == ProposalState.Active, "GovernorAlpha::_castVote: voting is closed");
Proposal storage proposal = proposals[proposalId];
Receipt storage receipt = proposal.receipts[voter];
require(receipt.hasVoted == false, "GovernorAlpha::_castVote: voter already voted");
uint96 votes = wse.getPriorVotes(voter, proposal.startBlock);
if (support) {
proposal.forVotes = add256(proposal.forVotes, votes);
} else {
proposal.againstVotes = add256(proposal.againstVotes, votes);
}
receipt.hasVoted = true;
receipt.support = support;
receipt.votes = votes;
emit VoteCast(voter, proposalId, support, votes);
}
function add256(uint256 a, uint256 b) internal pure returns (uint) {
uint c = a + b;
require(c >= a, "addition overflow");
return c;
}
function sub256(uint256 a, uint256 b) internal pure returns (uint) {
require(b <= a, "subtraction underflow");
return a - b;
}
function getChainId() internal pure returns (uint) {
uint chainId;
assembly { chainId := chainid() }
return chainId;
}
}
interface TimelockInterface {
function delay() external view returns (uint);
function GRACE_PERIOD() external view returns (uint);
function acceptAdmin() external;
function queuedTransactions(bytes32 hash) external view returns (bool);
function queueTransaction(address target, uint value, string calldata signature, bytes calldata data, uint eta) external returns (bytes32);
function cancelTransaction(address target, uint value, string calldata signature, bytes calldata data, uint eta) external;
function executeTransaction(address target, uint value, string calldata signature, bytes calldata data, uint eta) external payable returns (bytes memory);
}
interface WSEInterface {
function getPriorVotes(address account, uint blockNumber) external view returns (uint96);
}