如何使用Truffle开发以太坊投票DAPP

投票最担心的是暗箱操作、利用区块链的去中心化技术,来实现一个DAPP保证投票公平公正,来看看如何实现,通过本文可以了解到映射mapping 、结构体struct 及事件 event 的使用。

投票需求分及实现效果

要实现一个投票DApp,对于合约来说有两个基本需求:

  1. 每人(账号)只能投一票;
  2. 记录下一共有多少候选人
  3. 记录每个候选人的得票数。

投票合约实现

数据存储

每人(账号)只能投一票很容易实现,只需要使用一个mapping 来记录每个地址的投票信息

定义一个 mapping 记录投票记录:

mapping(address => bool) public voters;
记录候选人及得票数, 我们思考下,如何合约中表示一个候选人,这里我们用一个结构体来表示候选人:
struct Candidate {
    uint id;
    string name;  // 候选人的名字
    uint voteCount;
}

在Candidate结构体中,用voteCount表示得票数。我们还需要记录下一共有多少个候选人,直觉是保存到一个数组,前端需要候选人列表时,直接把这个数组返回给前端。

基于EVM的限制,外部函数是没法返回动态的内容,所以这里我们需要使用一个变通的方案。

用一个变量保存一共有多少个候选人uint public candidatesCount,然后定义一个映射:

mapping(uint => Candidate) public candidates

通过id作为key访问映射candidates来取候选人。

投票功能实现

接下来就是添加功能: 主要是两个功能: 添加候选人及投票。

每添加一个候选人就加入到candidates映射中,同时候选人数量加1,添加候选人addCandidate函数实现为:

function addCandidate (string memory _name) private {
        candidatesCount ++;
        candidates[candidatesCount] = Candidate(candidatesCount, _name, 0);
    }

我们在合约创建的时候,就把候选人添加好,在构造函数中,调用addCandidate,构造函数实现如下:

   constructor () public {
        addCandidate("xx1");
        addCandidate("xx2");
    }

投票就是在对应的候选人的voteCount加1,同时这个函数需要一个参数即给哪一个候选人投票,另外需要进行一些合法性检查: 候选人是有效的,投票人必须没有投过票,投票vote函数实现如下:

 function vote (uint _candidateId) public {
        require(!voters[msg.sender]);
        require(_candidateId > 0 && _candidateId <= candidatesCount);

        voters[msg.sender] = true;
        candidates[_candidateId].voteCount ++;
    }

事件Event

为了有更好的前端体验, 在用户投票之后,应该及时的刷新页面, 这就需要用到事件了。

先定义一个事件:

 // voted event
    event votedEvent (
        uint indexed _candidateId
    );

然后在投票vote函数中最后一行加入发出事件:

emit votedEvent(_candidateId);

合约部分代码编写完了

合约部署

为合约Election,添加一个部署脚本:

var Election = artifacts.require("./Election.sol");

module.exports = function(deployer) {
  deployer.deploy(Election);
};

在部署之前,还需要打开以太坊的模拟节点Ganache,并确保Truffle配置文件truffle.js 链接节点的地址和端口与Ganache 一致。然后运行一下命令进行部署:

truffle migrate

前端界面

有一个html table标签显示候选人列表:

 <table class="table">
      <thead>
        <tr>
          <th scope="col">#</th>
          <th scope="col">候选人</th>
          <th scope="col">得票数</th>
        </tr>
      </thead>
      <tbody id="candidatesResults">
      </tbody>
    </table>

candidatesResults的内容,需要使用web3.js从合约中读取候选人信息后动态填入。

使用form标签执行投票操作:

    <form onSubmit="App.castVote(); return false;">
      <div class="form-group">
        <label for="candidatesSelect">选择候选人</label>
        <select class="form-control" id="candidatesSelect">
        </select>
      </div>
      <button type="submit" class="btn btn-primary">投票</button>
      <hr />
    </form>

合约交互

分三个部分:

  1. 初始化 web3 及合约
  2. 获取候选人填充到前端页面
  3. 用户提交投票

初始化

initWeb3函数中,完成web3的初始化

  initWeb3: async function() {
    if (window.ethereum) {
      App.web3Provider = window.ethereum;
      try {
        await window.ethereum.enable();
      } catch (error) {
        console.error("User denied account access")
      }
    }
    else if (window.web3) {
      App.web3Provider = window.web3.currentProvider;
    }
    else {
      App.web3Provider = new Web3.providers.HttpProvider('http://localhost:9545');
    }
    web3 = new Web3(App.web3Provider);
    return App.initContract();
}

web3的初始化,调用App.initContract进行合约初始化:

 initContract: function() {
    $.getJSON("Election.json", function(election) {
      App.contracts.Election = TruffleContract(election);
      App.contracts.Election.setProvider(App.web3Provider);
      App.listenForEvents();
      return App.render();
    });
  }

监听投票事件

listenForEvents: function() {    App.contracts.Election.deployed().then(function(instance) {
      instance.votedEvent({}, {
        fromBlock: 0,
        toBlock: 'latest'
      }).watch(function(error, event) {
        App.render();
      });
    });
  }

候选人界面渲染

 render: function() {
    var electionInstance;
    App.contracts.Election.deployed().then(function(instance) {
      electionInstance = instance;
      return electionInstance.candidatesCount();  // ❶
    }).then(function(candidatesCount) {
      var candidatesResults = $("#candidatesResults");
      candidatesResults.empty();

      var candidatesSelect = $('#candidatesSelect');
      candidatesSelect.empty();

      for (var i = 1; i <= candidatesCount; i++) {
        electionInstance.candidates(i).then(function(candidate) {  // ❷
          var id = candidate[0];
          var name = candidate[1];
          var voteCount = candidate[2];

          // Render candidate Result
          var candidateTemplate = "<tr><th>" + id + "</th><td>" + name + "</td><td>" + voteCount + "</td></tr>"
          candidatesResults.append(candidateTemplate); // ❸

          // Render candidate ballot option
          var candidateOption = "<option value='" + id + "' >" + name + "</ option>"
          candidatesSelect.append(candidateOption);  // ❹
        });
      }
    }
  • ❶ 获取候选人数量
  • ❷ 依次获取某一个候选人信息
  • ❸ 候选人信息写入候选人表格内
  • ❹ 候选人信息写入投票选项

运行DApp

使用以下命令,启动DApp 服务:

npm run dev

 在浏览器打开http://localhost:3000 , 浏览器的MetaMask 小狐狸插件需要连接到Ganache网络, 因为只有网络一致才可以读取到网络上的合约数据。

合约源码请私信本人

 文章不断跟新,欢迎长期关注,同时如有错误请评论区或私信纠正互相研究学习!

VX:LALAKO_2

社区:http://t.csdnimg.cn/CNPxZ

免责声明:我们研究和内容中提供的信息仅供参考,不应被视为财务或投资建议。投资或参与金融活动的任何决定完全由你负责。我们不认可或推荐任何特定的投资策略,个人在做出任何投资决策之前应进行自己的研究并寻求专业建议。我们不对因将我们的内容用于投资目的而导致的任何财务损失、损害或不便承担责任。请始终注意与金融市场相关的风险,并在做出任何投资选择之前仔细考虑你的财务状况和风险承受能力。

  • 45
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

HongYingClub

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值