20

How can I catch "revert" error in truffle test written in javascript?

Error: VM Exception while processing transaction: revert

Here is part of test contract,

  function doRevert() public {
    revert();
  }

and here is my part of test javascript,

  it("test1.....", async() => {
    let instant = await myContract.deployed();    

    await instant.doRevert();
.
.
.

I do not want to stop my test code at the first test step. I searched that I can import expectThrow.js, but I couldn't to import it, same issue as here. I know this question is very related to How can you handle an expected throw in a contract test using truffle and ethereum-testRPC?, but still cannot figure it out how to use the library, expectThrow.js, and throw is deprecated, so there is some other ways to do that?

31

Answer by example:

Suppose you have a function which can be invoked only by the owner (deployer) of the contract.

If it's deployed by anyone other than the owner, then it reverts (require with a false condition).

File exceptions.js:

module.exports.errTypes = {
    revert            : "revert",
    outOfGas          : "out of gas",
    invalidJump       : "invalid JUMP",
    invalidOpcode     : "invalid opcode",
    stackOverflow     : "stack overflow",
    stackUnderflow    : "stack underflow",
    staticStateChange : "static state change"
}

module.exports.tryCatch = async function(promise, errType) {
    try {
        await promise;
        throw null;
    }
    catch (error) {
        assert(error, "Expected an error but did not get one");
        assert(error.message.startsWith(PREFIX + errType), "Expected an error starting with '" + PREFIX + errType + "' but got '" + error.message + "' instead");
    }
};

const PREFIX = "VM Exception while processing transaction: ";

File MyContractTest.js:

contract("MyContractTest", function(accounts) {
    let myContract;
    let owner    = accounts[0];
    let nonOwner = accounts[1];
    let tryCatch = require("./exceptions.js").tryCatch;
    let errTypes = require("./exceptions.js").errTypes;

    describe("sanity assertion:", function() {
        before(async function() {
            myContract = await artifacts.require("MyContract.sol").new();
        });
        it("should complete successfully", async function() {
            await myContract.func({from: owner});
        });
        it("should abort with an error", async function() {
            await tryCatch(myContract.func({from: nonOwner}), errTypes.revert);
        });
    });
});

UPDATE: Version 2

I would like to update this answer with slightly simpler code. Its behavior should be identical, but it "saves a few characters" in the test itself, thus improves readability and allows easier maintenance.

File exceptions.js:

const PREFIX = "VM Exception while processing transaction: ";

async function tryCatch(promise, message) {
    try {
        await promise;
        throw null;
    }
    catch (error) {
        assert(error, "Expected an error but did not get one");
        assert(error.message.startsWith(PREFIX + message), "Expected an error starting with '" + PREFIX + message + "' but got '" + error.message + "' instead");
    }
};

module.exports = {
    catchRevert            : async function(promise) {await tryCatch(promise, "revert"             );},
    catchOutOfGas          : async function(promise) {await tryCatch(promise, "out of gas"         );},
    catchInvalidJump       : async function(promise) {await tryCatch(promise, "invalid JUMP"       );},
    catchInvalidOpcode     : async function(promise) {await tryCatch(promise, "invalid opcode"     );},
    catchStackOverflow     : async function(promise) {await tryCatch(promise, "stack overflow"     );},
    catchStackUnderflow    : async function(promise) {await tryCatch(promise, "stack underflow"    );},
    catchStaticStateChange : async function(promise) {await tryCatch(promise, "static state change");},
};

File MyContractTest.js:

contract("MyContractTest", function(accounts) {
    let myContract;
    let owner       = accounts[0];
    let nonOwner    = accounts[1];
    let catchRevert = require("./exceptions.js").catchRevert;

    describe("sanity assertion:", function() {
        before(async function() {
            myContract = await artifacts.require("MyContract.sol").new();
        });
        it("should complete successfully", async function() {
            await myContract.func({from: owner});
        });
        it("should abort with an error", async function() {
            await catchRevert(myContract.func({from: nonOwner}));
        });
    });
});
  • Can I use tryCatch() with constructor as well? For example, like this: let myContract = await tryCatch(myContract.new(), errTypes.revert); – Gagan Aug 14 '18 at 11:29
  • Because I'm trying to catch a 'revert' type error when creating an instance with wrong params to myContract's constructor. And so I'm using tryCatch as mentioned above. It works fine with other functions but I'm facing issues when used with constructor. – Gagan Aug 14 '18 at 11:32
  • @Gagan: What issues? – goodvibration Aug 14 '18 at 11:40
  • 2
    @Gagan: You could probably do something like let myContract = null; await tryCatch(async function() {myContract = myContract.new()}, errTypes.revert);. And then choose your course of action according to whether or not myContract is valid. – goodvibration Aug 14 '18 at 12:13
  • 2
    To me, this is an incredible use of OOP. – BryceD Aug 30 '18 at 21:02
13

Most of these answers are straightforward enough, but the ones that do an inline try catch make for quite a bit of boilerplate, and the creation of the utility functions is not really something you want to do yourself.

This is exactly why I added revert and other failure testing as a feature to my truffle-assertions library. This allows you to assert reverts and other failures in a straightforward way. It also includes support for the new Truffle v5 functionality that finally enables us to retrieve the revert reason string.

The library can be installed through npm and imported at the top of the test javascript file:

npm install truffle-assertions

const truffleAssert = require('truffle-assertions');

After which it can be used inside the tests (including Truffle v5 revert reason string):

await truffleAssert.reverts(contract.revertingFunction(), "only owner");

I wrote a blog post that goes over the functionality with a detailed use case for further reading: Asserting reverts when testing Solidity smart contracts with Truffle

  • 1
    This is super duper cool! – Paul Berg Nov 10 '18 at 19:16
  • 1
    Thanks, this makes the tests much cleaner. Keep up the good work :D – TheLukeMcCarthy Dec 16 '18 at 5:15
2

You can do something like this:

it("test1.....", async() => {
  let instant = await myContract.deployed();    
  try{
    await instant.doRevert();
  }
  catch(e){
    myHandleException(e);
  }
.
.
.

myHandleException being whatever function you want.

If you're using callbacks instead of async(if you do something after your call) you can plug the catch right after the then:

  instance.otherFunction().then(function (result){
      //do things
  }).catch(function (e) {
      myHandleException(e);
  )};
.
.
.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.