正文
文: Xwang
本文原创,转载请注明作者及出处
上一篇文章我们介绍了一些
DAPP基本原理与基础开发知识
,以及从实战的角度从零开始实现了一个基于区块链简单投票系统,这篇文章会接着
上一篇
继续开发,并且引入数字通证(Token)的概念。
一.引入数字代币进行第三次迭代
1.1
在以太坊中,一个重要概念就是通证(token),也就是常说的加密数字币,或者代币,比如以下这些。
通证就是在以太坊上构建的一种数字资产,可以用它来表示现实世界里的东西,比如黄金,或者是自己的数字资产(就像货币一样)。实际上就是智能合约,并没有什么太多的神奇之处,举个栗子:
-
黄金通证:银行可以有1千克的黄金储备,然后发行1千个通证。买100个黄金通证就等于买100克的黄金。
-
公司股票:公司股票可以用以太坊上的代币来表示。通过支付以太币,人们可以购买公司股票。
-
游戏币:在一个多玩家游戏中,游戏者可以用以太币购买游戏币,并在游戏中进行消费。
-
忠诚度积分:商店可以给购物者发行通证作为忠诚度积分,它可以在将来作为现金回收,或是在第三方市场售卖。
在合约中如何实现通证,实际上并没有限制。但是,以太坊有一个叫做
ERC20
的通证标准,该标准还在不断进化中。
ERC20
通证的优点是很容易与其他的符合
ERC20
标准的通证进行交换,同时,也更容易将你的通证集成到其他DApp中。 总的来说,后续将讨论以下内容:
提到投票,通常会想起普通的选举,例如,通过投票来选出一个国家的首相或总统。在这种情况下,每个公民都会有一票,可以投给他们支持的候选人。
还有另外一种
加权投票(weighted voting)
,它常常用于公开上市交易的公司。 在这些公司,股东的投票权取决于其持有的股票数量。比如,如果你拥有 10,000 股公司股票,你就有 10,000 个投票权(而不是普通选举中的一票)。
例如,假设有一个叫做Block的上市公司。公司有 3 个空闲职位,分别是总裁、副总裁和部长,以及一组候选人。该公司希望通过股东投票的方式来决定哪个候选人得到哪个职位。获得最高票数的候选人将会成为总裁,然后是副总裁,最后是部长。
针对这个应用场景,我们可以构建一个DApp来发行公司股票,该应用允许任何人购买股票从而成为股东。 股东基于其拥有的股票数为候选人投票。例如,如果你持有10,000 股,你可以一个候选人投 5,000 股, 另一个候选人 3,000 股,第三个候选人 2,000 股。
以下是我们将要在本章实现应用的图示,任何人都可以调用合约的
buy()
方法来购买公司发行的股票通证,然后就可以调用合约的
voteForCandidate()
方法为特定的候选人投票:
1.2
综上,我们可以按以下思路来实现加权投票应用:
首先初始化一个新的truffle项目,然后修改关键代码文件:
在部署合约时初始化参与竞争的候选人名单。我们前面已经知道了如何实现这一点,在迁移脚本
2_deploy_contracs.js
中完成这个任务。
由于投票人需要先持有公司股票。所以,我们还需要在部署合约时初始化公司发行的股票总量。 这些股票就是构成公司的数字资产。在以太坊的世界中,这些数字资产被称为通证
(Token)
。 因此,从现在开始,我们将会把这些股票称为股票通证。
当然,股票可以看做是一种通证,但是并非所有的以太坊通证都是股票。股票仅仅是我们前一节中提到的通证使用场景的一种。
我们还需要向投票合约中增加一个新的方法,以便任何人都可以购买这些通证。容易理解,投票人给候选人投票时将使用(消耗)这些股票通证。
接下来还需要添加一个方法来查询投票人的信息,以及他们分别给谁投了票、总共持有多少股票通证、 还有多少可用的通证余额等等。
为了跟踪所有这些数据,我们需要使用几个mapping类型的字段,同时还需要引入新的数据类型
struct(结构体)
来组织投票人信息。
和原来一样,我们使用truffle的
webpack
项目模版来初始化一个新项目, 并从contracts目录下移除无用的合约文件:
~$ mkdir -p ~/repo/tkapp
~$ cd ~/repo/tkapp
~/repo/tkapp$ truffle unbox webpack
~/repo/tkapp$ rm contracts/ConvertLib.sol contracts/MetaCoin.sol
1.3
新的合约设计如下
之前的投票合约仅仅包含两个状态:数组
candidateList
保存候选人名单,字典
votesReceived
跟踪每个候选人获得的投票。
在加权投票合约中,我们需要额外跟踪一些数据:
struct voter {
address voterAddress; //投票人账户地址
uint tokensBought; //投票人持有的股票通证总量
uint[] tokensUsedPerCandidate; //为每个候选人消耗的股票通证数量
}
mapping (address => voter) public voterInfo。
在部署合约时,除了指定候选人名单,我们还需要声明股票通证发行总量和股票单价。 因此在合约的构造函数中,需要补充声明这些参数。例如:
contract Voting{
function Voting(uint tokens, uint pricePerToken, bytes32[] candidateNames) public {}
}
当股东调用
voteForCandidate()
方法投票给特定候选人时,还需要声明其支持力度 —— 用多少股票来支持 这个候选人。因此,我们需要为该方法添加额外的参数以便传入股票通证数量。例如:
contract Voting{
function voteForCandidate(bytes32 candidate, uint votesInTokens) public {}
}
任何人都可以调用
buy()
方法来购买公司发行的股票通证,从而成为公司的股东并获得投票权。 你应该已经注意到了该方法的
payable
修饰符。在Sodility合约中,只有声明为
payable
的方法, 才可以接收支付的货币(
msg.value值
)。
-
contract
Voting{
-
function buy() payable
public returns
(uint)
{
-
//使用msg.value来读取用户的支付金额,这要求方法必须具有payable声明
-
}
-
}
所有合约代码可以在git目录中查看。
1.4
合约中的
buy()
方法用于提供购买股票的接口。注意关键字
payable
,有了它买股票的人才可以付钱给你,接收钱如此简单。
-
function
buy() payable public
returns (
uint)
{
-
uint tokensToBuy
= msg
.value
/ tokenPrice
;
//根据购买金额和通证单价,计算出购买量
-
require(tokensToBuy <=
balanceTokens);
//继续执行合约需要确认合约的通证余额不小于购买量
-
voterInfo
[msg.sender
].voterAddress
= msg
.sender
;
//保存购买人地址
-
voterInfo
[msg.sender
].tokensBought
+= tokensToBuy
;
//更新购买人持股数量
-
balanceTokens
-= tokensToBuy
;
//将售出的通证数量从合约的余额中剔除
-
return tokensToBuy
;
//返回本次购买的通证数量
-
}
当用户(或程序)调用合约的
buy()
方法时,需要在请求消息里利用
value
属性设置用于购买股票通证的以太币金额。例如:
-
contract
.buy({
-
value
:web3
.toWei
('1'
,'ether'
),
//购买者支付的以太币金额
-
from:
web3.
eth.
accounts[
1]
//购买者账户地址
-
})
在合约的
payable
方法实现代码中使用
msg.value
来读取用户支付的以太币数额。 基于用户支付额和股票通证单价,就可以计算出购买数量,并将这些通证赋予购买人, 购买人的账户地址可以通过
msg.sender
获取。
当然,也可以从truffle控制台调用
buy()
方法来购买股票通证:
-
truffle
(development
)>
Voting.
deployed().
then(
function(
contract)
{
contract.
buy({
value:
web3.
toWei(
'1',
'ether'
),
from:
web3.
eth.
accounts[
1]})})
如前所述,加权投票方法不仅要指定候选人名称,还要指定使用多少股票通证来支持该候选人。 我们分别用
candidate
和
votesInTokens
来表示这两个参数: