[Ethereum] How to catch solidity contract revert message using web3j

go-ethereumrevert-opcodesolidityweb3jweb3js

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

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?

Best Answer

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();
}
Related Topic