From d356ade3ca08d555db265cdf75c73adbb50844b7 Mon Sep 17 00:00:00 2001 From: yann300 Date: Mon, 21 Jun 2021 18:03:22 +0200 Subject: [PATCH] add natspec info to error report --- .../src/tests/transactionExecution.spec.ts | 13 +++++++++++-- apps/remix-ide/src/app/ui/universal-dapp-ui.js | 8 +++++--- apps/remix-ide/src/blockchain/blockchain.js | 5 +++-- libs/remix-lib/src/execution/txExecution.ts | 14 ++++++++++++-- 4 files changed, 31 insertions(+), 9 deletions(-) diff --git a/apps/remix-ide-e2e/src/tests/transactionExecution.spec.ts b/apps/remix-ide-e2e/src/tests/transactionExecution.spec.ts index e1d5ebb82f..d000d5e206 100644 --- a/apps/remix-ide-e2e/src/tests/transactionExecution.spec.ts +++ b/apps/remix-ide-e2e/src/tests/transactionExecution.spec.ts @@ -148,9 +148,14 @@ module.exports = { .click('.instance:nth-of-type(3) > div > button') .clickFunction('g - transact (not payable)') .journalLastChildIncludes('Error provided by the contract:') - .journalLastChildIncludes('CustomError') + .journalLastChildIncludes('CustomError: error description') .journalLastChildIncludes('Parameters:') - .journalLastChildIncludes('2,3,error_string_2') + .journalLastChildIncludes('"value": "2",') + .journalLastChildIncludes('"value": "3",') + .journalLastChildIncludes('"value": "error_string_2",') + .journalLastChildIncludes('"documentation": "param1"') + .journalLastChildIncludes('"documentation": "param2"') + .journalLastChildIncludes('"documentation": "param3"') .journalLastChildIncludes('Debug the transaction to get more information.') }, @@ -256,6 +261,10 @@ contract C { pragma solidity ^0.8.4; + /// error description + /// @param a param1 + /// @param b param2 + /// @param c param3 error CustomError(uint a, uint b, string c); contract C { function f() public pure { diff --git a/apps/remix-ide/src/app/ui/universal-dapp-ui.js b/apps/remix-ide/src/app/ui/universal-dapp-ui.js index 17fe7058c8..217217503f 100644 --- a/apps/remix-ide/src/app/ui/universal-dapp-ui.js +++ b/apps/remix-ide/src/app/ui/universal-dapp-ui.js @@ -44,14 +44,14 @@ UniversalDAppUI.prototype.renderInstance = function (contract, address, contract noInstances.parentNode.removeChild(noInstances) } const abi = txHelper.sortAbiFunction(contract.abi) - return this.renderInstanceFromABI(abi, address, contractName) + return this.renderInstanceFromABI(abi, address, contractName, contract) } // TODO this function was named before "appendChild". // this will render an instance: contract name, contract address, and all the public functions // basically this has to be called for the "atAddress" (line 393) and when a contract creation succeed // this returns a DOM element -UniversalDAppUI.prototype.renderInstanceFromABI = function (contractABI, address, contractName) { +UniversalDAppUI.prototype.renderInstanceFromABI = function (contractABI, address, contractName, contract) { const self = this address = (address.slice(0, 2) === '0x' ? '' : '0x') + address.toString('hex') address = ethJSUtil.toChecksumAddress(address) @@ -117,7 +117,8 @@ UniversalDAppUI.prototype.renderInstanceFromABI = function (contractABI, address funABI: funABI, address: address, contractABI: contractABI, - contractName: contractName + contractName: contractName, + contract })) }) @@ -255,6 +256,7 @@ UniversalDAppUI.prototype.runTransaction = function (lookupOnly, args, valArr, i args.contractName, args.contractABI, args.funABI, + args.contract, inputsValues, args.address, params, diff --git a/apps/remix-ide/src/blockchain/blockchain.js b/apps/remix-ide/src/blockchain/blockchain.js index f3dc841646..2b156e2a9b 100644 --- a/apps/remix-ide/src/blockchain/blockchain.js +++ b/apps/remix-ide/src/blockchain/blockchain.js @@ -249,7 +249,7 @@ class Blockchain { return txlistener } - runOrCallContractMethod (contractName, contractAbi, funABI, value, address, callType, lookupOnly, logMsg, logCallback, outputCb, confirmationCb, continueCb, promptCb) { + runOrCallContractMethod (contractName, contractAbi, funABI, contract, value, address, callType, lookupOnly, logMsg, logCallback, outputCb, confirmationCb, continueCb, promptCb) { // contractsDetails is used to resolve libraries txFormat.buildData(contractName, contractAbi, {}, false, funABI, callType, (error, data) => { if (error) { @@ -265,6 +265,7 @@ class Blockchain { if (data) { data.contractName = contractName data.contractABI = contractAbi + data.contract = contract } const useCall = funABI.stateMutability === 'view' || funABI.stateMutability === 'pure' this.runTx({ to: address, data, useCall }, confirmationCb, continueCb, promptCb, (error, txResult, _address, returnValue) => { @@ -490,7 +491,7 @@ class Blockchain { 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) + const vmError = txExecution.checkVMError(execResult, args.data.contractABI, args.data.contract) if (vmError.error) { return cb(vmError.message) } diff --git a/libs/remix-lib/src/execution/txExecution.ts b/libs/remix-lib/src/execution/txExecution.ts index e87db02eb1..035726c35e 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) { +export function checkVMError (execResult, abi, contract) { const errorCode = { OUT_OF_GAS: 'out of gas', STACK_UNDERFLOW: 'stack underflow', @@ -107,9 +107,19 @@ export function checkVMError (execResult, abi) { let functionDesc = fn.getFunction(item.name) let decodedCustomErrorInputs = fn.decodeFunctionData(functionDesc, returnData) decodedCustomErrorInputsClean = {} + let devdoc = {} + if (contract && fn.functions && Object.keys(fn.functions).length) { + const functionSignature = Object.keys(fn.functions)[0] + devdoc = contract.object.devdoc.errors[functionSignature][0] || {} + let userdoc = contract.object.userdoc.errors[functionSignature][0] || {} + if (userdoc) customError += ' : ' + (userdoc as any).notice + } for (const input of functionDesc.inputs) { const v = decodedCustomErrorInputs[input.name] - decodedCustomErrorInputsClean[input.name] = v.toString ? v.toString() : v + decodedCustomErrorInputsClean[input.name] = { + value: v.toString ? v.toString() : v, + documentation: (devdoc as any).params[input.name] + } } break }