目录
目录结构
创建包管理
安装依赖
编译脚本
安装依赖
准备合约源码
准备编译脚本
执行编译脚本
部署脚本
安装依赖
准备部署脚本
执行部署脚本
测试脚本
安装依赖
准备测试脚本
执行测试脚本
测试例子
测试脚本
执行测试脚本
将以上工作流步骤串起来
修改 package.json
执行命令
手动创建 contract_workflow 文件夹
并在其目录下创建 compiled、contracts、scripts、test 四个文件夹
- contracts 存放源代码
- scripts 存放编译脚本
- compiled 存放编译结果
- test 存放测试文件
在 contract_workflow 目录下,执行以下命令
会生成 package.json 文件
npm init
在 contract_workflow 路径下,执行以下命令
安装ganache-cli,附加生成 node_modules 和 package-lock.json 文件
npm install [email protected] --save-dev
ganache-cli 仅仅用作测试用,每一次重新运行都会重新生成10个新的账号
运行 ganache-cli 后面用作测试用
./node_modules/.bin/ganache-cli
在 contract_workflow 路径下,执行以下命令
npm install [email protected]
contracts 目录下 新建合约 Voting.sol
// 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) public{
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];
}
}
scripts 目录下 新建脚本 compile.js
const fs = require('fs-extra');
const solc = require('solc');
const path = require('path');
// resolve 将后面的路径拼接起来
// __dirname 表示当前目录, 两个下划线_
// 清除编译结果, 保留最新编译结果, 保障一致性
const compiledDir = path.resolve(__dirname, '../compiled');
fs.removeSync(compiledDir);
fs.ensureDirSync(compiledDir);
const contractDir = path.resolve(__dirname, '../contracts', 'Voting.sol')
// 同步读取文件
const contractSource = fs.readFileSync(contractDir,'utf-8');
let compiledResult = solc.compile(contractSource, 1);
console.log(compiledResult);
// solidity合约, 如果报错, 错误定位
if(Array.isArray(compiledResult.errors) && compiledResult.errors.length){
throw new Error(compiledResult.errors[0])
}
// 导出编译后结果 compiledResult, 也可以使用 module.export
Object.keys(compiledResult.contracts).forEach(name=>{
// 正则表达式取:开头之后的字符串
let contractName = name.replace(/^:/,'');
let filePath = path.resolve(__dirname, '../compiled', `${contractName}.json`);
// 输出
fs.outputJsonSync(filePath, compiledResult.contracts[name]);
console.log("Saving json file to ",filePath);
})
在 contract_workflow 路径下,执行以下命令
node ./scripts/compile.js
在 contract_workflow 路径下,执行以下命令
npm install [email protected]
scripts 目录下 新建脚本 deploy.js
const Web3 = require('web3');
let web3;
if (typeof web3 !== 'undefined') {
//当你之前如果使用过MetaMask钱包等时,你的provider可能已经设置好了,web3.currentProvider就是你目前的provider
web3 = new Web3(web3.currentProvider);
} else {
// set the provider you want from Web3.providers
web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
}
const fs = require('fs-extra');
const path = require('path');
const contractDir = path.resolve(__dirname, '../compiled', 'Voting.json');
const {interface, bytecode} = require(contractDir);
let arguments = [[web3.utils.fromAscii('Alice'),web3.utils.fromAscii('Bob'),web3.utils.fromAscii('Cary')]]
let gas = 5000000;
(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:arguments
})
.send({
from:accounts[0],
gas:gas
})
console.timeEnd("deploy time")
// 输出合约地址
console.log("contract address: ",result.options.address);
})();
在 contract_workflow 路径下,执行以下命令
执行前确定你的私链正在运行
node ./scripts/deploy.js
mocha 是一个 JavaScript 的测试框架
既可以在浏览器环境中运行,也可以在 node.js 环境下运行
mocha 主要特点有:
- 既可以测试简单的 JavaScript 函数,又可以测试异步代码
- 可以自动运行所有测试,也可以只运行特定的测试
- 可以支持 before、after、beforeEach 和 afterEach 来编写初始化代码
npm install [email protected] --save-dev
在 test 目录下,新建脚本 VotingTest.js
const assert = require('assert');
const path = require('path');
const ganache = require('ganache-cli');
const Web3 = require('web3');
const web3 = new Web3(ganache.provider());
const contractDir = path.resolve(__dirname, '../compiled', 'Voting.json');
const {interface, bytecode} = require(contractDir);
let contract;
let accounts;
let arguments = [[web3.utils.fromAscii('Alice'),web3.utils.fromAscii('Bob'),web3.utils.fromAscii('Cary')]]
let gas = 5000000;
describe('#contract',()=>{
before(()=>{
console.log('test start')
})
after(()=>{
console.log('test end')
})
afterEach(()=>{
console.log('current test completed')
})
// beforeEach:使每一次测试的环境都是干净的, 每次合约执行前都执行
beforeEach(async()=>{
accounts = await web3.eth.getAccounts();
contract = await new web3.eth.Contract(JSON.parse(interface))
.deploy({
data:bytecode,
arguments:arguments
})
.send({
from:accounts[0],
gas:gas
})
console.log('current test start')
});
it('deployed contract successfully',()=>{
assert.ok(contract.options.address);
})
it('should be able to check the number of votes',async()=>{
let aVoter = web3.utils.fromAscii('Alice');
assert.ok(contract.methods.totalVotesFor(aVoter).call({from:accounts[0]}));
})
it('should be able to vote',async()=>{
let voteTo = web3.utils.fromAscii('Bob')
let BeforeNumOfVote = parseInt(await contract.methods.totalVotesFor(voteTo).call({from:accounts[0]}));
await contract.methods.vote(voteTo).send({from:accounts[0]});
let AfterNumOfVote = parseInt(await contract.methods.totalVotesFor(voteTo).call({from:accounts[0]}));
assert.equal(BeforeNumOfVote + 1,AfterNumOfVote);
})
})
在 test 目录下执行,以下命令
../node_modules/mocha/bin/mocha VotingTest.js
在 test 下创建 sum.js 和 testSum.js
在 contract_workflow 目录下,执行以下命令
mocha 会自动寻找目录下叫 test 的文件夹,并执行其中 所有的 js 文件
./node_modules/mocha/bin/mocha
在 test 目录下,执行以下命令
测试特定的 js 文件 或 文件夹
../node_modules/mocha/bin/mocha testSum.js
参考链接:
npm scripts 使用指南 - 阮一峰的网络日志https://www.ruanyifeng.com/blog/2016/10/npm_scripts.html
我们可以通过 npm script 机制,将这些智能合约的工作流串起来
修改 package.json 如下图
./node_modules/mocha/bin/ 可以省略 ,因为npm scripts 在执行是将./node_modules/中的包暂时加入了环境变量中
"scripts": {
"compile": "node scripts/compile.js",
"predeploy": "npm run compile",
"deploy": "node scripts/deploy.js",
"pretest": "npm run compile",
"test": "mocha test/"
},
npm run test