TRUFFLE03:智能合约开发与测试
2020-04-29 本文已影响0人
杨强AT南京
智能合约开启所谓的DApp的概念,相对于传统的以Web Server位中心的服务供应;以太坊提供的是基于Javascript的调用,可以在浏览器环境中调用 ,这也许就是去中心化?可能更加吸引人的是基于EVM的数据存储与访问的概念。基于浏览器的Cookie存储与内置运行环境安全性是与可信性是需要用EVM替代。
本主题最后给出了一个在Node.js环境中访问以太坊智能合约的编程应用模式,还是把合约访问放在中心化的服务器后端。官方的DApp例子是基于浏览器环境的。
创建并编译合约
- 注意:
- 合约名与合约文件名必须一致。
创建合约
- 在contracts目录下创建
- 这里我们使用一个老的可用的合约。
pragma solidity ^0.5.0;
contract Contrac_Base{
uint age;
constructor() public {
age = 200;
}
function getAge() public view returns (uint){
// return 99;
return age;
}
function setAge(uint age_) public {
age = age_;
}
}
- 创建好的文件结构截图
Truffle项目测试结构
编译合约
- 实际部署的时候会自动编译,这个过程可以不要。
- 编译指令:
truffle compile --all
- 编译指令:
- 注意:
- all是编译所有的。这个与老版本不一致。
智能合约编译
编译合约与部署合约
编写部署脚本
- 这个可以模仿他的原始部署脚本。
const Contrac_Base = artifacts.require("Contrac_Base");
module.exports = function(deployer) {
deployer.deploy(Contrac_Base);
};
- 文件存放在migrations目录下
- 部署的时候,truffle会执行migrations下的所有脚本。
- 已经执行过的不会再执行,可以使用 --reset强制全部执行。
智能合约测试部署
部署
- 直接执行
truffle deploy --reset或者truffle deploy即可。
C:\01works\08eth\Tru\tru_3rd>truffle deploy --reset
Compiling your contracts...
===========================
> Compiling .\contracts\Contrac_Base.sol
> Compiling .\contracts\Migrations.sol
> Artifacts written to C:\01works\08eth\Tru\tru_3rd\build\contracts
> Compiled successfully using:
- solc: 0.5.16+commit.9c3226ce.Emscripten.clang
Starting migrations...
======================
> Network name: 'development'
> Network id: 1585295887794
> Block gas limit: 0x6691b7
1_initial_migration.js
======================
Replacing 'Migrations'
----------------------
> transaction hash: 0xedf830f94ba41b3082462f56f8251a38a70904244e655be91d1b45637c463721
> Blocks: 0 Seconds: 0
> contract address: 0x76b01681c7263378c63A48F2055AB4DD1A8dFf9D
> block number: 9
> block timestamp: 1585300375
> account: 0x9977bAea7Bc77E315EF36E6d1B9371CEAbF7949E
> balance: 99.98145234
> gas used: 164175
> gas price: 20 gwei
> value sent: 0 ETH
> total cost: 0.0032835 ETH
> Saving migration to chain.
> Saving artifacts
-------------------------------------
> Total cost: 0.0032835 ETH
2_init_deploy.js
================
Deploying 'Contrac_Base'
------------------------
> transaction hash: 0xa78f77cef0e3045d0d7888b544b5f044e79ac42d1982994d8abcc44577d51c6b
> Blocks: 0 Seconds: 0
> contract address: 0x90C267bf7CeE9C62d5aa1255Efa7213C3118Cbde
> block number: 11
> block timestamp: 1585300375
> account: 0x9977bAea7Bc77E315EF36E6d1B9371CEAbF7949E
> balance: 99.97827914
> gas used: 116319
> gas price: 20 gwei
> value sent: 0 ETH
> total cost: 0.00232638 ETH
> Saving migration to chain.
> Saving artifacts
-------------------------------------
> Total cost: 0.00232638 ETH
Summary
=======
> Total deployments: 2
> Final cost: 0.00560988 ETH
- 提示部署成功2个合同。
测试
理解测试的工作过程
测试命令
- 使用
truffle test开始测试,- 可以全部测试,
- 命令:
truffle test
- 命令:
- 也可以指定测试:
- 命令:
truffle test .\test\2_test_base.js - 需要指定路径。
- 命令:
- 可以全部测试,
测试脚本文件
const Contrac_Base = artifacts.require("Contrac_Base");
// 可以直接使用web3实例对象
// console.log(web3);
console.log("开始测试!");
- 测试结果
C:\01works\08eth\Tru\tru_3rd>truffle test .\test\2_test_base.js
Using network 'development'.
Compiling your contracts...
===========================
> Compiling .\contracts\Contrac_Base.sol
> Compiling .\contracts\Migrations.sol
> Artifacts written to C:\Users\gaoke\AppData\Local\Temp\test-2020227-6316-p465kl.ndst
> Compiled successfully using:
- solc: 0.5.16+commit.9c3226ce.Emscripten.clang
开始测试!
0 passing (0ms)
- 因为这是一个集成环境,所以不要使用node来执行,这样导致很多对象无法使用。比如:
artifacts
编写测试脚本
-
在Truffle测试环境,自动提供账号来做测试。
-
使用
artifacts.require()加载合同信息。
const Contrac_Base = artifacts.require("Contrac_Base");
contract(
'Contrac_Base',
async function (accounts){ // 使用同步操作
// console.log(accounts);
it("简单合同测试:", async function(){
var contract_instance = await Contrac_Base.deployed();
// 调用交易
await contract_instance.setAge(88);
var result = await contract_instance.getAge.call(); // 可以传递参数
var age = result.toNumber();
console.log(age)
assert.equal(result, 88, "不是200");
assert.equal(age, 88, "不是200");
});
}
);
- 测试脚本有同步与异步两种方式
const Contrac_Base = artifacts.require("Contrac_Base");
contract(
'Contrac_Base',
function (accounts){ // 使用同步操作
console.log(accounts);
it("简单合同测试:", function(){
var contract_instance;
return Contrac_Base.deployed().then(function (instance){
contract_instance = instance;
// return contract_instance.getAge();
return contract_instance.getAge.call(); // 上面方式也可以
}).then(function(result){
console.log("---------------------");
console.log(result);
console.log("---------------------");
console.log(result.toNumber());
console.log("---------------------");
assert.equal(result, 200, "数据不正确");
});
});
}
);
使用Truffle的结果开发
-
构建一个Node项目
- 本地App或者Web App都可以
-
调用方式:
var Web3 = require("web3");
var contrtact = require("../build/contracts/Contrac_Base.json");
async function run(){
console.log("start");
var web3 = new Web3(new Web3.providers.HttpProvider("http://127.0.0.1:8545"));
// console.log(web3);
const networkId = await web3.eth.net.getId();
// 获取部署的地址
const deployedNetwork = contrtact.networks[networkId];
console.log(networkId);
// 创建合约实例
var instance_contract = new web3.eth.Contract(
contrtact.abi,
deployedNetwork.address,
);
// 获取账号
const accounts = await web3.eth.getAccounts();
console.log(accounts)
// 调用
var result = await instance_contract.methods.getAge().call()
console.log(result);
// 发起一个交易
await instance_contract.methods.setAge(128).send({ from: accounts[0]});;
// 查询交易的结果
result = await instance_contract.methods.getAge().call()
console.log(result);
}
// 这个函数可以在web服务,浏览器等地方都可以编写。
run();
附录
- 这里是一个简单的数据上链的例子,使用的是来自模拟健康手环的数据。
pragma solidity >=0.4.21 <0.7.0;
contract Health{
// 上链的数据结构
struct H_Data{ // 需要可以增加时间点
uint32 heartbeat; // 心跳
uint32 stepnumber; // 步数
uint32 temperature; // 体温
}
struct User_Info{
address owner; // 数据拥有者
string name; // 身份证编号
string birth; // 出生日期
uint32 visitCount; // 总访问次数
}
struct Visitor{
string name; // 数据访问者
uint32 visitCount; // 数据访问者访问次数
}
// 拥有者信息
User_Info user;
// 上链数据
H_Data[] h_data;
// 授权用户
mapping(address => Visitor) public visitors;
// 构造器:初始化时需要拥有者信息
constructor(string memory name, string memory birth) public{
// 初始化拥有者信息
user.name = name;
user.birth = birth;
user.owner = msg.sender;
}
// 查询用户信息
function getUserInfo() public view returns (string memory name, string memory birth){
// 可以考虑授权限制
name = user.name;
birth = user.birth;
}
// 更新链上数据
function addData(uint32 heartbeat, uint32 stepnumber, uint32 temperature) public {
// 可以考虑授权限制
// 创建一个数据记录
H_Data memory item = H_Data(
{
heartbeat: heartbeat,
stepnumber: stepnumber,
temperature: temperature});
h_data.push(item);
}
// 用户访问健康数据
function getHData(uint idx) public view returns (uint32 heartbeat, uint32 stepnumber, uint32 temperature) {
// 权限控制
require(visitors[msg.sender].visitCount > 0 || msg.sender == user.owner, "没有访问权限");
// 增加计数
// 自己实现了 ----------------
// 返回数据
if(idx < h_data.length){
heartbeat = h_data[idx].heartbeat;
stepnumber = h_data[idx].stepnumber;
temperature = h_data[idx].temperature;
}
else{
heartbeat = 0;
stepnumber = 0;
temperature = 0;
}
}
function getRecordCount() public view returns(uint256 len){
len = h_data.length;
}
// 授权用户访问
function authToUser(address to, string memory name) public{
// 只有拥有者可以授权
require(
msg.sender == user.owner,
"只有拥有者可以授权!"
);
visitors[to].name = name;
visitors[to].visitCount = 1;
}
}
- 访问代码
var Web3 = require("web3");
var contrtact = require("../build/contracts/Health.json");
// python(Django) + go + javascript(Express)
async function run(){
console.log("start");
var web3 = new Web3(new Web3.providers.HttpProvider("http://127.0.0.1:8545"));
// console.log(web3);
const networkId = await web3.eth.net.getId();
// 获取部署的地址
const deployedNetwork = contrtact.networks[networkId];
console.log(networkId);
// 创建合约实例
var instance_contract = new web3.eth.Contract(
contrtact.abi,
deployedNetwork.address,
);
// 获取账号
const accounts = await web3.eth.getAccounts();
console.log(accounts)
// ----------------------------------------------------------------------------
// 调用
// var result = await instance_contract.methods.getUserInfo().call()
// console.log(result);
// 发起一个交易
await instance_contract.methods.addData(88, 100000, 355).send({ from: accounts[0]});
// 查询交易的结果
console.log("---------------------------------");
result = await instance_contract.methods.getHData(0).call()
console.log(result);
console.log("---------------------------------");
result = await instance_contract.methods.getHData(1).call()
console.log(result);
result = await instance_contract.methods.getHData(2).call()
console.log(result);
console.log("---------------------------------");
}
// 这个函数可以在web服务,浏览器等地方都可以编写。
run();