What is the Solidity contract cast processing pattern in tests

I have a function on a Solidity contract that makes a throw. For instance.

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

In a Truffle environment, I have a js test something like this:

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

Returning contract.do () raises a condition that results in a throw. Which gives the following result in the Truffle test 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? A throw is the correct behavior.

+6
source share
3 answers

The โ€œmost correctโ€ solution to this problem that I could come up with is checking all the sent gas that occurs during the throw, but there is an additional wrinkle to make the solution work like on TestRPC (which, I assume, you are using, given the actual error ) and Geth. When a throw occurs in Geth, the transaction is still created, posting all the gas, but no state changes occur. TestRPC really throws an 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 would happily implement a simpler solution if another appears.

NB If you spend all gas on a transaction that is random, this will not be noticed - it is assumed that the gas was consumed due to a throw inside the virtual machine.

+3
source

The zeppelin project as a wonderful 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 the full example

+2
source

Just to let everyone know, I ran into this problem and used the following:

 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 uses the transaction identifier to get available gas and used gas, and returns an error if all gas is used. On testrpc, it just catches the thrown exception and returns it. I use it inside the test as follows:

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

Naturally, you can also leave the catch, in which case the promise will simply end in error if it is thrown.

0
source

All Articles