From 53242fcb4121244f60f0898d312ad5cd8cadc0b3 Mon Sep 17 00:00:00 2001 From: yann300 Date: Wed, 17 Nov 2021 14:31:29 +0100 Subject: [PATCH] fix cumstom error --- .../src/tests/transactionExecution.spec.ts | 47 +++++++- apps/remix-ide/src/blockchain/blockchain.js | 3 +- .../src/lib/compiler-artefacts.ts | 2 +- libs/remix-lib/src/execution/txExecution.ts | 101 +++++++++--------- 4 files changed, 102 insertions(+), 51 deletions(-) diff --git a/apps/remix-ide-e2e/src/tests/transactionExecution.spec.ts b/apps/remix-ide-e2e/src/tests/transactionExecution.spec.ts index 55f2eadacb..6c1e832869 100644 --- a/apps/remix-ide-e2e/src/tests/transactionExecution.spec.ts +++ b/apps/remix-ide-e2e/src/tests/transactionExecution.spec.ts @@ -178,8 +178,29 @@ module.exports = { .journalLastChildIncludes('"documentation": "param2"') .journalLastChildIncludes('"documentation": "param3"') .journalLastChildIncludes('Debug the transaction to get more information.') + }, + + 'Should Compile and Deploy a contract which define a custom error in a library, the error should be logged in the terminal': function (browser: NightwatchBrowser) { + browser.testContracts('customError.sol', sources[5]['customErrorLib.sol'], ['D']) + .clickLaunchIcon('udapp') + .selectAccount('0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c') // this account will be used for this test suite + .click('#runTabView button[class^="instanceButton"]') + .waitForElementPresent('.instance:nth-of-type(3)') + .click('.instance:nth-of-type(3) > div > button') + .clickFunction('h - transact (not payable)') + .pause(5000) + .journalLastChildIncludes('Error provided by the contract:') + .journalLastChildIncludes('CustomError : error description from library') + .journalLastChildIncludes('Parameters:') + .journalLastChildIncludes('"value": "48"') + .journalLastChildIncludes('"value": "46"') + .journalLastChildIncludes('"value": "error_string_from_library"') + .journalLastChildIncludes('"documentation": "param1 from library"') + .journalLastChildIncludes('"documentation": "param2 from library"') + .journalLastChildIncludes('"documentation": "param3 from library"') + .journalLastChildIncludes('Debug the transaction to get more information.') .end() - } + }, } // @TODO test: bytes8[3][] type as input @@ -281,5 +302,29 @@ contract C { } }` } + }, + { + 'customErrorLib.sol': { + content: `// SPDX-License-Identifier: GPL-3.0 + + pragma solidity ^0.8.7; + + library lib { + /// error description from library + /// @param a param1 from library + /// @param b param2 from library + /// @param c param3 from library + error CustomError(uint a, uint b, string c); + function set() public { + revert CustomError(48, 46, "error_string_from_library"); + } + } + + contract D { + function h() public { + lib.set(); + } + }` + } } ] diff --git a/apps/remix-ide/src/blockchain/blockchain.js b/apps/remix-ide/src/blockchain/blockchain.js index f0daf8d880..f280e47d8b 100644 --- a/apps/remix-ide/src/blockchain/blockchain.js +++ b/apps/remix-ide/src/blockchain/blockchain.js @@ -524,7 +524,8 @@ class Blockchain extends Plugin { if (execResult) { // if it's not the VM, we don't have return value. We only have the transaction, and it does not contain the return value. returnValue = execResult ? execResult.returnValue : toBuffer(addHexPrefix(txResult.result) || '0x0000000000000000000000000000000000000000000000000000000000000000') - const vmError = txExecution.checkVMError(execResult, args.data.contractABI, args.data.contract) + const compiledContracts = await this.call('compilerArtefacts', 'getAllContractDatas') + const vmError = txExecution.checkVMError(execResult, compiledContracts) if (vmError.error) { return cb(vmError.message) } diff --git a/libs/remix-core-plugin/src/lib/compiler-artefacts.ts b/libs/remix-core-plugin/src/lib/compiler-artefacts.ts index 52e1c7730b..b7f907f270 100644 --- a/libs/remix-core-plugin/src/lib/compiler-artefacts.ts +++ b/libs/remix-core-plugin/src/lib/compiler-artefacts.ts @@ -4,7 +4,7 @@ import { CompilerAbstract } from '@remix-project/remix-solidity' const profile = { name: 'compilerArtefacts', - methods: ['get', 'addResolvedContract', 'getCompilerAbstract'], + methods: ['get', 'addResolvedContract', 'getCompilerAbstract' , 'getAllContractDatas'], events: [], version: '0.0.1' } diff --git a/libs/remix-lib/src/execution/txExecution.ts b/libs/remix-lib/src/execution/txExecution.ts index f7e34333ee..5171c7c4e2 100644 --- a/libs/remix-lib/src/execution/txExecution.ts +++ b/libs/remix-lib/src/execution/txExecution.ts @@ -57,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, abi, contract) { +export function checkVMError (execResult, compiledContracts) { const errorCode = { OUT_OF_GAS: 'out of gas', STACK_UNDERFLOW: 'stack underflow', @@ -91,56 +91,61 @@ export function checkVMError (execResult, abi, contract) { const returnData = execResult.returnValue const returnDataHex = returnData.slice(0, 4).toString('hex') let customError - if (abi) { + if (compiledContracts) { let decodedCustomErrorInputsClean - 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 - 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.object.devdoc.errors && contract.object.devdoc.errors[functionSignature][0]) || {} - } catch (e) { - console.error(e.message) + 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 } - // we check in the 'userdoc' if there's an user documentation for this error - try { - const userdoc = (contract.object.userdoc.errors && contract.object.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) {