利用Truffle开发一个DAPP投票应用

  • Kit
  • 更新于 2024-10-26 18:52
  • 阅读 410

实现一个投票DAPP的基本需求: 每人(钱包/账号)只能投票一次 记录一共有多少个候选人 记录每个候选人的的得票数完整的项目结构

实现一个投票DAPP的基本需求是:

  1. 每人(钱包/账号)只能投票一次
  2. 记录一共有多少个候选人
  3. 记录每个候选人的的得票数

完整的项目结构

image.png

1. 创建项目

创建一个目录,进入目录并使用 truffle inti 初始化项目,生成项目结构及配置文件

image 1.png

image 2.png

image 3.png

  • contracts:存放智能合约的文件夹
  • migrations:用来指示如何部署(迁移)智能合约的文件夹,编写部署合约 JavaScript 文件
  • test:智能合约测试用例的文件夹
  • truffle-config.js:配置文件,包括truffle连接的网络(本地测试或公网)以及编译选项(版本)
  • src:web资源文件夹(可自己创建)

启动 Ganache

image 4.png

image 5.png

在配置文件 truffle-config.js 文件中 修改对应RPC,端口以及solidity的版本

image 6.png

image 7.png

2.编写智能合约

在项目的contract目录下新建一个合约 Election.sol

// SPDX-License-Identifier: SEE LICENSE IN LICENSE
pragma solidity ^0.5.0;

contract Election {
    // 记录候选人及得票数
    struct Candidate {
        uint id;
        string name; // 候选人名字
        uint voteCount; //得票数
    }

    // 定义一个mapping记录投票纪录:每人(账号)只能投票一次
    mapping(address => bool) public voters;

    // 通过 id 作为 key 访问映射candidates来获取候选人名单
    mapping(uint => Candidate) public candidates;

    // 共有多少个候选人
    uint public candidatesCount;

    // 投票事件
    event votedEvent(uint indexed _candidateId);

![image 6.png](https://img.learnblockchain.cn/attachments/2024/10/iGTh1izd671cc8f915f45.png)

![image 7.png](https://img.learnblockchain.cn/attachments/2024/10/ASATZKJn671cc8f8eec19.png)
    // 构造函数,部署合约时,初始化添加2个候选人
    constructor() public {
        addCandidate("Tiny 熊");
        addCandidate("Big 牛");
    }

    // 添加候选人方法,把名字添加到candidates映射中,同时候选人数量加1
    function addCandidate(string memory _name) private {
        candidatesCount++;
        candidates[candidatesCount] = Candidate(candidatesCount, _name, 0);
    }

    // 投票方法,在对应候选人的voteCount加1
    function vote(uint _candidateId) public {
        // 检查是否已经投票
        require(!voters[msg.sender]);
        // 检查投票id是否在候选人名单中
        require(_candidateId > 0 && _candidateId <= candidatesCount);
        // 记录谁投票了
        voters[msg.sender] = true;
        // 候选人票数加1
        candidates[_candidateId].voteCount++;
        // 触发投票事件
        emit votedEvent(_candidateId);
    }

3.合约编译及部署

在项目目录下,使用 truffle compile 进行编译合约

image 8.png

在项目的 migrations 的目录下,创建一个用来部署 Election 合约的脚本 1_deploy_contracts.js

const Election = artifacts.require("Election")

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

最后用 truffle migrate 进行迁移(部署)

image 9.png

4. 合约测试

在项目 test 目录中,用 JavaScript 编写一个测试合约的脚本,新建一个 election.js 的文件

const Election = artifacts.require("Election");

contract("Election",function (accounts){
    let instance;

    // it 定义一个测试用例
    it("Election",async function(){
        instance = await Election.deployed();
        // 获取候选人数量
        count =  await instance.candidatesCount();
                // 断言测试用例,满足 ✔,异常 ×
        assert.equal(count, 2, "候选人数目应为2");
    })
});

使用命令 truffle test 运行测试用例

image 10.png

5. 编写前端

新建一个 src 目录用来存放前端资源代码,新建一个html文件

<table class="table">
        <thead>
            <tr>
                <th scope="col">#</th>
                <th scope="col">候选人</th>
                <th scope="col">得票数</th>
            </tr>
        </thead>
        <tbody id="candidatesResults"></tbody>
  </table>
  <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>

新建一个文件 app.js 用来完成交互功能,分别涉及三个功能:

  1. 初始化 web3 及合约
  2. 获取候选人填充到前端页面
  3. 用户提交投票
  • 在 App 类中使用initWeb3 完成web3的初始化
initWeb3: async function() {

        // 检查浏览器 ethereum对象
        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;
        }
        // 如果没有检测到注入的 web3 实例,则回退到 Ganache 网络
        else {
          App.web3Provider = new Web3.providers.HttpProvider('http://localhost:7545 ');
        }

        web3 = new Web3(App.web3Provider);
      return App.initContract();
    },
      // 用来进行合约初始化
    initContract: function() {
      $.getJSON("Election.json", function(election) {
        // Instantiate a new truffle contract from the artifact
        App.contracts.Election = TruffleContract(election);
        // Connect provider to interact with contract
        App.contracts.Election.setProvider(App.web3Provider);

        App.listenForEvents();

        return App.render();
      });
    }
  • 创建完合约对象就可以调用合约函数,render() 函数用来获取候选人进行界面渲染
render: function() {
    var electionInstance;
    var loader = $("#loader");
    var content = $("#content");

    loader.show();
    content.hide();

    // Load account data
    web3.eth.getCoinbase(function(err, account) {
      if (err === null) {
        App.account = account;
        $("#accountAddress").html("Your Account: " + account);
      }
    });

    // Load contract data
    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) {
        // 渲染候选人 candidate 结构体
          var id = candidate[0];
          var name = candidate[1];
          var voteCount = candidate[2];

          // 将候选人信息存入候选人表格中
          var candidateTemplate = "<tr><th>" + id + "</th><td>" + name + "</td><td>" + voteCount + "</td></tr>"
          candidatesResults.append(candidateTemplate);

          // 将候选人信息存入投票选项
          var candidateOption = "<option value='" + id + "' >" + name + "</ option>"
          candidatesSelect.append(candidateOption);
        });
      }
      return electionInstance.voters(App.account);
    }).then(function(hasVoted) {
      // Do not allow a user to vote
      if(hasVoted) {
        $('form').hide();
      }
      loader.hide();
      content.show();
    }).catch(function(error) {
      console.warn(error);
    });
  }

6. 运行DAPP

此案例是一个web应用,需要安装一个web服务器,这里我们选择最简单的 lite-server

  • 使用 npm 安装 lite-server:
npm install --save-dev lite-server
  • 添加一个服务器配置文件:bs-config.json,用来配置 lite-server 的加载目录
{
  "server": {
         // ./src是网页文件目录 ./build/contracts是Truffle编译部署合约输出的目录
    "baseDir": ["./src", "./build/contracts"]
  }
}
  • 新建 package.json 文件,在 scripts 中添加 dev 命令,用来启动 lite-server
{
  "scripts": {
    "dev": "lite-server",
    "test": "echo \"Error: no test specified\" && exit 1"
  }
}
  • 最后在命令行使用命令 npm run dev 启动 DAPP

image 11.png

7. 运行结果

在小狐狸钱包中导入 Ganache 的钱包私钥

image 12.png

image 13.png

image 14.png

image 15.png

image 16.png

image 17.png

image 18.png

image 19.png

image 20.png

image 21.png

在浏览器中输入http://localhost:3000访问投票DAPP

  • 选择候选人进行投票

image 22.png

  • 点击投票后回弹出 web3 钱包,确认交易即是对合约调用 vote() 函数进行投票

image 23.png

  • 投票成功后,所选的候选人得票数加一

image 24.png

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Kit
Kit
江湖只有他的大名,没有他的介绍。