区块链 区块链技术 比特币公众号手机端

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

liumuhui 11个月前 (11-12) 阅读数 88 #区块链
文章标签 Solidity合约

实现一个投票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. 运行结果

  • 首先需要安装钱包插件 Metamask 小狐狸钱包:https://chromewebstore.google.com/detail/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn

在小狐狸钱包中导入 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

版权声明

本文仅代表作者观点,不代表区块链技术网立场。
本文系作者授权本站发表,未经许可,不得转载。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门