【本文目標(biāo)】
通過本文的學(xué)習(xí)和時(shí)間,你將熟悉以太坊開發(fā)框架Truffle的配置和運(yùn)行玷过,并借助Truffle完成一個(gè)智能合約的部署。
【技術(shù)收獲】
通過本文的學(xué)習(xí)勤篮,你將掌握以下內(nèi)容:
1,了解TRUFFLE的功能
2色罚,了解TRUFFLE的安裝碰缔,配置和啟動(dòng)
3,借助TRUFFLE完成METACOIN一個(gè)智能合約的運(yùn)行
4戳护,Testrpc,Geth環(huán)境的使用
【實(shí)操課程列表】
第一課 如何在WINDOWS環(huán)境下搭建以太坊開發(fā)環(huán)境
第二課 如何實(shí)現(xiàn)以太坊最簡智能合約“Hello World”的運(yùn)行
第四課 以太坊開發(fā)框架Truffle從入門到實(shí)戰(zhàn)
第六課 技術(shù)小白如何開發(fā)一個(gè)DAPP區(qū)塊鏈應(yīng)用(以寵物商店為例)
第七課 技術(shù)小白如何在45分鐘內(nèi)發(fā)行通證(TOKEN)并上線交易
第八課 如何調(diào)試以太坊官網(wǎng)的智能合約眾籌案例
【說明】未列出的課程為知識(shí)普及的非實(shí)操類課程金抡,所有區(qū)塊鏈文章參考“區(qū)塊鏈入口”專欄。
1. TRUFFLE是什么姑尺?
Truffle是一個(gè)世界級的開發(fā)環(huán)境竟终,測試框架蝠猬,以太坊的資源管理通道切蟋,致力于讓以太坊上的開發(fā)變得簡單,Truffle有以下:
- 內(nèi)置的智能合約編譯榆芦,鏈接柄粹,部署和二進(jìn)制文件的管理。
- 快速開發(fā)下的自動(dòng)合約測試匆绣。
- 腳本化的驻右,可擴(kuò)展的部署與發(fā)布框架。
- 部署到不管多少的公網(wǎng)或私網(wǎng)的網(wǎng)絡(luò)環(huán)境管理功能
- 使用EthPM&NPM提供的包管理崎淳,使用ERC190標(biāo)準(zhǔn)堪夭。
- 與合約直接通信的直接交互控制臺(tái)(寫完合約就可以命令行里驗(yàn)證了)。
- 可配的構(gòu)建流程,支持緊密集成森爽。
- 在Truffle環(huán)境里支持執(zhí)行外部的腳本恨豁。
【說明】更多以太坊術(shù)語可參考此篇文章:
http://www.reibang.com/p/03666198619d
1.1 TRUFFLE的安裝
在Ubuntu命令上窗口輸入以下命令,完成安裝:
如果安裝成功爬迟,可輸入truffle version名稱橘蜜,正常情況下會(huì)有版本顯示:$ npm install -g truffle
環(huán)境要求
NodeJS 5.0+
Windows,Linux(推薦Ubuntu)付呕,或Mac OS X
Truffle客戶端
有許多的以太坊客戶端可以選擇计福。我們推薦在開發(fā)和部署時(shí)使用不同客戶端。
適用開發(fā)的客戶端
當(dāng)開發(fā)基于Truffle的應(yīng)用時(shí)徽职,我們推薦使用EthereumJS TestRPC象颖。它是一個(gè)完整的在內(nèi)存中的區(qū)塊鏈僅僅存在于你開發(fā)的設(shè)備上。它在執(zhí)行交易時(shí)是實(shí)時(shí)返回活箕,而不等待默認(rèn)的出塊時(shí)間力麸,這樣你可以快速驗(yàn)證你新寫的代碼,當(dāng)出現(xiàn)錯(cuò)誤時(shí)育韩,也能即時(shí)反饋給你克蚂。它同時(shí)還是一個(gè)支持自動(dòng)化測試的功能強(qiáng)大的客戶端。Truffle充分利用它的特性筋讨,能將測試運(yùn)行時(shí)間提速近90%埃叭。
適用正式發(fā)布的客戶端
對此有許多官方和非官方的以太坊客戶端可供選擇。最好使用TestRPC客戶端充分測試后悉罕,再使用這些客戶端赤屋。這些是完整的客戶端實(shí)現(xiàn),包括挖礦壁袄,網(wǎng)絡(luò)类早,塊及交易的處理,Truffle可以在不需要額外配置的情況下發(fā)布到這些客戶端嗜逻。
當(dāng)發(fā)布到私有網(wǎng)絡(luò)中
私人網(wǎng)絡(luò)中使用了相同的技術(shù)涩僻,但卻有不同的配置。所以你可以將上面提及的客戶端來運(yùn)行一個(gè)私有的網(wǎng)絡(luò)栈顷,部署到這樣的網(wǎng)絡(luò)也是使用同樣的方式逆日。
【說明】作者使用TestRPC和 Geth (go-ethereum)這2種客戶端,他們的安裝方式參考文章:http://www.reibang.com/p/683ea7d62a39
2. 下載TRUFFLE MetaCoin樣例進(jìn)行環(huán)境搭建實(shí)戰(zhàn)
2.1 MetaCoin初始化
我們假設(shè)前面的安裝和環(huán)境搭建已全部成功萄凤,此時(shí)應(yīng)該可以直接使用命令truffle了室抽,下面我們建立一個(gè)工作間truffle-workspace,然后在工作間執(zhí)行:
mkdir MetaCoin
cd MetaCoin
truffle unbox metacoin
執(zhí)行截圖如下:原來使用truffle init靡努,但現(xiàn)在它存在于unbox坪圾。
unbox
Truffle 的盒子Boxs裝有很多非常實(shí)用的項(xiàng)目樣板,可以讓你忽略一些環(huán)境配置問題兽泄,從而可以集中與開發(fā)你自己的DApp的業(yè)務(wù)唯一性已维。除此之外,Truffle Boxes能夠容納其他有用的組件已日、Solidity合約或者庫垛耳,前后端視圖等等。所有這些都是一個(gè)完整的實(shí)例Dapp程序飘千。都可以下載下來逐一研究堂鲜,尋找適合自己公司目前業(yè)務(wù)模型的組件。
可以看到护奈,現(xiàn)在官方盒子還不多缔莲,總共7個(gè),有三個(gè)是關(guān)于react的霉旗,兩個(gè)是truffle自己的項(xiàng)目痴奏,可以下載體驗(yàn),剩下兩個(gè)是我們比較關(guān)心的厌秒,一個(gè)是metacoin读拆,非常好的入門示例,另一個(gè)是webpack鸵闪,顧名思義檐晕,它是一套比起metacoin更加完整的模板的存在。既然我們是初學(xué)蚌讼,下面我們就從metacoin入手學(xué)習(xí)辟灰。
1) tutorialtoken
1] This box has all you need to get started with our Open Zeppelin (TutorialToken) tutorial.
2] https://truffleframework.com/boxes/tutorialtoken
2)PET-SHOP
1] This box has all you need to get started with our Pet Shop tutorial.
2] https://truffleframework.com/boxes/pet-shop
3) METACOIN
truffle unbox metacoin
4) ENDLESS-NAMELESS-INC/CHESHIRE(加密貓)
1] An Ethereum testnet running the CryptoKitties smart contracts
An HTTP server running a minimal implementation of the CryptoKitties web API:
A simple Node.js framework for seeding the development environment with realistic data and bootstraping your dApp.
2] https://truffleframework.com/boxes/cheshire
2.2 目錄結(jié)構(gòu)及文件解讀
進(jìn)入metacoin目錄,當(dāng)前目錄已經(jīng)被初始化成一個(gè)新的空的以太坊工程篡石,目錄結(jié)構(gòu)如下:
contracts
* ConvertLib.sol
* MetaCoin.sol
* Migrations.sol
* .placeholder
migrations
* 1_initial_migration.js
* 2_deploy_contracts.js
test
* metacoin.js
* TestMetacoin.sol
* .placeholder
- truffle-config.js
- truffle.js
初始化文件解釋1:Migrations.sol
pragma solidity ^0.4.2;
contract Migrations {
address public owner;
uint public last_completed_migration;
modifier restricted() {
if (msg.sender == owner) _;
}
function Migrations() public {
owner = msg.sender;
}
function setCompleted(uint completed) public restricted {
last_completed_migration = completed;
}
function upgrade(address new_address) public restricted {
Migrations upgraded = Migrations(new_address);
upgraded.setCompleted(last_completed_migration);
}
}
上面我們學(xué)習(xí)了Solidity具體的類型語法芥喇,我們來分析一下這個(gè)文件:
- 它定義了一個(gè)名字為“遷移”的合約
- 有一個(gè)任意訪問的全局變量,存儲(chǔ)于storage的地址類型變量owner
- 有一個(gè)可任意訪問的全局變量凰萨,存儲(chǔ)于storage的無符號整型類型的變量last_completed_migration
- modifier下面細(xì)說继控,此處略過
- msg.sender下面細(xì)說,此處略過
- 構(gòu)造函數(shù)沟蔑,初始化將發(fā)送方賦值給owner保存
- 一個(gè)setCompleted賦值方法湿诊,賦值給last_completed_migration狱杰,其中該方法被聲明為restricted瘦材,下面細(xì)說,此處略過
- upgrade方法弹砚,調(diào)用當(dāng)前合約自己的方法允粤,得到合約的實(shí)例upgraded,然后通過該是咧調(diào)用setCompleted賦值方法谊娇。
Solidity語法補(bǔ)充說明1:function modifier
modifier的使用方法簿晓,就看上面的Migrations合約的例子即可眶拉,它可以自動(dòng)改變函數(shù)的行為,例如你可以給他預(yù)設(shè)一個(gè)條件憔儿,他會(huì)不斷檢查忆植,一旦符合條件即可走預(yù)設(shè)分支。它可以影響當(dāng)前合約以及派生合約谒臼。
pragma solidity ^0.4.11;
contract owned {
function owned() public { owner = msg.sender; }
address owner;
// 這里僅定義了一個(gè)modifier但是沒有使用朝刊,它將被子類使用,方法體在這里“_;”蜈缤,這意味著如果owner調(diào)用了這個(gè)函數(shù)拾氓,函數(shù)會(huì)被執(zhí)行,其他人調(diào)用會(huì)拋出一個(gè)異常底哥。
modifier onlyOwner {
require(msg.sender == owner);
_;
}
}
// 通過is關(guān)鍵字來繼承一個(gè)合約類咙鞍,mortal是owned的子類,也叫派生類趾徽。
contract mortal is owned {
// 當(dāng)前合約派生了owned续滋,此方法使用了父類的onlyOwner的modifier
// public onlyOwner, 這種寫法挺讓人困惑孵奶,下面給出了我的思考吃粒,暫理解為派生類要使用基類的modifier。
function close() public onlyOwner {
selfdestruct(owner);
}
}
contract priced {
// Modifiers可以接收參數(shù)
modifier costs(uint price) {
// 這里modifier方法體是通過條件判斷拒课,是否滿足徐勃,滿足則執(zhí)行“_;”分支。
if (msg.value >= price) {
_;
}
}
}
contract Register is priced, owned {
mapping (address => bool) registeredAddresses;
uint price;
// 構(gòu)造函數(shù)給全局變量price賦值早像。
function Register(uint initialPrice) public { price = initialPrice; }
// payable關(guān)鍵字重申僻肖,如果不聲明的話,函數(shù)關(guān)于以太幣交易的操作都會(huì)被拒回卢鹦。
function register() public payable costs(price) {
registeredAddresses[msg.sender] = true;
}
// 此派生類也要使用基類的modifier臀脏。
function changePrice(uint _price) public onlyOwner {
price = _price;
}
}
contract Mutex {
bool locked;
modifier noReentrancy() {
require(!locked);
locked = true;
_;
locked = false;
}
function f() public noReentrancy returns (uint) {
require(msg.sender.call());
return 7;
}
}
又延伸出來一個(gè)盲點(diǎn):require關(guān)鍵字,它是錯(cuò)誤判斷冀自,提到assert就懂了揉稚,官方文檔的解釋為:
require(bool condition):
throws if the condition is not met - to be used for errors in inputs or external components.
總結(jié)一下modifier:
- 聲明modifier時(shí),特殊符號“_;”的意思有點(diǎn)像TODO熬粗,是一個(gè)“占位符”搀玖,指出了你要寫的具體方法體內(nèi)容的位置。
- function close() public onlyOwner驻呐,派生類某方法想“如虎添翼”加入基類的某個(gè)modifier功能灌诅,就可以這樣寫芳来,這行的具體意思就是:close方法也必須是owner本人執(zhí)行,否則報(bào)錯(cuò)猜拾!
Solidity語法補(bǔ)充說明2:Restricting Access
限制訪問一種針對合約的常見模式即舌。但其實(shí)你永遠(yuǎn)不可能限制得了任何人或電腦讀取你的交易內(nèi)容或者你的合同狀態(tài)。你可以使用加密增大困難挎袜,但你的合約就是用來讀取數(shù)據(jù)的顽聂,那么其他人也會(huì)看到。所以盯仪,其實(shí)上面的modifier onlyOwner是一個(gè)特別好的可讀性極高的限制訪問的手段芜飘。
那么restricted關(guān)鍵字如何使用呢?
好吧磨总,我剛剛帶著modifier的知識(shí)重新看了上面的Migrations合約的內(nèi)容發(fā)現(xiàn)嗦明,restricted并不是關(guān)鍵字,而是modifier的方法名蚪燕,在其下的想增加該modifier功能的函數(shù)中娶牌,都使用了public restricted的方式來聲明。
說到這里馆纳,我又明白了為什么要使用public onlyOwner這種寫法诗良,因?yàn)閜ublic是函數(shù)可見性修飾符,onlyOwner是自定義的限制訪問的modifier方法鲁驶,他們都是關(guān)于函數(shù)使用限制方面的鉴裹,所以會(huì)寫在一起,可以假想一個(gè)括號將它倆括起來钥弯,他們占一個(gè)位置径荔,就是原來屬于public|private|internal|external的那個(gè)位置。
Solidity語法補(bǔ)充說明3:Special Variables and Functions
這一點(diǎn)很重要了脆霎,我們研究一下Solidity自身攜帶的特殊變量以及函數(shù):
- block.blockhash(uint blockNumber) returns (bytes32): 返回參數(shù)區(qū)塊編號的hash值总处。(范圍僅限于最近256塊,還不包含當(dāng)然塊)
- block.coinbase (address): 當(dāng)前區(qū)塊礦工地址
- block.difficulty (uint): 當(dāng)前區(qū)塊難度
- block.gaslimit (uint): 當(dāng)前區(qū)塊的gaslimit
- block.number (uint): 當(dāng)前區(qū)塊編號
- block.timestamp (uint): 當(dāng)前區(qū)塊的timestamp睛蛛,使用UNIX時(shí)間秒
- msg.data (bytes): 完整的calldata
- msg.gas (uint): 剩余的gas
- msg.sender (address): 信息的發(fā)送方 (當(dāng)前調(diào)用)
- msg.sig (bytes4): calldata的前四個(gè)字節(jié) (i.e. 函數(shù)標(biāo)識(shí)符)
- msg.value (uint): 消息發(fā)送的wei的數(shù)量
- now (uint): 當(dāng)前區(qū)塊的timestamp (block.timestamp別名)
- tx.gasprice (uint): 交易的gas單價(jià)
- tx.origin (address): 交易發(fā)送方地址(完全的鏈調(diào)用)
msg有兩個(gè)屬性鹦马,一個(gè)是msg.sender,另一個(gè)是msg.value忆肾,這兩個(gè)值可以被任何external函數(shù)調(diào)用荸频,包含庫里面的函數(shù)。
注意謹(jǐn)慎使用block.timestamp, now and block.blockhash客冈,因?yàn)樗麄兌际怯锌赡鼙淮鄹牡摹?/p>
初始化文件解釋2:MetaCoin.sol
pragma solidity ^0.4.18;
import "./ConvertLib.sol";
// 這是一個(gè)簡單的仿幣合約的例子旭从。它并不是標(biāo)準(zhǔn)的可兼容其他幣或token的合約,
// 如果你想創(chuàng)建一個(gè)標(biāo)準(zhǔn)兼容的token郊酒,請轉(zhuǎn)到 https://github.com/ConsenSys/Tokens(TODO:一會(huì)兒我們再過去轉(zhuǎn))
contract MetaCoin {
mapping (address => uint) balances;// 定義了一個(gè)映射類型變量balances遇绞,key為address類型,值為無符整型燎窘,應(yīng)該是用來存儲(chǔ)每個(gè)賬戶的余額摹闽,可以存多個(gè)。
event Transfer(address indexed _from, address indexed _to, uint256 _value);// Solidity語法event褐健,TODO:見下方詳解付鹿。
function MetaCoin() public {// 構(gòu)造函數(shù),tx.origin查查上面蚜迅,找到它會(huì)返回交易發(fā)送方的地址舵匾,也就是說合約實(shí)例創(chuàng)建時(shí)會(huì)默認(rèn)為當(dāng)前交易發(fā)送方的余額塞10000,單位應(yīng)該是你的仿幣谁不。
balances[tx.origin] = 10000;
}
function sendCoin(address receiver, uint amount) public returns(bool sufficient) {// 函數(shù)聲明部分沒有盲點(diǎn)坐梯,方法名,參數(shù)列表刹帕,函數(shù)可見性吵血,返回值類型定義。
if (balances[msg.sender] < amount) return false;// 如果余額不足偷溺,則返回發(fā)送幣失敗
balances[msg.sender] -= amount;// 否則從發(fā)送方余額中減去發(fā)送值蹋辅,注意Solidity也有 “-=”,“+=” 的運(yùn)算符哦
balances[receiver] += amount;// 然后在接收方的余額中加入發(fā)送值數(shù)量挫掏。
Transfer(msg.sender, receiver, amount);// 使用以上event關(guān)鍵字聲明的方法
return true;
}
function getBalanceInEth(address addr) public view returns(uint){// 獲取以太幣余額
return ConvertLib.convert(getBalance(addr),2);// 調(diào)用了其他合約的方法侦另,TODO:稍后介紹ConvertLib合約時(shí)說明。
}
function getBalance(address addr) public view returns(uint) {// 獲取當(dāng)前賬戶的仿幣余額
return balances[addr];
}
}
Solidity語法補(bǔ)充說明4:Events
Events allow the convenient usage of the EVM logging facilities, which in turn can be used to “call” JavaScript callbacks in the user interface of a dapp, which listen for these events.
Events提供了日志支持尉共,進(jìn)而可用于在用戶界面上“調(diào)用”dapp JavaScript回調(diào),監(jiān)聽了這些事件褒傅。簡單來說,我們的DApp是基于web服務(wù)器上的web3.js與EVM以太坊結(jié)點(diǎn)進(jìn)行交互的袄友,而智能合約是部署在EVM以太坊結(jié)點(diǎn)上的樊卓。舉一個(gè)例子:
contract ExampleContract {
// some state variables ...
function foo(int256 _value) returns (int256) {
// manipulate state ...
return _value;
}
}
合約ExampleContract有個(gè)方法foo被部署在EVM的一個(gè)結(jié)點(diǎn)上運(yùn)行了,此時(shí)用戶如果想在DApp上調(diào)用合約內(nèi)部的這個(gè)foo方法杠河,如何操作呢碌尔,有兩種辦法:
- var returnValue = exampleContract.foo.call(2);// 通過web3 的message的call來調(diào)用。
- 合約內(nèi)部再聲明一個(gè)event ReturnValue(address indexed _from, int256 _value);并在foo方法內(nèi)使用該event用來返回方法執(zhí)行結(jié)果券敌。
第一種辦法在方法本身比較耗時(shí)的情況下會(huì)阻塞唾戚,或者不會(huì)獲取到準(zhǔn)確的返回值。所以采用第二種辦法:就是通過Solidity的關(guān)鍵字event待诅。event在這里就是一個(gè)回調(diào)函數(shù)的概念叹坦,當(dāng)函數(shù)運(yùn)行結(jié)束以后(交易進(jìn)塊),會(huì)通過event返回給web3卑雁,也就是DApp用戶界面相應(yīng)的結(jié)果募书。這是以太坊一種客戶端異步調(diào)用方法绪囱。關(guān)于這個(gè)回調(diào),要在DApp使用web3時(shí)顯示編寫:
exampleEvent.watch(function(err, result) {
if (err) {
console.log(err)
return;
}
console.log(result.args._value)
// 檢查合約方法是否反返回結(jié)果莹捡,若有則將結(jié)果顯示在用戶界面并且調(diào)用exampleEvent.stopWatching()方法停止異步回調(diào)監(jiān)聽鬼吵。
})
寫Solidity最大的不同在于,我們要隨時(shí)計(jì)算好我們的gas消耗篮赢,方法的復(fù)雜度齿椅,變量類型的存儲(chǔ)位置(memory,storage等等)都會(huì)決定gas的消耗量启泣。
使用event可以獲得比storage更便宜的gas消耗涣脚。
總結(jié)一下event,就是如果你的Dapp客戶端web3.js想調(diào)用智能合約內(nèi)部的函數(shù)寥茫,則使用event作為橋梁遣蚀,它能方便執(zhí)行異步調(diào)用同時(shí)又節(jié)約gas消耗。
初始化文件解釋3:ConvertLib.sol
pragma solidity ^0.4.4;
library ConvertLib{
function convert(uint amount,uint conversionRate) public pure returns (uint convertedAmount)
{
return amount * conversionRate;
}
}
與MetaCoin智能合約不同的是纱耻,ConvertLib是由library聲明的一個(gè)庫妙同,它只有一個(gè)方法,就是返回給定的兩個(gè)無符整數(shù)值相乘的結(jié)果膝迎。返回到上面的MetaCoin中該庫的使用位置去分析粥帚,即可知道,MetaCoin的仿幣的價(jià)格是以太幣的一倍限次,所以MetaCoin是以以太幣為標(biāo)桿芒涡,通過智能合約發(fā)布的一個(gè)token,仿幣卖漫。
這似乎就可以很好地解決我在《以太坊RPC機(jī)制與API實(shí)例》文章中需要發(fā)布三倍以太幣的token的需求了费尽,而我們完全不必更改以太坊源碼,但那篇文章通過這個(gè)需求的路線研究了以太坊的Go源碼也算功不可沒羊始。
初始化文件解釋4:1_initial_migration.js
var Migrations = artifacts.require("./Migrations.sol");
module.exports = function(deployer) {
deployer.deploy(Migrations);
};
這個(gè)js文件是nodejs的寫法旱幼,看上去它的作用就是部署了上面的Migrations智能合約文件。
初始化文件解釋5: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);
deployer.deploy(MetaCoin);
};
這個(gè)文件是meatcoin智能合約的部署文件突委,里面約定了部署順序柏卤,依賴關(guān)系。這里我們看到了MetaCoin智能合約是要依賴于庫ConvertLib的匀油,所以要先部署ConvertLib缘缚,然后link他們,再部署MetaCoin敌蚜,這部分js的寫法可以參照官方文檔DEPLOYER API桥滨,主要就是介紹了一下deploy、link以及then三個(gè)方法的詳細(xì)用法,不難這里不再贅述齐媒。
初始化文件解釋6:truffle-config.js, truffle.js
module.exports = {
// See <http://truffleframework.com/docs/advanced/configuration>
// to customize your Truffle configuration!
};
module.exports = {
// See <http://truffleframework.com/docs/advanced/configuration>
// to customize your Truffle configuration!
};
這兩個(gè)文件也都是nodejs蒲每,他們都是配置文件,可能作用域不同喻括,目前它倆是完全相同的(因?yàn)樯兑矝]有)邀杏。我們?nèi)ニ扑]的網(wǎng)站看一看。給出了一個(gè)例子:
module.exports = {
networks: {
development: {
host: "127.0.0.1",
port: 8545,
network_id: "*" // Match any network id
}
}
};
這個(gè)例子展示了該配置文件可以配置網(wǎng)絡(luò)環(huán)境双妨,暫先到這淮阐,以后遇上了針對該配置文件進(jìn)行研究叮阅。
初始化文件解釋7:.placeholder
This is a placeholder file to ensure the parent directory in the git repository. Feel free to remove.
翻譯過來就是:placeholder文件是用來保證在git庫中父級目錄的刁品,可以刪除。
初始化文件解釋8:metacoin.js
和下面的文件一樣浩姥,他們的功能都是用來做單元測試的挑随,truffle在編譯期間會(huì)自動(dòng)執(zhí)行這些測試腳本。當(dāng)前文件為js版本勒叠,模擬用戶在DApp客戶端用戶界面操作的情形兜挨。
var MetaCoin = artifacts.require("./MetaCoin.sol"); // 這與1_initial_migration.js文件的頭是一樣的,引入了一個(gè)智能合約文件眯分。
contract('MetaCoin', function(accounts) {
it("should put 10000 MetaCoin in the first account", function() {
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");
});
});
});
我們來分析一波這個(gè)truffle metacoin js版本的單元測試:
- 直接函數(shù)contract走起拌汇,第一個(gè)參數(shù)為智能合約名字,第二個(gè)參數(shù)為匿名內(nèi)部函數(shù)
- 匿名函數(shù)傳入了當(dāng)前賬戶地址弊决,函數(shù)體是單元測試集
- 每個(gè)單元測試是由關(guān)鍵字it函數(shù)來做噪舀,第一個(gè)參數(shù)傳入單元測試的comments,第二個(gè)參數(shù)傳入一個(gè)無參匿名函數(shù)
- 進(jìn)到無參匿名函數(shù)的函數(shù)體內(nèi)飘诗,就是正式的單元測試內(nèi)容与倡,可以定義自己的成員屬性,通過調(diào)用truffle內(nèi)部組件自動(dòng)部署合約逐一測試昆稿,使用成員屬性接收返回值纺座,最后使用關(guān)鍵字assert來判斷是否符合預(yù)期。具體業(yè)務(wù)不詳細(xì)展開溉潭,可根據(jù)自己業(yè)務(wù)內(nèi)容隨意更改净响。
這是官方文檔,詳細(xì)說明如何使用JS來編寫智能合約的單元測試喳瓣。
初始化文件解釋9:TestMetacoin.sol
好下面來看看Solidity智能合約版本的單元測試别惦。一般來講,這種文件的命名規(guī)則是Test加待測智能合約的名字拼串組成夫椭。
pragma solidity ^0.4.2;
import "truffle/Assert.sol";
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");
}
}
繼續(xù)分析:
- 首先import了truffle的幾個(gè)類庫掸掸,用來支持我們接下來的測試內(nèi)容。然后import了待測智能合約。
- 建立單元測試智能合約扰付,根據(jù)合約不同方法定義對應(yīng)的test測試方法堤撵。
- 方法體內(nèi)部去調(diào)用待測智能合約的方法,傳參接收返回值羽莺,然后使用關(guān)鍵字assert判斷是否符合預(yù)期实昨。
這是官方文檔,詳細(xì)說明如何使用Solidity來編寫智能合約的單元測試盐固。
2.3 編譯合約
鍵入
輸出情況:truffle compile