add custom error

pull/5370/head
yann300 4 years ago
parent f949792297
commit 90f2d77519
  1. 54
      libs/remix-lib/src/execution/txExecution.ts
  2. 20
      libs/remix-lib/src/execution/txHelper.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
}

@ -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) {

Loading…
Cancel
Save