pragma solidity ^0.4.0
//投票
contract toupiao {
//结构体,一个投票人
struct Voter {
uint weight; //通过代理积累权重
bool voted; //是否投票
address delegate; // 代理人
uint vote; //选择的提案编号
}
//结构体,一个提案
struct Proposal {
bytes32 name; //提案名(最多32个字符)
uint voteCount; //投票数量
}
address public chairperson; //主持人
//保存从地址到投票人数据的映射
mapping (address => Voter) public voters;
//提案数组
Proposal[] public proposals;
//函数基于一组提案,构建一个投票协议
function Ballots(bytes32[] proposalNames) public {
chairperson = msg.sender; //协议创建人为主持人
voters[chairperson].weight = 1; //主持人的投票权重为1
//针对每一个提案名,创建一个对应的提案,并且保存在 Proposal中,
for (uint i=0;i<proposalNames.length; i++){
//
proposals.push(
Proposal{
name:proposalNames[i];
voteCount:0;
})
}
}
// 主席给予一个人投票的权利
function giveRightToVote(address voter) public {
// 如果require的执行失败,则会终止程序,之前的所有修改都会被还原
// 在历史的EVM版本中,这会消耗所有的gas,但现在已经被取消
// 建议使用Require来检查函数的正确性和安全性
// 可以选择传入第二个参数,对错误加以解释
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]
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."); // 不能自己代理自己
// 因为被代理人也可能找人代理,因此要找到最初的代理人
// 这个循环可能很危险,因为执行时间可能很长,从而消耗大量的gas
// 当gas被耗尽,将无法代理
while (voters[to].delegate != address(0)) {
to = voters[to].delegate;
// 防止出现循环,但是并没有检查不包含sender的loop,也许不会出现呢:)
require(to != msg.sender, "Found loop in delegation.");
}
// 因为sender是引用传递,因此会修改全局变量voters[msg.sender]的值
sender.voted = true;
sender.delegate = to;
Voter storage delegate_ = voters[to];
if (delegate_.voted) {
// 如果已经投票了,增加提案的权重;QY:这里最好也能增加代理人权重
proposals[delegate_.vote].voteCount += sender.weight;
} else {
// 如果没有投票,增加代理人的权重
delegate_.weight += sender.weight;
}
}
// 进行投票
function vote(uint proposal) public {
Voter storage sender = voters[msg.sender];
require(!sender.voted, "Already voted.");
sender.voted = true;
sender.vote = proposal;
// 如果proposals的数组越界,会自动失败,并且还原变化
proposals[proposal].voteCount += sender.weight;
}
//计算胜出的提案,QY:如果都是0怎么办?
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;
}
}
涉及知识点
状态变量
状态变量是永久地存储在合约存储中的值
局部变量
局部变量是仅在函数执行过程中有效的变量,函数退出后,变量无效。局部变量的数据存储在内存里,不上链,gas
低
全局变量
全局变量是全局范围工作的变量,都是solidity
预留关键字。他们可以在函数内不声明直接使用
数据位置
solidity数据存储位置有三类:storage
,memory
和calldata
。不同存储位置的gas
成本不同。storage
类型的数据存在链上,类似计算机的硬盘,消耗gas
多;memory
和calldata
类型的临时存在内存里,消耗gas
少。大致用法:
-
storage
:合约里的状态变量默认都是storage
,存储在链上。 -
memory
:函数里的参数和临时变量一般用memory
,存储在内存中,不上链。 -
calldata
:和memory
类似,存储在内存中,不上链。与memory
的不同点在于calldata
变量不能修改,一般用于函数的参数
流程如下:
- 基于一系列的提案创建一个投票的智能合约
- 主持人可以添加多个投票人
- 每个投票人可以自己投票,或者让他人代理自己
- 我们可以统计当前最高票的提案
合约调用