智能合約調試指南

 

不像你在其餘地方看到的紙質合約,以太坊的智能合約是代碼組成的,須要你以很是謹慎的態度去對待它。html

(這是一件好事,想象下若是現實世界的合同須要編譯的話會更清晰麼?)express

若是咱們的合同沒有被正確的編碼出來, 咱們的交易可能會失敗,致使以太幣的損失(以 gas 的形式),更不用說浪費時間和精力。網絡

幸運的是,Truffle (版本 4 以上) 內置了逐步調試的功能,因此一旦發生錯誤,你能夠很快發現並修復它。編輯器

在本教程中,咱們將在測試的區塊鏈環境中部署一個基礎的合同,並引入一些錯誤,經過 Truffle 內置調試器修復它們。函數

一個基礎的智能合約

一個最基礎的合同是一個簡單的存儲類型的智能合約。(這個例子改編自 Solidity documentation)區塊鏈

pragma solidity ^0.4.17;

contract SimpleStorage {
  uint myVariable;

  function set(uint x) public {
    myVariable = x;
  }

  function get() constant public returns (uint) {
    return myVariable;
  }
}

此合約作了兩件事:測試

  • 容許你設置一個變量(myVariable)爲特定整數值。
  • 容許你查詢一個選定的值。

這不是一個很是有趣的合約,可是這不是重點。咱們想看看出錯後會發生什麼當事情。ui

首先咱們配置環境。this

部署智能合約

  1. 首先爲咱們的合約建立一個新的本地目錄:
mkdir simple-storage
cd simple-storage

2. 建立一個空的 Truffle 項目編碼

truffle init

這個命令將建立目錄,好比contracts/和migrations/,並生成一些文件用於幫助部署合約到區塊鏈。

3.contracts/目錄中有一個Store.sol文件。

pragma solidity ^0.4.17;
contract SimpleStorage {
  uint myVariable;
function set(uint x) public {
    myVariable = x;
  }
function get() constant public returns (uint) {
    return myVariable;
  }
}

這是咱們須要調試的合約,詳細的合約內容的解釋超出了本教程的範圍,注意咱們有一個名爲SimpleStorage的合約,裏面有個數字類型的變量myVariable和兩個函數set()和get()。第一個函數設置變量內容,第二個獲取變量。

4. migrations/ 目錄下,有個 2_deploy_contracts.js 文件。

var SimpleStorage = artifacts.require("SimpleStorage");
module.exports = function(deployer) {
  deployer.deploy(SimpleStorage);
};

這個文件是管理部署SimpleStorage合約的。

5. 在終端中,編譯此合約

truffle compile

6. 再開一個終端,運行 truffle develop ,開啓 truffle 內置的測試區塊鏈,這樣咱們可使用它來測試合約。

truffle develop

這個命令將出現提示符 truffle(develop)>, 從如今開始,全部的命令都在此提示符下輸入(特殊狀況會說明)

7. 當開發控制檯運行起來後,咱們能夠部署咱們的合約了。

migrate

下面的相應有些相似,除了 id 不一樣。

Running migration: 1_initial_migration.js
   Replacing Migrations...
   ... 0xe4f911d95904c808a81f28de1e70a377968608348b627a66efa60077a900fb4c
   Migrations: 0x3ed10fd31b3fbb2c262e6ab074dd3c684b8aa06b
 Saving successful migration to network...
   ... 0x429a40ee574664a48753a33ea0c103fc78c5ca7750961d567d518ff7a31eefda
 Saving artifacts...
 Running migration: 2_deploy_contracts.js
   Replacing SimpleStorage...
   ... 0x6783341ba67d5c0415daa647513771f14cb8a3103cc5c15dab61e86a7ab0cfd2
   SimpleStorage: 0x377bbcae5327695b32a1784e0e13bedc8e078c9c
 Saving successful migration to network...
   ... 0x6e25158c01a403d33079db641cb4d46b6245fd2e9196093d9e5984e45d64a866
 Saving artifacts...

和基礎的智能合約交互

智能合約經過truffle develop部署到了測試網絡中,運行 console 而不是 Ganache,一個內置在 Truffle 的本地區塊鏈。

  1. 在truffle develop運行的終端中,輸入以下命令:
SimpleStorage.deployed().then(function(instance){return instance.get.call();}).then(function(value){return value.toNumber()});

這個命令找到 SimpleStorage 合約,而後調用get()命令,一般返回一個字符串並轉化爲數字。

0

myVariable被設置爲 0,儘管咱們還沒給它賦值。這是由於 Solidity 數值型變量會自動被賦值爲 0,不像其餘語言會是NULL和undefined。

2. 如今咱們運行一條交易命令,調用set()設置咱們的變量爲其餘值。

SimpleStorage.deployed().then(function(instance){return instance.set(4);});

設置變量爲 4,返回的信息包括交易(交易 id ,交易receipt 和一些交易的時間 log)

{ tx: '0x7f799ad56584199db36bd617b77cc1d825ff18714e80da9d2d5a0a9fff5b4d42',
   receipt:
{ transactionHash: '0x7f799ad56584199db36bd617b77cc1d825ff18714e80da9d2d5a0a9fff5b4d42',
  transactionIndex: 0,
  blockHash: '0x60adbf0523622dc1be52c627f37644ce0a343c8e7c8955b34c5a592da7d7c651',
  blockNumber: 5,
  gasUsed: 41577,
  cumulativeGasUsed: 41577,
  contractAddress: null,
  logs: [] },
   logs: [] }

最重要的是交易的 id (在這裏是 tx 和 transactionHash)。咱們須要賦值這個值用來調試。

3. 想驗證值是否已經改變,運行get()

SimpleStorage.deployed().then(function(instance){return instance.get.call();}).then(function(value){return value.toNumber()});

輸出

4

調試錯誤

上文展現了智能合約如何工做,如今咱們引入一些錯誤到合約中。

針對以下幾個錯誤

  • 無限循環
  • 無效錯誤檢查
  • 無錯誤,可是函數沒有按預期執行

錯誤 #1: 無限循環

在以太坊區塊鏈中,交易不能永遠執行下去。

一個交易會一直執行到 gas 用盡。一旦發生這種狀況,交易會返回out of gas錯誤。

由於 gas 是以以太幣計費的,因此會形成真實的資產損失,因此修復這種錯誤迫在眉睫。

引入錯誤

  1. 在編輯器打開contracts/目錄中的Store.sol
  2. 替換set()
function set(uint x) public {
  while(true) {
    myVariable = x;
  }
}

由於while(true)因此函數永遠不會退出。

測試合約

Truffle 的開發終端不重啓就能夠從新部署合約。咱們能夠經過migrate一步編譯和部署合約

  1. Truffle 的開發終端中,更新合約
migrate --reset

2. 爲了更好的捕獲錯誤,咱們打開第二個終端

truffle develop --log

而後回到以前的終端中

3. 如今咱們能夠運行交易了,運行set()命令

SimpleStorage.deployed().then(function(instance){return instance.set(4);});

捕獲到錯誤

Error: VM Exception while processing transaction: out of gas

在 log 的終端中,咱們能夠看到更多信息

develop:testrpc eth_sendTransaction +0ms
 develop:testrpc  +1s
 develop:testrpc   Transaction: 0xe493340792ab92b95ac40e43dca6bc88fba7fd67191989d59ca30f79320e883f +2ms
 develop:testrpc   Gas usage: 4712388 +11ms
 develop:testrpc   Block Number: 6 +15ms
 develop:testrpc   Runtime Error: out of gas +0ms
 develop:testrpc  +16ms

經過這些信息,咱們能夠調試這個交易

調試錯誤

調出 debug 的命令是在 Truffle 開發終端輸入debug <Transaction ID>或者直接在終端中輸入truffle debug <Transaction ID>,如今讓咱們開始吧!

  1. 在 Truffle 開發終端中,複製粘貼交易 id 到 debug 命令後
debug 0xe493340792ab92b95ac40e43dca6bc88fba7fd67191989d59ca30f79320e883f

你將看到以下輸出

Gathering transaction data...
Addresses affected:
     0x377bbcae5327695b32a1784e0e13bedc8e078c9c - SimpleStorage
Commands:
   (enter) last command entered (step next)
   (o) step over, (i) step into, (u) step out, (n) step next
   (;) step instruction, (p) print instruction, (h) print this help, (q) quit
Store.sol | 0x377bbcae5327695b32a1784e0e13bedc8e078c9c:
1: pragma solidity ^0.4.17;
   2:
   3: contract SimpleStorage {
      ^^^^^^^^^^^^^^^^^^^^^^^
debug(develop:0xe4933407...)>

這是個交互式命令行,你能夠用列出的命令和程序交互

1. 最經常使用的命令是 `step next`,命令執行一次往下一行代碼,快捷鍵是 `Enter` 或者 `n`
輸出

 Store.sol | 0x377bbcae5327695b32a1784e0e13bedc8e078c9c:
    4:   uint myVariable;
    5:
    6: function set(uint x) public {
       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^

注意程序已經移動到了下一個命令,第六行中。
2. 鍵入回車

 Store.sol | 0x377bbcae5327695b32a1784e0e13bedc8e078c9c:

        5:
        6: function set(uint x) public {
        7:   while(true) {
        ^^^^^^^^^^^^

3. 不斷按回車

 Store.sol | 0x377bbcae5327695b32a1784e0e13bedc8e078c9c:

        5:
        6: function set(uint x) public {
        7:   while(true) {
               ^^^^

        debug(develop:0xe4933407...)>

        Store.sol | 0x377bbcae5327695b32a1784e0e13bedc8e078c9c:

        5:
        6: function set(uint x) public {
        7:   while(true) {
         ^^^^^^^^^^^^

        debug(develop:0xe4933407...)>

        Store.sol | 0x377bbcae5327695b32a1784e0e13bedc8e078c9c:

        6: function set(uint x) public {
        7:   while(true) {
        8:     myVariable = x;
                        ^

        debug(develop:0xe4933407...)>

        Store.sol | 0x377bbcae5327695b32a1784e0e13bedc8e078c9c:

        6: function set(uint x) public {
        7:   while(true) {
        8:     myVariable = x;
           ^^^^^^^^^^

        debug(develop:0xe4933407...)>

        Store.sol | 0x377bbcae5327695b32a1784e0e13bedc8e078c9c:

        6: function set(uint x) public {
        7:   while(true) {
        8:     myVariable = x;
           ^^^^^^^^^^^^^^

        debug(develop:0xe4933407...)>

        Store.sol | 0x377bbcae5327695b32a1784e0e13bedc8e078c9c:

        5:
        6: function set(uint x) public {
        7:   while(true) {
         ^^^^^^^^^^^^

請注意,最終步驟會一直重複。事實上,按回車會永遠重複那些交易(直到用完 gas )。而這會告訴你問題在哪裏。

錯誤 #2: 無效錯誤檢查

智能合約能夠用 assert() 來保證必要特定條件會出現。這種和合約狀態的衝突是不可調和的。
引入錯誤
如今咱們來引入這個錯誤,看看調試器如何發現它。
1. 再次打開 `Store.sol`
2. 替換 `set()` 函數

 function set(uint x) public {
      assert(x == 0);
      myVariable = x;
    }

和前一個版本同樣,只是多了 `assert()` 函數,保證 `x == 0`,若是咱們設置 x 爲其餘值,咱們就會發現錯誤。

測試合約
和以前同樣,咱們重置下合約
1. migrate --reset
2. SimpleStorage.deployed().then(function(instance){return instance.set(4);});
咱們會看到以下錯誤

 Error: VM Exception while processing transaction: invalid opcode

調試錯誤
1. 複製交易 id 到 debug 命令下

debug 0xe493340792ab92b95ac40e43dca6bc88fba7fd67191989d59ca30f79320e883f

回到調試器

Store.sol | 0x377bbcae5327695b32a1784e0e13bedc8e078c9c:
1: pragma solidity ^0.4.17;
   2:
   3: contract SimpleStorage {
      ^^^^^^^^^^^^^^^^^^^^^^^
debug(develop:0xe4933407...)>

1. 鍵入回車

Store.sol | 0x377bbcae5327695b32a1784e0e13bedc8e078c9c:

    5:
    6:   function set(uint x) public {
    7:     assert(x == 0);
           ^^^^^^^^^^^^^^

    debug(develop:0x7e060037...)>

    Transaction halted with a RUNTIME ERROR.

    This is likely due to an intentional halting expression, like 
    assert(), require() or revert(). It can also be due to out-of-gas
    exceptions. Please inspect your transaction parameters and 
    contract code to determine the meaning of this error.

咱們能夠看到最後的事件中觸發了錯誤.

錯誤 #3: 無錯誤,可是函數沒有按預期執行

有時候,錯誤不必定是真正的錯誤,它在運行時間內不會引發問題,只是不會按預期執行。
舉個例子,一個事件將會在變量是奇數的時候執行,而另外一個事件在偶數的時候執行。若是咱們調換了這個條件,讓相反的事件執行了。它斌不會觸發錯誤,然而,合約會不按照咱們的預期執行下去。
咱們再次用調試器來找出錯誤。
引入錯誤
1. 再次打開 `Store.sol`
2. 替換 `set()` 函數

 event Odd();

    event Even();

    function set(uint x) public {
      myVariable = x;
      if (x % 2 == 0) {
        Odd();
      } else {
        Even();
      }
    }

代碼有兩個假的事件,`Odd()` 和 `Even()` 是否執行取決於 x 是否能被 2 整除。
可是咱們發現 x 能被 2 整除時, `Odd()` 事件觸發了。
測試合約
1. migrate --reset
2. SimpleStorage.deployed().then(function(instance){return instance.set(4);});
沒有錯誤產生,輸出以下

{ tx: '0x7f799ad56584199db36bd617b77cc1d825ff18714e80da9d2d5a0a9fff5b4d42',
  receipt:
   { transactionHash: '0x7f799ad56584199db36bd617b77cc1d825ff18714e80da9d2d5a0a9fff5b4d42',
     transactionIndex: 0,
     blockHash: '0x08d7c35904e4a93298ed5be862227fcf18383fec374759202cf9e513b390956f',
     blockNumber: 5,
     gasUsed: 42404,
     cumulativeGasUsed: 42404,
     contractAddress: null,
     logs: [ [Object] ] },
  logs:
   [ { logIndex: 0,
       transactionIndex: 0,
       transactionHash: '0x7f799ad56584199db36bd617b77cc1d825ff18714e80da9d2d5a0a9fff5b4d42',
       blockHash: '0x08d7c35904e4a93298ed5be862227fcf18383fec374759202cf9e513b390956f',
       blockNumber: 5,
       address: '0x377bbcae5327695b32a1784e0e13bedc8e078c9c',
       type: 'mined',
       event: 'Odd',
       args: {} } ] }

logs 裏面顯示調用了 Odd 事件,這是不對的,咱們的任務是找到這個事件爲何會被觸發。
調試錯誤
1. 複製交易 id 到 debug 命令下

debug 0x7f799ad56584199db36bd617b77cc1d825ff18714e80da9d2d5a0a9fff5b4d42

2. 鍵入回車幾回,最後咱們將看到調用 Odd 事件的條件

Store.sol | 0x377bbcae5327695b32a1784e0e13bedc8e078c9c:
10:   function set(uint x) public {
11:     myVariable = x;
12:     if (x % 2 == 0) {
        ^^^^^^^^^^^^^^^^
debug(develop:0x7f799ad5...)>
Store.sol | 0x377bbcae5327695b32a1784e0e13bedc8e078c9c:
11:     myVariable = x;
12:     if (x % 2 == 0) {
13:       Odd();
          ^^^^^
debug(develop:0x7f799ad5...)>

錯誤找到了,這個條件致使了錯誤的事件調用。

結論

有了在 Truffle 中的調試能力,你能夠編寫更健壯的智能合約。

相關文章
相關標籤/搜索