Table of contents
Prerequisite: Install the package
Simple voting Dapp design process
(2) Read the content of the contract
Method 1: Obtain the contract address and abi
(Unpassed version) Simple voting Dapp design process
(2) Read the content of the contract
Realize the effect preview
Prerequisite: Install the package
Bag:
"ganache-cli": "^6.1.8",
"solc": "^0.4.25" // Before using "solc": "^0.7.3", there are many differences between the two compilations, and many problems have been changed , and finally got stuck with the version
"web3": "^1.7.0"system:
"ubuntu-20.04.4-desktop-amd64"
Below is the Linux based installation guide. Here we are required to pre-install nodejs and npm, and then use npm to install ganache , solc , web3 , and then we can continue to the next step of the project
Install the following environment in the simple_vote_dapp folder
mkdir ~/桌面/simple_vote_dapp
cd ~/桌面/simple_vote_dapp
npm init
sudo npm install [email protected] [email protected] [email protected]
If the installation is successful, run the following command, you should be able to see the following output
~/桌面/simple_vote_dapp/node_modules/.bin/ganache-cli
Create a new contract to store the sol contract file
mkdir ~/桌面/simple_vote_dapp/contracts
Directory Structure
Make sure that ganache is already running in another window at the same time
Start another terminal , and in the simple_vote_dapp directory
Run node to enter the node console, initialize the web3 object, and query the blockchain to get all accounts.
node
var Web3 = require('web3')
var web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
var accounts;
web3.eth.getAccounts().then((res)=>{accounts = res;console.log(accounts)});
Simple voting Dapp design process
solidity contract
We design a contract called Voting, which has the following contents:
- A constructor to initialize some candidates
- A method for voting (count of votes + 1)
- A method that returns the total number of votes a candidate has received
When you deploy the contract to the blockchain, the constructor is called, and only once. Unlike in the web world where every deployment of code will overwrite the old code, the contract deployed on the blockchain is immutable, that is, if you update the contract and deploy it again, the old contract will still exist on the blockchain
Create Voting.sol contract
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.25 <0.9.0;
contract Voting{
bytes32[] public candidateList;
mapping(bytes32 => uint8) public votesReceived;
constructor(bytes32[] memory candidateListName){
candidateList = candidateListName;
}
function validateCandidate(bytes32 candidateName) internal view returns(bool){
for (uint8 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(uint8){
require(validateCandidate(candidateName));
return votesReceived[candidateName];
}
}
To compile the contract, first load the code from Voting.sol and bind it to a variable of type string
Compile the contract
Under the node console
(1) Import solc and fs
var solc = require('solc')
var fs = require('fs')
(2) Read the content of the contract
var contractContent = fs.readFileSync('./contracts/Voting.sol', 'utf-8').toString()
(3) Compile the contract code
var contractCompiled = solc.compile(contractContent);
(4) Take abi, byteCode
var abi = contractCompiled['contracts'][':Voting']['interface']
var byteCode = contractCompiled['contracts'][':Voting']['bytecode']
deploy contract
(1) Create a Web3 instance
var Web3 = require('web3')
var web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
(2) Create a Contract object
var contract = new web3.eth.Contract(JSON.parse(abi));
(3) Deployment contract
var accounts;
web3.eth.getAccounts().then((res)=>{accounts = res;console.log(accounts)});
// 合约拥有者账户
var account = accounts[0];
var gasLimit = 3000000;
// 参数需要两个数组是因为 deploy 参数 arguments 本身接收的就是一个数组,用以接收多个数据,所以最外层是一个数组,里面的数组是我们 Voting合约构造函数定义的参数为 bytes32[]
// 因为构造函数参数定义为 bytes32[],所以我们需要传入 bytes32,而不能直接传入字符串
var argument = [[web3.utils.fromAscii('Alice'),web3.utils.fromAscii('Bob'),web3.utils.fromAscii('Cary')]]
// 或
// var argument = [["0x416c696365000000000000000000000000000000000000000000000000000000","0x426f620000000000000000000000000000000000000000000000000000000000","0x4361727900000000000000000000000000000000000000000000000000000000"]]
contract.deploy({
data:byteCode,
arguments:argument
}).send({
from:account,
gas:gasLimit,
}).then(instance => {
contractInstance = instance;
console.log("contract address:", instance.options.address)
})
Write down the contract address contract address, and later modify the contract address in fontend-Voting.js to your own
full code
var solc = require('solc')
var fs = require('fs')
var Web3 = require('web3')
var web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
var contractContent = fs.readFileSync('./contracts/Voting.sol', 'utf-8').toString()
var contractCompiled = solc.compile(contractContent);
var abi = contractCompiled['contracts'][':Voting']['interface']
var byteCode = contractCompiled['contracts'][':Voting']['bytecode']
var contract = new web3.eth.Contract(JSON.parse(abi));
var accounts;
web3.eth.getAccounts().then((res)=>{accounts = res;console.log(accounts)});
var account = accounts[0];
var gasLimit = 3000000;
var argument = [[web3.utils.fromAscii('Alice'),web3.utils.fromAscii('Bob'),web3.utils.fromAscii('Cary')]]
// 或
// var argument = [["0x416c696365000000000000000000000000000000000000000000000000000000","0x426f620000000000000000000000000000000000000000000000000000000000","0x4361727900000000000000000000000000000000000000000000000000000000"]]
contract.deploy({
data:byteCode,
arguments:argument
}).send({
from:account,
gas:gasLimit,
}).then(instance => {
contractInstance = instance;
console.log("contract address:", instance.options.address)
})
Call the contract method
Call the vote method in the contract to vote for Alice
var voteTo = web3.utils.fromAscii('Alice')
contractInstance.methods.vote(voteTo).send({from:accounts[0]}).then(console.log)
See how many votes a candidate has received
var aVoter = web3.utils.fromAscii('Alice')
contractInstance.methods.totalVotesFor(aVoter).call({from:accounts[0]}).then(res=>console.log(res))
Front page HTML
<!DOCTYPE html>
<html lang="en">
<head>
<title>Voting 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>Simple Voting DApp</h1>
<div class="table-responsive">
<table class="table table-bordered">
<thead>
<th>Candidate</th>
<th>Vote Count</th>
</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>
<input type="text" id="candidate" />
<a href="#" onclick="" class="btn btn-primary">Vote</a>
</div>
</body>
<script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/ethereum/[email protected]/dist/web3.min.js"></script>
<script src="./fontend-Voting.js"></script>
</html>
Front-end JS
Method 1: Obtain the contract address and abi
solcjs --abi --bin Voting.sol
Method 2: Get abi
solc --abi --bin Voting.sol
let voteForCandidate;
let initial = async() => {
// 不需要先 var Web3 = require('web3'), 因为已经网络引入了
var web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
var abi = JSON.parse('[{"constant":true,"inputs":[{"name":"candidateName","type":"bytes32"}],"name":"totalVotesFor","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"getLengthList","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"votesReceived","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"tlength","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"candidateName","type":"bytes32"}],"name":"vote","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"candidateList","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"candidateListName","type":"bytes32[]"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"}]')
var contractAddress = '使用你自己的,上面步骤中有输出';
var contractInstance = await new web3.eth.Contract(abi, contractAddress);
var accounts = await web3.eth.getAccounts();
console.log(accounts)
// 对应关系
var candidates = { "Alice": "candidate-1", "Bob": "candidate-2", "Cary": "candidate-3" };
(async() => {
var candidateList = Object.keys(candidates); // 拿出 candidates 中的 key 值
for (let i = 0; i < candidateList.length; i++) {
let name = candidateList[i];
let aVoter = await web3.utils.fromAscii(name)
let count = await contractInstance.methods.totalVotesFor(aVoter).call({ from: accounts[0] });
console.log('count=', count)
$("#" + candidates[name]).html(count)
}
})();
voteForCandidate = async() => {
var candidateName = $("#candidate").val()
try {
var voteTo = await web3.utils.fromAscii(candidateName)
contractInstance.methods.vote(voteTo).send({ from: accounts[0] }, async(err, res) => {
if (err) {
console.log("Error: ", err);
} else {
let id = candidates[candidateName];
var count = await contractInstance.methods.totalVotesFor(voteTo).call({ from: accounts[0] });
console.log(count)
$("#" + id).html(count);
}
})
} catch (err) {
console.log(err)
}
}
}
$(document).ready(function() {
initial();
});
Write server.js
Create server.js in the fontend directory
var http = require('http');
var fs = require('fs');
var url = require('url');
// 创建服务器
let server = http.createServer( function (request, response) {
// 解析请求,包括文件名
var pathname = url.parse(request.url).pathname;
// 输出请求的文件名
console.log("Request for " + pathname + " received.");
// 从文件系统中读取请求的文件内容
fs.readFile(pathname.substr(1), function (err, data) {
if (err) {
console.log(err);
// HTTP 状态码: 404 : NOT FOUND
// Content Type: text/html
response.writeHead(404, {'Content-Type': 'text/html'});
}else{
// HTTP 状态码: 200 : OK
// Content Type: text/html
response.writeHead(200, {'Content-Type': 'text/html'});
// 响应文件内容
response.write(data.toString());
}
// 发送响应数据
response.end();
});
})
server.listen(8888, '0.0.0.0', () => {
console.log('Server running at http://0.0.0.0:8888/');
})
Execute server.js
Call server.js, provided that ganache-cli has been started in the background, and the contract address contract address in fontend-Voting.js is newly created in the above operation
node server.js
After that, you can open the front-end page in the browser and test it using
http://127.0.0.1:8888/fontend-Voting.html
(Unpassed version) Simple voting Dapp design process
!!!!!!!!!
I encountered a problem in this part, but I didn’t get it through, so I changed the version and wrote a new one, which is only for reference
Bag:
"ganache-cli": "^6.1.8",
"solc": "^0.7.3" // Failed, changed to ^0.4.25
"web3": "^1.7.0"system:
"ubuntu-20.04.4-desktop-amd64"
solidity contract
We design a contract called Voting, which has the following contents:
- A constructor to initialize some candidates
- A method for voting (count of votes + 1)
- A method that returns the total number of votes a candidate has received
When you deploy the contract to the blockchain, the constructor is called, and only once. Unlike in the web world where every deployment of code will overwrite the old code, the contract deployed on the blockchain is immutable, that is, if you update the contract and deploy it again, the old contract will still exist on the blockchain
Create Voting.sol contract
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.25 <0.9.0;
contract Voting{
bytes32[] public candidateList;
mapping(bytes32 => uint8) public votesReceived;
constructor(bytes32[] memory candidateListName){
candidateList = candidateListName;
}
function validateCandidate(bytes32 candidateName) internal view returns(bool){
for (uint8 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(uint8){
require(validateCandidate(candidateName));
return votesReceived[candidateName];
}
}
To compile the contract, first load the code from Voting.sol and bind it to a variable of type string
Compile the contract
Under the node console
(1) Import solc and fs
var solc = require('solc')
var fs = require('fs')
(2) Read the content of the contract
var contractContent = fs.readFileSync('./contracts/Voting.sol', 'utf-8').toString()
(3) Compile the contract code
The contract content is converted into JSON format
var input = {
language: 'Solidity',
sources: {
contract: {
content: contractContent
}
},
settings: {
outputSelection: {
'*': {
'*': '*'
}
}
}
}
var contractCompiled = JSON.parse(solc.compile(JSON.stringify(input)));
(4) Take abi and byteCode
var contractCompliedContent = contractCompiled.contracts.contract.Voting;
var abi = contractCompliedContent.abi;
var byteCode = contractCompliedContent.evm.bytecode.object;
deploy contract
(1) Create a Web3 instance
var Web3 = require('web3')
var web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
(2) Create a Contract object
var contract = new web3.eth.Contract(abi);
(3) Deployment contract
var accounts;
web3.eth.getAccounts().then((res)=>{accounts = res;console.log(accounts)});
// 合约拥有者的帐号
var account = accounts[0];
var gasLimit = 3000000;
// 参数需要两个数组是因为 deploy 参数 arguments 本身接收的就是一个数组,用以接收多个数据,所以最外层是一个数组,里面的数组是我们 Voting合约构造函数定义的参数为 bytes32[]
// 因为构造函数参数定义为 bytes32[],所以我们需要传入 bytes32,而不能直接传入字符串
var argument = [[web3.utils.fromAscii('Alice'),web3.utils.fromAscii('Bob'),web3.utils.fromAscii('Cary')]]
// 或
// var argument = [["0x7465737400000000000000000000000000000000000000000000000000000000","0x7465737400000000000000000000000000000000000000000000000000000000"]]
var contractInstance;
contract.deploy({
data:byteCode,
arguments:argument
}).send({
from:account,
gas:gasLimit,
}).then(instance => {
contractInstance = instance;
console.log("contract address:", instance.options.address)
})
Call the contract method
After that, I couldn’t call the vote contract method. I tried a lot of methods and it didn’t work.
The error VM Exception while processing transaction: invalid opcode has been reported. If anyone knows, please let me know.
Change to the 0.4.25 version and compile it. The guess is because of the input step above, because the input step is not used after changing the 0.4.25 version.
The specific reason is unknown
contractInstance.methods.vote("0x416c6963650000000000000000000000").send({from:accounts[0]}).then(console.log)
Run external network access
Under the fontend folder, create a server.js file
var http = require('http');
var fs = require('fs');
var url = require('url');
// 创建服务器
let server = http.createServer( function (request, response) {
// 解析请求,包括文件名
var pathname = url.parse(request.url).pathname;
// 输出请求的文件名
console.log("Request for " + pathname + " received.");
// 从文件系统中读取请求的文件内容
fs.readFile(pathname.substr(1), function (err, data) {
if (err) {
console.log(err);
// HTTP 状态码: 404 : NOT FOUND
// Content Type: text/html
response.writeHead(404, {'Content-Type': 'text/html'});
}else{
// HTTP 状态码: 200 : OK
// Content Type: text/html
response.writeHead(200, {'Content-Type': 'text/html'});
// 响应文件内容
response.write(data.toString());
}
// 发送响应数据
response.end();
});
})
server.listen(8890, '0.0.0.0', () => {
console.log('Server running at http://0.0.0.0:8890/');
})
Put server.js on the cloud server and execute the following command
node server.js