本篇文章将通过分析Truffle官网提供的简单示例项目metacoin,webpack学习怎样写智能合约,迁移脚本和测试脚本,为开发自己的DApp打下基础。
MetaCoin是一个简单的代币项目。下载代码来看看吧~
zhujiantao@ubuntu:~$ mkdir truffle-examples
zhujiantao@ubuntu:~$ cd truffle-examples/
zhujiantao@ubuntu:~/truffle-examples$ mkdir MetaCoin
zhujiantao@ubuntu:~/truffle-examples$ cd MetaCoin/
zhujiantao@ubuntu:~/truffle-examples/MetaCoin$ truffle unbox metacoin
Downloading...
Unpacking...
Setting up...
Unbox successful. Sweet!
Commands:
Compile contracts: truffle compile
Migrate contracts: truffle migrate
Test contracts: truffle test
zhujiantao@ubuntu:~/truffle-examples/MetaCoin$ tree
.
├── contracts
│ ├── ConvertLib.sol
│ ├── MetaCoin.sol
│ └── Migrations.sol
├── migrations
│ ├── 1_initial_migration.js
│ └── 2_deploy_contracts.js
├── test
│ ├── metacoin.js
│ └── TestMetacoin.sol
├── truffle-config.js
└── truffle.js
3 directories, 9 files
项目架构在上篇文章中也有介绍
contracts目录下存放合约文件,其中Migrations.sol合约较为特殊,会在项目初始化的时候被创建,是Truffle框架里必须的文件,你运行的迁移历史记录会被他记录在区块链上。
migrations下存放迁移部署脚本,用来帮助你把合约发布到以太坊网络中。
test目录下存放测试脚本。Truffle内置自动化测试框架,让我们可以方便的测试自己的合约。该框架允许我们以两种不同的方式编写测试用例:1,使用Javascript编写测试用例,测试从外部执行合约 2,使用Solidity编写测试用例,测试从内部调用合约。
truffle.js和truffle-config.js为Truffle配置文件,配置了运行网络等相关信息。
合约Migrations.sol
pragma solidity ^0.4.2; //声明Solidity版本,兼容0.4.2以下版本
contract Migrations { //contract声明为合约,在Truffle中合约名称需和文件名一致
address public owner; //公开(外部可见),地址类型全局变量owner,用于保存合约所有者地址
uint public last_completed_migration; //公开,无符号整型全局变量last_completed_migration,在每次合约部署时更新
modifier restricted() { //修饰符,通常用于函数执行前检查某种前置条件是否满足
if (msg.sender == owner) _; //判断调用合约的是不是合约所有者
}
constructor() public { //构造函数,在合约部署时执行
owner = msg.sender; //将合约所有者赋值给owner变量//msg为solidity中的全局变量,msg.sender表示合约发送者地址
}
function setCompleted(uint completed) public restricted { //用于更新last_completed_migration变量的函数
last_completed_migration = completed;
}
function upgrade(address new_address) public restricted { //用于更新合约所有者
Migrations upgraded = Migrations(new_address);
upgraded.setCompleted(last_completed_migration);
}
}
Solidity在全局命名空间中预设了一些特殊的变量和函数,用来提供关于区块链的信息和一些通用的工具函数
- block.blockhash(uint blockNumber) returns (bytes32):指定区块的区块哈希——仅可用于最新的 256 个区块且不包括当前区块;而 blocks 从 0.4.22 版本开始已经不推荐使用,由 blockhash(uint blockNumber) 代替
- block.coinbase (address): 挖出当前区块的矿工地址
- block.difficulty (uint): 当前区块难度
- block.gaslimit (uint): 当前区块 gas 限额
- block.number (uint): 当前区块号
- block.timestamp (uint): 自 unix epoch 起始当前区块以秒计的时间戳
- gasleft() returns (uint256):剩余的 gas
- msg.data (bytes): 完整的 calldata
- msg.gas (uint): 剩余 gas - 自 0.4.21 版本开始已经不推荐使用,由 gesleft() 代替
- msg.sender (address): 消息发送者(当前调用)
- msg.sig (bytes4): calldata 的前 4 字节(也就是函数标识符)
- msg.value (uint): 随消息发送的 wei 的数量
- now (uint): 目前区块时间戳(block.timestamp)
- tx.gasprice (uint): 交易的 gas 价格
- tx.origin (address): 交易发起者(完全的调用链)
合约MetaCoin.sol
pragma solidity ^0.4.18;
import "./ConvertLib.sol"; //导入外部类库
contract MetaCoin {
mapping (address => uint) balances; //mapping类型变量,用于保存地址对应余额信息
event Transfer(address indexed _from, address indexed _to, uint256 _value);//event事件,用于通知外部 //indexd修饰符可用于事件过滤
constructor() public {
balances[tx.origin] = 10000; //给交易发起者账号余额赋值
}
function sendCoin(address receiver, uint amount) public returns(bool sufficient) { //发送MetaCoin方法,参数为接收者地址和交易币数量,返回值为交易成功失败状态
if (balances[msg.sender] < amount) return false; //需要合约发起者余额充足
balances[msg.sender] -= amount;
balances[receiver] += amount;
emit Transfer(msg.sender, receiver, amount); //交易后触发事件,通知外部
return true;
}
function getBalanceInEth(address addr) public view returns(uint){ //获取某个地址余额,转换为Eth返回//view修饰符保证不修改状态
return ConvertLib.convert(getBalance(addr),2);
}
function getBalance(address addr) public view returns(uint) { //根据地址获取余额
return balances[addr];
}
}
库ConvertLib.sol
pragma solidity ^0.4.4;
library ConvertLib{
function convert(uint amount,uint conversionRate) public pure returns (uint convertedAmount)
{
return amount * conversionRate;
}
}
library是一种不同类型的合约,没有存储,不用有以太币。库中的代码可以被其他合约调用,而不需要重新部署,这样可以节省大量gas。该库提供了convert函数转换MetaCoin和ETH,一个MetaCoin值conversionRate个ETH。
迁移脚本1_initial_migration.js
var Migrations = artifacts.require("./Migrations.sol"); //告诉测试脚本要和哪个合约交互
module.exports = function(deployer) {
deployer.deploy(Migrations); //迁移脚本使用deployer对象部署合约
};
可以看到迁移脚本命名规则是以数字开头,后面加上描述性字符串。数字前缀是必须的,用于记录迁移记录,在前面Migration.sol中可以看到相关代码。
迁移脚本2_deploy_contracts.js
var ConvertLib = artifacts.require("./ConvertLib.sol");
var MetaCoin = artifacts.require("./MetaCoin.sol");
module.exports = function(deployer) {
deployer.deploy(ConvertLib);
deployer.link(ConvertLib, MetaCoin); //把已部署好的库链接到MetaCoin合约
deployer.deploy(MetaCoin);
};
测试脚本metacoin.js
var MetaCoin = artifacts.require("./MetaCoin.sol"); //指明要测试的合约
contract('MetaCoin', function(accounts) { //contract 表示test sunit测试套件,表示一组相关测试,第一个参数MetaCoin为套件名称,第二个参数为执行函数
it("should put 10000 MetaCoin in the first account", function() { //it 表示test case测试用例,第一个参数为测试用例名称,第二个参数为执行函数
return MetaCoin.deployed().then(function(instance) {
return instance.getBalance.call(accounts[0]);
}).then(function(balance) {
assert.equal(balance.valueOf(), 10000, "10000 wasn't in the first account");
});
});
it("should call a function that depends on a linked library", function() {
var meta;
var metaCoinBalance;
var metaCoinEthBalance;
return MetaCoin.deployed().then(function(instance) {
meta = instance;
return meta.getBalance.call(accounts[0]);
}).then(function(outCoinBalance) {
metaCoinBalance = outCoinBalance.toNumber();
return meta.getBalanceInEth.call(accounts[0]);
}).then(function(outCoinBalanceEth) {
metaCoinEthBalance = outCoinBalanceEth.toNumber();
}).then(function() {
assert.equal(metaCoinEthBalance, 2 * metaCoinBalance, "Library function returned unexpected function, linkage may be broken");
});
});
it("should send coin correctly", function() {
var meta;
// Get initial balances of first and second account.
var account_one = accounts[0];
var account_two = accounts[1];
var account_one_starting_balance;
var account_two_starting_balance;
var account_one_ending_balance;
var account_two_ending_balance;
var amount = 10;
return MetaCoin.deployed().then(function(instance) {
meta = instance;
return meta.getBalance.call(account_one);
}).then(function(balance) {
account_one_starting_balance = balance.toNumber();
return meta.getBalance.call(account_two);
}).then(function(balance) {
account_two_starting_balance = balance.toNumber();
return meta.sendCoin(account_two, amount, {from: account_one});
}).then(function() {
return meta.getBalance.call(account_one);
}).then(function(balance) {
account_one_ending_balance = balance.toNumber();
return meta.getBalance.call(account_two);
}).then(function(balance) {
account_two_ending_balance = balance.toNumber();
assert.equal(account_one_ending_balance, account_one_starting_balance - amount, "Amount wasn't correctly taken from the sender");
assert.equal(account_two_ending_balance, account_two_starting_balance + amount, "Amount wasn't correctly sent to the receiver");
});
});
});
一大串,,,不过可以看到是从外部执行,在测试结束后可以看到各个测试用例测试结果及所用时间信息。
测试脚本TestMetacoin.sol
pragma solidity ^0.4.2;
import "truffle/Assert.sol"; //导入Truffle内置断言库
import "truffle/DeployedAddresses.sol";
import "../contracts/MetaCoin.sol";
contract TestMetacoin {
function testInitialBalanceUsingDeployedContract() public {
MetaCoin meta = MetaCoin(DeployedAddresses.MetaCoin()); //部署合约并获取合约抽象,记住这个写法就好啦
uint expected = 10000;
Assert.equal(meta.getBalance(tx.origin), expected, "Owner should have 10000 MetaCoin initially");
}
function testInitialBalanceWithNewMetaCoin() public {
MetaCoin meta = new MetaCoin();
uint expected = 10000;
Assert.equal(meta.getBalance(tx.origin), expected, "Owner should have 10000 MetaCoin initially");
}
}
这个就简洁多了,emmmm。。
需要补充的是当执行编译后,在build文件夹下生成了对应的json文件,json文件由ABI(Application Binary Interface)应用二进制接口,bytecode合约编译后二进制内容等组成
zhujiantao@ubuntu:~/truffle-examples/MetaCoin$ truffle compile
Compiling ./contracts/ConvertLib.sol...
Compiling ./contracts/MetaCoin.sol...
Compiling ./contracts/Migrations.sol...
Writing artifacts to ./build/contracts
zhujiantao@ubuntu:~/truffle-examples/MetaCoin$ tree
.
├── build
│ └── contracts
│ ├── ConvertLib.json
│ ├── MetaCoin.json
│ └── Migrations.json
├── contracts
│ ├── ConvertLib.sol
│ ├── MetaCoin.sol
│ └── Migrations.sol
├── migrations
│ ├── 1_initial_migration.js
│ └── 2_deploy_contracts.js
├── test
│ ├── metacoin.js
│ └── TestMetacoin.sol
├── truffle-config.js
└── truffle.js
5 directories, 12 files
哈,MetaCoin分析完毕。webpack相当于在MetaCoin项目的基础上进行了补充,增加了服务和前端界面,可以通过网页进行转账操作,厉害了。一起来玩一玩吧。
先下载编译
$ truffle unbox webpack
$ truffle compile
查看truffle.js可以看到webpack默认指定的网络是ganache。
module.exports = {
networks: {
ganache: {
host: '127.0.0.1',
port: 7545,
network_id: '*' // Match any network id
}
}
}
官网下载ganache并启动。
image.png
部署合约到ganache
zhujiantao@ubuntu:~/truffle-examples/WebPack$ truffle migrate --network ganache
Using network 'ganache'.
Running migration: 1_initial_migration.js
Deploying Migrations...
... 0xbe34f8f11c1dcf1706743fa3923f205a142a7abc85483fe7fba16e175229d9bc
Migrations: 0xa0e137e3429607bf15eeb1fa9aa7add5c586f9b3
Saving successful migration to network...
... 0x1c6690bb21c421d30a04c96d1fa2a56619c97b21e16dba31e42a079db2094557
Saving artifacts...
Running migration: 2_deploy_contracts.js
Deploying ConvertLib...
... 0xd28c5de62b584b4a4cf926a3e03c49f2efe23b5c261d088cf26952a0914a53e0
ConvertLib: 0x4fbaf174a9275377fd406433dc4a7d4c91ca9626
Linking ConvertLib to MetaCoin
Deploying MetaCoin...
... 0xadfe02879726a61ce7686bf69cdbbd382bf95e942a925a9c713bcc4e0669f31b
MetaCoin: 0x6c43768bcf9760e3338bf60786c9d6be36d4876e
Saving successful migration to network...
... 0xbe8fbc8e4fd8bd438452f220b77d812dc2299583d4b19790b40cfa1b0a5d709d
Saving artifacts...
把服务运行起来
zhujiantao@ubuntu:~/truffle-examples/WebPack$ npm run dev
> truffle-init-webpack@0.0.2 dev /home/zhujiantao/truffle-examples/WebPack
> webpack-dev-server
ℹ 「wds」: Project is running at http://localhost:8081/
ℹ 「wds」: webpack output is served from /
打开网页
image.png
哇,可以通过前端转账,接下来就自己去耍吧。
希望看了文章的同学能对Solidity语言,Truffle项目架构以及迁移、测试脚本的编写有一定了解,当然,更全面的学习,请上Solidity官方网站和Truffle官网。
接下来,还要去解锁更复杂的项目,并试着写出自己的Dapp。任重道远,加油加油!
网友评论