vechain-sdk-js icon indicating copy to clipboard operation
vechain-sdk-js copied to clipboard

💡 [REQUEST] - Improve Developer Experience with decoded Error Messages

Open ifavo opened this issue 9 months ago • 0 comments

Summary

Calling contract functions or simulating transactions currently throw an error with the raw encoded error message. As developer I need to learn how to decode the messages and manually decode these.

A great help would be the SDK automatically trying to decode error messages and provide a human readable version of it. If not possible, its likely a non-standard-error and can remain as now.

I suggest to:

  1. automatically decode error messages by using Error(string) and parseError()
  2. within a contracts instance, use parseError() based on the current interface definition, to support custom errors

Basic Example

The following example is a contract call that throws an error for insufficient balance:

import { ThorClient } from '@vechain/sdk-network';
import { ErrorDecoder } from 'ethers-decode-error';
import energyAbi from './energy.json' assert { type: 'json' };
const thor = ThorClient.fromUrl('https://mainnet.vechain.org');

const vtho = thor.contracts.load(
  '0x0000000000000000000000000000456e65726779',
  energyAbi
);

// failing simulation of transfer
try {
  vtho.setContractReadOptions({
    caller: '0x0000000000000000000000000000000000000003',
  });
  const failingTransfer = await vtho.read.transfer(
    '0x0000000000000000000000000000456e65726779',
    '1'
  );
  console.log('Failed Transfer Test', failingTransfer);
} catch (err) {
  const errorDecoder = ErrorDecoder.create();
  const decodedError = await errorDecoder.decode(err);
  console.log(`Revert reason: ${decodedError.reason}`);
}

-- or --

try {
  const failingTransfer = await thor.contracts.executeContractCall(
    '0x0000000000000000000000000000456e65726779',
    'transfer(address _to, uint256 _amount) returns(bool success)',
    ['0x0000000000000000000000000000456e65726779', '1'],
    {
      caller: '0x0000000000000000000000000000000000000003',
    }
  );
  console.log('Failed Transfer Test', failingTransfer);
} catch (err) {
  const errorDecoder = ErrorDecoder.create();
  const decodedError = await errorDecoder.decode(err);
  console.log(`Revert reason: ${decodedError.reason}`);
}

Instead of using another library to decode the message, I suggest that the error object already contains the decoded revertReason.

The next example is a simulation that throws an error for Already closed:

import { ThorClient } from '@vechain/sdk-network';
import { coder } from '@vechain/sdk-core';
const thor = ThorClient.fromUrl('https://mainnet.vechain.org');

const txId =
  '0x27b515344514d5feeeaf582a93edf3139604f58f5a66785350e400e21584c7bb';

// define your interface, especially with the errors
const contractInterface = coder.createInterface([]);

const transaction = await thor.transactions.getTransaction(txId);
const simulation = await thor.transactions.simulateTransaction(
  transaction.clauses,
  {
    revision: transaction.meta.blockID,
    gas: transaction.gas,
    caller: transaction.origin,
    gasPayer: transaction.delegator ?? transaction.origin,
    expiration: transaction.expiration,
    blockRef: transaction.blockRef,
  }
);

simulation.forEach((clause, clauseIndex) => {
  if (!clause.reverted) {
    return console.log(`Clause #${clauseIndex} was successful`);
  }

  const revertReason = contractInterface.parseError(clause.data);

  console.log(`Clause #${clauseIndex} reverted with`);
  console.log(' VM Error:', clause.vmError);
  console.log(' Revert Reason:', revertReason.args);
});

Instead of using importing a coder, creating a contract interface and use parseError() to decode the message, I suggest that the clause also already contains the decoded revertReason.

If custom errors are defined within the interface, using respecting those as well.

ifavo avatar Apr 29 '24 07:04 ifavo