不像你在其餘地方看到的紙質合約,以太坊的智能合約是代碼組成的,須要你以很是謹慎的態度去對待它。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;
}
}
此合約作了兩件事:測試
這不是一個很是有趣的合約,可是這不是重點。咱們想看看出錯後會發生什麼當事情。ui
首先咱們配置環境。this
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 的本地區塊鏈。
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
上文展現了智能合約如何工做,如今咱們引入一些錯誤到合約中。
針對以下幾個錯誤
在以太坊區塊鏈中,交易不能永遠執行下去。
一個交易會一直執行到 gas 用盡。一旦發生這種狀況,交易會返回out of gas錯誤。
由於 gas 是以以太幣計費的,因此會形成真實的資產損失,因此修復這種錯誤迫在眉睫。
引入錯誤
function set(uint x) public {
while(true) {
myVariable = x;
}
}
由於while(true)因此函數永遠不會退出。
測試合約
Truffle 的開發終端不重啓就能夠從新部署合約。咱們能夠經過migrate一步編譯和部署合約
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>,如今讓咱們開始吧!
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 )。而這會告訴你問題在哪裏。
智能合約能夠用 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.
咱們能夠看到最後的事件中觸發了錯誤.
有時候,錯誤不必定是真正的錯誤,它在運行時間內不會引發問題,只是不會按預期執行。
舉個例子,一個事件將會在變量是奇數的時候執行,而另外一個事件在偶數的時候執行。若是咱們調換了這個條件,讓相反的事件執行了。它斌不會觸發錯誤,然而,合約會不按照咱們的預期執行下去。
咱們再次用調試器來找出錯誤。
引入錯誤
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 中的調試能力,你能夠編寫更健壯的智能合約。