代码
pragma solidity ^0.4.11;
/**
创建投票活动,传入选项名称
授权某地址用户可以投票
用户投票
用户授权代理人
查看投票结果
需要一个投票选项(名称、票数)列表、一个投票人(地址=>{拥有的票数、代理人地址、是否已经投票、投给哪个选项})列表
*/
contract Ballot {
// 投票人
struct ballotVoter {
uint delegateWeight; // 代理给的累计票数
bool voteSpent; // 是否已经投票
address delegateTo; // 代理人地址
uint voteIndex; // 给谁投票,提案索引
}
// 提案
struct Proposal {
bytes32 proposalName; // 提案名称
uint voteCount; // 累计投票数
}
address public chairman; // 主席地址
mapping(address => ballotVoter) public ballotVoters; // 投票人列表
Proposal[] public proposalsOption; // 提案列表
/// 构造函数
// 创建人就是主席
// 设置主席的票数=1
// 遍历提案名称列表,构造提案,放入列表
constructor(bytes32[] proposalNames) public {
chairman = msg.sender;
ballotVoters[chairman].delegateWeight = 1;
// 对于每个提案名称,创建一个新的提案对象,放入列表
for (uint i = 0; i < proposalNames.length; i++) {
proposalsOption.push(Proposal({
proposalName : proposalNames[i],
voteCount : 0}));
}
}
// 给某人分配投票权
// 要求:1. 只有主席才能执行此动作;2. 此人没有投过票;3. 此人的票数为0
// 加入投票人列表,并设置票数为1
function giveVotingRights(address voter) public {
require((msg.sender == chairman) && !ballotVoters[voter].voteSpent && (ballotVoters[voter].delegateWeight == 0));
ballotVoters[voter].delegateWeight = 1;
}
/// 把投票权代理给某人
// 要求:1. 调用者没有投过票;2. 调用者不是目标代理人
function delegateTo(address to) public {
ballotVoter storage sender = ballotVoters[msg.sender]; // 根据调用者地址取得对应的投票人对象
require(!sender.voteSpent);
require(to != msg.sender);
// 当目标代理人的代理人地址不为空时时,目标代理人设置为代理人的代理人
// 相当于一直往上找,一直找到没有代理人的
while (ballotVoters[to].delegateTo != address(0)) {
to = ballotVoters[to].delegateTo;
require(to != msg.sender); // 调用发起人不能是代理人
}
sender.voteSpent = true; // 设置为已投票,不能再投了
sender.delegateTo = to; // 设置代理人
ballotVoter storage _delegateTo = ballotVoters[to]; // 取得代理人对应的投票人对象
// 如果代理人已经投过票,增加代理人所投的那个提案的票数,票数为调用者的票数
if (_delegateTo.voteSpent) {
proposalsOption[_delegateTo.voteIndex].voteCount += sender.delegateWeight;
}
// 如果代理人还没投票,就把票数给代理人
else {
_delegateTo.delegateWeight += sender.delegateWeight;
}
}
// 直接投票
// 根据调用者地址取得投票人对象
// 要求:还没有投过票
// 设置投票人已经投过票、投的提案
// 增加提案票数
function voteIndex(uint proposal) public {
ballotVoter storage sender = ballotVoters[msg.sender]; // 根据调用者地址,取得对应的投票人对象
require(!sender.voteSpent); // 必须还没投过
sender.voteSpent = true; // 设置已投票
sender.voteIndex = proposal; // 设置投票提案的索引值
proposalsOption[proposal].voteCount += sender.delegateWeight; // 增加目标提案的票数
}
// 遍历提案,找出票数最高的,返回胜出提案索引值
function winnerProposal() public constant
returns (uint _winnerProposal)
{
uint winnerVoteCount = 0;
for (uint p = 0; p < proposalsOption.length; p++) {
if (proposalsOption[p].voteCount > winnerVoteCount) {
winnerVoteCount = proposalsOption[p].voteCount;
_winnerProposal = p;
}
}
}
// 取得胜出提案名称
function winner() public constant
returns (bytes32 _winner)
{
_winner = proposalsOption[winnerProposal()].proposalName;
}
}
测试
(1)发布
构造参数是 bytes32[] 填:
["0x6f7074696f6e4100000000000000000000000000000000000000000000000000","0x6f7074696f6e4200000000000000000000000000000000000000000000000000","0x6f7074696f6e4300000000000000000000000000000000000000000000000000","0x6f7074696f6e4400000000000000000000000000000000000000000000000000"]
(2)授权
从 Account 列表中复制出几个地址:
投票人地址:
0x14723a09acff6d2a60dcdf7aa4aff308fddc160c
0x4b0897b0513fdc7c541b6d9d7e929c4e5364d2db
0x583031d1113ad414f02576bd6afabfb302140225
分别填到 giveVotingRights 参数输入框中,并执行。
debugger中可以看到投票人数据,和提案数据:
(3)投票
把account切换到尾号为 160c 的账号,执行投票操作,voteIndex
参数中填写提案索引,例如 1。
执行 winnerProposal 和 winner 查看效果:
debugger中也可以看到效果:
160c 的 voteIndex 为 1,提案1的 voteCount 为1
(4)代理
把account切换到尾号为 2db 的账号,执行设置代理的操作,代理给 160c。
debugger中查看:
2db 的 delegateTo 为 160c,提案1的 voteCount 变为2