智能合约案例:投票
本节将介绍一个用 Solidity 语言编写的智能合约案例。代码来源于 Solidity 官方文档 中的示例。
该智能合约实现了一个自动化的、透明的投票应用。投票发起人可以发起投票,将投票权赋予投票人;投票人可以自己投票,或将自己的票委托给其他投票人;任何人都可以公开查询投票的结果。

智能合约代码

实现上述功能的合约代码如下所示,并不复杂,语法跟 JavaScript 十分类似。
1
pragma solidity ^0.4.11;
2
3
contract Ballot {
4
struct Voter {
5
uint weight;
6
bool voted;
7
address delegate;
8
uint vote;
9
}
10
11
struct Proposal {
12
bytes32 name;
13
uint voteCount;
14
}
15
16
address public chairperson;
17
mapping(address => Voter) public voters;
18
Proposal[] public proposals;
19
20
// Create a new ballot to choose one of `proposalNames`
21
function Ballot(bytes32[] proposalNames) {
22
chairperson = msg.sender;
23
voters[chairperson].weight = 1;
24
25
for (uint i = 0; i < proposalNames.length; i++) {
26
proposals.push(Proposal({
27
name: proposalNames[i],
28
voteCount: 0
29
}));
30
}
31
}
32
33
// Give `voter` the right to vote on this ballot.
34
// May only be called by `chairperson`.
35
function giveRightToVote(address voter) {
36
require((msg.sender == chairperson) && !voters[voter].voted);
37
voters[voter].weight = 1;
38
}
39
40
// Delegate your vote to the voter `to`.
41
function delegate(address to) {
42
Voter sender = voters[msg.sender];
43
require(!sender.voted);
44
require(to != msg.sender);
45
46
while (voters[to].delegate != address(0)) {
47
to = voters[to].delegate;
48
49
// We found a loop in the delegation, not allowed.
50
require(to != msg.sender);
51
}
52
53
sender.voted = true;
54
sender.delegate = to;
55
Voter delegate = voters[to];
56
if (delegate.voted) {
57
proposals[delegate.vote].voteCount += sender.weight;
58
} else {
59
delegate.weight += sender.weight;
60
}
61
}
62
63
// Give your vote (including votes delegated to you)
64
// to proposal `proposals[proposal].name`.
65
function vote(uint proposal) {
66
Voter sender = voters[msg.sender];
67
require(!sender.voted);
68
sender.voted = true;
69
sender.vote = proposal;
70
71
proposals[proposal].voteCount += sender.weight;
72
}
73
74
// @dev Computes the winning proposal taking all
75
// previous votes into account.
76
function winningProposal() constant
77
returns (uint winningProposal)
78
{
79
uint winningVoteCount = 0;
80
for (uint p = 0; p < proposals.length; p++) {
81
if (proposals[p].voteCount > winningVoteCount) {
82
winningVoteCount = proposals[p].voteCount;
83
winningProposal = p;
84
}
85
}
86
}
87
88
// Calls winningProposal() function to get the index
89
// of the winner contained in the proposals array and then
90
// returns the name of the winner
91
function winnerName() constant
92
returns (bytes32 winnerName)
93
{
94
winnerName = proposals[winningProposal()].name;
95
}
96
}
Copied!

代码解析

指定版本

在第一行,pragma 关键字指定了和该合约兼容的编译器版本。
1
pragma solidity ^0.4.11;
Copied!
该合约指定,不兼容比 0.4.11 更旧的编译器版本,且 ^ 符号表示也不兼容从 0.5.0 起的新编译器版本。即兼容版本范围是 0.4.11 <= version < 0.5.0。该语法与 npm 的版本描述语法一致。

结构体类型

Solidity 中的合约(contract)类似面向对象编程语言中的类。每个合约可以包含状态变量、函数、事件、结构体类型和枚举类型等。一个合约也可以继承另一个合约。
在本例命名为 Ballot 的合约中,声明了 2 个结构体类型:VoterProposal
    struct Voter:投票人,其属性包括 uint weight(该投票人的权重)、bool voted(是否已投票)、address delegate(如果该投票人将投票委托给他人,则记录受委托人的账户地址)和 uint vote(投票做出的选择,即相应提案的索引号)。
    struct Proposal:提案,其属性包括 bytes32 name(名称)和 uint voteCount(已获得的票数)。
需要注意,address 类型记录了一个以太坊账户的地址。address 可看作一个数值类型,但也包括一些与以太币相关的方法,如查询余额 <address>.balance、向该地址转账 <address>.transfer(uint256 amount) 等。

状态变量

合约中的状态变量会长期保存在区块链中。通过调用合约中的函数,这些状态变量可以被读取和改写。
本例中定义了 3 个状态变量:chairpersonvotersproposals
    address public chairperson:投票发起人,类型为 address
    mapping(address => Voter) public voters:所有投票人,类型为 addressVoter 的映射。
    Proposal[] public proposals:所有提案,类型为动态大小的 Proposal 数组。
3 个状态变量都使用了 public 关键字,使得变量可以被外部访问(即通过消息调用)。事实上,编译器会自动为 public 的变量创建同名的 getter 函数,供外部直接读取。
状态变量还可设置为 internalprivateinternal 的状态变量只能被该合约和继承该合约的子合约访问,private 的状态变量只能被该合约访问。状态变量默认为 internal
将上述关键状态信息设置为 public 能够增加投票的公平性和透明性。

函数

合约中的函数用于处理业务逻辑。函数的可见性默认为 public,即可以从内部或外部调用,是合约的对外接口。函数可见性也可设置为 externalinternalprivate
本例实现了 6 个 public 函数,可看作 6 个对外接口,功能分别如下。

创建投票

函数 function Ballot(bytes32[] proposalNames) 用于创建一个新的投票。
所有提案的名称通过参数 bytes32[] proposalNames 传入,逐个记录到状态变量 proposals 中。同时用 msg.sender 获取当前调用消息的发送者的地址,记录为投票发起人 chairperson,该发起人投票权重设为 1。

赋予投票权

函数 function giveRightToVote(address voter) 实现给投票人赋予投票权。
该函数给 address voter 赋予投票权,即将 voter 的投票权重设为 1,存入 voters 状态变量。
这个函数只有投票发起人 chairperson 可以调用。这里用到了 require((msg.sender == chairperson) && !voters[voter].voted) 函数。如果 require 中表达式结果为 false,这次调用会中止,且回滚所有状态和以太币余额的改变到调用前。但已消耗的 Gas 不会返还。

委托投票权

函数 function delegate(address to) 把投票委托给其他投票人。
其中,用 voters[msg.sender] 获取委托人,即此次调用的发起人。用 require 确保发起人没有投过票,且不是委托给自己。由于被委托人也可能已将投票委托出去,所以接下来,用 while 循环查找最终的投票代表。找到后,如果投票代表已投票,则将委托人的权重加到所投的提案上;如果投票代表还未投票,则将委托人的权重加到代表的权重上。
该函数使用了 while 循环,这里合约编写者需要十分谨慎,防止调用者消耗过多 Gas,甚至出现死循环。

进行投票

函数 function vote(uint proposal) 实现投票过程。
其中,用 voters[msg.sender] 获取投票人,即此次调用的发起人。接下来检查是否是重复投票,如果不是,进行投票后相关状态变量的更新。

查询获胜提案

函数 function winningProposal() constant returns (uint winningProposal) 将返回获胜提案的索引号。
这里,returns (uint winningProposal) 指定了函数的返回值类型,constant 表示该函数不会改变合约状态变量的值。
函数通过遍历所有提案进行记票,得到获胜提案。

查询获胜者名称

函数 function winnerName() constant returns (bytes32 winnerName) 实现返回获胜者的名称。
这里采用内部调用 winningProposal() 函数的方式获得获胜提案。如果需要采用外部调用,则需要写为 this.winningProposal()
Last modified 1yr ago