专栏名称: 沪江技术学院
开发
目录
相关文章推荐
51好读  ›  专栏  ›  沪江技术学院

DAPP开发实战之投票系统(下)

沪江技术学院  · 掘金  ·  · 2018-04-19 08:01

正文

文: Xwang

本文原创,转载请注明作者及出处

上一篇文章我们介绍了一些 DAPP基本原理与基础开发知识 ,以及从实战的角度从零开始实现了一个基于区块链简单投票系统,这篇文章会接着 上一篇 继续开发,并且引入数字通证(Token)的概念。

一.引入数字代币进行第三次迭代

1.1

在以太坊中,一个重要概念就是通证(token),也就是常说的加密数字币,或者代币,比如以下这些。

通证就是在以太坊上构建的一种数字资产,可以用它来表示现实世界里的东西,比如黄金,或者是自己的数字资产(就像货币一样)。实际上就是智能合约,并没有什么太多的神奇之处,举个栗子:

  • 黄金通证:银行可以有1千克的黄金储备,然后发行1千个通证。买100个黄金通证就等于买100克的黄金。

  • 公司股票:公司股票可以用以太坊上的代币来表示。通过支付以太币,人们可以购买公司股票。

  • 游戏币:在一个多玩家游戏中,游戏者可以用以太币购买游戏币,并在游戏中进行消费。

  • 忠诚度积分:商店可以给购物者发行通证作为忠诚度积分,它可以在将来作为现金回收,或是在第三方市场售卖。

在合约中如何实现通证,实际上并没有限制。但是,以太坊有一个叫做 ERC20 的通证标准,该标准还在不断进化中。 ERC20 通证的优点是很容易与其他的符合 ERC20 标准的通证进行交换,同时,也更容易将你的通证集成到其他DApp中。 总的来说,后续将讨论以下内容:

  • 学习并掌握新的数据类型,比如结构体(struct),以便在区块链上组织和存储数据

  • 理解通证概念并实现投票应用的通证

  • 学习使用以太币进行支付,以太币是以太坊区块链平台的数字加密货币。

提到投票,通常会想起普通的选举,例如,通过投票来选出一个国家的首相或总统。在这种情况下,每个公民都会有一票,可以投给他们支持的候选人。

还有另外一种 加权投票(weighted voting) ,它常常用于公开上市交易的公司。 在这些公司,股东的投票权取决于其持有的股票数量。比如,如果你拥有 10,000 股公司股票,你就有 10,000 个投票权(而不是普通选举中的一票)。

例如,假设有一个叫做Block的上市公司。公司有 3 个空闲职位,分别是总裁、副总裁和部长,以及一组候选人。该公司希望通过股东投票的方式来决定哪个候选人得到哪个职位。获得最高票数的候选人将会成为总裁,然后是副总裁,最后是部长。

针对这个应用场景,我们可以构建一个DApp来发行公司股票,该应用允许任何人购买股票从而成为股东。 股东基于其拥有的股票数为候选人投票。例如,如果你持有10,000 股,你可以一个候选人投 5,000 股, 另一个候选人 3,000 股,第三个候选人 2,000 股。

以下是我们将要在本章实现应用的图示,任何人都可以调用合约的 buy() 方法来购买公司发行的股票通证,然后就可以调用合约的 voteForCandidate() 方法为特定的候选人投票:

1.2

综上,我们可以按以下思路来实现加权投票应用:

首先初始化一个新的truffle项目,然后修改关键代码文件:

  • 投票合约:Voting.sol

  • 合约迁移脚本:2deploycontracts.js

  • 前端代码:index.html、app.js和app.css

在部署合约时初始化参与竞争的候选人名单。我们前面已经知道了如何实现这一点,在迁移脚本 2_deploy_contracs.js 中完成这个任务。

由于投票人需要先持有公司股票。所以,我们还需要在部署合约时初始化公司发行的股票总量。 这些股票就是构成公司的数字资产。在以太坊的世界中,这些数字资产被称为通证 (Token) 。 因此,从现在开始,我们将会把这些股票称为股票通证。

当然,股票可以看做是一种通证,但是并非所有的以太坊通证都是股票。股票仅仅是我们前一节中提到的通证使用场景的一种。

我们还需要向投票合约中增加一个新的方法,以便任何人都可以购买这些通证。容易理解,投票人给候选人投票时将使用(消耗)这些股票通证。

接下来还需要添加一个方法来查询投票人的信息,以及他们分别给谁投了票、总共持有多少股票通证、 还有多少可用的通证余额等等。

为了跟踪所有这些数据,我们需要使用几个mapping类型的字段,同时还需要引入新的数据类型 struct(结构体) 来组织投票人信息。

和原来一样,我们使用truffle的 webpack 项目模版来初始化一个新项目, 并从contracts目录下移除无用的合约文件:

  1. ~$ mkdir -p ~/repo/tkapp

  2. ~$ cd ~/repo/tkapp

  3. ~/repo/tkapp$ truffle unbox webpack

  4. ~/repo/tkapp$ rm contracts/ConvertLib.sol contracts/MetaCoin.sol

1.3

新的合约设计如下

之前的投票合约仅仅包含两个状态:数组 candidateList 保存候选人名单,字典 votesReceived 跟踪每个候选人获得的投票。

在加权投票合约中,我们需要额外跟踪一些数据:

  • 投票人信息:solidity的 结构体( struct) 类型可以将相关数据组织在一起(类似于JAVA BEAN)。用结构体来存储投票人信息非常好。我们将使用一个 struct 来存储投票人的账户、已经购买的股票通证数量以及给每个候选人投票时所用的股票数量。例如:

  1. struct voter {

  2.  address voterAddress; //投票人账户地址

  3.  uint tokensBought;    //投票人持有的股票通证总量

  4.  uint[] tokensUsedPerCandidate; //为每个候选人消耗的股票通证数量

  5. }

  • 投票人信息字典:使用一个mapping字典来保存所有的投票人信息,键为投票人账户地址,值为投票人信息。 这样给定一个投票人的账户地址,就可以很方面地提取他的相关信息。我们使用voterInfo来表示该字典。 例如:

  1. mapping (address => voter) public voterInfo。

  • 股票通证的相关信息:使用 totalTokens 来保存通证发行总量, balanceTokens 保存通证余额, tokenPrice 保存通证的价格。

在部署合约时,除了指定候选人名单,我们还需要声明股票通证发行总量和股票单价。 因此在合约的构造函数中,需要补充声明这些参数。例如:

  1. contract Voting{

  2.  function Voting(uint tokens, uint pricePerToken, bytes32[] candidateNames) public {}

  3. }

当股东调用 voteForCandidate() 方法投票给特定候选人时,还需要声明其支持力度 —— 用多少股票来支持 这个候选人。因此,我们需要为该方法添加额外的参数以便传入股票通证数量。例如:

  1. contract Voting{

  2. function voteForCandidate(bytes32 candidate, uint votesInTokens) public {}

  3. }

任何人都可以调用 buy() 方法来购买公司发行的股票通证,从而成为公司的股东并获得投票权。 你应该已经注意到了该方法的 payable 修饰符。在Sodility合约中,只有声明为 payable 的方法, 才可以接收支付的货币( msg.value值 )。

                                                        
  1. contract Voting{

  2.   function buy() payable public returns (uint) {

  3.     //使用msg.value来读取用户的支付金额,这要求方法必须具有payable声明

  4.   }

  5. }

所有合约代码可以在git目录中查看。

1.4

合约中的 buy() 方法用于提供购买股票的接口。注意关键字 payable ,有了它买股票的人才可以付钱给你,接收钱如此简单。

                                                            
  1. function buy() payable public returns ( uint) {

  2.   uint tokensToBuy = msg .value / tokenPrice ;   //根据购买金额和通证单价,计算出购买量  

  3.   require(tokensToBuy <= balanceTokens);       //继续执行合约需要确认合约的通证余额不小于购买量  

  4.  voterInfo [msg.sender ].voterAddress = msg .sender ;     //保存购买人地址

  5.  voterInfo [msg.sender ].tokensBought += tokensToBuy ;   //更新购买人持股数量

  6.  balanceTokens -= tokensToBuy ;                 //将售出的通证数量从合约的余额中剔除  

  7.   return tokensToBuy ;                           //返回本次购买的通证数量

  8. }

当用户(或程序)调用合约的 buy() 方法时,需要在请求消息里利用 value 属性设置用于购买股票通证的以太币金额。例如:

                                                                
  1. contract .buy({

  2.  value :web3 .toWei ('1' ,'ether' ), //购买者支付的以太币金额

  3.   from: web3. eth. accounts[ 1]      //购买者账户地址

  4. })

在合约的 payable 方法实现代码中使用 msg.value 来读取用户支付的以太币数额。 基于用户支付额和股票通证单价,就可以计算出购买数量,并将这些通证赋予购买人, 购买人的账户地址可以通过 msg.sender 获取。

当然,也可以从truffle控制台调用 buy() 方法来购买股票通证:

                                                                        
  1. truffle (development )> Voting. deployed(). then( function( contract) { contract. buy({ value: web3. toWei( '1', 'ether' ), from: web3. eth. accounts[ 1]})})

如前所述,加权投票方法不仅要指定候选人名称,还要指定使用多少股票通证来支持该候选人。 我们分别用 candidate votesInTokens 来表示这两个参数:







请到「今天看啥」查看全文