在测试中处理 Solidity 合约抛出的模式是啥
Posted
技术标签:
【中文标题】在测试中处理 Solidity 合约抛出的模式是啥【英文标题】:What is the pattern for handling throw on a Solidity contract in tests在测试中处理 Solidity 合约抛出的模式是什么 【发布时间】:2016-08-04 09:10:40 【问题描述】:我在 Solidity 合约上有一个抛出异常的函数。例如。
function do(x,y)
if ( msg.sender != owner )
throw;
// ...
在 Truffle 环境中,我有一个类似的测试 js:
//.... part of a promise chain
.then(
function (_bool0)
assert.isTrue(_bool0,"whoops - should be true");
return contract.do( "okdoke" , from: accounts[1] );
).then(
function (tx_id)
//..
done();
// ...
return contract.do() 导致引发抛出的条件。这会在此测试的 Truffle test 输出中生成以下内容:
Error: VM Exception while executing transaction: invalid JUMP
在这样的测试中处理合约函数抛出的习语是什么? throw 是正确的行为。
【问题讨论】:
对遵循“检查是否使用所有气体”方法的任何人的警告:这将在未来发生变化,因为不推荐使用 throw 以支持“revert()”。 Revert() 不会用完所有的 gas,只会用完调用 revert() 之前的 gas。 这个问题仍然给我带来了麻烦,特别是因为我正在使用revert()。我能够在 TestRPC 上发现错误,但现在我在 rinkeby 测试网络上,恢复的事务返回就好像它们成功了一样。你那边有什么更新吗? 【参考方案1】:zeppelin 项目是实现这一目标的绝佳方式:
it("should fail to withdraw", async () =>
try
await receiver.withdrawToken(0x0);
assert.fail('should have thrown before');
catch(error)
assertJump(error);
);
function assertJump(error)
assert.isAbove(error.message.search('invalid opcode'), -1, 'Invalid opcode error must be returned');
https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/test/Ownable.js查看完整示例
【讨论】:
【参考方案2】:对于这个问题,我能想出的“最正确”的解决方案是检查所有发送的气体是否已用完,这就是投掷时发生的情况,但还有一个额外的问题可以使解决方案发挥作用在 TestRPC (我猜你正在使用,考虑到实际抛出的错误)和 Geth 上。当 Geth 发生 throw 时,仍然会创建一个交易,消耗所有的 gas,但不会发生状态变化。 TestRPC 实际上会抛出错误,这对于调试目的很有用。
//Somewhere where global functions can be defined
function checkAllGasSpent(gasAmount, gasPrice, account, prevBalance)
var newBalance = web3.eth.getBalance(account);
assert.equal(prevBalance.minus(newBalance).toNumber(), gasAmount*gasPrice, 'Incorrect amount of gas used');
function ifUsingTestRPC()
return;
//Some default values for gas
var gasAmount = 3000000;
var gasPrice = 20000000000;
....
//Back in your actual test
it('should fail ', function (done)
var prevBalance;
....
.then(function (_bool0)
assert.isTrue(_bool0,"whoops - should be true");
prevBalance = web3.eth.getBalance(accounts[1]);
return contract.do( "okdoke" , from: accounts[1], gasPrice:gasPrice, gas:gasAmount );
)
.catch(ifUsingTestRPC)
.then(function()
checkAllGasSpent(gasAmount, gasPrice, accounts[1], prevBalance);
)
.then(done)
.catch(done);
不过,如果出现另一个解决方案,我会很乐意实施更直接的解决方案。
注意,如果您将所有 gas 用于意外有效的交易,则不会发现这一点 - 它会假设 gas 是由于在 VM 中抛出而被消耗的。
【讨论】:
感谢您花时间考虑,但我正在寻找更多如何处理投掷。 VM Exception 似乎破坏了一切,但我希望有更多的 try/catch 技术来保持控制。而不是一切都崩溃了。【参考方案3】:只是为了让大家知道,我也遇到了这个问题,并且一直在使用以下:
function getTransactionError(func)
return Promise.resolve().then(func)
.then(function(txid)
var tx = web3.eth.getTransaction(txid);
var txr = web3.eth.getTransactionReceipt(txid);
if (txr.gasUsed === tx.gas) throw new Error("all gas used");
)
.catch(function(err)
return err;
);
在 geth 上,它使用交易 ID 来获取可用的 gas 和已用的 gas,并在所有 gas 都用完的情况下返回错误。在 testrpc 上,它只是捕获抛出的异常并返回它。我在测试中使用它如下:
return getTransactionError(function()
return contract.doSomething();
).then(function(err)
assert.isDefined(err, "transaction should have thrown");
);
当然,也可以省略 catch,在这种情况下,如果它被抛出,promise 将简单地失败并出现错误。
【讨论】:
【参考方案4】:在我看来,最干净的方法是:
it("should revert", async function ()
try
await deployedInstance.myOperation1();
assert.fail("The transaction should have thrown an error");
catch (err)
assert.include(err.message, "revert", "The error message should contain 'revert'");
);
【讨论】:
【参考方案5】:自从第一次提出这个问题以来,Solidity、Truffle 和整个以太坊开发生态系统已经有了很多改进,使得断言恢复和其他抛出变得更加容易。
我的truffle-assertions
库允许您以非常直接的方式对任何类型的 Solidity 抛出或函数故障进行断言。
该库可以通过npm安装并在测试javascript文件的顶部导入:
npm install truffle-assertions
const truffleAssert = require('truffle-assertions');
之后可以在测试中使用:
await truffleAssert.fails(contract.failingFunction(), truffleAssert.ErrorType.INVALID_JUMP);
【讨论】:
以上是关于在测试中处理 Solidity 合约抛出的模式是啥的主要内容,如果未能解决你的问题,请参考以下文章