目录
实现效果预览图
前提条件:安装包
目录结构
简单投票 Dapp 设计流程
solidity 合约
创建 Voting.sol 合约
编译合约
(1)导入 solc 和 fs
(2)读取合约内容
(3)编译合约代码
(4)取abi 、byteCode
部署合约
(1)创建Web3实例
(2)创建Contract对象
(3)部署合约
完整代码
调用合约方法
前端页面HTML
前端JS
方法一:获得合约地址和abi
方法二:获得 abi
编写 server.js
执行 server.js
(没走通版本)简单投票 Dapp 设计流程
!!!!!!!!!
这部分我遇到问题,没有走通,换版本重新写了一个,仅有借鉴意义
solidity 合约
创建 Voting.sol 合约
编译合约
(1)导入 solc 和 fs
(2)读取合约内容
(3)编译合约代码
(4)取abi 和 byteCode
部署合约
(1)创建Web3实例
(2)创建Contract对象
(3)部署合约
调用合约方法
包:
"ganache-cli": "^6.1.8",
"solc": "^0.4.25" // 之前用"solc":"^0.7.3",二者编译有好多不同,改了好多个问题,最后卡住没办法版本了
"web3": "^1.7.0"系统:
"ubuntu-20.04.4-desktop-amd64"
下面是基于 Linux 的安装指南。这里要求我们预先安装 nodejs 和 npm,再用 npm 安装 ganache、solc、web3 ,就可以继续项目的下一步了
simple_vote_dapp 文件夹 中安装以下环境
mkdir ~/桌面/simple_vote_dapp
cd ~/桌面/simple_vote_dapp
npm init
sudo npm install [email protected] [email protected] [email protected]
如果安装成功,运行如下命令,应该能够看到如下的输出
~/桌面/simple_vote_dapp/node_modules/.bin/ganache-cli
新建一个contracts用来存放 sol 合约文件
mkdir ~/桌面/simple_vote_dapp/contracts
确保此时同时 ganache 已经在另一个窗口中运行
另起一个终端中,并在simple_vote_dapp目录下
运行 node 进入 node 控制台,初始化 web3 对象,并向区块链查询获取所有的账户。
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)});
我们设计一个叫做 Voting 的合约,这个合约有以下内容:
- 一个构造函数,用来初始化一些候选人
- 一个用来投票的方法(对投票数 + 1)
- 一个返回候选者所获得的总票数的方法
当你把合约部署到区块链的时候,就会调用构造函数,并只调用一次。与 web 世界里每次部署代码都会覆盖旧代码不同,在区块链上部署的合约是不可改变的,也就是说,如果你更新合约并再次部署,旧的合约仍然会在区块链上存在
// 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];
}
}
为了编译合约,先从 Voting.sol 中加载代码并绑定到一个 string 类型的变量
参考链接:Web3部署智能合约_zhongliwen1981的专栏-CSDN博客_web3部署智能合约一、web3介绍web3是一个专门与以太坊交互的node.js库。我们先回顾一下使用remix部署合约的步骤:第一步:编写合约。第二步:编译合约(之前我们设置了自动编译)。第三步:部署合约,部署成功后返回合约地址。第四步:调用合约。remix底层就是使用了web3实现了编译、部署、调用合约的功能。那么web3是如何实现这些功能呢?看完这篇文章就一清二楚了!!!二、web...https://blog.csdn.net/zhongliwen1981/article/details/89926975
在 node 控制台下
var solc = require('solc')
var fs = require('fs')
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 Web3 = require('web3')
var web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
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;
// 参数需要两个数组是因为 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)
})
记下合约地址 contract address,后面修改 fontend-Voting.js 中的contract address 为你自己的
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)
})
调用合约中的 vote 方法,投票给Alice
var voteTo = web3.utils.fromAscii('Alice')
contractInstance.methods.vote(voteTo).send({from:accounts[0]}).then(console.log)
查看候选人的所得票数
var aVoter = web3.utils.fromAscii('Alice')
contractInstance.methods.totalVotesFor(aVoter).call({from:accounts[0]}).then(res=>console.log(res))
Voting DApp
Simple Voting DApp
Candidate
Vote Count
Alice
Bob
Cary
Vote
solcjs --abi --bin Voting.sol
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();
});
在 fontend 目录下创建 server.js
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/');
})
调用 server.js ,前提ganache-cli已经在后台启动,且 fontend-Voting.js 中的合约地址 contract address 是上面操作中新创建的
node server.js
之后就可以在浏览器中打开前端页面,测试使用了
http://127.0.0.1:8888/fontend-Voting.html
包:
"ganache-cli": "^6.1.8",
"solc": "^0.7.3" // 走不通,换^0.4.25了
"web3": "^1.7.0"系统:
"ubuntu-20.04.4-desktop-amd64"
我们设计一个叫做 Voting 的合约,这个合约有以下内容:
- 一个构造函数,用来初始化一些候选人
- 一个用来投票的方法(对投票数 + 1)
- 一个返回候选者所获得的总票数的方法
当你把合约部署到区块链的时候,就会调用构造函数,并只调用一次。与 web 世界里每次部署代码都会覆盖旧代码不同,在区块链上部署的合约是不可改变的,也就是说,如果你更新合约并再次部署,旧的合约仍然会在区块链上存在
// 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];
}
}
为了编译合约,先从 Voting.sol 中加载代码并绑定到一个 string 类型的变量
参考链接:Web3部署智能合约_zhongliwen1981的专栏-CSDN博客_web3部署智能合约一、web3介绍web3是一个专门与以太坊交互的node.js库。我们先回顾一下使用remix部署合约的步骤:第一步:编写合约。第二步:编译合约(之前我们设置了自动编译)。第三步:部署合约,部署成功后返回合约地址。第四步:调用合约。remix底层就是使用了web3实现了编译、部署、调用合约的功能。那么web3是如何实现这些功能呢?看完这篇文章就一清二楚了!!!二、web...https://blog.csdn.net/zhongliwen1981/article/details/89926975
在 node 控制台下
var solc = require('solc')
var fs = require('fs')
var contractContent = fs.readFileSync('./contracts/Voting.sol', 'utf-8').toString()
合约内容转化为 JSON 格式
var input = {
language: 'Solidity',
sources: {
contract: {
content: contractContent
}
},
settings: {
outputSelection: {
'*': {
'*': '*'
}
}
}
}
var contractCompiled = JSON.parse(solc.compile(JSON.stringify(input)));
var contractCompliedContent = contractCompiled.contracts.contract.Voting;
var abi = contractCompliedContent.abi;
var byteCode = contractCompliedContent.evm.bytecode.object;
var Web3 = require('web3')
var web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
var contract = new web3.eth.Contract(abi);
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)
})
之后我调用这 vote 合约方法走不通了,尝试了好多方法都不行
一直报错误 VM Exception while processing transaction: invalid opcode,如果有好心人知道,望告知
换成0.4.25版本 编译之后就可以了,猜测是因为上面 input 那步骤,因为换了0.4.25版本之后没有用到 input 步骤
具体原因暂时不知
contractInstance.methods.vote("0x416c6963650000000000000000000000").send({from:accounts[0]}).then(console.log)
运行外部网络访问
在 fontend文件夹下,创建 server.js 文件
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/');
})
将 server.js 放入云服务器上,执行以下命令
node server.js