GovernorAlpha是社区治理投票的智能合约,包含提案部分和投票结果部分。
本文源码解析,文章的所有内容均不构成任何投资比特币或其他数字货币的意见和建议,也不赞成个人炒作任何数字货币!
代码解析
-
function quorumVotes() public pure returns (uint) { return 25000000e18; }
-
function proposalThreshold() public pure returns (uint) { return 2500000e18; }
提案门槛,只有至少拥有这么多票的时候才能创建提案,这里是2500000,即至少拥有2500000个Tribe代币才能创建提案。 -
function proposalMaxOperations() public pure returns (uint) { return 10; }
最多参数,即提案执行时可以执行的步骤数量,这里为10,意味着提案可以进行10个以内的外部调用。 -
function votingDelay() public pure returns (uint) { return 3333; }
投票准备期,3333个区块后才能开始投票,约13.9小时后 -
function votingPeriod() public pure returns (uint) { return 10000; }
投票周期10000个区块,约41.7小时 -
uint public proposalCount
;
提案的数量 -
mapping (uint => Proposal) public proposals;
根据提案id索引提案的键值对 -
mapping (address => uint) public latestProposalIds;
用户的上个提案id -
bytes32 public constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)");
-
bytes32 public constant BALLOT_TYPEHASH = keccak256("Ballot(uint256 proposalId,bool support)");
-
Proposal
提案结构体
id
提案的id ,
proposer
发起人地址 ,
eta
提案执行的时间,初始为0.在加入队列时设定为当前区块时间+宽限时间,和队列的调用有关系,队列详见timelock合约。
targets
目标 ,提案通过后生效时执行的合约地址
values
值,提案生效后生效时传递的参数
signatures
签名,提案生效后生效时传递的签名
calldatas
回调参数,提案生效后生效时传递的回调参数
startBlock
开始投票区块,
endBlock
结束投票区块,
forVotes
支持票的数量,
againstVotes
反对票的数量,
canceled
作废,提案状态变量之一,只有调用作废方法时,该变量变为true,表示该提案作废,将无法进行别的操作
executed
生效,提案状态变量之一,初始为false,只有提案执行后才会变为true。
receipts
投票纪录。
其中targets
,values
,signatures
,calldatas
四个数组相同下标的为一组,代表着一次外部调用,这四个数组长度相同。笔者认为这里用结构体更合适。在提案通过后可以通过这些数组内的参数进行提案的执行。
struct Proposal {
uint id;
address proposer;
uint eta;
address[] targets;
uint[] values;
string[] signatures;
bytes[] calldatas;
uint startBlock;
uint endBlock;
uint forVotes;
uint againstVotes;
bool canceled;
bool executed;
mapping (address => Receipt) receipts;
}
Receipt
结构体 投票记录
hasVoted 是否投票,标记某地址是否在此提案投过票,support 是否支持提案,votes所投的票数。struct Receipt { bool hasVoted; bool support; uint96 votes; }
- ProposalState 提案状态
枚举类型,代表着提案的各个状态
enum ProposalState {
Pending, //未开始状态
Active, //活跃状态
Canceled, //作废状态
Defeated, //未通过状态
Succeeded,//通过状态
Queued, //待执行状态
Expired, //过期状态
Executed //执行状态
}
constructor
构造方法
传入三个地址,时间锁地址,投票代币的地址,管理员的地址。constructor(address timelock_, address tribe_, address guardian_) public { timelock = TimelockInterface(timelock_); tribe = TribeInterface(tribe_); guardian = guardian_; }
-
function propose
方法 创建提案
前提 创建人在上个区块的票数大于proposalThreshold。
前提 提案的执行的外物调用格式没问题。
前提 提案存在外部调用,不支持无外部调用的提案,即提案完成后必须可以执行。
前提 提案的外部调用不用能超过最大的外部调用,即10个。
获取该账户上个提案的id ,如果不为0即该账户之前有过提案且该提案在未开始或活跃状态则不允许创建新的提案。
设置开始区块为当前区块+投票准备期。
设置结束区块为投票开始区块+投票周期。
将总的提案数量+1,创建提案,并将提案存入id=>提案的mapping中,更新创建人地址中最后一个区块的id;function propose(address[] memory targets, uint[] memory values, string[] memory signatures, bytes[] memory calldatas, string memory description) public returns (uint) { require(tribe.getPriorVotes(msg.sender, sub256(block.number, 1)) > proposalThreshold(), "GovernorAlpha: proposer votes below proposal threshold"); require(targets.length == values.length && targets.length == signatures.length && targets.length == calldatas.length, "GovernorAlpha: proposal function information arity mismatch"); require(targets.length != 0, "GovernorAlpha: must provide actions"); require(targets.length <= proposalMaxOperations(), "GovernorAlpha: too many actions"); uint latestProposalId = latestProposalIds[msg.sender]; if (latestProposalId != 0) { ProposalState proposersLatestProposalState = state(latestProposalId); require(proposersLatestProposalState != ProposalState.Active, "GovernorAlpha: one live proposal per proposer, found an already active proposal"); require(proposersLatestProposalState != ProposalState.Pending, "GovernorAlpha: one live proposal per proposer, found an already pending proposal"); } uint startBlock = add256(block.number, votingDelay()); uint endBlock = add256(startBlock, votingPeriod()); proposalCount++; Proposal memory newProposal = Proposal({ id: proposalCount, proposer: msg.sender, eta: 0, targets: targets, values: values, signatures: signatures, calldatas: calldatas, startBlock: startBlock, endBlock: endBlock, 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, endBlock, description); return newProposal.id; }
- function queue(uint proposalId)将提案加入队列
前提提案的状态是 Succeeded,通过状态
遍历本提案要执行的队列并将其加入时间锁队列中。
eta设置为 当前区块时间+宽限时间。function queue(uint proposalId) public { require(state(proposalId) == ProposalState.Succeeded, "GovernorAlpha: proposal can only be queued if it is succeeded"); Proposal storage proposal = proposals[proposalId]; // solhint-disable-next-line not-rely-on-time 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)
内部方法 将如果当前任务不在队列中则将任务加入队列中,已经执行过可以重复添加,但在本合约中应该没有这种调用。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: proposal action already queued at eta"); timelock.queueTransaction(target, value, signature, data, eta); }
function execute(uint proposalId)
执行提案
前提提案的状态是 Queued,待执行状态
遍历本提案要执行的队列并执行。function execute(uint proposalId) public payable { require(state(proposalId) == ProposalState.Queued, "GovernorAlpha: 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)
作废提案
未开始的提案或活跃的提案可以作废,只有管理员或者票数不够的情况下可以作废提案。
作废提案时同时作废队列中相应的提案部署方法。function cancel(uint proposalId) public { ProposalState state = state(proposalId); require(state == ProposalState.Active || state == ProposalState.Pending, "GovernorAlpha: can only cancel Active or Pending Proposal"); Proposal storage proposal = proposals[proposalId]; require(msg.sender == guardian || tribe.getPriorVotes(proposal.proposer, sub256(block.number, 1)) < proposalThreshold(), "GovernorAlpha: 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)
获取提案的信息,这些信息是执行提案的重要信息
传入提案的id,返回提案的 targets, values,signatures,calldatasfunction 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)
获取某地址对某个提案的投票结果,传入提案的id和地址的idfunction getReceipt(uint proposalId, address voter) public view returns (Receipt memory) { return proposals[proposalId].receipts[voter]; }
function state(uint proposalId)
返回提案的状态
如果提案canceled设置为true
则为作废状态
,作废状态是提案的终极状态之一。
如果状态未确定且当前区块小于开始区块
,则为未开始状态
,未开始的提案
如果状态未确定且当前区块小于结束区块
,则为活跃状态
如果状态未确定且获得的票数小于标记票数,或者小于平均票数
则为未通过状态
如果状态未确定且eta 等于0
则为通过状态
,此时提案还未加入执行队列
如果状态未确定且合约的执行为true
则为执行状态
,即合约为生效状态
如果状态未确定且区块的时间大于 eta+宽限期
则为过期状态
否则为待执行状态
function state(uint proposalId) public view returns (ProposalState) { require(proposalCount >= proposalId && proposalId > 0, "GovernorAlpha: 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; // solhint-disable-next-line not-rely-on-time } else if (block.timestamp >= add256(proposal.eta, timelock.GRACE_PERIOD())) { return ProposalState.Expired; } else { return ProposalState.Queued; } }
function castVote
投票,参数提案的id,是否支持function castVote(uint proposalId, bool support) public { return _castVote(msg.sender, proposalId, support); }
function castVoteBySig
使用签名进行投票
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: invalid signature");
return _castVote(signatory, proposalId, support);
}
-
function _castVote
投票
检查,只有活跃状态的提案可以投票
获取voter的投票纪录,如果已经投过票则不允许再次投票
获取voter在提案开始区块的票数
根据support的结果往forVotes或againstVotes增加票数。
纪录voter在此提案的投票纪录。function _castVote(address voter, uint proposalId, bool support) internal { require(state(proposalId) == ProposalState.Active, "GovernorAlpha: voting is closed"); Proposal storage proposal = proposals[proposalId]; Receipt storage receipt = proposal.receipts[voter]; require(receipt.hasVoted == false, "GovernorAlpha: voter already voted"); uint96 votes = tribe.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 __acceptAdmin()
内部方法 任职时间锁的管理员,只有管理员能够调用。function __acceptAdmin() public { require(msg.sender == guardian, "GovernorAlpha: sender must be gov guardian"); timelock.acceptAdmin(); }
function __abdicate()
内部方法辞职,设置管理员为0地址。function __abdicate() public { require(msg.sender == guardian, "GovernorAlpha: sender must be gov guardian"); guardian = address(0); }
function __transferGuardian(address newGuardian)
转移管理员function __transferGuardian(address newGuardian) public { require(msg.sender == guardian, "GovernorAlpha: sender must be gov guardian"); guardian = newGuardian; }
function __queueSetTimelockPendingAdmin
在队列中添加时间锁的设置待定管理员方法,并将时间锁的待定管理员设置为newPendingAdmin。function __queueSetTimelockPendingAdmin(address newPendingAdmin, uint eta) public { require(msg.sender == guardian, "GovernorAlpha: sender must be gov guardian"); timelock.queueTransaction(address(timelock), 0, "setPendingAdmin(address)", abi.encode(newPendingAdmin), eta); }
function __executeSetTimelockPendingAdmin
内部函数
在队列中执行时间锁的设置待定管理员方法,并将时间锁的待定管理员设置为newPendingAdmin。function __executeSetTimelockPendingAdmin(address newPendingAdmin, uint eta) public { require(msg.sender == guardian, "GovernorAlpha: sender must be gov guardian"); timelock.executeTransaction(address(timelock), 0, "setPendingAdmin(address)", abi.encode(newPendingAdmin), eta); }
-
function add256 function sub256
安全加减函数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()
方法获取链idfunction getChainId() internal pure returns (uint) { uint chainId; // solhint-disable-next-line no-inline-assembly assembly { chainId := chainid() } return chainId; }