7

Is there any way to catch solidity revert message like Remix Javascript VM in web3j (Java)

Revert message(Remix)

transact to Erecruitment.issueNewAdmitCard errored: VM error: revert.
revert  The transaction has been reverted to the initial state.
Reason provided by the contract: "Admit card ID already exists in Blockchain".  Debug the transaction to get more information. 

Event message (Remix)

 logs   [
    {
        "from": "0x692a70d2e424a56d2c6c27aa97d1a86395877b3a",
        "topic": "0xbf970614f4ff9483c34fa1a053bc6614e06003a1faef2d0876e9255bfa3167fc",
        "event": "LogIssueNewCard",
        "args": {
            "0": "2",
            "1": "4",
            "2": "New Card is issued in Blockchain",
            "admitCardId": "2",
            "applicantName": "4",
            "message": "New Card is issued in Blockchain",
            "length": 3
        }
    }
]

I get event messages for successful transactions in web3j. So, I can be sure that whether my transaction is completed without any error. If transaction is reverted there will be no event as usual(it's ok). But I am not getting any revert message too for a failed transaction.

And I am getting transaction status null each and every time using web3j as byzantiumBlock is missing in my genesis file. Then how can I check the transaction status?

4 Answers 4

6

In solidity 0.4.22 the require and revert reason were added. As can be seen here, they are abi-encoded as if it were a call to a function "Error(string)".

You'll need to make an eth_call to your contract.

This blog post gives an example: an eth_call to a function

function myFunction(uint256 input) public view returns (uint256) {
    require(input >= 5, "myFunction only accepts arguments which are greather than or equal to 5");
    return input * input - 25;
}

with an invalid input argument (less than 5 in this example), will return

0x08c379a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000476d7946756e6374696f6e206f6e6c79206163636570747320617267756d656e747320776869636820617265206772656174686572207468616e206f7220657175616c20746f203500000000000000000000000000000000000000000000000000

which is

0x08c379a0                                                       // Function selector
0000000000000000000000000000000000000000000000000000000000000020 // Offset of string return value
0000000000000000000000000000000000000000000000000000000000000047 // Length of string return value (the revert reason)
6d7946756e6374696f6e206f6e6c79206163636570747320617267756d656e74 // first 32 bytes of the revert reason
7320776869636820617265206772656174686572207468616e206f7220657175 // next 32 bytes of the revert reason
616c20746f203500000000000000000000000000000000000000000000000000 // last 7 bytes of the revert reason

So decoding the returned string will give you the revert reason.

With Web3j this could be done like:

public Optional<String> getRevertReason(EthCall ethCall) {
    String errorMethodId = "0x08c379a0"; // Numeric.toHexString(Hash.sha3("Error(string)".getBytes())).substring(0, 10)
    List<TypeReference<Type>> revertReasonTypes = Collections.singletonList(TypeReference.create((Class<Type>) AbiTypes.getType("string")));

    if (!ethCall.hasError() && ethCall.getValue() != null && ethCall.getValue().startsWith(errorMethodId)) {
        String encodedRevertReason = ethCall.getValue().substring(errorMethodId.length());
        List<Type> decoded = FunctionReturnDecoder.decode(encodedRevertReason, revertReasonTypes);
        Utf8String decodedRevertReason = (Utf8String) decoded.get(0);
        return Optional.of(decodedRevertReason.getValue());
    }
    return Optional.empty();
}
1

I'm not sure that getting the revert message is possible with web3j, however I get the status of my transactions with this command (but ofc you should wait until the transaction is mined and your Node is synchronized)

EthGetTransactionReceipt transactionReceipt = web3j.ethGetTransactionReceipt("The Hash of your Transaction").send();
    if (transactionReceipt.getResult() != null && !transactionReceipt.hasError()) {
        System.out.println(transactionReceipt.getTransactionReceipt().get().getStatus());
    } else {
      "TRY AGAIN SOMEHOW LATER.. :-)"
    }

For your question about events, I think in the generated smart contract wrappers there is event methods that you can easily use. see : https://docs.web3j.io/smart_contracts.html

3
  • I appreciate your answer but you will get result for every mined transaction but may not get result field ( as it depends on byzantiumBlock field in genesis file). I agree that events may help you but event will not be triggered unless it is executed that is execution reached to that line. what do your think?
    – sharif2008
    Nov 30, 2018 at 6:05
  • Yes with events you can't be sure if your transaction failed or just stuck. I have not tried it yet, but maybe you can somehow use a FailedEvent that got emitted when (Admit card ID already exists in Blockchain), and undo what happend before that emit.
    – Majd TL
    Nov 30, 2018 at 9:19
  • @sharif2008 maybe that could help somehow: I was using Ganache with automine then this code gave me the revert message EthSendTransaction transactionTx = web3j.ethSendRawTransaction(sigendTransactionData).sendAsync().get(); if (transactionTx.hasError()) { System.out.println(transactionTx.getError().getMessage() ); }
    – Majd TL
    Dec 15, 2018 at 10:59
1

As of web3j 4.6 the RevertReasonExtractor class exists for this purpose

import org.web3j.utils.RevertReasonExtractor;
...
  String revertReason;
  try {
    revertReason = RevertReasonExtractor.extractRevertReason(
                                              transactionReceipt,
                                              inputData,
                                              web3,
                                              true);
  } catch (IOException e) {
     revertReason="?? could not reach network " + e.getMessage();
  }
  return(revertReason);
0

I made an NPM package for just this reason: eth-revert-reason


It is difficult to decode the revert reason in a general manner. Many different factors, such as Parity vs. Geth, ethers vs. web3.js, etc. will result in different results. Some issues are:

  1. For a Kovan transaction, you need a custom provider that exposes Parity trace methods.
  2. Transactions may result in different messages depending on the context of the block they are called from. Because of this, you may need to be running a full-archival node in order to retrieve the correct error message.

In a happy-path case, the code to retrieve the revert reason is:

const provider = customProvider || ethers.getDefaultProvider(network)
const tx = await provider.getTransaction(txHash)
const code = await provider.call(tx)

While the code to produce the revert reason in the normal case is simple, the non-standard cases are surprisingly difficult to handle, however this package attempts to appease this.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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