9

我在 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 测试输出中产生以下内容:

Error: VM Exception while executing transaction: invalid JUMP

在这样的测试中处理合约函数抛出的习语是什么?投掷是正确的行为。

4

5 回答 5

4

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查看完整示例

于 2017-08-02T18:01:17.763 回答
3

对于这个问题,我能想出的“最正确”的解决方案是检查所有发送的气体是否已用完,这是一次投掷时发生的事情,但还有一个额外的问题是使解决方案在两个 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 内抛出而被消耗的。

于 2016-04-13T13:18:19.197 回答
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 来获取可用的 gas 和已用的 gas,并在所有 gas 都用完的情况下返回错误。在 testrpc 上,它只是捕获抛出的异常并返回它。我在测试中使用它如下:

return getTransactionError(function() {
    return contract.doSomething();
}).then(function(err) {
    assert.isDefined(err, "transaction should have thrown");
});

当然,也可以省略 catch,在这种情况下,如果它被抛出,promise 将简单地失败并出现错误。

于 2016-11-02T17:29:23.287 回答
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);
于 2018-10-14T22:09:57.963 回答
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'");
    }
});
于 2018-08-14T21:06:59.727 回答