前面一篇文章带你用一个简单的模拟银行的合约讲解了 solidity 的基本语法特性。你大概已经对如何编写智能合约有了基本的认识。但是要编写产品级别的智能合约,只有这些基础是远远不够的。
这篇文章我们来一起编写一个稍微复杂一些的投票合约,来进一步学习 solidity。
电子投票功能要解决的主要问题是如果分配投票权以及如何避免数据被篡改。本篇实现的合约思路是对于每次投票表决都创建一个合约,合约的创建者就是投票委员会的主席,可以给不同的账户投票的权利。拥有投票权的账户可以自己投票也可以委托给他所信任的人代理投票。
需要说明的是,里面的语法如果之前的文章已经讲过的,我这里不会再重复,有兴趣的可以看专栏的其它文章。
//定义一个投票者结构(对象)
struct Voter {
uint weight;//
bool voted;//是否已经投票
address delegate;//委托投票的人
uint vote;//所投的决议对应的索引
}
//决议,投票时针对某个决议的
struct Proposal {
bytes32 name;//决议的名称
uint voteCount;//获取的投票数量
}
首先定义了两个结构体,用来表示对象。这种语法在 golang 里也有用到。struct 属于引用类型,同样可以作为数组或者 maping 的元素,比如下面这样:
struct Funder {
address addr;
uint amount;
}
mapping (uint => Funder) funders;
接续看代码,
address public chairperson;//投票委员会的主席,也是合约的创建者
//所有参与投票的人
mapping (address => Voter) voters;
Proposal [] public 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
}));
}
}
这是一个构造方法,主要是做一些初始化的动作,比如初始化投票委员会的主席,初始化决议数组。bytes32 是一个新的类型,之前没见过,它表示最大可以支持 32 长度的 byte [],比如下面就是一个 bytes32 类型的变量示例:
0 x05416460 deb76 d57 af601 be17 e777 b93592 d8 d4 d4 a4096 c57876 a91 c84 f4 a733
所以,bytes32 [] 就是多个像上面那样的变量组成的数组。
//顾名思义,给某个 voter 投票权
function giveRightToVote (address voter) external {
//只有主席可以调用该方法
require (msg.sender == chairperson);
//投过票的就不能投了
require (!voters [voter].voted);
//没有被赋予过投票权
require (voters [voter].weight == 0);
voters [voter].weight = 1;
}
这个方法是用来给某个账户赋予投票权,实际上就是给它的 weight 字段赋一个大于 0 的值。
//委托代理人帮你投票,委托给 to 这个账户
function delegate (address to) external {
Voter storage sender = voters [msg.sender];
require (!sender.voted, "you already voted.");
//不能自己委托给自己
require (to != msg.sender, "Self-delegation is disallowed.");
//address (0)表示地址为空,这个循环的意思是如果 to 这个账户也委托别人,就一路委托下去
//但是不能形成依赖循环
while (voters [to].delegate != address (0)) {
to = voters [to].delegate;
require (to != msg.sender);
}
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;
}
}
这个方法是调用者委托给另一个账户帮自己投票,这里面有个关键字 storage,这个关键字可以理解为引用,我们可以类比其他编程语言里引用类型,一个变量如果是引用类型,对其的修改同样造成被引用对象的修改。这里的 sender 变量就是调用者对应的投票对象的引用。
//投票
function vote (uint proposal) external {
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;
}
这个是真正发起投票的方法,这里有个问题值得注意,就是如果 proposal 的长度超过了数组的大小,程序会抛出异常,并且发生在链上的交易会回滚。
我这里就不实际演示程序的运行效果了,如果需要可以参考专栏的其他文章,有专门讲工具使用的,可以自己测试下。
公众号:犀牛的技术笔记
------
参考:
https://docs.soliditylang.org/en/v0.8.10/solidity-by-example.HTML
本文链接:https://www.defidaonews.com/article/6771440
转载请注明文章出处