前面一篇文章带你用一个简单的模拟银行的合约讲解了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类型的变量示例:
0x05416460deb76d57af601be17e777b93592d8d4d4a4096c57876a91c84f4a733
所以,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
的长度超过了数组的大小,程序会抛出异常,并且发生在链上的交易会回滚。
我这里就不实际演示程序的运行效果了,如果需要可以参考专栏的其他文章,有专门讲工具使用的,可以自己测试下。
公众号:犀牛的技术笔记
参考: