每日总结
github上拉取项目
使用yarn安装项目所需要的依赖
学会查看pckage.json文件判断该项目通过什么依赖进行运行
区块链项目两大测试框架梳理
trufflehardhat
truffle
什么是truffle
truffle是针对基于以太坊的solidity语言的一套开发框架,本身给予javascript
truffle的前置知识:
javascript--->但是对于这个的要求比较低solidity- 一些以太坊的基础
truffle的用处
- 对客户端做了深度集成.开发、测试、部署一行命令搞定.
- 提供一套类似
maven或gradle的项目构建机制,自动生成相关目录,默认是基于web,支持自定义打包流程 - 提供合约抽象接口,可以直接通过
var meta = MetaCoin.deployed();拿到合约对象然后在js中直接操作对应的合约函数--->非常重要,这是测试的基础.原理是用了基于web3.js封装的Ether Pudding工具包 - 提供了控制台,使用框架构建项目以后可以直接在命令行调用输出结果
- 提供了监控合约,配置变化的自动发布、部署流程.无需每个修改后都重走整个流程
truffle的一些特点
- 内置智能合约的编译、链接、部署和二进制文件的管理
- 快速开发下的自动合约测试
- 脚本化的可拓展的部署与发布框架
- 部署到不管多少的公网或私网的网络环境管理功能
- 使用
EthPM&NPM提供的包管理,使用ERC190标准 - 与合约直接通信的直接交互控制台(写完合约就可以命令行里验证了)
- 可配的构建流程,支持紧密集成
- 在
truffle环境里支持执行外部的脚本
truffle的环境要求
NodeJS 5.0+- 操作系统
truffle需要以太坊客户端,需要支持标准的JSON RPC API
truffle工程目录结构
truffle框架支持工程初始化
- 创建工程目录
- 执行初始化
truffle指令 --->truffle init
执行完成以后有以下三个目录:
contractsmigrationstest
运行truffle框架下的solidity代码需要什么环境?
集成NodeJS:
特点:
-
truffle console命令会默认集成web3. --->想要在自己的NodeJS环境使用truffle合约需要手动集成 -
集成前需要创建工程的
npm包管理环境
步骤:
- 进入`truffle`的工程目录下
- 初始化`npm`的包管理环境 --->`npm init` --->如果不进行包管理环境初始化那么写入`package.json`的包依赖会失败(包`no such file or directory`)的错,所以需要先初始化`npm`的包管理环境
NodeJS中用到的truffle运行时需要web3环境 --->使用npm进行安装 --->npm install web3---> 如果没有集成web3环境会运行truffle会报错 --->ReferenceError: Web3 is not defined
truffle测试合约
框架:
truffle使用mocha测试框架做自动化测试,使用chai来做断言.
文件位置:
对于测试人员来说,测试文件应位于./tests目录,truffle只会运行以.js、.es、.es6、.jsx结尾的测试文件
一个完整的测试流程
创建自己的合约文件:
pragma solidity ^0.4.4;
contract Test{ function f() returns (string){ return "method f()"; } function g() returns (string){ return "method g()"; } } /* 上述代码提供了两个函数用于返回结果字符串 */
声明合约实例并进行自动部署:
修改migrations/2_deploy_contrcts.js为:
var ConvertLib = artifacts.require("./ConvertLib.sol"); var MetaCoin = artifacts.require("./MetaCoin.sol"); var Test = artifacts.require("./Test.sol");
module.exports = function(deployer) { deployer.deploy(ConvertLib); deployer.link(ConvertLib, MetaCoin); deployer.deploy(MetaCoin); deployer.deploy(Test); };
分析:
-
声明新合约实例并命名: --->
var Test = artifacts.require("./Test.sol") -
将声明的合约进行部署: --->
deployer.deploy(Test)
过程分析:
- 先经过移植 --->将合约移植
- 在通过部署器进行部署
- 移植是指由一些
js文件组成来协助发布到以太坊网络.然后在以太坊网络上进行部署 **移植的目的**
缓存发布任务--->运行移植的历史记录通过一个特殊的migrations合约来记录到链上
移植的方式:
- 命令移植 --->
truffle migrate--->执行所有位于migrations目录内的移植脚本将编译后的合约部署到网络上,可以使用选项--reset来从头执行移植脚本
移植的脚本文件示例:
- 文件名由数字+描述性后缀结尾.数字用于记录是否移植成功.后缀为了提高可读性
文件示例:
module.exports = function(deployer) { // deployment steps deployer.deploy(MyContract); }; 分析:
deployer对象是用来缓存(stage)发布任务的主要操作接口truffle提供了合约抽象层(contract abstractions)并进行了初始化,方便可以便利的与以太坊网络交互
移植和部署合约:
要使用移植特性必须首先进行部署合约
部署合约示例代码:
文件名:
migrations/1_initial_migrations.js
module.exports = function(deployer) { // Deploy the Migrations contract as our only task deployer.deploy(Migrations); }; 然后可以创建其他移植脚本进行移植部署
部署器(deployer)--->移植脚本第一个参数
移植文件的特点:
- 移植文件使用部署器来缓存部署任务,可以按照一定顺序排列发布任务,会按照正确顺序执行
// Stage deploying A before B deployer.deploy(A); deployer.deploy(B);也可以使用Promise将部署任务做成一个队列,根据逻辑判断是否部署依赖于前一个合约的执行情况:
// Deploy A, then deploy B, passing in A's newly deployed address deployer.deploy(A).then(function() { return deployer.deploy(B, A.address); });
网络相关--->移植脚本第二个参数
实现不同条件的不同部署步骤,移植脚本中需要添加第二个参数network
示例代码:
module.exports = function(deployer, network) { // Add demo data if we're not deploying to the live network. if (network != "live") { deployer.exec("add_demo_data.js"); } }
部署器API
DEPLOYER.DEPLOY(CONTRACT, ARGS...)
示例代码:
` // Deploy a single contract without constructor arguments deployer.deploy(A);
// Deploy a single contract with constructor arguments deployer.deploy(A, arg1, arg2, ...);
// Deploy multiple contracts, some with arguments and some without. // This is quicker than writing three deployer.deploy() statements as the deployer // can perform the deployment as a batched request. deployer.deploy([ [A, arg1, arg2, ...], B, [C, arg1] ]); `
注意:
如果库的地址可用,deploy会自动为这个部署的合约联接任何需要的库.如果合约依赖某个库,应该先部署这个库.
DEPLOYER.LINK(LIBRARY, DESTINATIONS)
示例代码:
` // Deploy library LibA, then link LibA to contract B deployer.deploy(LibA); deployer.link(LibA, B);
// Link LibA to many contracts deployer.link(LibA, [B, C, D]); `
联接一个已经发布到库的一个或多个合约,destinations可以是一个合约或多个合约组成的数组
示例代码:
` // Deploy library LibA, then link LibA to contract B deployer.deploy(LibA); deployer.link(LibA, B);
// Link LibA to many contracts deployer.link(LibA, [B, C, D]); `
DEPLOYER.AUTOLINK(CONTRACT)
示例代码:
// Link *all* libraries to all available contracts deployer.autolink();
自动关联合约依赖的所有库.需要保证在调用这个函数前,所有被需要的库已经部署
DEPLOYER.THEN(FUNCTION() {...})
示例代码:
deployer.then(function() { // Create a new version of A return A.new(); }).then(function(instance) { // Set the new instance of A's address on B. var b = B.deployed(); return b.setA(instance.address); });
Promise语法糖,执行做生意的部署流程.
DEPLOYER.EXEC(PATHTOFILE)
示例代码:
// Run the script, relative to the migrations file. deployer.exec("../path/to/file/demo_data.js");
执行truffle exec做为部署的一部分
编译合约:
- 使用
truffle migrate --reset强制编译并发布所有合约, --->运行truffle migrate前需要确认节点处处于运行状态
注意:
在NodeJS当中使用truffle框架需要先引入web3库
truffle-contract的contract()方法 --->使用方法是将truffle3.0编译后的.json文件(该文件就是一个json对象)当作参数放入contract()方法中
示例:
// 先引用该依赖 var contract = require("truffle-contract")
// 调用contract方法 var Test = contract(/这里传入json参数/)
// 最后可以使用.deployed()或者at(/*某个地址*/)进行调用
注意:
如果报:Cannot read property 'filter' of undefined则需要是因为传入的json对象有更改,将合约编译后的.json文件完全的copy到contract()方法中
测试用例的标准
-
方式一:每个测试文件至少应该包含至少一个对
Mocha的describe()(描述)函数的调用. --->参考文档 -
方式二使用
truffle自定义的contract()函数,该函数有一些特性- 每一个
contract()函数执行前,合约都会被重部署到以太坊客户端当中,这样测试用例会在一个干净状态下执行 contract()函数支持传入多个可用账户作为第二个参数传入,可以用这些来进行测试
- 每一个
注意:
当测试需要与所写的合约进行交互的时候,使用contract()函数,否则使用describe()函数
测试用例:
- 使用
contract()部署合约 - 执行
it()代码块中指定的测试用例
示例代码:
contract('MetaCoin', function(accounts) { it("should put 10000 MetaCoin in the first account", function() { // Get a reference to the deployed MetaCoin contract, as a JS object. var meta = MetaCoin.deployed(); // Get the MetaCoin balance of the first account and assert that it's 10000. return meta.getBalance.call(accounts[0]).then(function(balance) { assert.equal(balance.valueOf(), 10000, "10000 wasn't in the first account"); }); }); });
注意:
contract()函数传入的MetaCoin仅仅用来展示,说明它是MetaCoin相关的测试,无本质作用
合约
truffle提供了接口抽象,通过var meta = MetaCoin.deployed()与合约进行交互.
合约交互
背景:
truffle使用ether pudding库进行以太坊网络交互,该库基于web3库
读写数据:
- 以太坊网络把在网络上读数据称为调用
(call) - 在网络上写数据称为交易
(transaction)
交易(transaction):
本质是改变整个以太坊网络的数据状态
触发场景:
- 一个账户向另一个账户发送
ether(以太坊网络代币) - 执行合约函数,添加一个新合约到以太坊网络
交易的特征:
- 需要
gas(ether) - 改变网络的状态
- 不会立即执行
- 不会暴露返回结果(仅有交易
id)
调用(call):
在网络上执行代码,但是没有数据会被改变
调用的特征:
- 免费
- 不改变网络状态
- 立即执行
- 有返回结果
solidity的接口(abstract)--->注意针对接口特有的实例函数
举例:
import "ConvertLib.sol";
contract MetaCoin { mapping (address => uint) balances;
event Transfer(address indexed _from, address indexed _to, uint256 _value);
function MetaCoin() {
balances[tx.origin] = 10000;
}
function sendCoin(address receiver, uint amount) returns(bool sufficient) {
if (balances[msg.sender] < amount) return false;
balances[msg.sender] -= amount;
balances[receiver] += amount;
Transfer(msg.sender, receiver, amount);
return true;
}
function getBalanceInEth(address addr) returns(uint){
return ConvertLib.convert(getBalance(addr),2);
}
function getBalance(address addr) returns(uint) {
return balances[addr];
}
}
分析:
上诉代码当中有三个函数和一个构造函数
truffle和ether pudding提供了一个MetaCoin的js对象,可以在前端中使用:
console.log(MetaCoin.deployed()); --->接口层提供了合约中以应的函数名.它还包含一个地址,指向到MetaCoin合约的部署版本.
每一个抽象出来的合约接口都有一个deployed()函数,调用这个函数返回一个实例
执行交易(transaction)
上述MetaCoin合约中,有三个可执行函数,只有sendCoin函数会对网络造成更改,这些更改需要永久保存下来
示例代码:
调用十个币,从一个账户发到另一个账户:
// Instantiation address var account_one = "0x1234..."; // an address var account_two = "0xabcd..."; // another address
var meta = MetaCoin.deployed(); meta.sendCoin(account_two, 10, {from: account_one}).then(function(tx_id) { // If this callback is called, the transaction was successfully processed. // Note that Ether Pudding takes care of watching the network and triggering // this callback. alert("Transaction successful!") }).catch(function(e) { // There was an error! Handle it. })
代码分析:
- 直接调用
sendCoin函数 - 交易被成功时回调函数才会真正被触发
- 对
sendCoin函数传递了第三个参数,注意原始合约函数的定义中并没有第三个参数.这里该参数是一个特殊的对象,用于编辑一些交易中的指定细节,它可以总是做为第三个参数传进.
执行交易(call)
上述MetaCoin中getBalance函数就是一个读取函数
示例代码:
var account_one = "0x1234..."; // an address
var meta = MetaCoin.deployed(); meta.getBalance.call(account_one, {from: account_one}).then(function(balance) { // If this callback is called, the call was successfully executed. // Note that this returns immediately without any waiting. // Let's print the return value. console.log(balance.toNumber()); }).catch(function(e) { // There was an error! Handle it. })
分析:
- 必须通过
.call()向以太坊网络表明不会持久化一些数据变化 - 得到返回结果,不是一个
交易id
注意:
上述的例子中将返回值转成了一个number类型,是因为例子中的返回值比较小,如果将一个BigNumber转换为比javascript支持的number最大整数都大,会出现错误或不可预期的行为
捕捉事件(Catching Events)
合约可以触发事件,可以进行捕捉以进行更多的控制
示例代码:
var meta = MetaCoin.deployed(); var transfers = meta.Transfer({fromBlock: "latest"}); transfers.watch(function(error, result) { // This will catch all Transfer events, regardless of how they originated. if (error == null) { console.log(result.args); } }
涉及到的方法:
METHOD:DEPLOYED()
每一个抽象的合约接口都有一个deployed()函数,调用该函数返回一个实例.代表之前部署到网络的合约所对应的抽象接口的实例
var meta = MetaCoin.deployed()
仅对使用truffle deploy部署的合约,且一定是在project configuration中配置发布的才有效.如果不是这样,这个函数执行时会抛出异常.
METHOD:AT()
通过一个地址来得到一个代表合约的接口实例.该地址一定是这个合约的部署地址
Var meta - MetaCoin.at("0x1234...")
地址不正确,或地址对应的合约不正确时.函数并不会抛出异常.但调用接口时会报错.请保证在使用at()时输入正确的地址
METHOD:NEW()
部署一个完全全新的合约到网络中
MetaCoin.new().then(function(instance) { // `instance` is a new instance of the abstraction. // If this callback is called, the deployment was successful. console.log(instance.address); }).catch(function(e) { // There was an error! Handle it. });
这个操作会改变网络状态
使用truffle框架部署合约的全流程
- 合约部署 --->初始化
truffle框架的结构 --->truffle init - 编译合约 --->
truffle compile - 编写测试脚本 --->使用
javascript或者typescript进行编写 - 执行测试用例 --->
truffle test
浙公网安备 33010602011771号