风险提示:理性看待区块链,提高风险意识!
如何编写智能合约(Smart Contract)- 从零构建和部署去中心化投票App
首页 > 币界资讯 > 区块链知识 2017-10-30 09:00:00

课程目标

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

项目效果图

去中心化投票App

编辑器选择

理论上讲任何编辑器都可以编写Solidity合约代码,比如:WebStorm,VSCode,Sublime,等等。我选择的是Atom,没有任何理由,因为Atom轻量并且界面漂亮。

  • 移步https://atom.io/地址,下载安装Atom。
  • autocomplete-solidity代码自动补齐

p2

  • linter-soliumlinter-solidity代码错误检查
  • language-ethereum支持Solidity代码高亮以及Solidity代码片段

p3

安装所需工具

首先开发机上必须装好Node.js,再使用以下命令安装所需的工具:

$ npm install -g ethereumjs-testrpc truffle
liyuechun:~ yuechunli$ npm install -g ethereumjs-testrpc truffle
/usr/local/bin/testrpc -> /usr/local/lib/node_modules/ethereumjs-testrpc/build/cli.node.js
/usr/local/bin/truffle -> /usr/local/lib/node_modules/truffle/build/cli.bundled.js
+ [email protected]
+ [email protected]
added 1 package and updated 7 packages in 76.132s
liyuechun:~ yuechunli$ 

创建项目

/Users/liyuechun/Desktop/1012/Voting
liyuechun:Voting yuechunli$ ls
liyuechun:Voting yuechunli$ pwd
/Users/liyuechun/Desktop/1012/Voting
liyuechun:Voting yuechunli$ truffle unbox react-box
Downloading...
Unpacking...
Setting up...
Unbox successful. Sweet!

Commands:

  Compile:              truffle compile
  Migrate:              truffle migrate
  Test contracts:       truffle test
  Test dapp:            npm test
  Run dev server:       npm run start
  Build for production: npm run build
liyuechun:Voting yuechunli$ 

项目结构

p4

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

编写投票Dapp智能合约

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

pragma solidity ^0.4.4;

contract Voting {

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

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

  // 构造函数 初始化候选人名单
  function Voting(bytes32[] candidateNames) {

    candidateList = candidateNames;
  }

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

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

  // 检索投票的姓名是不是候选人的名字
  function validCandidate(bytes32 candidate) constant returns (bool) {
    for(uint i = 0; i < candidateList.length; i++) {
      if (candidateList[i] == candidate) {
        return true;
      }
    }
    return false;
  }
}

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

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

p5

  • 打开https://remix.ethereum.org将合约代码拷贝到里面

p6

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

拷贝合约地址

p7

0xd3f33a2e553b363b432d7f81f721a2a6202ecc67

编译合约

liyuechun:Voting yuechunli$ truffle compile
Compiling ./contracts/Migrations.sol...
Compiling ./contracts/SimpleStorage.sol...
Compiling ./contracts/Voting.sol...
Writing artifacts to ./build/contracts

liyuechun:Voting yuechunli$ 

编译完合约以后在build/contracts文件夹下面会有一个Voting.jsonabi文件。

查看Voting.json文件内容

{
  "contract_name": "Voting",
  "abi": [
    {
      "constant": true,
      "inputs": [
        {
          "name": "candidate",
          "type": "bytes32"
        }
      ],
      "name": "totalVotesFor",
      "outputs": [
        {
          "name": "",
          "type": "uint8"
        }
      ],
      "payable": false,
      "type": "function"
    },
    {
      "constant": true,
      "inputs": [
        {
          "name": "candidate",
          "type": "bytes32"
        }
      ],
      "name": "validCandidate",
      "outputs": [
        {
          "name": "",
          "type": "bool"
        }
      ],
      "payable": false,
      "type": "function"
    },
    {
      "constant": true,
      "inputs": [
        {
          "name": "",
          "type": "bytes32"
        }
      ],
      "name": "votesReceived",
      "outputs": [
        {
          "name": "",
          "type": "uint8"
        }
      ],
      "payable": false,
      "type": "function"
    },
    {
      "constant": true,
      "inputs": [
        {
          "name": "",
          "type": "uint256"
        }
      ],
      "name": "candidateList",
      "outputs": [
        {
          "name": "",
          "type": "bytes32"
        }
      ],
      "payable": false,
      "type": "function"
    },
    {
      "constant": false,
      "inputs": [
        {
          "name": "candidate",
          "type": "bytes32"
        }
      ],
      "name": "voteForCandidate",
      "outputs": [],
      "payable": false,
      "type": "function"
    },
    {
      "inputs": [
        {
          "name": "candidateNames",
          "type": "bytes32[]"
        }
      ],
      "payable": false,
      "type": "constructor"
    }
  ],
  "unlinked_binary": "0x6060604052341561000f57600080fd5b6040516103113803806103118339810160405280805190910190505b600181805161003e929160200190610046565b505b506100b5565b828054828255906000526020600020908101928215610083579160200282015b828111156100835782518255602090920191600190910190610066565b5b50610090929150610094565b5090565b6100b291905b80821115610090576000815560010161009a565b5090565b90565b61024d806100c46000396000f300606060405263ffffffff7c01000000000000000000000000000000000000000000000000000000006000350416632f265cf78114610069578063392e6678146100955780637021939f146100bf578063b13c744b146100eb578063cc9ab26714610113575b600080fd5b341561007457600080fd5b61007f60043561012b565b60405160ff909116815260200160405180910390f35b34156100a057600080fd5b6100ab60043561015d565b604051901515815260200160405180910390f35b34156100ca57600080fd5b61007f6004356101af565b60405160ff909116815260200160405180910390f35b34156100f657600080fd5b6101016004356101c4565b60405190815260200160405180910390f35b341561011e57600080fd5b6101296004356101e7565b005b60006101368261015d565b151560011461014457600080fd5b5060008181526020819052604090205460ff165b919050565b6000805b6001548110156101a457600180548491908390811061017c57fe5b906000526020600020900160005b5054141561019b57600191506101a9565b5b600101610161565b600091505b50919050565b60006020819052908152604090205460ff1681565b60018054829081106101d257fe5b906000526020600020900160005b5054905081565b6101f08161015d565b15156001146101fb57fe5b6000818152602081905260409020805460ff8082166001011660ff199091161790555b505600a165627a7a723058206783a7ff47eae16f18011a9db2a3cc983350b779bf3181b7623c18d4bce363180029",
  "networks": {},
  "schema_version": "0.0.5",
  "updated_at": 1507806214330
}

这个文件是编译后的abi文件,待会儿需要将这个文件的json导入到App.json中。

查看src/utils/getWeb3.js文件内容

import Web3 from 'web3'

let getWeb3 = new Promise(function(resolve, reject) {
  // Wait for loading completion to avoid race conditions with web3 injection timing.
  window.addEventListener('load', function() {
    var results
    var web3 = window.web3

    // Checking if Web3 has been injected by the browser (Mist/MetaMask)
    if (typeof web3 !== 'undefined') {
      // Use Mist/MetaMask's provider.
      web3 = new Web3(web3.currentProvider)

      results = {
        web3: web3
      }

      console.log('Injected web3 detected.');

      resolve(results)
    } else {
      // Fallback to localhost if no web3 injection.
      var provider = new Web3.providers.HttpProvider('http://localhost:8545')

      web3 = new Web3(provider)

      results = {
        web3: web3
      }

      console.log('No web3 instance injected, using Local web3.');

      resolve(results)
    }
  })
})

export default getWeb3

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

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

import React, { Component } from 'react'
import VotingContract from '../build/contracts/Voting.json'
import getWeb3 from './utils/getWeb3'

import './css/oswald.css'
import './css/open-sans.css'
import './css/pure-min.css'
import './App.css'

const contractAddress = "0xd3f33a2e553b363b432d7f81f721a2a6202ecc67";
var votingContractInstance;

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

    console.log("---------");
    console.log(candidates);
    console.log(i);
    console.log(votingCount);

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

class App extends Component {
  constructor(props) {
    super(props)

    this.state = {
      candidates: [
                    {
                      "name": "Rama",
                      "id": 100,
                      "votingCount": 0
                    },
                    {
                      "name": "Nick",
                      "id": 101,
                      "votingCount": 0
                    },
                    {
                      "name": "Jose",
                      "id": 102,
                      "votingCount": 0
                    },
                    {
                      "name": "liyuechun",
                      "id": 103,
                      "votingCount": 0
                    }
                  ],
      candidatesVoteCount: ["0","0","0","0"],
      web3: null
    }
  }

  componentWillMount() {
    // Get network provider and web3 instance.
    // See utils/getWeb3 for more info.

    getWeb3
    .then(results => {
      this.setState({
        web3: results.web3
      })

      // Instantiate contract once web3 provided.
      this.instantiateContract()
    })
    .catch(() => {
      console.log('Error finding web3.')
    })
  }

  instantiateContract() {
    /*
     * SMART CONTRACT EXAMPLE
     *
     * Normally these functions would be called in the context of a
     * state management library, but for convenience I've placed them here.
     */

    const contract = require('truffle-contract')
    const votingContract = contract(VotingContract)
    votingContract.setProvider(this.state.web3.currentProvider)

    // Declaring this for later so we can chain functions on SimpleStorage.

    // Get accounts.
    this.state.web3.eth.getAccounts((error, accounts) => {
      votingContract.at(contractAddress).then((instance) => {

        votingContractInstance = instance;
        for (let i = 0; i < this.state.candidates.length; i++) {
            let object = this.state.candidates[i];
            console.log(accounts[0]);
            console.log(votingContractInstance);
            console.log(votingContractInstance.totalVotesFor(object.name));
            votingContractInstance.totalVotesFor(object.name).then(result => {
              console.log(i);
              console.log(result.c[0]);
              this.setState({
                candidates: _modifyVotingCount(this.state.candidates,i,result.c[0])
              });
            });
        }
      })
    })
  }

  render() {
    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=
            placeholder="请输入候选人姓名..."
            ref="candidateInput"
      />

      <button style= onClick={() => {
        console.log(this.refs.candidateInput);
        console.log(this.refs.candidateInput.value);
        let candidateName = this.refs.candidateInput.value;
        console.log(this.state.web3.eth.accounts[0]);
        votingContractInstance.voteForCandidate(candidateName).then((result => {
          console.log(result);
          console.log(candidateName);
          let number = 0;
          for(let i = 0; i < this.state.candidates.length; i++) {
            let object = this.state.candidates[i];
            if (object.name === candidateName) {
              number = i;
              break;
            }
          }
          votingContractInstance.totalVotesFor(candidateName).then(result => {

            this.setState({
              candidates: _modifyVotingCount(this.state.candidates,number,result.c[0])
            });
          });

        }));
      }}>Voting</button>

      </div>
    );
  }
}

export default App

去中心化投票App

上一篇: 以太坊(Ethereum)私链建立 、合约编译、部署完全教程(1)
下一篇: 如何通过 MyEtherWallet 创建钱包以及如何通过 Ethereum Wallet 和 MetaMask 恢复钱包账号
推荐专栏
web3首席知识博主
一位相信价值投资的币圈KOL。稳定盈利的缠论野生交易员 #BTC行情分析师 #价值投资 #链上数据分析
爱Web 3,爱生活,爱科技,爱炒币的老韭菜
热门币种
更多
币种
价格
24H涨跌幅
BTC比特币
¥264,810.46
37,103.37 USDT
+0.1%
ETH以太坊
¥14,413.37
2,019.50 USDT
0%
USDT泰达币
¥7.20
1.01 USDT
0%
BNB币安币
¥1,629.54
228.32 USDT
+0.63%
XRP瑞波币
¥4.32
0.60480 USDT
+0.45%
USDC
¥7.14
1.00 USDT
+0.04%
SOLSolana
¥400.45
56.11 USDT
+1.59%
OKBOK币
¥399.37
55.96 USDT
-1.23%
ADA艾达币
¥2.67
0.37480 USDT
-1.32%
DOGE狗狗币
¥0.55330
0.07753 USDT
-1.01%
热搜币种
更多
币种
价格
24H涨跌幅
Terra Classic
¥0.00
9.481E-5 USDT
-18.15%
Gala
¥0.18
0.025383 USDT
-4.86%
dYdX
¥22.63
3.1984 USDT
-0.73%
比特股
¥0.05
0.006569 USDT
-1.93%
PancakeSwap
¥15.60
2.2054 USDT
-2.16%
Conflux
¥1.08
0.1531 USDT
-2.36%
Filecoin
¥31.55
4.4597 USDT
-0.5%
FTX Token
¥29.65
4.1911 USDT
+15.24%
Yield Guild Games
¥2.56
0.3615 USDT
-0.55%
Shiba Inu
¥0.00
8.15E-6 USDT
-2.16%
比特币
¥262,467.38
37103.37 USDT
+0.1%
比原链
¥0.07
0.010022 USDT
-4.68%
最新快讯
更多
汇丰、恒生、渣打、富邦华一四家外资银行入围首批“数字人民币”业务试点名单
2023-11-28 19:06:57
摩根大通和Apollo计划建立代币化“企业主网”
2023-11-28 19:03:57
Nansen2公测版本上线,新增链上数据异动、智能搜索等功能
2023-11-28 18:59:52
西班牙公民需在明年3月底前申报其海外平台上加密货币持仓
2023-11-28 18:53:43
Nansen2已公开测试
2023-11-28 18:53:38
dYdX基金会:主网启动以来超过1645万DYDX被质押
2023-11-28 18:52:07
NicCarter等比特币倡导者发文:比特币挖矿是清洁能源和平衡电网的关键工具
2023-11-28 18:47:58
下载币界网APP