欢迎光临
黎跃春区块链技术博客

【Truffle系列第05篇】从0到1构建去中心化投票DApp

课程目标

  1. 了解区块链智能合约
  2. 学会搭建智能合约开发环境
  3. 学会如何编译智能合约
  4. 学会如何将智能合约部署到区块链
  5. 学会如何通过WebApp和智能合约尽心互动
  6. 掌握DApp(去中心化App)的整个开发部署流程
  7. 掌握去中心化在实战产品中应用的重大意义

项目效果图

去中心化投票App

一、项目模板下载安装

localhost:votingDApp liyuechun$ truffle unbox react-box

Starting unbox...
=================

✔ Preparing to download box
✔ Downloading
node-pre-gyp WARN Using request for node-pre-gyp https download 
node-pre-gyp WARN Using request for node-pre-gyp https download 
✔ cleaning up temporary files
✔ Setting up box

Unbox successful, sweet!

Commands:

  Compile:              truffle compile
  Migrate:              truffle migrate
  Test contracts:       truffle test
  Test dapp:            cd client && npm test
  Run dev server:       cd client && npm run start
  Build for production: cd client && npm run build

二、项目模板结构

  • contracts:编写智能合约的文件夹,所有的智能合约文件都放置在这里
  • migrations:部署合约配置的文件夹
  • client:基于React的Web端源码
  • test:智能合约测试用例文件夹

三、编写投票Dapp智能合约

contracts文件夹下创建Voting.sol文件,将下面的代码拷贝到文件中。

// SPDX-License-Identifier: MIT
pragma experimental ABIEncoderV2;
pragma solidity >=0.5.0 <0.7.0;

contract Voting {

    // liyuechun -> 10
  // xietingfeng -> 5
  // liudehua -> 20
  mapping (string => uint8) public votesReceived;

  // 存储候选人名字的数组
  string[] public candidateList;

  // 构造函数 初始化候选人名单
  constructor(string[] memory _candidateList) public{

    candidateList = _candidateList;
  }

  // 查询某个候选人的总票数
  function totalVotesFor(string memory candidate)  public view returns (uint8) {
    require(validCandidate(candidate) == true);
    // 或者
    // assert(validCandidate(candidate) == true);
    return votesReceived[candidate];
  }

  // 为某个候选人投票
  function voteForCandidate(string memory candidate) public {
    assert(validCandidate(candidate) == true);
    votesReceived[candidate] += 1;
  }

  // 检索投票的姓名是不是候选人的名字
  function validCandidate(string memory candidate) view public returns (bool) {
    for(uint i = 0; i < candidateList.length; i++) {

      if (keccak256(abi.encodePacked(candidateList[i])) == keccak256(abi.encodePacked(candidate))) {
        return true;
      }
    }
    return false;
  }
}

四、通过remix + metamask部署合约到Kovan Test Net

  • 在Google浏览器里面安装MetaMask插件

  • 确保MetaMask账号处于等于状态,并且有一定的以太币支付给矿工。
  • 确保EnvironmentInjected Web3,如果切换不过来,关掉浏览器重新启动
  • Deploy后面的输入框输入一个数组,数组里面的内容为候选人名单
  • 点击Deploy按钮,会弹出MetaMask界面让你确认,确认提交,过一会儿,合约就部署成功
  • 可以测试给某个候选人投票,查询某个候选人的票数

五、查看合约地址

https://ropsten.etherscan.io/tx/0xf03a5f5d9b392534c2420d7f0c9c73795baa5ba0c473d69ba73b27b839291623


合约地址为:0x2fbfd7a34626150ef0977f6d286566435822e43c
备注:这个合约地址后面有用。

六、编译合约

liyc1215:votingDApp liyuechun$ pwd
/Users/liyuechun/Desktop/DAppDemo/votingDApp
liyc1215:votingDApp liyuechun$ ls
LICENSE         contracts       test
client          migrations      truffle-config.js
liyc1215:votingDApp liyuechun$ truffle compile

Compiling your contracts...
===========================
> Compiling ./contracts/Migrations.sol
> Compiling ./contracts/SimpleStorage.sol
> Compiling ./contracts/Voting.sol
> Compilation warnings encountered:

    /Users/liyuechun/Desktop/DAppDemo/votingDApp/contracts/Voting.sol:2:1: Warning: Experimental features are turned on. Do not use experimental features on live deployments.
pragma experimental ABIEncoderV2;
^-------------------------------^

> Artifacts written to /Users/liyuechun/Desktop/DAppDemo/votingDApp/client/src/contracts
> Compiled successfully using:
   - solc: 0.5.16+commit.9c3226ce.Emscripten.clang

liyc1215:votingDApp liyuechun$ 

七、 查看ABI文件

打开/client/src/contracts/Voting.json文件,可以看到ABI json数据,(调用合约时会用到这个ABI文件)。

{
    "contractName": "Voting",
    "abi": [
    {
      "inputs": [
        {
          "internalType": "string[]",
          "name": "_candidateList",
          "type": "string[]"
        }
      ],
      "payable": false,
      "stateMutability": "nonpayable",
      "type": "constructor"
    },
    {
      "constant": true,
      "inputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "name": "candidateList",
      "outputs": [
        {
          "internalType": "string",
          "name": "",
          "type": "string"
        }
      ],
      "payable": false,
      "stateMutability": "view",
      "type": "function"
    },
    {
      "constant": true,
      "inputs": [
        {
          "internalType": "string",
          "name": "",
          "type": "string"
        }
      ],
      "name": "votesReceived",
      "outputs": [
        {
          "internalType": "uint8",
          "name": "",
          "type": "uint8"
        }
      ],
      "payable": false,
      "stateMutability": "view",
      "type": "function"
    },
    {
      "constant": true,
      "inputs": [
        {
          "internalType": "string",
          "name": "candidate",
          "type": "string"
        }
      ],
      "name": "totalVotesFor",
      "outputs": [
        {
          "internalType": "uint8",
          "name": "",
          "type": "uint8"
        }
      ],
      "payable": false,
      "stateMutability": "view",
      "type": "function"
    },
    {
      "constant": false,
      "inputs": [
        {
          "internalType": "string",
          "name": "candidate",
          "type": "string"
        }
      ],
      "name": "voteForCandidate",
      "outputs": [],
      "payable": false,
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "constant": true,
      "inputs": [
        {
          "internalType": "string",
          "name": "candidate",
          "type": "string"
        }
      ],
      "name": "validCandidate",
      "outputs": [
        {
          "internalType": "bool",
          "name": "",
          "type": "bool"
        }
      ],
      "payable": false,
      "stateMutability": "view",
      "type": "function"
    }
  ]
}

八、 修改networks

打开/client/src/contracts/Voting.json文件,可以看到networks数据,我们前面是将合约部署到Ropsten网络,它的networks ID3
访问https://ropsten.etherscan.io/tx/0xf03a5f5d9b392534c2420d7f0c9c73795baa5ba0c473d69ba73b27b839291623查看合约地址和交易Hash,内容填充如下:

"networks": {
    "3": {
      "events": {},
      "links": {},
      "address": "0x2fbfd7a34626150ef0977f6d286566435822e43c",
      "transactionHash": "0xf03a5f5d9b392534c2420d7f0c9c73795baa5ba0c473d69ba73b27b839291623"
    }
  }

九、查看getWeb3.js内容

import Web3 from "web3";

const getWeb3 = () =>
  new Promise((resolve, reject) => {
    // Wait for loading completion to avoid race conditions with web3 injection timing.
    window.addEventListener("load", async () => {
      // Modern dapp browsers...
      if (window.ethereum) {
        const web3 = new Web3(window.ethereum);
        try {
          // Request account access if needed
          await window.ethereum.enable();
          // Acccounts now exposed
          resolve(web3);
        } catch (error) {
          reject(error);
        }
      }
      // Legacy dapp browsers...
      else if (window.web3) {
        // Use Mist/MetaMask's provider.
        const web3 = window.web3;
        console.log("Injected web3 detected.");
        resolve(web3);
      }
      // Fallback to localhost; use dev console port by default...
      else {
        const provider = new Web3.providers.HttpProvider(
          "http://127.0.0.1:8545"
        );
        const web3 = new Web3(provider);
        console.log("No web3 instance injected, using Local web3.");
        resolve(web3);
      }
    });
  });

export default getWeb3;

这个文件主要是封装了一个getWeb3promiss供我们直接使用,可以从getWeb3直接获取到web3对象供App.js文件中使用。

十、修改app.js前端代码和合约进行互动

import React, {Component} from "react";
import VotingContract from "./contracts/Voting.json";
import getWeb3 from "./getWeb3";

import "./App.css";

var _modifyVotingCount = (candidates, i, votingCount) => {

  let obj = candidates[i];
  obj.votingCount = votingCount;
  return candidates;
}

class App extends Component {
  state = {
    candidates: [
      {
        "name": "liyuechun",
        "id": 100,
        "votingCount": 0
      }, {
        "name": "xietingfeng",
        "id": 101,
        "votingCount": 0
      }, {
        "name": "liudehua",
        "id": 102,
        "votingCount": 0
      }, {
        "name": "chenglong",
        "id": 103,
        "votingCount": 0
      }
    ],
    candidatesVoteCount: [
      "0", "0", "0", "0"
    ],
    storageValue: 0,
    web3: null,
    accounts: null,
    contract: null
  };

  componentDidMount = async () => {
    try {
      // Get network provider and web3 instance.
      const web3 = await getWeb3();

      // Use web3 to get the user's accounts.
      const accounts = await web3.eth.getAccounts();

      // Get the contract instance.
      const networkId = await web3.eth.net.getId();
      const deployedNetwork = VotingContract.networks[networkId];
      const instance = new web3.eth.Contract(VotingContract.abi, deployedNetwork && deployedNetwork.address,);

      // Set web3, accounts, and contract to the state, and then proceed with an
      // example of interacting with the contract's methods.
      this.setState({
        web3,
        accounts,
        contract: instance
      }, this.readInitialVotingCount);
    } catch (error) {
      // Catch any errors for any of the above operations.
      alert(`Failed to load web3, accounts, or contract. Check console for details.`,);
      console.error(error);
    }
  };

  readInitialVotingCount = async () => {
    const {accounts, contract} = this.state;

    for (let i = 0; i < this.state.candidates.length; i++) {
      let object = this.state.candidates[i];
      var result = await contract.methods.totalVotesFor(object.name).call();
      console.log(result);
      this.setState({
        candidates: _modifyVotingCount(this.state.candidates, i, result)
      });
    }
    console.log(this.state.candidates);
  };

  handleSubmit = async (event) => {
    event.preventDefault();
    // alert('A name was submitted: ' + this.state.value);

    let candidateName = this.refs.candidateInput.value;
    const {accounts, contract} = this.state;

    await contract.methods.voteForCandidate(candidateName).send({from:accounts[0]});

    this.readInitialVotingCount();

  };

  render() {
    if (!this.state.web3) {
      return <div>Loading Web3, accounts, and contract...</div>;
    }
    return (<div className="App">
      <ul>
        {
          this.state.candidates.map((object) => {
            console.log(object);
            return (<li key={object.id}>候选人:{object.name}
              支持票数:{object.votingCount}</li>)
          })
        }
      </ul>

      <input style={{
          width: 200,
          height: 30,
          borderWidth: 2,
          marginLeft: 40
        }} placeholder="请输入候选人姓名..." ref="candidateInput"/>

      <button style={{
          height: 30,
          borderWidth: 2,
          marginLeft: 20
        }} onClick={this.handleSubmit}>Voting</button>

    </div>);
  }
}

export default App;
赞(0) 打赏
未经允许不得转载:黎跃春区块链技术博客 » 【Truffle系列第05篇】从0到1构建去中心化投票DApp

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

区块链在线课程、区块链职业技术水平认证考试一网打尽

区块链技术在线课程区块链技术职业认证

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏