一、投票合约介绍
本实例实现一个投票的智能合约。当然,电子投票的主要问题是如何将投票权分配给正确的人员以及如何防止被操纵。我们不会在这里解决所有的问题,但至少我们会展示如何进行委托投票,同时,计票又是 自动和完全透明的 。
想法:为每个投票创建一个合约,然后作为合约的创建者——主席,将给予每个独立的地址以投票权。
拥有主席给予投票权的地址,可以选择自己投票,也可以委托给他们信任的人投票,在投票时间结束时,winningProposal() 将返回获得最多投票的提案。
二、投票合约的实现过程
1.首先是建立两个存储的结构体:
1)Voter(用于存储投票人的相关信息)
2)Proposal(用于存储候选人的相关信息)
2.建立主席的地址存储变量chairperson,和投票人的地址映射对: mapping(address => Voter) public voters;
3.编写投票等相关功能的函数,如:
主席给投票人(地址)授权函数giveRightToVote();
投票人投票函数 vote();
投票人委托函数delegate();
最后是获胜者输出函数winningProposal()与winnerName() 函数。
三、合约代码
本合约的编写过程是采用Visual Studio Code软件,借助 solidity 插件进行编码,借助 Ethereum Remix 插件进行合约编译运行和部署工作。
// SPDX-License-Identifier: GPL-3.0
pragma experimental ABIEncoderV2;
pragma solidity >=0.7.0 <0.9.0;
/// @title 委托投票
contract Ballot {
// 这里声明了一个新的复合类型用于稍后的变量
// 它用来表示一个选民
struct Voter {
uint weight; // 计票的权重
bool voted; // 若为真,代表该人已投票
address delegate; // 被委托人
uint vote; // 投票提案的索引
}
// 提案的类型
struct Proposal {
string name; // 简称(最长32个字节)
uint voteCount; // 得票数
}
address public chairperson;
// 这声明了一个状态变量,为每个可能的地址存储一个 `Voter`。
mapping(address => Voter) public voters;
// 一个 `Proposal` 结构类型的动态数组
Proposal[] public proposals;
/// 为 `proposalNames` 中的每个提案,创建一个新的(投票)表决
//修改,将候选人的名称修改为字符串数组
// constructor(string[] memory proposalNames)
constructor() {
chairperson = msg.sender;
voters[chairperson].weight = 1;
//对于提供的每个提案名称,
//创建一个新的 Proposal 对象并把它添加到数组的末尾。
// for (uint i = 0; i < proposalNames.length; i++) {
// // `Proposal({...})` 创建一个临时 Proposal 对象,
// // `proposals.push(...)` 将其添加到 `proposals` 的末尾
// proposals.push(Proposal({
// name: proposalNames[i],
// voteCount: 0
// }));
// }
}
// 添加候选人,Proposal对象。将其从构造函数中分离出来,
// 我们可以进行随意的添加候选人,而不是只在部署合约的时候指定
function addProposals(string[] memory proposalNames) public{
require(
msg.sender == chairperson,
"Only chairpersion can add proposals!"
);
for(uint i=0;i<proposalNames.length;i++){
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."
);
require(
!voters[voter].voted,
"The voter already voted."
);
require(
voters[voter].weight == 0,
// 新增提示:"The voter has been given right!"
"The voter has been given right!"
);
voters[voter].weight = 1;
}
/// 把你的投票委托到投票者 `to`。
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.");
// 委托是可以传递的,只要被委托者 `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;
sender.delegate = to;
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 {
Voter storage sender = voters[msg.sender];
require(!sender.voted, "Already voted.");
sender.voted = true;
sender.vote = proposal;
// 如果 `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 (string memory winnerName_)
{
winnerName_ = proposals[winningProposal()].name;
//修改 加上得票总数
//voteCount_ = proposals[winningProposal()].voteCount;
}
}
4.函数执行介绍
合约中创建的函数,不同的节点执行不同的函数,否则就会报错,比如:addProposals(),giveRightToVote()函数,只允许由主持人节点执行(合约的创建节点),其他节点不能执行该函数。
合约开始我们需要执行addProposals()函数,进行添加候选人,接着就可以进行节点投票权的授权,执行giveRightToVote()函数,为节点授权。之后我们就可以切换到拥有投票权的节点,执行vote()函数进行投票操作。
切换到节点地址进行投票:
节点切换位置展示: