我有一个执行投掷的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()会导致导致throw的条件。这会在此测试的松露测试输出中生成以下内容:
Error: VM Exception while executing transaction: invalid JUMP
在像这样的测试中处理合同函数抛出的习惯用法是什么?抛出是正确的行为。
答案 0 :(得分:3)
我能够想出的这个问题的“最正确”的解决方案是检查已经花费的所有气体,这是投掷时发生的事情,但还有一个额外的皱纹使解决方案工作在两个TestRPC(我猜你正在使用,考虑到实际错误被抛出)和Geth。当在Geth中发生抛出时,仍会创建一个事务,消耗所有气体,但不会发生状态变化。 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);
如果出现另一个解决方案,我会愉快地实施更直接的解决方案。
注意如果你把所有的汽油都用在一个意外有效的交易中,那么这就不会发现 - 它会假设燃气是由于机内投入而浪费的。
答案 1 :(得分:2)
齐柏林飞艇项目作为一种非常棒的方式:
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 :(得分:0)
只是为了让每个人都知道,我也遇到了这个问题并且一直在使用以下内容:
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获取可用的气体和用过的气体,并在使用所有气体时返回错误。在testrpc上,它只是捕获抛出的异常并返回它。我在测试中使用它如下:
return getTransactionError(function() {
return contract.doSomething();
}).then(function(err) {
assert.isDefined(err, "transaction should have thrown");
});
当然,人们也可以省去捕获物,在这种情况下,如果它被抛出,则承诺将失败并出现错误。
答案 3 :(得分:0)
我认为最干净的方法是:
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'");
}
});
答案 4 :(得分:0)
自从首次提出这个问题以来,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);