不像你在其余地方看到的纸质合约,以太坊的智能合约是代码组成的,须要你以很是谨慎的态度去对待它。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 中的调试能力,你能够编写更健壮的智能合约。