第四課 以太坊開發(fā)框架Truffle從入門到實(shí)戰(zhàn)

【本文目標(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命令上窗口輸入以下命令,完成安裝:

$ npm install -g truffle

如果安裝成功爬迟,可輸入truffle version名稱橘蜜,正常情況下會(huì)有版本顯示:
truffle version

環(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ò)也是使用同樣的方式逆日。
【說明】作者使用TestRPCGeth (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

原來使用truffle init靡努,但現(xiàn)在它存在于unbox坪圾。

執(zhí)行截圖如下:
下載樣例

unbox

Truffle 的盒子Boxs裝有很多非常實(shí)用的項(xiàng)目樣板,可以讓你忽略一些環(huán)境配置問題兽泄,從而可以集中與開發(fā)你自己的DApp的業(yè)務(wù)唯一性已维。除此之外,Truffle Boxes能夠容納其他有用的組件已日、Solidity合約或者庫垛耳,前后端視圖等等。所有這些都是一個(gè)完整的實(shí)例Dapp程序飘千。都可以下載下來逐一研究堂鲜,尋找適合自己公司目前業(yè)務(wù)模型的組件。

Truffle的官方Boxes地址

可以看到护奈,現(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ù):

  1. block.blockhash(uint blockNumber) returns (bytes32): 返回參數(shù)區(qū)塊編號的hash值总处。(范圍僅限于最近256塊,還不包含當(dāng)然塊)
  2. block.coinbase (address): 當(dāng)前區(qū)塊礦工地址
  3. block.difficulty (uint): 當(dāng)前區(qū)塊難度
  4. block.gaslimit (uint): 當(dāng)前區(qū)塊的gaslimit
  5. block.number (uint): 當(dāng)前區(qū)塊編號
  6. block.timestamp (uint): 當(dāng)前區(qū)塊的timestamp睛蛛,使用UNIX時(shí)間秒
  7. msg.data (bytes): 完整的calldata
  8. msg.gas (uint): 剩余的gas
  9. msg.sender (address): 信息的發(fā)送方 (當(dāng)前調(diào)用)
  10. msg.sig (bytes4): calldata的前四個(gè)字節(jié) (i.e. 函數(shù)標(biāo)識(shí)符)
  11. msg.value (uint): 消息發(fā)送的wei的數(shù)量
  12. now (uint): 當(dāng)前區(qū)塊的timestamp (block.timestamp別名)
  13. tx.gasprice (uint): 交易的gas單價(jià)
  14. 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方法杠河,如何操作呢碌尔,有兩種辦法:

  1. var returnValue = exampleContract.foo.call(2);// 通過web3 的message的call來調(diào)用。
  2. 合約內(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版本的單元測試:

  1. 直接函數(shù)contract走起拌汇,第一個(gè)參數(shù)為智能合約名字,第二個(gè)參數(shù)為匿名內(nèi)部函數(shù)
  2. 匿名函數(shù)傳入了當(dāng)前賬戶地址弊决,函數(shù)體是單元測試集
  3. 每個(gè)單元測試是由關(guān)鍵字it函數(shù)來做噪舀,第一個(gè)參數(shù)傳入單元測試的comments,第二個(gè)參數(shù)傳入一個(gè)無參匿名函數(shù)
  4. 進(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

輸出情況:
輸出結(jié)果

根據(jù)編譯輸出的路徑地址./build/contracts荒给,我們?nèi)ゲ榭匆幌?div id="5tpexa2" class="image-package">
產(chǎn)生文件列表

可以看到原來所在在contracts目錄下的智能合約文件(有合約contract,有庫library)均被編譯成了json文件刁卜。

這些json文件就是truffle用來部署合約的編譯文件志电。

2.4 配置以太坊本地環(huán)境

truffle.js是truffle的配置文件,啟動(dòng)好以太坊本地結(jié)點(diǎn)以后蛔趴,我們需要讓truffle去識(shí)別它并使用它挑辆,這就需要在truffle.js中配置相關(guān)屬性:

module.exports = {
  networks: {
    development: {
      host: "127.0.0.1",
      port: 8545,
      network_id: "*" // Match any network id
    }
  }
};

【說明】如果不啟動(dòng)TestRPC,直接執(zhí)行部署合約的話,會(huì)有以下錯(cuò)誤提示:
無網(wǎng)絡(luò)

2.5 啟動(dòng)本地以太坊客戶端結(jié)點(diǎn)

啟動(dòng)適合開發(fā)的RPC客戶端

啟動(dòng)之前安裝好的EthereumJS RPC客戶端孝情。

testrpc

【說明】一定要啟動(dòng)一個(gè)新的客戶端執(zhí)行testrpc命令鱼蝉,可以觀察到默認(rèn)賬戶和私鑰信息。
本地客戶端

2.6 部署合約

移植(migrate)箫荡,對這里叫移植魁亦,但下面我們?nèi)允褂谩安渴稹边@個(gè)詞,truffle中部署的命令為:

truffle migrate

輸出結(jié)果截圖如下:
部署智能合約的輸出結(jié)果

查看testrpc的輸出窗口羔挡,可以看到這筆交易和花費(fèi)的區(qū)塊:
image.png

2.7 測試合約

我們知道在執(zhí)行編譯時(shí)會(huì)自動(dòng)執(zhí)行這些單元測試洁奈,如果有一個(gè)測試未通過則會(huì)中斷編譯過程。而在開發(fā)階段婉弹,我們也可以自己使用命令來測試睬魂。

truffle test

沒有報(bào)錯(cuò)就說明通過了,綠條“5 passing(2s)”镀赌,有報(bào)錯(cuò)就會(huì)打印在下方氯哮。


輸出截圖1

輸出截圖2

3. 用Truffle框架運(yùn)行一個(gè)“Hello World!”智能合約

3.1 創(chuàng)建工程目錄

返回父級目錄,創(chuàng)建一個(gè)文件夾HelloWorld商佛,來做為你的工程根目錄喉钢。

mkdir HelloWorld

輸入結(jié)果:
創(chuàng)建并進(jìn)入該目錄

3.2 初始化框架

在工作目錄HelloWorld目錄下,執(zhí)行truffle初始化動(dòng)作:

truffle init

輸出截圖:
初始化成功

采用SFTP下載文件到本地良姆,可查看目錄結(jié)構(gòu):

│  truffle-config.js
│  truffle.js
│  
├─contracts
│      Migrations.sol
│      
├─migrations
│      1_initial_migration.js
│      
└─test

目錄結(jié)構(gòu)簡單說明如下:

contract/ - Truffle默認(rèn)的合約文件存放地址肠虽。
migrations/ - 存放發(fā)布腳本文件
test/ - 用來測試應(yīng)用和合約的測試文件
truffle.js - Truffle的配置文件

3.3 新建新合約

在./contract目錄下創(chuàng)建一個(gè)自己的合約文件Greeter.sol。

pragma solidity ^0.4.17;

contract Greeter         
{
    address creator;     
    string greeting;     

    function Greeter(string _greeting) public   
    {
        creator = msg.sender;
        greeting = _greeting;
    }
    

    function greet() public constant returns (string)           
    {
        return greeting;
    }
    
    function setGreeting(string _newgreeting) public
    {
        greeting = _newgreeting;
    }
    
     /**********
     Standard kill() function to recover funds 
     **********/
    
    function kill()public
    { 
        if (msg.sender == creator)
            suicide(creator);  // kills this contract and sends remaining funds back to creator
    }

}

3.4 新建發(fā)布腳本

在./migrations/目錄下新建一個(gè)文件:2_deploy_contracts.js玛追,增加發(fā)布代碼税课。

var Greeter = artifacts.require("./Greeter.sol");

module.exports = function(deployer) {
  deployer.deploy(Greeter,"Hello, World!");//"參數(shù)在第二個(gè)變量攜帶"
};

3.5 編譯

進(jìn)入到工程根目錄./HelloWorld目錄下闲延,進(jìn)行編譯:

truffle compile

輸出截圖如下:
編譯成功截圖

3.6 啟動(dòng)你的客戶端

如果之前沒有啟動(dòng)RPC客戶端的話,則需要啟動(dòng)之前安裝好的EthereumJS RPC客戶端韩玩。如果已啟動(dòng)的則忽略此步垒玲。

$ testrpc

3.7 部署合約(migrate)

執(zhí)行部署命令(truffle migrate)提示出錯(cuò)。

truffle migrate

錯(cuò)誤截圖輸出:
部署失敗找颓,提示網(wǎng)絡(luò)未配置

修改文件./HelloWorld/truffle.js文件合愈,增加網(wǎng)絡(luò)配置:

module.exports = {
  // See <http://truffleframework.com/docs/advanced/configuration>
  // to customize your Truffle configuration!
     networks: {
        development: {
            host: "localhost",
            port: 8545,
            network_id: "*" // 匹配任何network id
         }
    }
};

重新執(zhí)行編譯命令,重新執(zhí)行部署命令(truffle migrate)击狮,則運(yùn)行正確佛析。對應(yīng)Greeter的智能合約地址為“0x7d62724f397a99613b84923a1166d683de2db680”
部署成功

3.8 TRUFFLE測試環(huán)境運(yùn)行合約

Truffle提供了一種更加簡單的方式,通過交互式控制臺(tái)來與你的那些準(zhǔn)備好的合約進(jìn)行交互彪蓬。
truffle console
一個(gè)基本的交互控制臺(tái)寸莫,可以連接任何EVM客戶端。如果你已經(jīng)有了自己的ganache或者geth等EVM的本地環(huán)境寞焙,那么就可以使用truffle console來交互储狭,所以如果你已經(jīng)有一個(gè)現(xiàn)成的小組共享的開發(fā)用EVM互婿,那么使用這個(gè)沒錯(cuò)捣郊。
truffle develop
一個(gè)交互控制臺(tái),啟動(dòng)時(shí)會(huì)自動(dòng)生成一個(gè)開發(fā)用區(qū)塊鏈環(huán)境(其實(shí)我認(rèn)為它與ganache就是一個(gè)底層實(shí)現(xiàn)機(jī)制慈参,都是默認(rèn)生成10個(gè)賬戶)呛牲。如果你沒有自己的EVM環(huán)境的話,直接使用truffle develop非常方便驮配。

truffle console

輸入Greeter智能合約命令娘扩,顯示打印出一個(gè)json結(jié)構(gòu),展示了它的各種屬性內(nèi)容壮锻。


查看Greeter結(jié)構(gòu)

根據(jù)你的Greeter智能合約地址琐旁,運(yùn)行Greeter智能合約命令:
hello,world智能合約運(yùn)行成功

3.8 GETH正式環(huán)境運(yùn)行合約

啟動(dòng)GETH環(huán)境

本節(jié)假設(shè)GETH環(huán)境已安裝好了。如果還沒有安裝的同學(xué)猜绣,可參考文章《第一課 如何在WINDOWS環(huán)境下搭建以太坊開發(fā)環(huán)境》(http://www.reibang.com/p/683ea7d62a39)的描述步驟灰殴。

然后在IDE內(nèi)部打開一個(gè)terminal,啟動(dòng)GETH的EVM環(huán)境掰邢。

geth --datadir testNet3 --dev --rpc console

截圖1

截圖2

GETH 中是通過abi來注冊合約對象的牺陶。
首先我們找到./build/contracts/Greeter.json中的abi的value:

"abi": [
    {
      "inputs": [
        {
          "name": "_greeting",
          "type": "string"
        }
      ],
      "payable": false,
      "stateMutability": "nonpayable",
      "type": "constructor"
    },
    {
      "constant": true,
      "inputs": [],
      "name": "greet",
      "outputs": [
        {
          "name": "",
          "type": "string"
        }
      ],
      "payable": false,
      "stateMutability": "view",
      "type": "function"
    },
    {
      "constant": false,
      "inputs": [
        {
          "name": "_newgreeting",
          "type": "string"
        }
      ],
      "name": "setGreeting",
      "outputs": [],
      "payable": false,
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "constant": false,
      "inputs": [],
      "name": "kill",
      "outputs": [],
      "payable": false,
      "stateMutability": "nonpayable",
      "type": "function"
    }
  ],

通過json壓縮成一行得到
var abi = [{"inputs": [{"name": "_greeting","type": "string"}],"payable": false,"stateMutability": "nonpayable","type": "constructor"},{"constant": true,"inputs": [],"name": "greet","outputs": [{"name": "","type": "string"}],"payable": false,"stateMutability": "view","type": "function"},{"constant": false,"inputs": [{"name": "_newgreeting","type": "string"}],"name": "setGreeting","outputs": [],"payable": false,"stateMutability": "nonpayable","type": "function"},{"constant": false,"inputs": [],"name": "kill","outputs": [],"payable": false,"stateMutability": "nonpayable","type": "function"}];

重新部署智能合約到Geth環(huán)境

啟動(dòng)一個(gè)新的命令窗口,到

cd /usr/work/HelloWorld
truffle migrate

成功部署輸出截圖:
智能合約部署成功

獲得Greeter的地址為 0xb52bb3ce336f71a14345c78e5b2f8e63685e3f92

切換到GETH環(huán)境下辣之,利用api和智能合約地址(你自己Greeter智能合約的地址哦)注冊合約對象掰伸。

var abi = [{"inputs": [{"name": "_greeting","type": "string"}],"payable": false,"stateMutability": "nonpayable","type": "constructor"},{"constant": true,"inputs": [],"name": "greet","outputs": [{"name": "","type": "string"}],"payable": false,"stateMutability": "view","type": "function"},{"constant": false,"inputs": [{"name": "_newgreeting","type": "string"}],"name": "setGreeting","outputs": [],"payable": false,"stateMutability": "nonpayable","type": "function"},{"constant": false,"inputs": [],"name": "kill","outputs": [],"payable": false,"stateMutability": "nonpayable","type": "function"}];
var HelloWorld = eth.contract(abi).at('0xb52bb3ce336f71a14345c78e5b2f8e63685e3f92')
HelloWorld.greet()

輸出截圖顯示成功:


又是成功

4. 總結(jié)及參考

本文站在巨人的肩膀上摇邦,完成了以太坊開發(fā)框架Truffle從入門到實(shí)戰(zhàn)的演示茧彤。對巨人的文章表示感謝:
1,Solidity的Truffle框架實(shí)戰(zhàn)(手把手)
2, 【精解】開發(fā)一個(gè)智能合約
3牌柄,官網(wǎng)參考:
http://truffleframework.com/docs/
4, 官網(wǎng)GITHUB的代碼: https://github.com/trufflesuite

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末歧蕉,一起剝皮案震驚了整個(gè)濱河市历筝,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌廊谓,老刑警劉巖梳猪,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蒸痹,居然都是意外死亡春弥,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門叠荠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來匿沛,“玉大人,你說我怎么就攤上這事榛鼎√雍簦” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵者娱,是天一觀的道長抡笼。 經(jīng)常有香客問我,道長黄鳍,這世上最難降的妖魔是什么推姻? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮框沟,結(jié)果婚禮上藏古,老公的妹妹穿的比我還像新娘。我一直安慰自己忍燥,他們只是感情好拧晕,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著梅垄,像睡著了一般厂捞。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上哎甲,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天蔫敲,我揣著相機(jī)與錄音,去河邊找鬼炭玫。 笑死奈嘿,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的吞加。 我是一名探鬼主播裙犹,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼尽狠,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了叶圃?” 一聲冷哼從身側(cè)響起袄膏,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎掺冠,沒想到半個(gè)月后沉馆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡德崭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年斥黑,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片眉厨。...
    茶點(diǎn)故事閱讀 40,040評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡锌奴,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出憾股,到底是詐尸還是另有隱情鹿蜀,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布服球,位于F島的核電站茴恰,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏有咨。R本人自食惡果不足惜琐簇,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一蒸健、第九天 我趴在偏房一處隱蔽的房頂上張望座享。 院中可真熱鬧,春花似錦似忧、人聲如沸渣叛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽淳衙。三九已至,卻和暖如春饺著,著一層夾襖步出監(jiān)牢的瞬間箫攀,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工幼衰, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留靴跛,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓渡嚣,卻偏偏與公主長得像梢睛,于是被迫代替她去往敵國和親肥印。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容