1. 程式人生 > >智慧合約除錯指南

智慧合約除錯指南

不像你在其他地方看到的紙質合約,以太坊的智慧合約是程式碼組成的,需要你以非常謹慎的態度去對待它。

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

如果我們的合同沒有被正確的編碼出來, 我們的交易可能會失敗,導致以太幣的損失(以 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)為特定整數值。
  • 允許你查詢一個選定的值。

這不是一個非常有趣的合約,但是這不是重點。我們想看看出錯後會發生什麼當事情。

首先我們配置環境。

部署智慧合約

  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 中的除錯能力,你可以編寫更健壯的智慧合約。

相關推薦

智慧合約除錯指南

不像你在其他地方看到的紙質合約,以太坊的智慧合約是程式碼組成的,需要你以非常謹慎的態度去對待它。(這是一件好事,想象下如果現實世界的合同需要編譯的話會更清晰麼?)如果我們的合同沒有被正確的編碼出來, 我們的交易可能會失敗,導致以太幣的損失(以 gas 的形式),更不用說浪費時

區塊鏈100講:智慧合約審計指南

智慧合約程式碼的審計,目前還不是技術社群內經常會討論的主題。今年3月6日,發表在部落格網站【Schneier on Security】上的一篇部落格(原文連結:【https://www.schneier.com/blog/archives/2018/0

什麼是智慧合約智慧合約初學者指南

Nick Szabo提出了“智慧合約”的概念,其目的是將POS(銷售點)等電子交易方式的功能擴充套件到數字領域。 智慧合約可以避免第三方中間商的服務,幫助您以透明、無衝突的方式交換財產、股份或任何有價值的東西。以下是我們討論智慧合約相關內容:什麼是智慧合約?智慧合約是一種在滿

EOS入門指南PART8——智慧合約入門實戰

上一章我們細緻地學習了 索引和迭代器的關係; 如何生成和使用索引以及迭代器 介紹了multi_index的相關操作 相信大家對multi_index已經有了比較全面的理論理解以及掌握了一些基礎的操作。這一章將會教大家如何完

第九課 如何除錯以太坊官網的智慧合約眾籌案例

#2.眾籌和代幣(TOKEN)的投資邏輯 **ICO(Initial Crypto-Token Offering,首次代幣眾籌)**被認為是區塊鏈生態內生的一種新型投融資方式,概念起源於IPO,只不過募集的貨幣變為比特幣、以太坊等通用數字貨幣,從而支援專案的

EOS安全開發智慧合約的終極指南

EOS智慧合約安全終極指南。當世界上最大的ICO,EOS於2018年6月推出時,加密社群變得持懷疑態度,並且由於軟體錯誤而被凍結了2天。但快進4個月,EOS今天佔了乙太網今天所做交易的兩倍以上。通過免費和更快速交易的承諾,EOS最頂級的Dapp擁有大約13,000個每日活躍使用者,而乙太網的最頂級D

【以太坊系列-003】以太坊智慧合約 —— 最佳安全開發指南

1基本理念 以太坊和其他複雜的區塊鏈專案都處於早期階段並且有很強的實驗性質。因此,隨著新的bug和安全漏洞被發現,新的功能不斷被開發出來,其面臨的安全威脅也是不斷變化的。這篇文章對於開發人員編寫安全的智慧合約來說只是個開始。 開發智慧合約需要一個全新的工程思維,它不同於我們以往專案的開發。因

mist連線私有網路除錯智慧合約

環境 ubuntu 16.04, 64位 首先確保私有網路已經搭建好,可以參考以前的文章。 安裝mist 安裝依賴項 $ sudo curl https://install.meteor.com/ | sh $ sudo curl -o

EOS 智慧合約如何除錯

為了能夠除錯智慧合約,需要配置本地節點。這個本地節點可以作為單獨的私有鏈或公有鏈的擴充套件來執行。這個本地節點還需要執行在合約控制檯選項上,或者通過命令列 加引數--contracts-console,或者通過config.ini設定contracts-console = t

智慧合約安全審計指南

譯者注:智慧合約程式碼的審計,目前還不是技術社群內經常會討論的主題。今年3月6日,發表在部落格網站【Schneier on Security】上的一篇部落格(原文連結:【https://www.schneier.com/blog/archives/2018/03/securi

EOS智慧合約開發(四)EOS智慧合約部署及除錯(附程式設計示例)

EOS的智慧合約裡面有一個action(動作)和transaction(交易)的概念。 對於我們開發以太坊開發者來說,基本上只有transaction的概念。如果我只要執行一種操作,而且是隻讀操作,就不需要簽名。如果需要劃資金,有一些寫的操作,那就需要使用者用

EOS入門指南PART6——別忙著開發,先來看看智慧合約資料是怎麼存的

上一章我們學習了開發智慧合約之前需要知道的必要概念: 什麼是webAssembly以及它在智慧合約上下游中的位置; 什麼是ABI以及怎樣使用eosiocpp工具產生ABI和wasm、wast hello智慧合約的簡單入門:部署和呼叫

編寫除錯以太坊智慧合約/blockchain

一、        智慧合約IDE簡介     目前以太坊上支援三種語言編寫智慧合約,     Solidity:類似JavaScript,這是以太坊官方推薦語言,也是最流行的智慧合約語言。具體用法參加Solidity文件,地址:https://solidity

比原鏈猜謎合約使用指南

abc 才會 安裝 .net 答案 成功 com 文章 別人 準備工作: 1、安裝全節點錢包V1.0.5以上並同步完成; 2、已經發行一種資產,發行資產的方法具體見文章《如何在Bytom上發布資產?》 3、準備好一些BTM作為手續費; 設置謎語(鎖定資產): 1、打開錢包,

EOS學習筆記(三)- 智慧合約

環境:自己啟動的單測試節點 注意: 1.一個賬號只能釋出一個合約,釋出多個會導致呼叫合約的時候出錯,具體原因需要檢視原始碼,或者是我的使用方法不正確 2.一個key可以繫結多個錢包和賬戶 3.同一個賬號同一個合約可以更新 *4.合約中的資料庫儲存結構在第一次的時候要設計好,否則以後如果對儲

兩大智慧合約簽名驗證漏洞分析

可重入(Reentrancy)或整數溢位漏洞,是大多數開發人員知道或者至少聽說過的,關於智慧合約當中容易出現的安全問題。另一方面,在考慮智慧合約的安全性時,你可能不會立即想到針對密碼簽名實現的攻擊方式。它們通常是與網路協議相關聯的。例如,簽名重放攻擊(signature replay

EOS Dawn 3.0 智慧合約 -- 新格式

1、簡介 隨著EOS Dawn 3.0釋出,智慧合約的坑又要重新踩了o(╥﹏╥)o;3.0不僅將原來本身就在鏈裡的基礎合約獨立出來,簡單的介紹見3.0合約改變,合約的書寫方式也有巨大變化,相比之前更加“面向物件”;這邊文章就從最簡單的hello合約,講解下,詳細的例子會在之後文章介紹;

以太坊智慧合約學習筆記:網頁互動

沒搞過web程式,花了幾天研究一下,總算是搞懂了網頁與以太坊節點的互動流程。 網頁與智慧合約互動,需要使用web3.js,它實現了通用JSON PRC規範,通過JSON RPC協議與以太坊節點進行互動。除了js以外,以太坊還提供了Java、Python等語言的API,對於沒有提供API的語言

以太坊智慧合約學習筆記:使用Truffle框架開發部署智慧合約

truffle是一個智慧合約的開發框架,具體的就不介紹了,我們主要是說說怎麼使用這個框架來進行智慧合約的開發,官網戳這裡。 文章目錄 #安裝 首先我們要先安裝npm和truffle,安裝命令如下 sudo apt install npm sudo n

以太坊智慧合約學習筆記:開發流程及工具鏈使用

本文主要介紹開發流程和工具鏈的使用,安裝過程百度上有好多,這裡就不贅述了 網上隨便找了一個智慧合約的例子,咱們來做一個投票系統,先用傳統的中心化方案去實現,然後在過度到區塊鏈1.0,最後再用區塊鏈2.0,感受一下開發思想的不同。 業務分析 傳統