智能合约调试指南

xiaoxiao2022-05-17  48

智能合约调试指南

facert

Pythoner

9 人赞了该文章

注意:本教程的 Truffle 版本 4.4.0 及以上

不像你在其他地方看到的纸质合约,以太坊的智能合约是代码组成的,需要你以非常谨慎的态度去对待它。

(这是一件好事,想象下如果现实世界的合同需要编译的话会更清晰么?)

如果我们的合同没有被正确的编码出来, 我们的交易可能会失败,导致以太币的损失(以 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)为特定整数值。允许你查询一个选定的值。

这不是一个非常有趣的合约,但是这不是重点。我们想看看出错后会发生什么当事情。

 

首先我们配置环境。

部署智能合约

首先为我们的合约创建一个新的本地目录: 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 的本地区块链。

在 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 是以以太币计费的,所以会造成真实的资产损失,所以修复这种错误迫在眉睫。

 

引入错误

在编辑器打开 contracts/ 目录中的 Store.sol替换 set() function set(uint x) public { while(true) { myVariable = x; } }

因为 while(true) 所以函数永远不会退出。

 

测试合约

Truffle 的开发终端不重启就可以重新部署合约。我们可以通过 migrate 一步编译和部署合约

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>,现在让我们开始吧!

在 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 中的调试能力,你可以编写更健壮的智能合约。

翻译自: http://truffleframework.com/tutorials/debugging-a-smart-contract

 

更多内容请访问:区块链日报 - 关注区块链 - 推送高质量区块链文章

订阅区块链日报

微博: @区块链_daily RSS: rss Twitter: @区块链日报 知乎: 区块链日报 Medium: 区块链日报 微信公众号: 搜索 【区块链daily】或 点击扫二维码关注

发布于 2018-04-06

智能合约

以太坊

区块链(Blockchain)

​赞同 9​

转载请注明原文地址: https://www.6miu.com/read-4884254.html

最新回复(0)