本篇文章將通過(guò)分析Truffle官網(wǎng)提供的簡(jiǎn)單示例項(xiàng)目metacoin,webpack學(xué)習(xí)怎樣寫(xiě)智能合約双炕,遷移腳本和測(cè)試腳本踱蠢,為開(kāi)發(fā)自己的DApp打下基礎(chǔ)。
MetaCoin是一個(gè)簡(jiǎn)單的代幣項(xiàng)目咪辱。下載代碼來(lái)看看吧~
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
項(xiàng)目架構(gòu)在上篇文章中也有介紹
contracts目錄下存放合約文件振劳,其中Migrations.sol合約較為特殊,會(huì)在項(xiàng)目初始化的時(shí)候被創(chuàng)建油狂,是Truffle框架里必須的文件历恐,你運(yùn)行的遷移歷史記錄會(huì)被他記錄在區(qū)塊鏈上。
migrations下存放遷移部署腳本专筷,用來(lái)幫助你把合約發(fā)布到以太坊網(wǎng)絡(luò)中弱贼。
test目錄下存放測(cè)試腳本。Truffle內(nèi)置自動(dòng)化測(cè)試框架磷蛹,讓我們可以方便的測(cè)試自己的合約吮旅。該框架允許我們以兩種不同的方式編寫(xiě)測(cè)試用例:1,使用Javascript編寫(xiě)測(cè)試用例味咳,測(cè)試從外部執(zhí)行合約 2庇勃,使用Solidity編寫(xiě)測(cè)試用例,測(cè)試從內(nèi)部調(diào)用合約槽驶。
truffle.js和truffle-config.js為Truffle配置文件责嚷,配置了運(yùn)行網(wǎng)絡(luò)等相關(guān)信息。
合約Migrations.sol
pragma solidity ^0.4.2; //聲明Solidity版本掂铐,兼容0.4.2以下版本
contract Migrations { //contract聲明為合約罕拂,在Truffle中合約名稱需和文件名一致
address public owner; //公開(kāi)(外部可見(jiàn))揍异,地址類型全局變量owner,用于保存合約所有者地址
uint public last_completed_migration; //公開(kāi)聂受,無(wú)符號(hào)整型全局變量last_completed_migration蒿秦,在每次合約部署時(shí)更新
modifier restricted() { //修飾符,通常用于函數(shù)執(zhí)行前檢查某種前置條件是否滿足
if (msg.sender == owner) _; //判斷調(diào)用合約的是不是合約所有者
}
constructor() public { //構(gòu)造函數(shù)蛋济,在合約部署時(shí)執(zhí)行
owner = msg.sender; //將合約所有者賦值給owner變量//msg為solidity中的全局變量棍鳖,msg.sender表示合約發(fā)送者地址
}
function setCompleted(uint completed) public restricted { //用于更新last_completed_migration變量的函數(shù)
last_completed_migration = completed;
}
function upgrade(address new_address) public restricted { //用于更新合約所有者
Migrations upgraded = Migrations(new_address);
upgraded.setCompleted(last_completed_migration);
}
}
Solidity在全局命名空間中預(yù)設(shè)了一些特殊的變量和函數(shù),用來(lái)提供關(guān)于區(qū)塊鏈的信息和一些通用的工具函數(shù)
- block.blockhash(uint blockNumber) returns (bytes32):指定區(qū)塊的區(qū)塊哈贤肼茫——僅可用于最新的 256 個(gè)區(qū)塊且不包括當(dāng)前區(qū)塊渡处;而 blocks 從 0.4.22 版本開(kāi)始已經(jīng)不推薦使用,由 blockhash(uint blockNumber) 代替
- block.coinbase (address): 挖出當(dāng)前區(qū)塊的礦工地址
- block.difficulty (uint): 當(dāng)前區(qū)塊難度
- block.gaslimit (uint): 當(dāng)前區(qū)塊 gas 限額
- block.number (uint): 當(dāng)前區(qū)塊號(hào)
- block.timestamp (uint): 自 unix epoch 起始當(dāng)前區(qū)塊以秒計(jì)的時(shí)間戳
- gasleft() returns (uint256):剩余的 gas
- msg.data (bytes): 完整的 calldata
- msg.gas (uint): 剩余 gas - 自 0.4.21 版本開(kāi)始已經(jīng)不推薦使用祟辟,由 gesleft() 代替
- msg.sender (address): 消息發(fā)送者(當(dāng)前調(diào)用)
- msg.sig (bytes4): calldata 的前 4 字節(jié)(也就是函數(shù)標(biāo)識(shí)符)
- msg.value (uint): 隨消息發(fā)送的 wei 的數(shù)量
- now (uint): 目前區(qū)塊時(shí)間戳(block.timestamp)
- tx.gasprice (uint): 交易的 gas 價(jià)格
- tx.origin (address): 交易發(fā)起者(完全的調(diào)用鏈)
合約MetaCoin.sol
pragma solidity ^0.4.18;
import "./ConvertLib.sol"; //導(dǎo)入外部類庫(kù)
contract MetaCoin {
mapping (address => uint) balances; //mapping類型變量医瘫,用于保存地址對(duì)應(yīng)余額信息
event Transfer(address indexed _from, address indexed _to, uint256 _value);//event事件,用于通知外部 //indexd修飾符可用于事件過(guò)濾
constructor() public {
balances[tx.origin] = 10000; //給交易發(fā)起者賬號(hào)余額賦值
}
function sendCoin(address receiver, uint amount) public returns(bool sufficient) { //發(fā)送MetaCoin方法旧困,參數(shù)為接收者地址和交易幣數(shù)量醇份,返回值為交易成功失敗狀態(tài)
if (balances[msg.sender] < amount) return false; //需要合約發(fā)起者余額充足
balances[msg.sender] -= amount;
balances[receiver] += amount;
emit Transfer(msg.sender, receiver, amount); //交易后觸發(fā)事件,通知外部
return true;
}
function getBalanceInEth(address addr) public view returns(uint){ //獲取某個(gè)地址余額吼具,轉(zhuǎn)換為Eth返回//view修飾符保證不修改狀態(tài)
return ConvertLib.convert(getBalance(addr),2);
}
function getBalance(address addr) public view returns(uint) { //根據(jù)地址獲取余額
return balances[addr];
}
}
庫(kù)ConvertLib.sol
pragma solidity ^0.4.4;
library ConvertLib{
function convert(uint amount,uint conversionRate) public pure returns (uint convertedAmount)
{
return amount * conversionRate;
}
}
library是一種不同類型的合約僚纷,沒(méi)有存儲(chǔ),不用有以太幣拗盒。庫(kù)中的代碼可以被其他合約調(diào)用怖竭,而不需要重新部署,這樣可以節(jié)省大量gas陡蝇。該庫(kù)提供了convert函數(shù)轉(zhuǎn)換MetaCoin和ETH痊臭,一個(gè)MetaCoin值conversionRate個(gè)ETH。
遷移腳本1_initial_migration.js
var Migrations = artifacts.require("./Migrations.sol"); //告訴測(cè)試腳本要和哪個(gè)合約交互
module.exports = function(deployer) {
deployer.deploy(Migrations); //遷移腳本使用deployer對(duì)象部署合約
};
可以看到遷移腳本命名規(guī)則是以數(shù)字開(kāi)頭登夫,后面加上描述性字符串广匙。數(shù)字前綴是必須的,用于記錄遷移記錄恼策,在前面Migration.sol中可以看到相關(guān)代碼鸦致。
遷移腳本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); //把已部署好的庫(kù)鏈接到MetaCoin合約
deployer.deploy(MetaCoin);
};
測(cè)試腳本metacoin.js
var MetaCoin = artifacts.require("./MetaCoin.sol"); //指明要測(cè)試的合約
contract('MetaCoin', function(accounts) { //contract 表示test sunit測(cè)試套件,表示一組相關(guān)測(cè)試戏蔑,第一個(gè)參數(shù)MetaCoin為套件名稱蹋凝,第二個(gè)參數(shù)為執(zhí)行函數(shù)
it("should put 10000 MetaCoin in the first account", function() { //it 表示test case測(cè)試用例,第一個(gè)參數(shù)為測(cè)試用例名稱总棵,第二個(gè)參數(shù)為執(zhí)行函數(shù)
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");
});
});
});
一大串鳍寂,,情龄,不過(guò)可以看到是從外部執(zhí)行迄汛,在測(cè)試結(jié)束后可以看到各個(gè)測(cè)試用例測(cè)試結(jié)果及所用時(shí)間信息捍壤。
測(cè)試腳本TestMetacoin.sol
pragma solidity ^0.4.2;
import "truffle/Assert.sol"; //導(dǎo)入Truffle內(nèi)置斷言庫(kù)
import "truffle/DeployedAddresses.sol";
import "../contracts/MetaCoin.sol";
contract TestMetacoin {
function testInitialBalanceUsingDeployedContract() public {
MetaCoin meta = MetaCoin(DeployedAddresses.MetaCoin()); //部署合約并獲取合約抽象,記住這個(gè)寫(xiě)法就好啦
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");
}
}
這個(gè)就簡(jiǎn)潔多了鞍爱,emmmm鹃觉。。
需要補(bǔ)充的是當(dāng)執(zhí)行編譯后睹逃,在build文件夾下生成了對(duì)應(yīng)的json文件盗扇,json文件由ABI(Application Binary Interface)應(yīng)用二進(jìn)制接口,bytecode合約編譯后二進(jìn)制內(nèi)容等組成
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相當(dāng)于在MetaCoin項(xiàng)目的基礎(chǔ)上進(jìn)行了補(bǔ)充,增加了服務(wù)和前端界面翼闹,可以通過(guò)網(wǎng)頁(yè)進(jìn)行轉(zhuǎn)賬操作斑鼻,厲害了。一起來(lái)玩一玩吧猎荠。
先下載編譯
$ truffle unbox webpack
$ truffle compile
查看truffle.js可以看到webpack默認(rèn)指定的網(wǎng)絡(luò)是ganache坚弱。
module.exports = {
networks: {
ganache: {
host: '127.0.0.1',
port: 7545,
network_id: '*' // Match any network id
}
}
}
官網(wǎng)下載ganache并啟動(dòng)。
部署合約到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...
把服務(wù)運(yùn)行起來(lái)
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 /
打開(kāi)網(wǎng)頁(yè)
哇关摇,可以通過(guò)前端轉(zhuǎn)賬荒叶,接下來(lái)就自己去耍吧。
希望看了文章的同學(xué)能對(duì)Solidity語(yǔ)言拒垃,Truffle項(xiàng)目架構(gòu)以及遷移停撞、測(cè)試腳本的編寫(xiě)有一定了解瓷蛙,當(dāng)然悼瓮,更全面的學(xué)習(xí),請(qǐng)上Solidity官方網(wǎng)站和Truffle官網(wǎng)艰猬。
接下來(lái)横堡,還要去解鎖更復(fù)雜的項(xiàng)目,并試著寫(xiě)出自己的Dapp冠桃。任重道遠(yuǎn)命贴,加油加油!