diff --git a/apps/remix-ide/src/app/providers/abstract-provider.tsx b/apps/remix-ide/src/app/providers/abstract-provider.tsx index 97682642a3..a5185b6129 100644 --- a/apps/remix-ide/src/app/providers/abstract-provider.tsx +++ b/apps/remix-ide/src/app/providers/abstract-provider.tsx @@ -15,6 +15,7 @@ export type JsonDataResult = { jsonrpc: string // version result?: any error?: any + errorData?: any } export type RejectRequest = (error: Error) => void diff --git a/apps/remix-ide/src/app/providers/injected-provider.tsx b/apps/remix-ide/src/app/providers/injected-provider.tsx index c59e98f8e1..1591e2c491 100644 --- a/apps/remix-ide/src/app/providers/injected-provider.tsx +++ b/apps/remix-ide/src/app/providers/injected-provider.tsx @@ -105,6 +105,14 @@ export abstract class InjectedProvider extends Plugin implements IProvider { resolve({jsonrpc: '2.0', error: 'no return data provided', id: data.id}) } } catch (error) { + if (error.data.originalError && error.data.originalError.data) { + resolve({ + jsonrpc: '2.0', + error: error.data.originalError.message, + errorData: error.data.originalError.data, + id: data.id + }) + } else resolve({ jsonrpc: '2.0', error: error.data && error.data.message ? error.data.message : error.message, diff --git a/apps/remix-ide/src/blockchain/blockchain.tsx b/apps/remix-ide/src/blockchain/blockchain.tsx index 06298095e6..67e5b71460 100644 --- a/apps/remix-ide/src/blockchain/blockchain.tsx +++ b/apps/remix-ide/src/blockchain/blockchain.tsx @@ -950,7 +950,6 @@ export class Blockchain extends Plugin { } } } - if (!isVM && tx && tx.useCall) { returnValue = toBuffer(addHexPrefix(txResult.result)) } @@ -962,6 +961,16 @@ export class Blockchain extends Plugin { cb(null, txResult, address, returnValue) } catch (error) { + if (this.isInjectedWeb3()) { + let errorObj = error.replace('Returned error: ', '') + errorObj = JSON.parse(errorObj) + if (errorObj.errorData) { + const compiledContracts = await this.call('compilerArtefacts', 'getAllContractDatas') + const injectedError = txExecution.checkInjectedError(errorObj, compiledContracts) + cb(injectedError) + } else + cb(error) + } else cb(error) } } diff --git a/libs/remix-lib/src/execution/txExecution.ts b/libs/remix-lib/src/execution/txExecution.ts index 781d9733ce..5ae8e089a0 100644 --- a/libs/remix-lib/src/execution/txExecution.ts +++ b/libs/remix-lib/src/execution/txExecution.ts @@ -173,3 +173,77 @@ export function checkVMError (execResult, compiledContracts) { ret.message = `${error}\n${exceptionError}\n${msg}\nDebug the transaction to get more information.` return ret } + +export function checkInjectedError (errorObj, compiledContracts) { + if (errorObj.error === 'execution reverted') { + const returnData = errorObj.errorData + const returnDataHex = returnData.slice(2, 10) + let customError + let msg = errorObj.error + if (compiledContracts) { + let decodedCustomErrorInputsClean + for (const file of Object.keys(compiledContracts)) { + for (const contractName of Object.keys(compiledContracts[file])) { + const contract = compiledContracts[file][contractName] + for (const item of contract.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 + const functionDesc = fn.getFunction(item.name) + // decoding error parameters + const decodedCustomErrorInputs = fn.decodeFunctionData(functionDesc, returnData) + decodedCustomErrorInputsClean = {} + let devdoc = {} + // "contract" reprensents the compilation result containing the NATSPEC documentation + if (contract && fn.functions && Object.keys(fn.functions).length) { + const functionSignature = Object.keys(fn.functions)[0] + // we check in the 'devdoc' if there's a developer documentation for this error + try { + devdoc = (contract.devdoc.errors && contract.devdoc.errors[functionSignature][0]) || {} + } catch (e) { + console.error(e.message) + } + // we check in the 'userdoc' if there's an user documentation for this error + try { + const userdoc = (contract.userdoc.errors && contract.userdoc.errors[functionSignature][0]) || {} + if (userdoc && (userdoc as any).notice) customError += ' : ' + (userdoc as any).notice // we append the user doc if any + } catch (e) { + console.error(e.message) + } + } + let inputIndex = 0 + for (const input of functionDesc.inputs) { + const inputKey = input.name || inputIndex + const v = decodedCustomErrorInputs[inputKey] + + decodedCustomErrorInputsClean[inputKey] = { + value: v.toString ? v.toString() : v + } + if (devdoc && (devdoc as any).params) { + decodedCustomErrorInputsClean[input.name].documentation = (devdoc as any).params[inputKey] // we add the developer documentation for this input parameter if any + } + inputIndex++ + } + break + } + } + } + } + } + if (decodedCustomErrorInputsClean) { + msg += '\tThe transaction has been reverted to the initial state.\nError provided by the contract:' + msg += `\n${customError}` + msg += '\nParameters:' + msg += `\n${JSON.stringify(decodedCustomErrorInputsClean, null, ' ')}` + } + } + return msg + } +} \ No newline at end of file