最近一直在学习BlockChain相关的知识,Ethereum网上也没有太好的资料,就先拿官网的一些example学习下吧。

本篇解析下使用智能合约编写的投票合约。

先看下代码,注释给的也比较清楚,虽然是一门新的语言,但读懂应该没有什么难度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
pragma solidity ^0.4.22;

/// @title 委托投票
contract Ballot {
// 这里声明了一个新的复合类型用于稍后的变量
// 它用来表示一个选民
struct Voter {
uint weight; // 计票的权重,也就是票数
bool voted; // 若为真,代表该人已投票
address delegate; // 被委托人
uint vote; // 投票提案的索引
}

// 提案的类型
struct Proposal {
bytes32 name; // 简称(最长32个字节)
uint voteCount; // 得票数
}

address public chairperson;

// 这声明了一个状态变量,为每个可能的地址存储一个 `Voter`。
// 有权利投票的用户map
mapping(address => Voter) public voters;

// 一个 `Proposal` 结构类型的动态数组
// 投票提案数组
Proposal[] public proposals;

// 为 `proposalNames` 中的每个提案,创建一个提案对象
// 合约的构造函数,用constructor标识
constructor(bytes32[] proposalNames) public {
// 合约的构造者为chairperson
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
}));
}
}

// 授权 `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);
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),
// 这种情况下,委托不会被执行。
// 而在另一些情况下,如果形成闭环,则会让合约完全卡住。
// A委托给B,B的权利已经委托了C,那A的权利直接委托给C
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;
}

// 结合之前所有的投票,计算出最终胜出的提案
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 (bytes32 winnerName_)
{
winnerName_ = proposals[winningProposal()].name;
}
}

合约测试可以在Solidity IDE中测试,也可以在geth中执行,先来看个简单一点的,使用remix在线IDE进行合约部署。

Remix IDE运行合约

Remix IDE可以使用在线的,也可能本地安装。(在线的IDE有时会比较慢,我就安装了个本地的,结果发现本地的也比较慢,不知道是不是我虚拟机的问题。。。)

这里先使用在线版的,随后写个本地安装Remix的教程。

Remix IDE的左上角有个”+”的图标,点击新建一个文件,将上述代码copy进去。
文件创建成功之后就是选择Solidity的版本,编译代码,如图:
编译合约

编译成功之后就是部署合约,这里点击Deploy之后会帮你将合约部署到以太坊的测试环境中,部署流程如图:
部署合约

因为合约的构造函数需要输入参数,所以在deploy的时候,需要传入参数。
这里参数是一个bytes32的数组,在有的版本中string可以转成bytes32,但在0.4.22中无法直接转,
所以这里我们要直接输入byte32的一个数组["0x616263", "0x646566", "0x676869"]
转化成字符串为["abc", "def", "ghi"]

合约部署好之后,就可以调用合约的一些方法了,比如votegiveRightToVote
vote方法需要传入的参数是uint,是proposals数组的索引,这里注意不要越界。
giveRightToVote方法传入的参数是address,意思是给某个地址授权,使其能够进行投票,
这个address可以在Account中选择(remix会默认创建5个账号),如图:
账号
将选择好的address粘贴到参数栏中调用该方法给address授权,
然后依然在Account选项中切换到该address,就可以以该账号进行操作了,比如调用vote给某个提案投票。

其它的方法也可以尝试着调用下,加深对代码的理解。

这是用Remix IDE进行测试开发,还可以使用geth命令行模式进行开发测试,geth相对于Remix IDE就比较繁琐,具体流程下次再续。。。