简单Dadpp投票
任务
开发环境–Linux准备
下面是基于 Linux的安装指南。这要求预先安装 nodeis 和 npm,再用 npm安装 ganache-cli、web3 和 solc0.4.25(当时版本)
mkdir simple_voting.dapp
cd simple_voting.dapp
npm init
npm install ganache-cli web3@0.20.1 solc
node node_modules/.bin/ganache-cli
输出以下内容则成功
Ganache-cli是一个用于以太坊开发和测试的命令行工具。它提供了一个本地的以太坊区块链环境,可以用于开发智能合约、部署合约和进行交易等操作。使用ganache-cli,开发者可以在本地快速搭建一个私有的以太坊网络,而无需连接到真实的以太坊网络。它还提供了一些方便的功能,如创建模拟账户、生成测试数据、调整区块链状态等,有助于简化以太坊应用程序的开发和测试过程。
Solidity 合约
Voting 的合约,这个合约有以下内容:
- 一个构造函数,用来初始化一些候选者。
- 一个用来投票的方法(对投票数加 1)
- 一个返回候选者所获得的总票数的方法
pragma solidity ^0.4.22;
// 选举合约
contract Voting {
// 候选人列表
bytes32[] public candidateList;
// 候选人得票数映射
mapping(bytes32 => uint8) public votesReceived;
// 构造函数,初始化候选人列表
constructor(bytes32[] candiListNames) public {
candidateList = candiListNames;
}
// 检查候选人是否有效
function validcandidate(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(validcandidate(candidateName)); // 确保候选人有效
votesReceived[candidateName] += 1; // 增加候选人得票数
}
// 查询某候选人的总得票数
function totalVotesFor(bytes32 candidateName) view public returns(uint8) {
require(validcandidate(candidateName)); // 确保候选人有效
return votesReceived[candidateName];
}
}
编译合约
在 node console输入以下命令
// 引入Web3库
var Web3 = require('web3')
// 创建一个Web3实例,连接到本地的以太坊节点
var web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"))
// 引入Solidity编译器
var solc = require('solc')
// 读取Voting.sol文件中的源代码
var SourceCode = fs.readFileSync('Voting.sol').toString()
// 编译Solidity源代码
var compiledCode = solc.compile(SourceCode)
// 从编译结果中提取合约的ABI(应用程序二进制接口)
var abi = JSON.parse(compiledCode.contracts[':Voting'].interface)
// 获取编译后的字节码
var bytecode = compiledCode.contracts[':Voting'].bytecode
// 使用ABI和字节码创建Voting合约对象
var Votingcontract = web3.eth.contract(abi)
// 构造部署智能合约的交易对象
var deployTxObj = {
data: bytecode, // 合约的字节码
from: web3.eth.accounts[0], // 从哪个账户发送部署交易
gas: 3000000 // 设定gas限制
}
// 部署智能合约并创建合约实例,传入候选人名单['Alice','Bob','Cary']
var contractInstance = Votingcontract.new(['Alice','Bob','cary'], deployTxObj)
// 给Alice投票
contractInstance.vote("Alice", {from: web3.eth.accounts[0]});
// 查询Alice的总得票数
contractInstance.totalVotesFor("Alice");
contractInstance.totalVotesFor("Alice").toString();
// 查询Bob的总得票数
contractInstance.totalVotesFor.call("Bob").toString();
// 给Cary投票
contractInstance.vote("Cary", {from: web3.eth.accounts[0]});
// 查询Cary的总得票数
contractInstance.totalVotesFor.call("Cary").toString();
网页交互
Html
<!DOCTYPE html>
<html>
<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>
<table class="table table-bordered">
<thead>
<tr>
<th>Candidate</th>
<th>Vote Count</th>
</tr>
</thead>
<tbody>
<tr>
<td>Alice</td>
<td id="candidate-1"></td>
</tr>
<tr>
<td>Bod</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="voteForCandidate" class="btn btn-primary">Vote</a>
</div>
</body>
<script src="https://code.jquery.com/jquery-3.1.1.slim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/web3@4.8.0/dist/web3.min.js"></script>
<script src="./index.js"></script>
</html>
index.js
var web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"))
var abi = JSON.parse('[{"constant":true,"inputs":[{"name":"candidate","type":"string"}],"name":"totalVotesFor","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"candidate","type":"string"}],"name":"votesReceived","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"candidate","type":"string"}],"name":"vote","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"candidateList","outputs":[{"name":"","type":"bytes32[]"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"candidateNames","type":"bytes32[]"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"}]');
var contractAddr = "0x69c93bc7ef2cc9b0be438387204cee27b43281f6";
var VotingContract = web3.eth.contract(abi);
var contractInstance = VotingContract.at(contractAddr);
var candidates = { "Alice": "candidate-1", "Bod": "candidate-2", "Cary": "candidate-3" };
function voteForCandidate() {
let candidateName = $("#candidate").val();
try {
contractInstance.vote(candidateName, { from: web3.eth.accounts[0] },(err, res)=> {
if(err) {
console.log("Error:", err);
}
else {
let id =candidates[candidateName];
let count =contractInstance.totalVotesFor(candidateName).toString();
$("#"+ id).html(count);
}
})
}catch (err){}
}
$(document).ready(function () {
var candidateList = Object.keys(candidates);
for (let i = 0; i < candidateList.length; i++){
let name = candidateList[i];
let count = contractInstance.totalVotesFor(name).toString();
$("#" + candidates[name]).html(count);
}
});
编译脚本
// 引入必要的模块
const fs = require('fs-extra'); // 用于文件操作的模块
const solc = require('solc'); // Solidity 编译器
const path = require('path'); // 用于处理文件路径的模块
// 定义合约编译结果存储的路径
const compiledPath = path.resolve(__dirname, '../compiled'); // 绝对路径
fs.readFileSync(compiledPath); // 读取编译路径的内容
fs.ensureDirSync(compiledPath); // 确保编译路径存在,如果不存在则创建
// 定义智能合约文件路径和读取合约源代码
const contractPath = path.resolve(__direname, '../contracts', 'Car.sol'); // 合约文件路径
const contractSource = fs.readFileSync(contractPath, 'utf-8'); // 读取合约源代码
// 使用 Solidity 编译器编译合约源代码
let compileResult = solc.compile(contractSource, 1); // 对合约源代码进行编译
// 输出编译结果
//consile.log(compileResult);
// 检查是否有编译错误,如果有则抛出异常
if (Array.isArray(compileResult.errors) && compileResult.errors.length) {
throw new Error(compileResult.errors[0]);
}
// 遍历编译结果中的合约对象
Object.keys(compileResult.contracts).forEach(name => {
let contractName = name.replace(/^:/, ''); // 获取合约名称
let filePath = path.resolve(compiledPath, '../compiled', '$(contractName).json'); // 确定 JSON 文件的路径
fs.outputJsonSync(filePath, compileResult.contracts[name]); // 将合约编译结果写入 JSON 文件
console.log("Saying json file to", filePath); // 输出 JSON 文件的保存路径
})
部署脚本
// 引入web3库
const Web3 = require('web3');
// 创建web3实例并连接到本地节点
const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
// 引入文件系统模块
const fs = require('fs-extra');
// 引入路径模块
const path = require('path');
// 引入readline模块的Interface类
const { Interface } = require('readline');
// 指定合约编译后的JSON文件路径
const filePath = path.resolve(__dirname, '../compiled.json');
// 从指定路径读取合约编译后的JSON文件,并获取其中的Interface和bytecode字段
const { Interface, bytecode } = require(filePath);
// 使用异步函数自执行
(async () => {
// 获取以太坊节点上的账户列表
let accounts = await web3.eth.getAccounts();
// 计时开始
console.time("deploy time");
// 部署合约并返回部署结果
let result = await new web3.eth.Contract(JSON.parse(Interface))
.deploy({ data: bytecode, arguments: ["BMW"] })
.send({ from: accounts[0], gas: 500000 });
// 计时结束
console.timeEnd("deploy time");
// 打印合约地址
console.log("contract address", result.options.address);
})();
测试脚本
const assert = require('assert')
const path = require("path")
const Web3 = require('web3')
const ganache = require('ganache-cli');
const { describe, before, beforeEach } = require('node:test');
const web3 = new Web3(ganache.provider());
const contractPath = path.resolve(__dirname, './compiled/Car.json');
const { interface, bytecode } = require(contractPath);
let contract;
let accounts;
const initialBrand = 'BMN';
describe('#contract', () => {
beforeEach(async () => {
accounts = await web3.eth.getAccounts();
contract = await web3.eth.Contract(JOSN.parse(interface))
.deploy({ data: bytecode, arguments: [initialBrand] })
.send({ from: accounts[0], gas: 300000 });
console.log('合约已部署!!');
})
it("deployed contract successfully", () => {
assert.ok(contract.options.address);
})
it ('should has ainitial brand', async () => {
const brand = await contract.methods.brand().call();
assert.equal(brand, initialBrand);
});
it ('should set a new brand', async () => {
const newBrand = 'Audi';
await contract.methods.setBrand(newBrand)
.send({ from: accounts[0] });
const brand = await contract.methods.brand().call();
assert.equal(brand, newBrand);
})
})