第五課 以太坊開發框架Truffle從入門到實戰

【本文目標】
經過本文的學習和時間,你將熟悉以太坊開發框架Truffle的配置和運行,並藉助Truffle完成一個智能合約的部署。javascript

【技術收穫】 經過本文的學習,你將掌握如下內容: 1,瞭解TRUFFLE的功能 2,瞭解TRUFFLE的安裝,配置和啓動 3,藉助TRUFFLE完成METACOIN一個智能合約的運行 4,Testrpc,Geth環境的使用html

【實操課程列表】 第一課 如何在WINDOWS環境下搭建以太坊開發環境java

第二課 如何實現以太坊最簡智能合約「Hello World」的運行node

第四課 以太坊開發框架Truffle從入門到實戰react

第六課 技術小白如何開發一個DAPP區塊鏈應用(以寵物商店爲例)webpack

第七課 技術小白如何在45分鐘內發行通證(TOKEN)並上線交易git

第八課 如何調試以太坊官網的智能合約衆籌案例github

【說明】未列出的課程爲知識普及的非實操類課程,全部區塊鏈文章參考「區塊鏈入口」專欄。web

1. TRUFFLE是什麼?

Truffle是一個世界級的開發環境,測試框架,以太坊的資源管理通道,致力於讓以太坊上的開發變得簡單,Truffle有如下:npm

  • 內置的智能合約編譯,連接,部署和二進制文件的管理。
  • 快速開發下的自動合約測試。
  • 腳本化的,可擴展的部署與發佈框架。
  • 部署到無論多少的公網或私網的網絡環境管理功能
  • 使用EthPM&NPM提供的包管理,使用ERC190標準。
  • 與合約直接通訊的直接交互控制檯(寫完合約就能夠命令行裏驗證了)。
  • 可配的構建流程,支持緊密集成。
  • 在Truffle環境裏支持執行外部的腳本。 【說明】更多以太坊術語可參考此篇文章: www.jianshu.com/p/036661986…

1.1 TRUFFLE的安裝

在Ubuntu命令上窗口輸入如下命令,完成安裝:

$ npm install -g truffle

若是安裝成功,可輸入truffle version名稱,正常狀況下會有版本顯示:

truffle version

環境要求

NodeJS 5.0+ Windows,Linux(推薦Ubuntu),或Mac OS X

Truffle客戶端

有許多的以太坊客戶端能夠選擇。咱們推薦在開發和部署時使用不一樣客戶端。 適用開發的客戶端

當開發基於Truffle的應用時,咱們推薦使用EthereumJS TestRPC。它是一個完整的在內存中的區塊鏈僅僅存在於你開發的設備上。它在執行交易時是實時返回,而不等待默認的出塊時間,這樣你能夠快速驗證你新寫的代碼,當出現錯誤時,也能即時反饋給你。它同時仍是一個支持自動化測試的功能強大的客戶端。Truffle充分利用它的特性,能將測試運行時間提速近90%。

適用正式發佈的客戶端

對此有許多官方和非官方的以太坊客戶端可供選擇。最好使用TestRPC客戶端充分測試後,再使用這些客戶端。這些是完整的客戶端實現,包括挖礦,網絡,塊及交易的處理,Truffle能夠在不須要額外配置的狀況下發布到這些客戶端。

當發佈到私有網絡中

私人網絡中使用了相同的技術,但卻有不一樣的配置。因此你能夠將上面說起的客戶端來運行一個私有的網絡,部署到這樣的網絡也是使用一樣的方式。 【說明】做者使用TestRPCGeth (go-ethereum)這2種客戶端,他們的安裝方式參考文章:www.jianshu.com/p/683ea7d62…

2. 下載TRUFFLE MetaCoin樣例進行環境搭建實戰

2.1 MetaCoin初始化

咱們假設前面的安裝和環境搭建已所有成功,此時應該能夠直接使用命令truffle了,下面咱們創建一個工做間truffle-workspace,而後在工做間執行:

mkdir MetaCoin
cd MetaCoin
truffle unbox metacoin
複製代碼

原來使用truffle init,但如今它存在於unbox。

執行截圖以下:

下載樣例

unbox

Truffle 的盒子Boxs裝有不少很是實用的項目樣板,可讓你忽略一些環境配置問題,從而能夠集中與開發你本身的DApp的業務惟一性。除此以外,Truffle Boxes可以容納其餘有用的組件、Solidity合約或者庫,先後端視圖等等。全部這些都是一個完整的實例Dapp程序。均可如下載下來逐一研究,尋找適合本身公司目前業務模型的組件。

Truffle的官方Boxes地址

能夠看到,如今官方盒子還很少,總共7個,有三個是關於react的,兩個是truffle本身的項目,能夠下載體驗,剩下兩個是咱們比較關心的,一個是metacoin,很是好的入門示例,另外一個是webpack,顧名思義,它是一套比起metacoin更加完整的模板的存在。既然咱們是初學,下面咱們就從metacoin入手學習。

1) tutorialtoken 1] This box has all you need to get started with our Open Zeppelin (TutorialToken) tutorial. 2] truffleframework.com/boxes/tutor…

2)PET-SHOP 1] This box has all you need to get started with our Pet Shop tutorial. 2] truffleframework.com/boxes/pet-s…

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] truffleframework.com/boxes/chesh…

2.2 目錄結構及文件解讀

進入metacoin目錄,當前目錄已經被初始化成一個新的空的以太坊工程,目錄結構以下:

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);
  }
}

複製代碼

上面咱們學習了Solidity具體的類型語法,咱們來分析一下這個文件:

  • 它定義了一個名字爲「遷移」的合約
  • 有一個任意訪問的全局變量,存儲於storage的地址類型變量owner
  • 有一個可任意訪問的全局變量,存儲於storage的無符號整型類型的變量last_completed_migration
  • modifier下面細說,此處略過
  • msg.sender下面細說,此處略過
  • 構造函數,初始化將發送方賦值給owner保存
  • 一個setCompleted賦值方法,賦值給last_completed_migration,其中該方法被聲明爲restricted,下面細說,此處略過
  • upgrade方法,調用當前合約本身的方法,獲得合約的實例upgraded,而後經過該是咧調用setCompleted賦值方法。
Solidity語法補充說明1:function modifier

modifier的使用方法,就看上面的Migrations合約的例子便可,它能夠自動改變函數的行爲,例如你能夠給他預設一個條件,他會不斷檢查,一旦符合條件便可走預設分支。它能夠影響當前合約以及派生合約。

pragma solidity ^0.4.11;

contract owned {
    function owned() public { owner = msg.sender; }
    address owner;
    // 這裏僅定義了一個modifier可是沒有使用,它將被子類使用,方法體在這裏「_;」,這意味着若是owner調用了這個函數,函數會被執行,其餘人調用會拋出一個異常。
    modifier onlyOwner {
        require(msg.sender == owner);
        _;
    }
}

// 經過is關鍵字來繼承一個合約類,mortal是owned的子類,也叫派生類。
contract mortal is owned {
    // 當前合約派生了owned,此方法使用了父類的onlyOwner的modifier
    // public onlyOwner, 這種寫法挺讓人困惑,下面給出了個人思考,暫理解爲派生類要使用基類的modifier。
    function close() public onlyOwner {
        selfdestruct(owner);
    }
}

contract priced {
    // Modifiers能夠接收參數
    modifier costs(uint price) {
        // 這裏modifier方法體是經過條件判斷,是否知足,知足則執行「_;」分支。
        if (msg.value >= price) {
            _;
        }
    }
}

contract Register is priced, owned {
    mapping (address => bool) registeredAddresses;
    uint price;

    // 構造函數給全局變量price賦值。
    function Register(uint initialPrice) public { price = initialPrice; }

    // payable關鍵字重申,若是不聲明的話,函數關於以太幣交易的操做都會被拒回。
    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;
    }
}
複製代碼

又延伸出來一個盲點:require關鍵字,它是錯誤判斷,提到assert就懂了,官方文檔的解釋爲:

require(bool condition):
throws if the condition is not met - to be used for errors in inputs or external components.
複製代碼

總結一下modifier:

  • 聲明modifier時,特殊符號「_;」的意思有點像TODO,是一個「佔位符」,指出了你要寫的具體方法體內容的位置。
  • function close() public onlyOwner,派生類某方法想「如虎添翼」加入基類的某個modifier功能,就能夠這樣寫,這行的具體意思就是:close方法也必須是owner本人執行,不然報錯!
Solidity語法補充說明2:Restricting Access

限制訪問一種針對合約的常見模式。但其實你永遠不可能限制得了任何人或電腦讀取你的交易內容或者你的合同狀態。你可使用加密增大困難,但你的合約就是用來讀取數據的,那麼其餘人也會看到。因此,其實上面的modifier onlyOwner是一個特別好的可讀性極高的限制訪問的手段。

那麼restricted關鍵字如何使用呢?

好吧,我剛剛帶着modifier的知識從新看了上面的Migrations合約的內容發現,restricted並非關鍵字,而是modifier的方法名,在其下的想增長該modifier功能的函數中,都使用了public restricted的方式來聲明。

說到這裏,我又明白了爲何要使用public onlyOwner這種寫法,由於public是函數可見性修飾符,onlyOwner是自定義的限制訪問的modifier方法,他們都是關於函數使用限制方面的,因此會寫在一塊兒,能夠假想一個括號將它倆括起來,他們佔一個位置,就是原來屬於public|private|internal|external的那個位置。

Solidity語法補充說明3:Special Variables and Functions

這一點很重要了,咱們研究一下Solidity自身攜帶的特殊變量以及函數:

  1. block.blockhash(uint blockNumber) returns (bytes32): 返回參數區塊編號的hash值。(範圍僅限於最近256塊,還不包含固然塊)
  2. block.coinbase (address): 當前區塊礦工地址
  3. block.difficulty (uint): 當前區塊難度
  4. block.gaslimit (uint): 當前區塊的gaslimit
  5. block.number (uint): 當前區塊編號
  6. block.timestamp (uint): 當前區塊的timestamp,使用UNIX時間秒
  7. msg.data (bytes): 完整的calldata
  8. msg.gas (uint): 剩餘的gas
  9. msg.sender (address): 信息的發送方 (當前調用)
  10. msg.sig (bytes4): calldata的前四個字節 (i.e. 函數標識符)
  11. msg.value (uint): 消息發送的wei的數量
  12. now (uint): 當前區塊的timestamp (block.timestamp別名)
  13. tx.gasprice (uint): 交易的gas單價
  14. tx.origin (address): 交易發送方地址(徹底的鏈調用)

msg有兩個屬性,一個是msg.sender,另外一個是msg.value,這兩個值能夠被任何external函數調用,包含庫裏面的函數。

注意謹慎使用block.timestamp, now and block.blockhash,由於他們都是有可能被篡改的。

初始化文件解釋2:MetaCoin.sol

pragma solidity ^0.4.18;

import "./ConvertLib.sol";

// 這是一個簡單的仿幣合約的例子。它並非標準的可兼容其餘幣或token的合約,
// 若是你想建立一個標準兼容的token,請轉到 https://github.com/ConsenSys/Tokens(TODO:一下子咱們再過去轉)

contract MetaCoin {
        mapping (address => uint) balances;// 定義了一個映射類型變量balances,key爲address類型,值爲無符整型,應該是用來存儲每一個帳戶的餘額,能夠存多個。

        event Transfer(address indexed _from, address indexed _to, uint256 _value);// Solidity語法event,TODO:見下方詳解。

        function MetaCoin() public {// 構造函數,tx.origin查查上面,找到它會返回交易發送方的地址,也就是說合約實例建立時會默認爲當前交易發送方的餘額塞10000,單位應該是你的仿幣。
                balances[tx.origin] = 10000;
        }

        function sendCoin(address receiver, uint amount) public returns(bool sufficient) {// 函數聲明部分沒有盲點,方法名,參數列表,函數可見性,返回值類型定義。
                if (balances[msg.sender] < amount) return false;// 若是餘額不足,則返回發送幣失敗
                balances[msg.sender] -= amount;// 不然從發送方餘額中減去發送值,注意Solidity也有 「-=」,「+=」 的運算符哦
                balances[receiver] += amount;// 而後在接收方的餘額中加入發送值數量。
                Transfer(msg.sender, receiver, amount);// 使用以上event關鍵字聲明的方法
                return true;
        }

        function getBalanceInEth(address addr) public view returns(uint){// 獲取以太幣餘額
                return ConvertLib.convert(getBalance(addr),2);// 調用了其餘合約的方法,TODO:稍後介紹ConvertLib合約時說明。
        }

        function getBalance(address addr) public view returns(uint) {// 獲取當前帳戶的仿幣餘額
                return balances[addr];
        }
}

複製代碼
Solidity語法補充說明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提供了日誌支持,進而可用於在用戶界面上「調用」dapp JavaScript回調,監聽了這些事件。簡單來講,咱們的DApp是基於web服務器上的web3.js與EVM以太坊結點進行交互的,而智能合約是部署在EVM以太坊結點上的。舉一個例子:

contract ExampleContract {
  // some state variables ...
  function foo(int256 _value) returns (int256) {
    // manipulate state ...
    return _value;
  }
}
複製代碼

合約ExampleContract有個方法foo被部署在EVM的一個結點上運行了,此時用戶若是想在DApp上調用合約內部的這個foo方法,如何操做呢,有兩種辦法:

  1. var returnValue = exampleContract.foo.call(2);// 經過web3 的message的call來調用。
  2. 合約內部再聲明一個event ReturnValue(address indexed _from, int256 _value);並在foo方法內使用該event用來返回方法執行結果。

第一種辦法在方法自己比較耗時的狀況下會阻塞,或者不會獲取到準確的返回值。因此採用第二種辦法:就是經過Solidity的關鍵字event。event在這裏就是一個回調函數的概念,當函數運行結束之後(交易進塊),會經過event返回給web3,也就是DApp用戶界面相應的結果。這是以太坊一種客戶端異步調用方法。關於這個回調,要在DApp使用web3時顯示編寫:

exampleEvent.watch(function(err, result) {
  if (err) {
    console.log(err)
    return;
  }
  console.log(result.args._value)
  // 檢查合約方法是否反返回結果,如有則將結果顯示在用戶界面而且調用exampleEvent.stopWatching()方法中止異步回調監聽。
})
複製代碼

寫Solidity最大的不一樣在於,咱們要隨時計算好咱們的gas消耗,方法的複雜度,變量類型的存儲位置(memory,storage等等)都會決定gas的消耗量。

使用event能夠得到比storage更便宜的gas消耗。

總結一下event,就是若是你的Dapp客戶端web3.js想調用智能合約內部的函數,則使用event做爲橋樑,它能方便執行異步調用同時又節約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聲明的一個庫,它只有一個方法,就是返回給定的兩個無符整數值相乘的結果。返回到上面的MetaCoin中該庫的使用位置去分析,便可知道,MetaCoin的仿幣的價格是以太幣的一倍,因此MetaCoin是以以太幣爲標杆,經過智能合約發佈的一個token,仿幣。

這彷佛就能夠很好地解決我在《以太坊RPC機制與API實例》文章中須要發佈三倍以太幣的token的需求了,而咱們徹底沒必要更改以太坊源碼,但那篇文章經過這個需求的路線研究了以太坊的Go源碼也算功不可沒。

初始化文件解釋4:1_initial_migration.js

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

module.exports = function(deployer) {
  deployer.deploy(Migrations);
};
複製代碼

這個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);
};

複製代碼

這個文件是meatcoin智能合約的部署文件,裏面約定了部署順序,依賴關係。這裏咱們看到了MetaCoin智能合約是要依賴於庫ConvertLib的,因此要先部署ConvertLib,而後link他們,再部署MetaCoin,這部分js的寫法能夠參照官方文檔DEPLOYER API,主要就是介紹了一下deploy、link以及then三個方法的詳細用法,不難這裏再也不贅述。

初始化文件解釋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!
};

複製代碼

這兩個文件也都是nodejs,他們都是配置文件,可能做用域不一樣,目前它倆是徹底相同的(由於啥也沒有)。咱們去它推薦的網站看一看。給出了一個例子:

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

這個例子展現了該配置文件能夠配置網絡環境,暫先到這,之後趕上了針對該配置文件進行研究。

初始化文件解釋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在編譯期間會自動執行這些測試腳本。當前文件爲js版本,模擬用戶在DApp客戶端用戶界面操做的情形。

var MetaCoin = artifacts.require("./MetaCoin.sol"); // 這與1_initial_migration.js文件的頭是同樣的,引入了一個智能合約文件。

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");
    });
  });
});

複製代碼

咱們來分析一波這個truffle metacoin js版本的單元測試:

  1. 直接函數contract走起,第一個參數爲智能合約名字,第二個參數爲匿名內部函數
  2. 匿名函數傳入了當前帳戶地址,函數體是單元測試集
  3. 每一個單元測試是由關鍵字it函數來作,第一個參數傳入單元測試的comments,第二個參數傳入一個無參匿名函數
  4. 進到無參匿名函數的函數體內,就是正式的單元測試內容,能夠定義本身的成員屬性,經過調用truffle內部組件自動部署合約逐一測試,使用成員屬性接收返回值,最後使用關鍵字assert來判斷是否符合預期。具體業務不詳細展開,可根據本身業務內容隨意更改。

這是官方文檔,詳細說明如何使用JS來編寫智能合約的單元測試

初始化文件解釋9:TestMetacoin.sol

好下面來看看Solidity智能合約版本的單元測試。通常來說,這種文件的命名規則是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");
  }

}

複製代碼

繼續分析:

  • 首先import了truffle的幾個類庫,用來支持咱們接下來的測試內容。而後import了待測智能合約。
  • 創建單元測試智能合約,根據合約不一樣方法定義對應的test測試方法。
  • 方法體內部去調用待測智能合約的方法,傳參接收返回值,而後使用關鍵字assert判斷是否符合預期。

這是官方文檔,詳細說明如何使用Solidity來編寫智能合約的單元測試

2.3 編譯合約

鍵入

truffle compile

輸出狀況:

輸出結果

根據編譯輸出的路徑地址./build/contracts,咱們去查看一下

產生文件列表

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

這些json文件就是truffle用來部署合約的編譯文件。 ##2.4 配置以太坊本地環境 truffle.js是truffle的配置文件,啓動好以太坊本地結點之後,咱們須要讓truffle去識別它並使用它,這就須要在truffle.js中配置相關屬性:

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

【說明】若是不啓動TestRPC,直接執行部署合約的話,會有如下錯誤提示:

無網絡

2.5 啓動本地以太坊客戶端結點

啓動適合開發的RPC客戶端

啓動以前安裝好的EthereumJS RPC客戶端。

testrpc

【說明】必定要啓動一個新的客戶端執行testrpc命令,能夠觀察到默認帳戶和私鑰信息。

本地客戶端

2.6 部署合約

移植(migrate),對這裏叫移植,但下面咱們仍使用「部署」這個詞,truffle中部署的命令爲:

truffle migrate

輸出結果截圖以下:

部署智能合約的輸出結果

查看testrpc的輸出窗口,能夠看到這筆交易和花費的區塊:

image.png

2.7 測試合約

咱們知道在執行編譯時會自動執行這些單元測試,若是有一個測試未經過則會中斷編譯過程。而在開發階段,咱們也能夠本身使用命令來測試。

truffle test

沒有報錯就說明經過了,綠條「5 passing(2s)」,有報錯就會打印在下方。

輸出截圖1
輸出截圖2

3. 用Truffle框架運行一個「Hello World!」智能合約

3.1 建立工程目錄

返回父級目錄,建立一個文件夾HelloWorld,來作爲你的工程根目錄。

mkdir HelloWorld

輸入結果:

建立並進入該目錄

3.2 初始化框架

在工做目錄HelloWorld目錄下,執行truffle初始化動做:

truffle init

輸出截圖:

初始化成功
採用SFTP下載文件到本地,可查看目錄結構:

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

目錄結構簡單說明以下:

contract/ - Truffle默認的合約文件存放地址。 migrations/ - 存放發佈腳本文件 test/ - 用來測試應用和合約的測試文件 truffle.js - Truffle的配置文件

3.3 新建新合約

在./contract目錄下建立一個本身的合約文件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 新建發佈腳本

在./migrations/目錄下新建一個文件:2_deploy_contracts.js,增長髮布代碼。

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

module.exports = function(deployer) {
  deployer.deploy(Greeter,"Hello, World!");//"參數在第二個變量攜帶"
};
複製代碼

3.5 編譯

進入到工程根目錄./HelloWorld目錄下,進行編譯:

truffle compile

輸出截圖以下:

編譯成功截圖

3.6 啓動你的客戶端

若是以前沒有啓動RPC客戶端的話,則須要啓動以前安裝好的EthereumJS RPC客戶端。若是已啓動的則忽略此步。

$ testrpc

3.7 部署合約(migrate)

執行部署命令(truffle migrate)提示出錯。

truffle migrate

錯誤截圖輸出:

部署失敗,提示網絡未配置
修改文件./HelloWorld/truffle.js文件,增長網絡配置:

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

從新執行編譯命令,從新執行部署命令(truffle migrate),則運行正確。對應Greeter的智能合約地址爲「0x7d62724f397a99613b84923a1166d683de2db680」

部署成功

3.8 TRUFFLE測試環境運行合約

Truffle提供了一種更加簡單的方式,經過交互式控制檯來與你的那些準備好的合約進行交互。 truffle console 一個基本的交互控制檯,能夠鏈接任何EVM客戶端。若是你已經有了本身的ganache或者geth等EVM的本地環境,那麼就可使用truffle console來交互,因此若是你已經有一個現成的小組共享的開發用EVM,那麼使用這個沒錯。 truffle develop 一個交互控制檯,啓動時會自動生成一個開發用區塊鏈環境(其實我認爲它與ganache就是一個底層實現機制,都是默認生成10個帳戶)。若是你沒有本身的EVM環境的話,直接使用truffle develop很是方便。

truffle console

輸入Greeter智能合約命令,顯示打印出一個json結構,展現了它的各類屬性內容。

查看Greeter結構
根據你的Greeter智能合約地址,運行Greeter智能合約命令:
hello,world智能合約運行成功

3.9 GETH正式環境運行合約

###啓動GETH環境 本節假設GETH環境已安裝好了。若是尚未安裝的同窗,可參考文章《第一課 如何在WINDOWS環境下搭建以太坊開發環境》(www.jianshu.com/p/683ea7d62…

而後在IDE內部打開一個terminal,啓動GETH的EVM環境。

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環境

啓動一個新的命令窗口,到

cd /usr/work/HelloWorld truffle migrate

成功部署輸出截圖:

智能合約部署成功
得到Greeter的地址爲 0xb52bb3ce336f71a14345c78e5b2f8e63685e3f92

切換到GETH環境下,利用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. 總結及參考

本文站在巨人的肩膀上,完成了以太坊開發框架Truffle從入門到實戰的演示。對巨人的文章表示感謝: 1,Solidity的Truffle框架實戰(手把手) 2, 【精解】開發一個智能合約 3,官網參考: truffleframework.com/docs/ 4, 官網GITHUB的代碼: github.com/trufflesuit…

知識對接服務: 輝哥和歐陽哥哥在知識星球開通了區塊鏈入門專欄,用於存放簡書區塊鏈入門專欄文章的工程源碼等內容,並創建專項微信羣用於技術交流,歡迎加入。

相關文章
相關標籤/搜索