基于Truffle的投票DApp实现

安装Truffle

1
npm install -g truffle 

创建项目

1
2
3
4
mkdir Voting_By_Truffle_DApp
cd Voting_By_Truffle_DApp
npm install -g webpack
truffle unbox webpack

注意:创建项目时的文件夹必须是一个空文件夹且要使用命令行创建项目文件夹,否则我们执行truffle unbox webpack会报错

truffle unbox webpack是一个truffle官方的Demo,我们直接对这个项目进行修改。也可以进入truffle官网查看其他的Demo。

项目实现

Voting.sol

删除contracts目录下除了Migrations.sol文件以外的其他两个合约,然后新建Voting.sol,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
pragma solidity >=0.4.22;

contract Voting {
bytes32[] public candidateList; //候选人数组
mapping(bytes32 => uint256) public votesReceived;

constructor(bytes32[] memory candidateListName) public {
candidateList = candidateListName;
}

//检测是否为有效的投票地址
function validateCandidate(bytes32 candidateName)
internal
view
returns (bool)
{
for (uint256 i = 0; i < candidateList.length; i++) {
if (candidateName == candidateList[i]) return true;
}
return false;
}

//投票函数
function vote(bytes32 candidateName) public {
require(validateCandidate(candidateName));
votesReceived[candidateName] += 1;
}

//返回某个候选人的得票
function totalVotesFor(bytes32 candidateName)
public
view
returns (uint256)
{
require(validateCandidate(candidateName));
return votesReceived[candidateName];
}
}

2_deploy_contracts.js

查看migrates目录下的2_deploy_contracts.js,我们可以看到该目录下有两个js文件,都是有编号的,项目在部署的时候就是根据编号的顺序进行部署。修改2_deploy_contracts.js。

1
2
3
4
5
6
const Voting = artifacts.require("./Voting.sol");

module.exports = function(deployer) {
//传参
deployer.deploy(Voting, ['Alice', 'Bob', 'Cary'].map(x => web3.utils.asciiToHex(x)), { gas: 300000 });
};

这里的deploy就是指部署

注意:这里有些人的truffle版本不一样,我如果不使用map(x => web3.utils.asciiToHex(x))的话项目会报错,使用了map(x => web3.utils.asciiToHex(x))则下面投票和查看票数时也需要使用,这里没使用则下面也不需要,web3.utils.asciiToHex()是使[‘Alice’, ‘Bob’, ‘Cary’]变为16进制字符串

truffle-config.js

这个文件主要就是一些配置方面的内容。做以下修改:

1
2
3
4
5
development: {
host: "127.0.0.1", // Localhost (default: none)
port: 7545, // Standard Ethereum port (default: none)
network_id: "*", // Any network (default: none)
},

这里就是配置连接链的端口,我们开启链的方式一般来说有3种。

  1. 使用truffle命令ruffle develop,这里就和ganache差不多。默认端口8545.
  2. 使用ganache。
  3. 使用geth搭建的私有链,不过如果使用的geth搭建的私有链,我们需要在部署和投票时挖矿才行(要挖矿才会出块)。

项目部署

编译项目

1
truffle compile

部署项目

1
truffle migrate

进入控制台

1
truffle console

测试

查看地址
1
2
var addresses =await web3.eth.getAccounts()
addresses

如果报错则:

1
accounts或web3.eth.accounts
投票
1
Voting.deployed().then(votingInstance=>{votingInstance.vote(web3.utils.asciiToHex('Alice')).then(res=>console.log(res));})
查看票数
1
Voting.deployed().then(votingInstance=>{votingInstance.totalVotesFor(web3.utils.asciiToHex('Alice')).then(res=>console.log(res.toString()));})

前端交互

index.html

在app目录下修改index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<!DOCTYPE html>
<html>

<head>
<meta charset="utf-8">
<title>投票DApp</title>
<link href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css' rel='stylesheet' type='text/css'>
</head>

<body class="container">
<h1>简单区块链投票系统</h1>
<div id="address"></div>
<div class="table-responsive">
<table class="table table-bordered">
<!-- 表头 -->
<thead>
<tr>
<th>候选人</th>
<th>得票数</th>
</tr>
</thead>
<!-- 内容 -->
<tbody>
<tr>
<td>Alice</td>
<td id="candidate-1"></td>
</tr>
<tr>
<td>Bob</td>
<td id="candidate-2"></td>
</tr>
<tr>
<td>Cary</td>
<td id="candidate-3"></td>
</tr>
</tbody>
</table>
<div id="msg"></div>

</div>
<input type="text" id="candidate" />
<a href="#" onclick="voteForCandidate()" class="btn btn-primary">投票</a>
</body>
<script src="https://cdn.jsdelivr.net/gh/ethereum/web3.js/dist/web3.min.js"></script>
<script src="https://libs.baidu.com/jquery/2.1.1/jquery.min.js"></script>
<script src="./index.js"></script>

</html>

index.js

在app目录下修改index.js,这里是新版的MetaMask,因此连接是使用window.ethereum而不是老版的window.web3。而且在交易时必须使用async和await达到同步效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import { default as Web3 } from 'web3';
import { default as contract } from 'truffle-contract';
import voting_artifacts from '../../build/contracts/Voting.json';

var Voting = contract(voting_artifacts);
let candidates = { "Alice": "candidate-1", "Bob": "candidate-2", "Cary": "candidate-3" };

window.voteForCandidate = async function() {
try {
// 使用async 和 await 达到同步效果。
let accAddrs = await web3.eth.getAccounts()
web3.eth.getAccounts().then(console.log);
let candidateName = $("#candidate").val();
$("#" + "candidate").val("");
Voting.deployed().then(votingInstance => {
votingInstance.vote(web3.utils.toHex(candidateName), { from: accAddrs[0] }).then(res => {
let div_id = candidates[candidateName];
votingInstance.totalVotesFor(web3.utils.toHex(candidateName)).then(res => {
$("#" + div_id).html(res.toString());
});
});
});
} catch (error) {
console.log(error);
}
};

$(document).ready(function() {
// console.log(web3.eth.getAccounts());
// console.log(web3.eth.defaultAccount);
var web3Provider;
if (window.ethereum) {
web3Provider = window.ethereum;
try {
// 请求用户授权
window.ethereum.enable();
} catch (error) {
// 用户不授权时
console.error("User denied account access")
}
} else if (window.web3) { // 老版 MetaMask Legacy dapp browsers...
web3Provider = window.web3.currentProvider;
} else {
web3Provider = new Web3.providers.HttpProvider('http://localhost:7545');
}
web3 = new Web3(web3Provider);

Voting.setProvider(web3.currentProvider);
let candidateNames = Object.keys(candidates);
for (let i = 0; i < candidateNames.length; i++) {
let name = candidateNames[i]
Voting.deployed().then(votingInstance => {
votingInstance.totalVotesFor(web3.utils.toHex(name)).then(res => {
$("#" + candidates[name]).html(res.toString());
})
})
}
});

进入app目录下npm run dev,如果报错Can’t resolve ‘truffle-contract’则需要 npm install –save truffle-contract,运行没问题后则投票DApp制作完成。