在 remix 中有三个例子,这是第3个Ballot案例,看这篇之前,请先确定看过之前的三篇。
这个投票案例,主要实现几个功能。
- 投票
- 委托投票给另一个地址
- 设置是否有投票的权利
- 计算投票获胜的选项
源码如下
pragma solidity >=0.7.0 <0.8.0;
/**
@title Ballot
@dev Implements voting process along with vote delegation
*/
contract Ballot {
struct Voter {
uint weight; // 授权权重
bool voted; // 如果是true,表示已经投票
address delegate; // 委托帮投
uint vote; // 已投票提案的索引
}
struct Proposal {
bytes32 name; // 提案的名称
uint voteCount; // 累计票数
}
address public chairperson;
mapping(address => Voter) public voters;
Proposal[] public proposals;
/**
@dev 创建一个投票选项 'proposalNames'.
@param proposalNames names of proposals
*/
constructor(bytes32[] memory proposalNames) {
chairperson = msg.sender;
voters[chairperson].weight = 1;
for (uint i = 0; i < proposalNames.length; i++) {
proposals.push(Proposal({
name: proposalNames[i],
voteCount: 0
}));
}
}
/**
@dev 给投票的权利(发选票)
@param voter address of voter
*/
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;
}
/**
@dev 把票委托给别人
@param to address to which vote is delegated
*/
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;
}
}
/**
@dev 投票,包括被委托的票
@param proposal index of proposal in the proposals array
*/
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;
}
/**
@dev 计算获胜的提案
@return winningProposal_ index of winning proposal in the proposals array
*/
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;
}
}
}
/**
@dev 获取投票数最多的提案
@return winnerName_ the name of the winner
*/
function winnerName() public view
returns (bytes32 winnerName_)
{
winnerName_ = proposals[winningProposal()].name;
}
}
下面开始分析新的知识点
struct
结构体,复杂的数据类型,有多个属性。类似于 golang 的结构体。
mapping
映射,一种数据类型,类似 python 的字典,php 的关联数组,javascript的对象。
Proposal[]
定义数组的方式,solidity 是一种静态类型语言,数组定义的方式跟其他语言一样。往数组追加数据的方法是 push
。
memory
和 storage
是存储变量的两种方式,storage
指的是永久存储在区块链中的数据,memory
是临时变量,当调用完成时即被移除。这两个相当于我们电脑的硬盘和内存。
address(0)
表示 address
的初始值,一般用于判断 address
是否已赋值。
总结
这个案例新的知识点有,struct
结构体、mapping
映射、数组、memory
和 storage
、address
的初始值 address(0)
。
Tips
部署的时候,需要 bytes32[]
数据,输入以下数组测试
["0xd4967590eb024589dfb6b9e48a576eb49ebc19d764b0d1d67dc21975e7258e97",
"0x0000000000000000000000000000000000000000000000000000000000000001",
"0x0000000000000000000000000000000000000000000000000000000000000002",
"0x0000000000000000000000000000000000000000000000000000000000000003",
"0x065e0be95fb43db528a20ba65c0e575e33cd4a9e1ca089dba4efff24596e8553"]