课程目标
- 了解区块链智能合约
- 学会搭建智能合约开发环境
- 学会如何编译智能合约
- 学会如何将智能合约部署到区块链
- 学会如何通过WebApp和智能合约尽心互动
- 掌握DApp(去中心化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
插件
- 打开https://remix.ethereum.org将合约代码拷贝到里面
- 确保
MetaMask
账号处于等于状态,并且有一定的以太币支付给矿工。 - 确保
Environment
是Injected 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 ID
为3
。
访问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;
这个文件主要是封装了一个getWeb3
的promiss
供我们直接使用,可以从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;
发布者:黎跃春,未经授权,禁止转载:https://fdigit.com/archives/2023