diff --git a/apps/remix-ide-e2e/src/commands/clickInstance.ts b/apps/remix-ide-e2e/src/commands/clickInstance.ts index 091d66ea94..f9d5ee8e6f 100644 --- a/apps/remix-ide-e2e/src/commands/clickInstance.ts +++ b/apps/remix-ide-e2e/src/commands/clickInstance.ts @@ -6,7 +6,7 @@ class ClickInstance extends EventEmitter { index = index + 2 const selector = '.instance:nth-of-type(' + index + ') > div > button' - this.api.waitForElementContainsText(selector, '', 60000).scrollAndClick(selector).perform(() => { this.emit('complete') }) + this.api.waitForElementPresent(selector).waitForElementContainsText(selector, '', 60000).scrollAndClick(selector).perform(() => { this.emit('complete') }) return this } } diff --git a/apps/remix-ide-e2e/src/tests/transactionExecution.spec.ts b/apps/remix-ide-e2e/src/tests/transactionExecution.spec.ts index 55f2eadacb..7f6124ccc4 100644 --- a/apps/remix-ide-e2e/src/tests/transactionExecution.spec.ts +++ b/apps/remix-ide-e2e/src/tests/transactionExecution.spec.ts @@ -137,6 +137,7 @@ module.exports = { .selectAccount('0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c') // this account will be used for this test suite .click('#runTabView button[class^="instanceButton"]') .waitForElementPresent('.instance:nth-of-type(2)') + .click('*[data-id="deployAndRunClearInstances"]') }, 'Should Compile and Deploy a contract which define a custom error, the error should be logged in the terminal': function (browser: NightwatchBrowser) { @@ -144,8 +145,7 @@ module.exports = { .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') + .clickInstance(0) .clickFunction('g - transact (not payable)') .pause(5000) .journalLastChildIncludes('Error provided by the contract:') @@ -158,6 +158,7 @@ module.exports = { .journalLastChildIncludes('"documentation": "param2"') .journalLastChildIncludes('"documentation": "param3"') .journalLastChildIncludes('Debug the transaction to get more information.') + .click('*[data-id="deployAndRunClearInstances"]') }, 'Should Compile and Deploy a contract which define a custom error, the error should be logged in the terminal , using London VM Fork': function (browser: NightwatchBrowser) { @@ -165,8 +166,7 @@ module.exports = { .click('*[data-id="settingsVMLondonMode"]') // switch to London fork .selectAccount('0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c') // this account will be used for this test suite .click('#runTabView button[class^="instanceButton"]') - .waitForElementPresent('.instance:nth-of-type(2)') - .click('.instance:nth-of-type(2) > div > button') + .clickInstance(0) .clickFunction('g - transact (not payable)') .journalLastChildIncludes('Error provided by the contract:') .journalLastChildIncludes('CustomError : error description') @@ -178,6 +178,25 @@ 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('customErrorLib.sol', sources[5]['customErrorLib.sol'], ['D']) + .clickLaunchIcon('udapp') + .click('#runTabView button[class^="instanceButton"]') + .clickInstance(1) + .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() } } @@ -281,5 +300,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..148aa292c7 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..da9274e89c 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,55 +91,60 @@ 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) - } - // 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] + 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 + 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 } - inputIndex++ } - break } } }