4

This is really a two part question.

First, the only way I can get the error message thrown by a Smart Contract require statement is to call the function first with a call rather than a send. send does not return the message thrown by a failed require statement whereas call does.

Example. In Solidity I might have the following:

require (org._isUser(_user),'Error: User not registered');

In my React frontend I want to be able to display 'Error: User not registered'.

My function call in React is as follows:

async function createPO(e) {
    e.preventDefault()
    const poeacct = e.target.elements[0].value
    const ponumber = e.target.elements[1].value
    const podate = e.target.elements[2].value
    let callOK = true
    try {
      await contract2.methods
        .createOrder('0xd2c4895e24095f23277df168f94a6c035813fe5d', poeacct, ponumber, podate)
        .call({ from: accounts[0] })
    } catch (err) {
      callOK = false
      setMess(err.message)
      console.log(err)
    }
    if (callOK) {
      await contract2.methods
        .createOrder('0xd2c4895e24095f23277df168f94a6c035813fe5d', poeacct, ponumber, podate)
        .send({ from: accounts[0] })

      setpouAcct(pouacct)
      setpoeAcct(poeacct)
      setpoNumber(ponumber)
      setpoDate(podate)
      setMess('Transaction successful')
    }
  }

This works but it's kind of klugey so I'm wondering if there's a better way.

Second. The error returned by the 'catch' in the Web3 call is JSON but there's a prefix:

Error: [object Object]
{
  "message": "VM Exception while processing transaction: revert Error: Order already exists",
  "code": -32000,
  "data": {
    "0x270bb8ddea630f3fc42a4f6196d7710cc50779a34994fd534a8ce139976b0ef5": {
      "error": "revert",
      "program_counter": 21219,
      "return": "0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001b4572726f723a204f7264657220616c7265616479206578697374730000000000",
      "reason": "Error: Order already exists"
    }

The Error: [object Object] prefix prevents me from parsing out the 'reason:' node. If I remove the prefix manually (i.e. hard code it) it works.

I've tried substring,slice and JSON.parse etc. but I can't find a solution.

5 Answers 5

1

Similar to JuliSmz answer, but I wasn't comfortable with the magic 56 character string position as I was worried it would be brittle. I wrote this little function which takes the error object returned by the catch

    function getRPCErrorMessage(err){
        var open = err.stack.indexOf('{')
        var close = err.stack.lastIndexOf('}')
        var j_s = err.stack.substring(open, close + 1);
        var j = JSON.parse(j_s);
        var reason = j.data[Object.keys(j.data)[0]].reason;
        return reason;
    }

0

I got the parse part to work like this:

setMess(JSON.parse(err.message.substring(15).trim()).message)

Still thinking there must be a cleaner way of handling errors passed from Web3.

1
  • 1
    Did you find any better way to parse the error?
    – Amin
    Commented Jan 16, 2021 at 6:41
0

In my case, using truffle, I've solved this way:

...catch((err) => {
    let message = JSON.parse(err.message.substring(56).trim().replace("'", "")).value.data.data;
    console.log(message[Object.keys(message)[0]].reason);

On this way I can show the message I set on my solidity contract.

0
  1. You can use estimateGas() for validating transaction
  2. You can differentiate errors based on the typeof error, now we can get message of any transaction error
async function createPO(e) {
    e.preventDefault()
    const poeacct = e.target.elements[0].value
    const ponumber = e.target.elements[1].value
    const podate = e.target.elements[2].value
    let callOK = true
    try {
      const contractCaller = contract2.methods.createOrder('0xd2c4895e24095f23277df168f94a6c035813fe5d', poeacct, ponumber, podate)
      const gas = await contractCaller.estimateGas({ from: accounts[0] })
      const response = await contractCaller.send({ from: accounts[0] })

      setpouAcct(pouacct)
      setpoeAcct(poeacct)
      setpoNumber(ponumber)
      setpoDate(podate)
      setMess('Transaction successful')

    } catch (err) {
        if(error.__proto__.name === "Error"){ 
            const start = error.message.indexOf("{");
            const end = error.message.indexOf("}");
            if(start && end) {
                error = JSON.parse(error.message.substring(start, end + 1));
            }
        }
        setMess(err.message)
        console.log(err)
    }
}
`
0

Pasting my answer from S.O. here. Her's how I did it:

For me, the "require" reason is there in the message property of the error object, but it is surrounded by lot of other words which I don't need.

An example error message:

[ethjs-query] while formatting outputs from RPC '{"value":{"code":-32603,"data":{"message":"VM Exception while processing transaction: revert Voting is closed","code":-32000,"data":{"0xf901429f12096d3b5c23a80e56fd2230fa37411bb1f8d3cdbd5c8f91c2670771":{"error":"revert","program_counter":43,"return":"0x08c379a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000165f5f5f566f74696e6720697320636c6f7365645f5f5f00000000000000000000","reason":"Voting is closed"},"stack":"RuntimeError: VM Exception while processing transaction: revert Voting is closed \n    at Function.RuntimeError.fromResults (/tmp/.mount_ganachreY1gT/resources/static/node/node_modules/ganache-core/lib/utils/runtimeerror.js:94:13)\n    at BlockchainDouble.processBlock (/tmp/.mount_ganachreY1gT/resources/static/node/node_modules/ganache-core/lib/blockchain_double.js:627:24)\n    at runMicrotasks (<anonymous>)\n    at processTicksAndRejections (internal/process/task_queues.js:93:5)","name":"RuntimeError"}}}}'

You can see the reason Voting is closed stuck in between. Not that user-friendly to read. I've seen answers that use regex to extract the error reason.

For those like me, who are not a big fan of the regex way, here is my approach.

  1. In your solidity contract, wrap the require reason with a unique delimiter of sorts. In my case, it is "___" (3 underscores).
    contract MyContract{
       ...
       ...
       function vote(address _addr) public payable{
          require(votingOpen, "___Voting closed___");
          ...
       }
       ...
       ...
    }
  1. Declare a helper function to extract the error using JavaScript string utilities. Here's where your delimiter coes in handy.
export const extractErrorCode = (str) => {
    const delimiter = '___'; //Replace it with the delimiter you used in the Solidity Contract.
    const firstOccurence = str.indexOf(delimiter);
    if(firstOccurence == -1) {
        return "An error occured";
    }

    const secondOccurence = str.indexOf(delimiter, firstOccurence + 1);
    if(secondOccurence == -1) {
        return "An error occured";
    }

    //Okay so far
    return str.substring(firstOccurence + delimiter.length, secondOccurence);
}
  1. Use this function where you catch the error in your frontend
const vote = async (_addr) => {
        setLoading(true);
        try {
            await contest.methods.vote(_addr).send({
                from: accounts[0],
            })
        }
        catch (e) {
            console.log('Voting failed with error object => ', e)
            console.log('Voting failed with the error => ', extractErrorCode(e.message))
        }
        setLoading(false);
    }

Like other answers here, this too is a workaround. However, I prefer this one over others because I am not that great with regex, and additionally, this one does not depend on a fixed starting position to extract the error code.

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.