1 場景
在投票的應用場景中氓奈,我們定義如下幾個關鍵要素:
- 發(fā)起人,投票的發(fā)起人呢铆,具有管理權限和能力
- 參與者,擁有投票權利的人
- 旁觀者蹲缠,不參與投票的人棺克,但是可以獲知投票結果
- 提案,對多個候選提案進行投票
2 邏輯
- 所有參與者持有一個區(qū)塊鏈賬戶
- 發(fā)起人創(chuàng)建投票合約线定,創(chuàng)建時指定多個提案
- 發(fā)起人為有權投票的賬戶進行賦權
- 投票人可以選擇委托投票或自主投票
- 投票結束娜谊,得票多者勝出,任意人可查看結果
3 完整代碼
源代碼地址 https://solidity.readthedocs.io/en/v0.5.1/solidity-by-example.html
pragma solidity >=0.4.22 <0.6.0;
contract Ballot {
struct Voter {
uint weight;
bool voted;
address delegate;
uint vote;
}
struct Proposal {
bytes32 name;
uint voteCount;
}
address public chairperson;
mapping(address => Voter) public voters;
Proposal[] public proposals;
constructor(bytes32[] memory proposalNames) public {
chairperson = msg.sender;
voters[chairperson].weight = 1;
for (uint i = 0; i < proposalNames.length; i++) {
proposals.push(Proposal({
name: proposalNames[i],
voteCount: 0
}));
}
}
function giveRightToVote(address voter) public {
require(
msg.sender == chairperson,
"Only chairperson can give right to vote."
);
require(
!voters[voter].voted,
"The voter already voted."
);
require(voters[voter].weight == 0);
voters[voter].weight = 1;
}
function delegate(address to) public {
Voter storage sender = voters[msg.sender];
require(!sender.voted, "You already voted.");
require(to != msg.sender, "Self-delegation is disallowed.");
while (voters[to].delegate != address(0)) {
to = voters[to].delegate;
require(to != msg.sender, "Found loop in delegation.");
}
sender.voted = true;
sender.delegate = to;
Voter storage delegate_ = voters[to];
if (delegate_.voted) {
proposals[delegate_.vote].voteCount += sender.weight;
} else {
delegate_.weight += sender.weight;
}
}
function vote(uint proposal) public {
Voter storage sender = voters[msg.sender];
require(sender.weight != 0, "Has no right to vote");
require(!sender.voted, "Already voted.");
sender.voted = true;
sender.vote = proposal;
proposals[proposal].voteCount += sender.weight;
}
function winningProposal() public view
returns (uint winningProposal_)
{
uint winningVoteCount = 0;
for (uint p = 0; p < proposals.length; p++) {
if (proposals[p].voteCount > winningVoteCount) {
winningVoteCount = proposals[p].voteCount;
winningProposal_ = p;
}
}
}
function winnerName() public view
returns (bytes32 winnerName_)
{
winnerName_ = proposals[winningProposal()].name;
}
}
4 解析
4.1 數(shù)據(jù)結構
每個投票人斤讥,在此處用solidity的 struct
數(shù)據(jù)結構來表示纱皆。注意,此處 voted
只能是 true
or false
芭商,因此派草,不管委托投票還是自主投票,只能一次性用掉所有的 weight
铛楣,不可拆分近迁。
struct Voter {
uint weight; // 256bit 的非負整數(shù)投票權重
bool voted; // 用戶是否已經投票
address delegate; // 被委托人賬戶
uint vote; // 投票提案編號
}
提案的數(shù)據(jù)結構相對簡單,一個是提案名稱簸州,一個是得票數(shù)鉴竭。
struct Proposal {
bytes32 name; // 提案名稱
uint voteCount; // 提案票數(shù)
}
下面三項全局變量(在 solidity 中歧譬,又稱狀態(tài) state
),都聲明為 public
搏存,這樣做的好處是缴罗,部署后,直接可以有類似于 Java
中的 getter 這樣的查詢函數(shù)供調用祭埂,不用再手動編寫面氓。
address public chairperson;
mapping(address => Voter) public voters;
Proposal[] public proposals;
基礎類型狀態(tài)查詢函數(shù)沒有參數(shù),mapping
狀態(tài)查詢函數(shù)參數(shù)為 key
蛆橡,array[]
狀態(tài)查詢函數(shù)參數(shù)為序號舌界。如下圖的合約列表所示,紅框中的三個查詢類函數(shù)(淺藍背景)泰演,就是編譯后自動生成的呻拌。
4.2 構造函數(shù)
此處 proposalNames 變量被聲明為 memory
,表示該變量的聲明周期只在函數(shù)調用期間,函數(shù)退出將被銷毀睦焕。這樣做的好處是節(jié)省空間藐握,消耗的 gas 也更少。相對的垃喊,狀態(tài)變量 state
是存儲在 storage
中的猾普。
在提案列表初始化時,使用了 struct
數(shù)據(jù)的創(chuàng)建語句本谜,注意相關語法初家。
constructor(bytes32[] memory proposalNames) public {
chairperson = msg.sender; // 指定合約部署賬戶為發(fā)起人
voters[chairperson].weight = 1;
for (uint i = 0; i < proposalNames.length; i++) {
// 提案列表初始化
proposals.push(Proposal({
name: proposalNames[i],
voteCount: 0
}));
}
}
4.3 賦權函數(shù)
該函數(shù)的第一個 require
限制了只能由投票發(fā)起人調用。第二個 require
限制了賦權人尚未進行投票且權重為 0乌助。
function giveRightToVote(address voter) public {
require(
msg.sender == chairperson,
"Only chairperson can give right to vote."
);
require(
!voters[voter].voted,
"The voter already voted."
);
require(voters[voter].weight == 0);
// 默認每個賬戶的初始權重一樣溜在,都是1
voters[voter].weight = 1;
}
注意,此處在賦權時他托,只設置了 Voter
結構體的 weight
變量掖肋。其余變量沒設置,代表使用默認值赏参。我們查詢某賦權賬戶的信息如下志笼。可以發(fā)現(xiàn)登刺,bool
的默認值為 false
; address
的默認值為 0x0
; uint
的默認值為 0
籽腕。
- uint256: weight 1
- bool: voted false
- address: delegate 0x0000000000000000000000000000000000000000
- uint256: vote 0
4.4 委托函數(shù)
這里的 sender
和 delegate_
變量使用了 storage
修飾,是因為他們都指向了全局的狀態(tài)變量纸俭,后續(xù)對他們的修改皇耗,將引起狀態(tài)變量的改變。前面兩個 require
揍很,限制了沒投票才能委托且不能委托自己郎楼。下面的 while
循環(huán)万伤,是為了實現(xiàn)冒泡式的委托,即如果 A 委托 B 投票呜袁,B 又委托了 C 投票敌买,那么最終,A 的投票權應該交接給 C阶界。
function delegate(address to) public {
// 從狀態(tài)變量取值虹钮,用 storage 修飾
Voter storage sender = voters[msg.sender];
require(!sender.voted, "You already voted.");
require(to != msg.sender, "Self-delegation is disallowed.");
// 找出最上游的被委托方(不一定是入?yún)?`to`)
while (voters[to].delegate != address(0)) {
to = voters[to].delegate;
// 受委托人不能又將自己的票委托給委托人,形成循環(huán)
require(to != msg.sender, "Found loop in delegation.");
}
sender.voted = true; // 委托等同于投票
sender.delegate = to;
Voter storage delegate_ = voters[to];
if (delegate_.voted) {
// 如果被委托人已經投票膘融,則直接行使委托人的投票權到相同提案
proposals[delegate_.vote].voteCount += sender.weight;
} else {
delegate_.weight += sender.weight;
}
}
4.5 投票函數(shù)
投票函數(shù)很好理解芙粱,一次性行使完所有權重。
function vote(uint proposal) public {
Voter storage sender = voters[msg.sender];
require(sender.weight != 0, "Has no right to vote");
require(!sender.voted, "Already voted.");
sender.voted = true;
sender.vote = proposal;
proposals[proposal].voteCount += sender.weight;
}
4.6 結果統(tǒng)計函數(shù)
這兩個函數(shù)都使用了 view
關鍵字修飾氧映,表示他們是查詢類函數(shù)春畔,不會改變狀態(tài)變量。.length
可以直接獲取數(shù)組的長度岛都。此處有一個小 bug 是律姨,如果多個提案最終得票數(shù)相同,則認為循環(huán)中先被訪問到的提案勝出臼疫。
function winningProposal() public view
returns (uint winningProposal_)
{
uint winningVoteCount = 0;
for (uint p = 0; p < proposals.length; p++) {
if (proposals[p].voteCount > winningVoteCount) {
winningVoteCount = proposals[p].voteCount;
winningProposal_ = p;
}
}
}
function winnerName() public view
returns (bytes32 winnerName_)
{
winnerName_ = proposals[winningProposal()].name;
}
5 執(zhí)行結果
一些 remix 下的調試結果择份。
(完)