From 90f2d77519c46f0778e167815120b341fa69ada7 Mon Sep 17 00:00:00 2001 From: yann300 Date: Mon, 10 May 2021 11:06:27 +0200 Subject: [PATCH] add custom error --- libs/remix-lib/src/execution/txExecution.ts | 54 ++++++++++++++++----- libs/remix-lib/src/execution/txHelper.ts | 20 ++++++-- 2 files changed, 59 insertions(+), 15 deletions(-) diff --git a/libs/remix-lib/src/execution/txExecution.ts b/libs/remix-lib/src/execution/txExecution.ts index 8eb1c927dd..f8111f60eb 100644 --- a/libs/remix-lib/src/execution/txExecution.ts +++ b/libs/remix-lib/src/execution/txExecution.ts @@ -1,5 +1,6 @@ 'use strict' import { ethers } from 'ethers' +import { getFunctionFragment } from './txHelper' /** * deploy the given contract @@ -56,7 +57,7 @@ export function callFunction (from, to, data, value, gasLimit, funAbi, txRunner, * @param {Object} execResult - execution result given by the VM * @return {Object} - { error: true/false, message: DOMNode } */ -export function checkVMError (execResult) { +export function checkVMError (txResult, abi) { const errorCode = { OUT_OF_GAS: 'out of gas', STACK_UNDERFLOW: 'stack underflow', @@ -74,10 +75,10 @@ export function checkVMError (execResult) { error: false, message: '' } - if (!execResult.exceptionError) { + if (!txResult.result.execResult.exceptionError) { return ret } - const exceptionError = execResult.exceptionError.error || '' + const exceptionError = txResult.result.execResult.exceptionError.error || '' const error = `VM error: ${exceptionError}.\n` let msg if (exceptionError === errorCode.INVALID_OPCODE) { @@ -87,20 +88,49 @@ export function checkVMError (execResult) { msg = '\tThe transaction ran out of gas. Please increase the Gas Limit.\n' ret.error = true } else if (exceptionError === errorCode.REVERT) { - const returnData = execResult.returnValue - // It is the hash of Error(string) - if (returnData && (returnData.slice(0, 4).toString('hex') === '08c379a0')) { - const abiCoder = new ethers.utils.AbiCoder() - const reason = abiCoder.decode(['string'], returnData.slice(4))[0] - msg = `\tThe transaction has been reverted to the initial state.\nReason provided by the contract: "${reason}".` - } else { - msg = '\tThe transaction has been reverted to the initial state.\nNote: The called function should be payable if you send value and the value you send should be less than your current balance.' + const returnData = txResult.result.execResult.returnValue + const returnDataHex = returnData.slice(0, 4).toString('hex') + let customError + if (abi) { + let decodedCustomErrorInputs + for (const item of abi) { + if (item.type === 'error') { + // ethers doesn't crash anymore if "error" type is specified, but it doesn't extract the errors. see: + // https://github.com/ethers-io/ethers.js/commit/bd05aed070ac9e1421a3e2bff2ceea150bedf9b7 + // we need here to fake the type, so the "getSighash" function works properly + const fn = getFunctionFragment({ ...item, type: 'function', stateMutability: "nonpayable" }) + if (!fn) continue + const sign = fn.getSighash(item.name) + if (!sign) continue + if (returnDataHex === sign.replace('0x', '')) { + customError = item.name + decodedCustomErrorInputs = fn.decodeFunctionData(fn.getFunction(item.name), returnData) + break + } + } + } + if (decodedCustomErrorInputs) { + msg = `\tThe transaction has been reverted to the initial state.\nError provided by the contract:` + msg += `\n${customError}` + msg += `\nParameters:` + msg += `\n${decodedCustomErrorInputs}` + } + } + if (!customError) { + // It is the hash of Error(string) + if (returnData && (returnDataHex === '08c379a0')) { + const abiCoder = new ethers.utils.AbiCoder() + const reason = abiCoder.decode(['string'], returnData.slice(4))[0] + msg = `\tThe transaction has been reverted to the initial state.\nReason provided by the contract: "${reason}".` + } else { + msg = '\tThe transaction has been reverted to the initial state.\nNote: The called function should be payable if you send value and the value you send should be less than your current balance.' + } } ret.error = true } else if (exceptionError === errorCode.STATIC_STATE_CHANGE) { msg = '\tState changes is not allowed in Static Call context\n' ret.error = true } - ret.message = `${error}${exceptionError}${msg}\tDebug the transaction to get more information.` + ret.message = `${error}\n${exceptionError}\n${msg}\Debug the transaction to get more information.` return ret } diff --git a/libs/remix-lib/src/execution/txHelper.ts b/libs/remix-lib/src/execution/txHelper.ts index 1ee42e14bb..236ad191e5 100644 --- a/libs/remix-lib/src/execution/txHelper.ts +++ b/libs/remix-lib/src/execution/txHelper.ts @@ -38,6 +38,11 @@ export function encodeFunctionId (funABI) { return abi.getSighash(funABI.name) } +export function getFunctionFragment (funABI): ethers.utils.Interface { + if (funABI.type === 'fallback' || funABI.type === 'receive') return null + return new ethers.utils.Interface([funABI]) +} + export function sortAbiFunction (contractabi) { // Check if function is constant (introduced with Solidity 0.6.0) const isConstant = ({ stateMutability }) => stateMutability === 'view' || stateMutability === 'pure' @@ -97,10 +102,14 @@ export function extractSize (type) { return size ? size[2] : '' } -export function getFunction (abi, fnName) { +export function getError (abi, fnName) { + return getFromInterface(abi, fnName, 'error') +} + +export function getFromInterface (abi, fnName, type) { for (let i = 0; i < abi.length; i++) { const fn = abi[i] - if (fn.type === 'function' && fnName === fn.name + '(' + fn.inputs.map((value) => { + if (fn.type === type && fnName === fn.name + '(' + fn.inputs.map((value) => { if (value.components) { const fullType = this.makeFullTypeDefinition(value) return fullType.replace(/tuple/g, '') // return of makeFullTypeDefinition might contain `tuple`, need to remove it cause `methodIdentifier` (fnName) does not include `tuple` keyword @@ -111,7 +120,12 @@ export function getFunction (abi, fnName) { return fn } } - return null + return null +} + + +export function getFunction (abi, fnName) { + return getFromInterface(abi, fnName, 'function') } export function getFallbackInterface (abi) {