Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the pattern for handling throw on a Solidity contract in tests

I have a function on a Solidity contract that does a throw. Eg.

   function do(x,y)  {
        if ( msg.sender != owner )
            throw;
        // ...
   }

In the Truffle environment I have a test js something like:

//.... 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();
            }
    // ...

The return contract.do() causes the condition that results in the throw. Which produces the following in the Truffle test output for this test:

Error: VM Exception while executing transaction: invalid JUMP

What is the idiom for handling a throw from a contract function in a test like this? The throw is the correct behavior.

like image 648
Interition Avatar asked Apr 13 '16 10:04

Interition


2 Answers

The zeppelin project as an awesome way to do just that:

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 To see a full example

like image 64
SylTi Avatar answered Oct 15 '22 22:10

SylTi


The 'most correct' solution to this problem I've been able to come up with is checking all the gas sent has been spent, which is what occurs on a throw, but there is an additional wrinkle to make the solution work on both TestRPC (which I am guessing you are using, given the actual error being thrown) and Geth. When a throw occurs in Geth, a transaction is still created, spending all the gas, but no state changes occur. TestRPC actually throws the error, which is useful for debugging purposes.

   //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);

I'd cheerfully implement a more straightforward solution if another appears, though.

NB If you spend all of gas the with a transaction that is accidentally valid, this won't spot that - it will assume that the gas was spent due a throw inside the VM.

like image 27
Alex Rea Avatar answered Oct 15 '22 22:10

Alex Rea