//摘自:https://solidity-cn.readthedocs.io/zh/develop/solidity-by-example.html#
//案例:投票
//结构:为每个投票表决创建一份合约,为每个选项提供简称,最后合约创建者赋予每个地址投票权
//投票人可以选择本人投票,也可选择委托他人
//投票时间结束时,winningProposal()将返回的票最多的提案
pragma solidity ^0.4.22;
//(投票,投票用纸)
/// @title 委托投票
contract Ballot {
// 这里声明了一个新的复合类型用于稍后的变量
// 它用来表示一个选民
struct Voter {
uint weight; // 计票的权重
bool voted; // 若为真,代表该人已投票
address delegate; // 被委托人
uint vote; // 投票提案的索引
}
// 提案的类型
struct Proposal {
bytes32 name; // 简称(最长32个字节)
uint voteCount; // 得票数
}
address public chairperson;
// 这声明了一个状态变量,为每个可能的地址存储一个 `Voter`。
mapping(address => Voter) public voters;
// 一个 `Proposal` 结构类型的动态数组
Proposal[] public proposals;
//部署合约时需要向此构造函数提交一个由多个提案名构成的数组
/// 为 `proposalNames` 中的每个提案,创建一个新的(投票)表决
constructor(bytes32[] proposalNames) public {
//将合约部署者的地址设置为主席
chairperson = msg.sender;
//将主席的投票权重设置为1
voters[chairperson].weight = 1;
//遍历提案名构成的数组,将提案名依次存入本合约定义的数组propsals中,同时初始化每个提案的得票数为0
//对于提供的每个提案名称,
//创建一个新的 Proposal 对象并把它添加到数组的末尾。
for (uint i = 0; i < proposalNames.length; i++) {
// `Proposal({...})` 创建一个临时 Proposal 对象,
// `proposals.push(...)` 将其添加到 `proposals` 的末尾
proposals.push(
Proposal(
{
name: proposalNames[i],
voteCount: 0
}
)
);
}
}
// 授权 `voter` 对这个(投票)表决进行投票
// 只有 `chairperson` 可以调用该函数。
function giveRightToVote(address voter) public {
// 若 `require` 的第一个参数的计算结果为 `false`,
// 则终止执行,撤销所有对状态和以太币余额的改动。
// 在旧版的 EVM 中这曾经会消耗所有 gas,但现在不会了。
// 使用 require 来检查函数是否被正确地调用,是一个好习惯。
// 你也可以在 require 的第二个参数中提供一个对错误情况的解释。
require(
msg.sender == chairperson,
"Only chairperson can give right to vote."
);
//检查该即将被赋予投票权的voter是否已经投过票
require(
!voters[voter].voted,
"The voter already voted."
);
//检查该即将被赋予投票权的voter的投票权重是否为0,若为0,则赋权为1
require(voters[voter].weight == 0);
voters[voter].weight = 1;
}
/// 把你的投票委托到投票者 `to`。
function delegate(address to) public {
//创建一个引用,sender指向voters列表中准备将投票权委托出去的用户的Voter结构体;
// 传引用
Voter storage sender = voters[msg.sender];
//验证准备将投票权委托出去的用户是否投过票
require(!sender.voted, "You already voted.");
//验证接受投票权的地址是否为原地值
require(to != msg.sender, "Self-delegation is disallowed.");
// 委托是可以传递的,只要被委托者 `to` 也设置了委托。
// 一般来说,这种循环委托是危险的。因为,如果传递的链条太长,
// 则可能需消耗的gas要多于区块中剩余的(大于区块设置的gasLimit),
// 这种情况下,委托不会被执行。
// 而在另一些情况下,如果形成闭环,则会让合约完全卡住。
//如果接收投票权的地址的委托地址不为空地址,则进入循环,将委托地址更新为被委托人所记录的他所委托的地址
//总终会追踪到实际接收投票权的用户地址
while (voters[to].delegate != address(0)) {
to = voters[to].delegate;
// 不允许闭环委托
require(to != msg.sender, "Found loop in delegation.");
}
// `sender` 是一个引用, 相当于对 `voters[msg.sender].voted` 进行修改
//将把自己投票权委托出去的用户的投票状态设置为已投票
sender.voted = true;
//将把自己投票权委托出去的用户的委托地址设置为更新过的to
sender.delegate = to;
//定义一个storage的Voter变量,存放被委托投票的用户的信息,此时voters中也已记录to的信息,并将引用传递给delegate_
Voter storage delegate_ = voters[to];
//如果被委托人已经投过票
if (delegate_.voted) {
//将其已经投过的提案增加与原投票人同样权重的票数
// 若被委托者已经投过票了,直接增加得票数
proposals[delegate_.vote].voteCount += sender.weight;
} else {
//若被委托人尚未投票,则给被委托人的权重增加上原委托人的权重数额
// 若被委托者还没投票,增加委托者的权重
delegate_.weight += sender.weight;
}
}
/// 把你的票(包括委托给你的票),
/// 投给提案 `proposals[proposal].name`.
function vote(uint proposal) public {
//sender变量用于读取调用本投票函数的投票人的信息
Voter storage sender = voters[msg.sender];
//验证投票人是否已投过票
require(!sender.voted, "Already voted.");
//将投票人的投票状态更改为已投过票
sender.voted = true;
//将投票人的投票提案地址进行记录
sender.vote = proposal;
//在proposals列表中给被投票的提案增加上相应权重的票数
// 如果 `proposal` 超过了数组的范围,则会自动抛出异常,并恢复所有的改动
proposals[proposal].voteCount += sender.weight;
}
/// @dev 结合之前所有的投票,计算出最终胜出的提案
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;
}
}
}
// 调用 winningProposal() 函数以获取提案数组中获胜者的索引,并以此返回获胜者的名称
function winnerName() public view
returns (bytes32 winnerName_)
{
winnerName_ = proposals[winningProposal()].name;
}
}