From dd00d1d13b045a3d22b06391738efa88a249e245 Mon Sep 17 00:00:00 2001 From: yann300 Date: Wed, 26 Oct 2022 15:58:01 +0200 Subject: [PATCH 01/40] use cached source location --- apps/debugger/src/app/debugger-api.ts | 3 ++- libs/remix-debug/src/debugger/debugger.ts | 2 +- .../src/solidity-decoder/internalCallTree.ts | 27 +++++++++++++++++-- .../src/source/sourceLocationTracker.ts | 15 +++++++++++ .../debugger-ui/src/lib/debugger-ui.tsx | 2 +- .../debugger-ui/src/lib/idebugger-api.ts | 2 +- 6 files changed, 45 insertions(+), 6 deletions(-) diff --git a/apps/debugger/src/app/debugger-api.ts b/apps/debugger/src/app/debugger-api.ts index 13238c6add..9c268e1360 100644 --- a/apps/debugger/src/app/debugger-api.ts +++ b/apps/debugger/src/app/debugger-api.ts @@ -41,8 +41,9 @@ export const DebuggerApiMixin = (Base) => class extends Base { await this.call('editor', 'discardHighlight') } - async highlight (lineColumnPos, path) { + async highlight (lineColumnPos, path, rawLocation) { await this.call('editor', 'highlight', lineColumnPos, path, '', { focus: true }) + this.call('') } async getFile (path) { diff --git a/libs/remix-debug/src/debugger/debugger.ts b/libs/remix-debug/src/debugger/debugger.ts index 41c846b937..14a0dcd0ed 100644 --- a/libs/remix-debug/src/debugger/debugger.ts +++ b/libs/remix-debug/src/debugger/debugger.ts @@ -76,7 +76,7 @@ export class Debugger { return } - this.debugger.callTree.sourceLocationTracker.getValidSourceLocationFromVMTraceIndex(address, index, compilationResultForAddress.data.contracts).then(async (rawLocation) => { + this.debugger.callTree.getValidSourceLocationFromVMTraceIndexFromCache(address, index, compilationResultForAddress.data.contracts).then(async (rawLocation) => { if (compilationResultForAddress && compilationResultForAddress.data) { const generatedSources = this.debugger.callTree.sourceLocationTracker.getGeneratedSourcesFromAddress(address) const astSources = Object.assign({}, compilationResultForAddress.data.sources) diff --git a/libs/remix-debug/src/solidity-decoder/internalCallTree.ts b/libs/remix-debug/src/solidity-decoder/internalCallTree.ts index b5440d2d7d..7900e50104 100644 --- a/libs/remix-debug/src/solidity-decoder/internalCallTree.ts +++ b/libs/remix-debug/src/solidity-decoder/internalCallTree.ts @@ -6,6 +6,17 @@ import { EventManager } from '../eventManager' import { parseType } from './decodeInfo' import { isContractCreation, isCallInstruction, isCreateInstruction, isJumpDestInstruction } from '../trace/traceHelper' import { extractLocationFromAstVariable } from './types/util' +import { Uint } from './types/Uint' + +export type StepDetail = { + depth: number, + gas: number, + gasCost: number, + memory: number[], + op: string, + pc: number, + stack: number[], +} /** * Tree representing internal jump into function. @@ -27,6 +38,9 @@ export class InternalCallTree { functionDefinitionByFile astWalker reducedTrace + locationAndOpcodePerVMTraceIndex: { + [Key: number]: any + } /** * constructor @@ -89,6 +103,7 @@ export class InternalCallTree { this.functionDefinitionByFile = {} this.astWalker = new AstWalker() this.reducedTrace = [] + this.locationAndOpcodePerVMTraceIndex = {} } /** @@ -145,6 +160,7 @@ export class InternalCallTree { try { const address = this.traceManager.getCurrentCalledAddressAt(step) const location = await this.sourceLocationTracker.getSourceLocationFromVMTraceIndex(address, step, this.solidityProxy.contracts) + return location } catch (error) { throw new Error('InternalCallTree - Cannot retrieve sourcelocation for step ' + step + ' ' + error) @@ -160,6 +176,10 @@ export class InternalCallTree { throw new Error('InternalCallTree - Cannot retrieve valid sourcelocation for step ' + step + ' ' + error) } } + + async getValidSourceLocationFromVMTraceIndexFromCache (address: string, step: number, contracts: any) { + return await this.sourceLocationTracker.getValidSourceLocationFromVMTraceIndexFromCache(address, step, contracts, this.locationAndOpcodePerVMTraceIndex) + } } async function buildTree (tree, step, scopeId, isExternalCall, isCreation) { @@ -201,8 +221,11 @@ async function buildTree (tree, step, scopeId, isExternalCall, isCreation) { if (!sourceLocation) { return { outStep: step, error: 'InternalCallTree - No source Location. ' + step } } - const isCallInstrn = isCallInstruction(tree.traceManager.trace[step]) - const isCreateInstrn = isCreateInstruction(tree.traceManager.trace[step]) + const stepDetail: StepDetail = tree.traceManager.trace[step] + tree.locationAndOpcodePerVMTraceIndex[step] = { ...sourceLocation, ...stepDetail } + console.log('locationAndOpcodePerVMTraceIndex', stepDetail) + const isCallInstrn = isCallInstruction(stepDetail) + const isCreateInstrn = isCreateInstruction(stepDetail) // we are checking if we are jumping in a new CALL or in an internal function if (isCallInstrn || sourceLocation.jump === 'i') { try { diff --git a/libs/remix-debug/src/source/sourceLocationTracker.ts b/libs/remix-debug/src/source/sourceLocationTracker.ts index f9967b5e98..d870f6c2d4 100644 --- a/libs/remix-debug/src/source/sourceLocationTracker.ts +++ b/libs/remix-debug/src/source/sourceLocationTracker.ts @@ -94,6 +94,21 @@ export class SourceLocationTracker { return map } + async getValidSourceLocationFromVMTraceIndexFromCache (address: string, vmtraceStepIndex: number, contracts: any, cache: Map) { + const amountOfSources = this.getTotalAmountOfSources(address, contracts) + let map: Record = { file: -1 } + /* + (map.file === -1) this indicates that it isn't associated with a known source code + (map.file > amountOfSources - 1) this indicates the current file index exceed the total number of files. + this happens when generated sources should not be considered. + */ + while (vmtraceStepIndex >= 0 && (map.file === -1 || map.file > amountOfSources - 1)) { + map = cache[vmtraceStepIndex] + vmtraceStepIndex = vmtraceStepIndex - 1 + } + return map + } + clearCache () { this.sourceMapByAddress = {} } diff --git a/libs/remix-ui/debugger-ui/src/lib/debugger-ui.tsx b/libs/remix-ui/debugger-ui/src/lib/debugger-ui.tsx index f7135f74bb..2a1be745d1 100644 --- a/libs/remix-ui/debugger-ui/src/lib/debugger-ui.tsx +++ b/libs/remix-ui/debugger-ui/src/lib/debugger-ui.tsx @@ -158,7 +158,7 @@ export const DebuggerUI = (props: DebuggerUIProps) => { return { ...prevState, sourceLocationStatus: '' } }) await debuggerModule.discardHighlight() - await debuggerModule.highlight(lineColumnPos, path) + await debuggerModule.highlight(lineColumnPos, path, rawLocation) } } }) diff --git a/libs/remix-ui/debugger-ui/src/lib/idebugger-api.ts b/libs/remix-ui/debugger-ui/src/lib/idebugger-api.ts index 3b3fa55d11..c0065558c9 100644 --- a/libs/remix-ui/debugger-ui/src/lib/idebugger-api.ts +++ b/libs/remix-ui/debugger-ui/src/lib/idebugger-api.ts @@ -44,7 +44,7 @@ export interface IDebuggerApi { onEditorContentChanged: (listener: onEditorContentChanged) => void onEnvChanged: (listener: onEnvChangedListener) => void discardHighlight: () => Promise - highlight: (lineColumnPos: LineColumnLocation, path: string) => Promise + highlight: (lineColumnPos: LineColumnLocation, path: string, rawLocation: any) => Promise fetchContractAndCompile: (address: string, currentReceipt: TransactionReceipt) => Promise getFile: (path: string) => Promise setFile: (path: string, content: string) => Promise From 6c9a04f7bc731c23f3fbd59eb8977cc621602f87 Mon Sep 17 00:00:00 2001 From: yann300 Date: Thu, 27 Oct 2022 18:12:03 +0200 Subject: [PATCH 02/40] show inline gas cost --- apps/debugger/src/app/debugger-api.ts | 24 +++++++++++++++++-- libs/remix-debug/src/debugger/debugger.ts | 6 +++-- .../src/solidity-decoder/internalCallTree.ts | 2 +- .../src/source/sourceLocationTracker.ts | 12 +++++++--- .../debugger-ui/src/lib/debugger-ui.tsx | 4 ++-- .../debugger-ui/src/lib/idebugger-api.ts | 2 +- 6 files changed, 39 insertions(+), 11 deletions(-) diff --git a/apps/debugger/src/app/debugger-api.ts b/apps/debugger/src/app/debugger-api.ts index 9c268e1360..0f9293b8e8 100644 --- a/apps/debugger/src/app/debugger-api.ts +++ b/apps/debugger/src/app/debugger-api.ts @@ -1,7 +1,13 @@ import Web3 from 'web3' import remixDebug, { TransactionDebugger as Debugger } from '@remix-project/remix-debug' +<<<<<<< HEAD import { CompilerAbstract } from '@remix-project/remix-solidity' +======= +import { CompilationOutput, Sources } from '@remix-ui/debugger-ui' +import { lineText } from '@remix-ui/editor' +import type { CompilationResult } from '@remix-project/remix-solidity-ts' +>>>>>>> show inline gas cost export const DebuggerApiMixin = (Base) => class extends Base { @@ -39,11 +45,25 @@ export const DebuggerApiMixin = (Base) => class extends Base { async discardHighlight () { await this.call('editor', 'discardHighlight') + await this.call('editor', 'discardLineTexts' as any) } - async highlight (lineColumnPos, path, rawLocation) { + async highlight (lineColumnPos, path, rawLocation, stepDetail) { await this.call('editor', 'highlight', lineColumnPos, path, '', { focus: true }) - this.call('') + const label = `${stepDetail.op} - ${stepDetail.gasCost} gas - ${stepDetail.gas} gas left` + const linetext: lineText = { + content: label, + position: lineColumnPos, + hide: false, + className: 'text-muted small', + afterContentClassName: 'text-muted small fas fa-gas-pump pl-4', + from: 'debugger', + hoverMessage: [{ + value: label, + }, + ], + } + await this.call('editor', 'addLineText' as any, linetext, path) } async getFile (path) { diff --git a/libs/remix-debug/src/debugger/debugger.ts b/libs/remix-debug/src/debugger/debugger.ts index 14a0dcd0ed..218cc58cc2 100644 --- a/libs/remix-debug/src/debugger/debugger.ts +++ b/libs/remix-debug/src/debugger/debugger.ts @@ -76,8 +76,10 @@ export class Debugger { return } - this.debugger.callTree.getValidSourceLocationFromVMTraceIndexFromCache(address, index, compilationResultForAddress.data.contracts).then(async (rawLocation) => { + this.debugger.callTree.getValidSourceLocationFromVMTraceIndexFromCache(address, index, compilationResultForAddress.data.contracts).then(async (rawLocationAndOpcode) => { if (compilationResultForAddress && compilationResultForAddress.data) { + const rawLocation = rawLocationAndOpcode.sourceLocation + const stepDetail = rawLocationAndOpcode.stepDetail const generatedSources = this.debugger.callTree.sourceLocationTracker.getGeneratedSourcesFromAddress(address) const astSources = Object.assign({}, compilationResultForAddress.data.sources) const sources = Object.assign({}, compilationResultForAddress.source.sources) @@ -88,7 +90,7 @@ export class Debugger { } } const lineColumnPos = await this.offsetToLineColumnConverter.offsetToLineColumn(rawLocation, rawLocation.file, sources, astSources) - this.event.trigger('newSourceLocation', [lineColumnPos, rawLocation, generatedSources, address]) + this.event.trigger('newSourceLocation', [lineColumnPos, rawLocation, generatedSources, address, stepDetail]) this.vmDebuggerLogic.event.trigger('sourceLocationChanged', [rawLocation]) } else { this.event.trigger('newSourceLocation', [null]) diff --git a/libs/remix-debug/src/solidity-decoder/internalCallTree.ts b/libs/remix-debug/src/solidity-decoder/internalCallTree.ts index 7900e50104..2d7e6b7329 100644 --- a/libs/remix-debug/src/solidity-decoder/internalCallTree.ts +++ b/libs/remix-debug/src/solidity-decoder/internalCallTree.ts @@ -222,7 +222,7 @@ async function buildTree (tree, step, scopeId, isExternalCall, isCreation) { return { outStep: step, error: 'InternalCallTree - No source Location. ' + step } } const stepDetail: StepDetail = tree.traceManager.trace[step] - tree.locationAndOpcodePerVMTraceIndex[step] = { ...sourceLocation, ...stepDetail } + tree.locationAndOpcodePerVMTraceIndex[step] = { sourceLocation, stepDetail } console.log('locationAndOpcodePerVMTraceIndex', stepDetail) const isCallInstrn = isCallInstruction(stepDetail) const isCreateInstrn = isCreateInstruction(stepDetail) diff --git a/libs/remix-debug/src/source/sourceLocationTracker.ts b/libs/remix-debug/src/source/sourceLocationTracker.ts index d870f6c2d4..92abb2ea00 100644 --- a/libs/remix-debug/src/source/sourceLocationTracker.ts +++ b/libs/remix-debug/src/source/sourceLocationTracker.ts @@ -96,17 +96,23 @@ export class SourceLocationTracker { async getValidSourceLocationFromVMTraceIndexFromCache (address: string, vmtraceStepIndex: number, contracts: any, cache: Map) { const amountOfSources = this.getTotalAmountOfSources(address, contracts) - let map: Record = { file: -1 } + let map: any = { file: -1 } /* (map.file === -1) this indicates that it isn't associated with a known source code (map.file > amountOfSources - 1) this indicates the current file index exceed the total number of files. this happens when generated sources should not be considered. */ + const originStep = cache[vmtraceStepIndex] + const nextStep = cache[vmtraceStepIndex + 1] + if (nextStep && originStep) { + originStep.stepDetail.gasCost = originStep.stepDetail.gas - nextStep.stepDetail.gas + } while (vmtraceStepIndex >= 0 && (map.file === -1 || map.file > amountOfSources - 1)) { - map = cache[vmtraceStepIndex] + map = cache[vmtraceStepIndex].sourceLocation vmtraceStepIndex = vmtraceStepIndex - 1 + originStep.sourceLocation = map } - return map + return originStep } clearCache () { diff --git a/libs/remix-ui/debugger-ui/src/lib/debugger-ui.tsx b/libs/remix-ui/debugger-ui/src/lib/debugger-ui.tsx index 2a1be745d1..45bd8a4aae 100644 --- a/libs/remix-ui/debugger-ui/src/lib/debugger-ui.tsx +++ b/libs/remix-ui/debugger-ui/src/lib/debugger-ui.tsx @@ -121,7 +121,7 @@ export const DebuggerUI = (props: DebuggerUIProps) => { }) }) - debuggerInstance.event.register('newSourceLocation', async (lineColumnPos, rawLocation, generatedSources, address) => { + debuggerInstance.event.register('newSourceLocation', async (lineColumnPos, rawLocation, generatedSources, address, stepDetail) => { if (!lineColumnPos) { await debuggerModule.discardHighlight() setState(prevState => { @@ -158,7 +158,7 @@ export const DebuggerUI = (props: DebuggerUIProps) => { return { ...prevState, sourceLocationStatus: '' } }) await debuggerModule.discardHighlight() - await debuggerModule.highlight(lineColumnPos, path, rawLocation) + await debuggerModule.highlight(lineColumnPos, path, rawLocation, stepDetail) } } }) diff --git a/libs/remix-ui/debugger-ui/src/lib/idebugger-api.ts b/libs/remix-ui/debugger-ui/src/lib/idebugger-api.ts index c0065558c9..e8100dde9b 100644 --- a/libs/remix-ui/debugger-ui/src/lib/idebugger-api.ts +++ b/libs/remix-ui/debugger-ui/src/lib/idebugger-api.ts @@ -44,7 +44,7 @@ export interface IDebuggerApi { onEditorContentChanged: (listener: onEditorContentChanged) => void onEnvChanged: (listener: onEnvChangedListener) => void discardHighlight: () => Promise - highlight: (lineColumnPos: LineColumnLocation, path: string, rawLocation: any) => Promise + highlight: (lineColumnPos: LineColumnLocation, path: string, rawLocation: any, stepDetail: any) => Promise fetchContractAndCompile: (address: string, currentReceipt: TransactionReceipt) => Promise getFile: (path: string) => Promise setFile: (path: string, content: string) => Promise From 0c8646696b1eba0c33f99b1f45e0d7fbb002b1e6 Mon Sep 17 00:00:00 2001 From: yann300 Date: Thu, 27 Oct 2022 20:04:42 +0200 Subject: [PATCH 03/40] fix label --- apps/debugger/src/app/debugger-api.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/debugger/src/app/debugger-api.ts b/apps/debugger/src/app/debugger-api.ts index 0f9293b8e8..f7ab74696a 100644 --- a/apps/debugger/src/app/debugger-api.ts +++ b/apps/debugger/src/app/debugger-api.ts @@ -50,7 +50,7 @@ export const DebuggerApiMixin = (Base) => class extends Base { async highlight (lineColumnPos, path, rawLocation, stepDetail) { await this.call('editor', 'highlight', lineColumnPos, path, '', { focus: true }) - const label = `${stepDetail.op} - ${stepDetail.gasCost} gas - ${stepDetail.gas} gas left` + const label = `${stepDetail.op} costs ${stepDetail.gasCost} gas - ${stepDetail.gas} gas left` const linetext: lineText = { content: label, position: lineColumnPos, From 935e781d2fc783915f27ffd1b2bc7d096ee30d1e Mon Sep 17 00:00:00 2001 From: yann300 Date: Thu, 27 Oct 2022 20:05:18 +0200 Subject: [PATCH 04/40] display gas cost at function scope --- .../src/solidity-decoder/internalCallTree.ts | 15 +++++++++++---- .../src/source/sourceLocationTracker.ts | 4 ---- .../src/lib/vm-debugger/vm-debugger-head.tsx | 2 +- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/libs/remix-debug/src/solidity-decoder/internalCallTree.ts b/libs/remix-debug/src/solidity-decoder/internalCallTree.ts index 2d7e6b7329..ca5cadae0e 100644 --- a/libs/remix-debug/src/solidity-decoder/internalCallTree.ts +++ b/libs/remix-debug/src/solidity-decoder/internalCallTree.ts @@ -10,7 +10,7 @@ import { Uint } from './types/Uint' export type StepDetail = { depth: number, - gas: number, + gas: number | string, gasCost: number, memory: number[], op: string, @@ -138,6 +138,7 @@ export class InternalCallTree { const scope = this.findScope(vmtraceIndex) if (!scope) return [] let scopeId = this.scopeStarts[scope.firstStep] + const scopeDetail = this.scopes[scopeId] const functions = [] if (!scopeId) return functions let i = 0 @@ -147,7 +148,7 @@ export class InternalCallTree { if (i > 1000) throw new Error('retrieFunctionStack: recursion too deep') const functionDefinition = this.functionDefinitionsByScope[scopeId] if (functionDefinition !== undefined) { - functions.push(functionDefinition) + functions.push({ ...functionDefinition, ...scopeDetail }) } const parent = this.parentScope(scopeId) if (!parent) break @@ -185,7 +186,7 @@ export class InternalCallTree { async function buildTree (tree, step, scopeId, isExternalCall, isCreation) { let subScope = 1 tree.scopeStarts[step] = scopeId - tree.scopes[scopeId] = { firstStep: step, locals: {}, isCreation } + tree.scopes[scopeId] = { firstStep: step, locals: {}, isCreation, gasCost: 0 } function callDepthChange (step, trace) { if (step + 1 < trace.length) { @@ -222,8 +223,14 @@ async function buildTree (tree, step, scopeId, isExternalCall, isCreation) { return { outStep: step, error: 'InternalCallTree - No source Location. ' + step } } const stepDetail: StepDetail = tree.traceManager.trace[step] + const nextStepDetail: StepDetail = tree.traceManager.trace[step + 1] + if (stepDetail && nextStepDetail) { + stepDetail.gasCost = parseInt(stepDetail.gas as string) - parseInt(nextStepDetail.gas as string) + } tree.locationAndOpcodePerVMTraceIndex[step] = { sourceLocation, stepDetail } - console.log('locationAndOpcodePerVMTraceIndex', stepDetail) + tree.scopes[scopeId].gasCost += stepDetail.gasCost + console.log(step, stepDetail.op, stepDetail.gas, nextStepDetail.gas) + const isCallInstrn = isCallInstruction(stepDetail) const isCreateInstrn = isCreateInstruction(stepDetail) // we are checking if we are jumping in a new CALL or in an internal function diff --git a/libs/remix-debug/src/source/sourceLocationTracker.ts b/libs/remix-debug/src/source/sourceLocationTracker.ts index 92abb2ea00..94264c5801 100644 --- a/libs/remix-debug/src/source/sourceLocationTracker.ts +++ b/libs/remix-debug/src/source/sourceLocationTracker.ts @@ -103,10 +103,6 @@ export class SourceLocationTracker { this happens when generated sources should not be considered. */ const originStep = cache[vmtraceStepIndex] - const nextStep = cache[vmtraceStepIndex + 1] - if (nextStep && originStep) { - originStep.stepDetail.gasCost = originStep.stepDetail.gas - nextStep.stepDetail.gas - } while (vmtraceStepIndex >= 0 && (map.file === -1 || map.file > amountOfSources - 1)) { map = cache[vmtraceStepIndex].sourceLocation vmtraceStepIndex = vmtraceStepIndex - 1 diff --git a/libs/remix-ui/debugger-ui/src/lib/vm-debugger/vm-debugger-head.tsx b/libs/remix-ui/debugger-ui/src/lib/vm-debugger/vm-debugger-head.tsx index 4402e7a57c..0ffc505833 100644 --- a/libs/remix-ui/debugger-ui/src/lib/vm-debugger/vm-debugger-head.tsx +++ b/libs/remix-ui/debugger-ui/src/lib/vm-debugger/vm-debugger-head.tsx @@ -31,7 +31,7 @@ export const VmDebuggerHead = ({ vmDebugger: { registerEvent, triggerEvent } }) const functions = [] for (const func of stack) { - functions.push(func.functionDefinition.name + '(' + func.inputs.join(', ') + ')') + functions.push(func.functionDefinition.name + '(' + func.inputs.join(', ') + ')' + ' - ' + func.gasCost + ' gas') } setFunctionPanel(() => functions) }) From 4d20da27598971dba32add09a6cd44843bce5a17 Mon Sep 17 00:00:00 2001 From: yann300 Date: Sun, 13 Nov 2022 15:40:36 +0100 Subject: [PATCH 05/40] gas cost per line --- apps/debugger/src/app/debugger-api.ts | 10 ++------ libs/remix-debug/src/Ethdebugger.ts | 10 +++++--- libs/remix-debug/src/debugger/debugger.ts | 12 +++++++-- .../src/solidity-decoder/internalCallTree.ts | 25 +++++++++++++++++-- .../src/solidity-decoder/solidityProxy.ts | 7 ++++-- .../remix-app/components/modals/matomo.tsx | 5 ++++ .../debugger-ui/src/lib/debugger-ui.tsx | 9 ++++--- .../debugger-ui/src/lib/idebugger-api.ts | 2 +- 8 files changed, 58 insertions(+), 22 deletions(-) diff --git a/apps/debugger/src/app/debugger-api.ts b/apps/debugger/src/app/debugger-api.ts index f7ab74696a..25530b9d0f 100644 --- a/apps/debugger/src/app/debugger-api.ts +++ b/apps/debugger/src/app/debugger-api.ts @@ -1,13 +1,7 @@ import Web3 from 'web3' import remixDebug, { TransactionDebugger as Debugger } from '@remix-project/remix-debug' -<<<<<<< HEAD import { CompilerAbstract } from '@remix-project/remix-solidity' - -======= -import { CompilationOutput, Sources } from '@remix-ui/debugger-ui' import { lineText } from '@remix-ui/editor' -import type { CompilationResult } from '@remix-project/remix-solidity-ts' ->>>>>>> show inline gas cost export const DebuggerApiMixin = (Base) => class extends Base { @@ -48,9 +42,9 @@ export const DebuggerApiMixin = (Base) => class extends Base { await this.call('editor', 'discardLineTexts' as any) } - async highlight (lineColumnPos, path, rawLocation, stepDetail) { + async highlight (lineColumnPos, path, rawLocation, stepDetail, lineGasCost) { await this.call('editor', 'highlight', lineColumnPos, path, '', { focus: true }) - const label = `${stepDetail.op} costs ${stepDetail.gasCost} gas - ${stepDetail.gas} gas left` + const label = `${stepDetail.op} costs ${stepDetail.gasCost} gas - this line costs ${lineGasCost} gas - ${stepDetail.gas} total gas left` const linetext: lineText = { content: label, position: lineColumnPos, diff --git a/libs/remix-debug/src/Ethdebugger.ts b/libs/remix-debug/src/Ethdebugger.ts index 644a98e57a..f04a740e34 100644 --- a/libs/remix-debug/src/Ethdebugger.ts +++ b/libs/remix-debug/src/Ethdebugger.ts @@ -32,9 +32,11 @@ export class Ethdebugger { storageResolver callTree breakpointManager + offsetToLineColumnConverter constructor (opts) { this.compilationResult = opts.compilationResult || function (contractAddress) { return null } + this.offsetToLineColumnConverter = opts.offsetToLineColumnConverter this.web3 = opts.web3 this.opts = opts @@ -49,7 +51,8 @@ export class Ethdebugger { this.traceManager, this.solidityProxy, this.codeManager, - { ...opts, includeLocalVariables }) + { ...opts, includeLocalVariables }, + this.offsetToLineColumnConverter) } setManagers () { @@ -63,7 +66,8 @@ export class Ethdebugger { this.traceManager, this.solidityProxy, this.codeManager, - { ...this.opts, includeLocalVariables }) + { ...this.opts, includeLocalVariables }, + this.offsetToLineColumnConverter) } resolveStep (index) { @@ -71,7 +75,7 @@ export class Ethdebugger { } setCompilationResult (compilationResult) { - this.solidityProxy.reset((compilationResult && compilationResult.data) || {}) + this.solidityProxy.reset((compilationResult && compilationResult.data) || {}, (compilationResult && compilationResult.source && compilationResult.source.sources) || {}) } async sourceLocationFromVMTraceIndex (address, stepIndex) { diff --git a/libs/remix-debug/src/debugger/debugger.ts b/libs/remix-debug/src/debugger/debugger.ts index 218cc58cc2..8327eb167a 100644 --- a/libs/remix-debug/src/debugger/debugger.ts +++ b/libs/remix-debug/src/debugger/debugger.ts @@ -26,7 +26,8 @@ export class Debugger { this.debugger = new Ethdebugger({ web3: options.web3, debugWithGeneratedSources: options.debugWithGeneratedSources, - compilationResult: this.compilationResult + compilationResult: this.compilationResult, + offsetToLineColumnConverter: this.offsetToLineColumnConverter }) const { traceManager, callTree, solidityProxy } = this.debugger @@ -90,7 +91,14 @@ export class Debugger { } } const lineColumnPos = await this.offsetToLineColumnConverter.offsetToLineColumn(rawLocation, rawLocation.file, sources, astSources) - this.event.trigger('newSourceLocation', [lineColumnPos, rawLocation, generatedSources, address, stepDetail]) + + let lineGasCost = -1 + try { + lineGasCost = await this.debugger.callTree.getGasCostPerLine(rawLocation.file, lineColumnPos.start.line) + } catch (e) { + console.log(e) + } + this.event.trigger('newSourceLocation', [lineColumnPos, rawLocation, generatedSources, address, stepDetail, lineGasCost]) this.vmDebuggerLogic.event.trigger('sourceLocationChanged', [rawLocation]) } else { this.event.trigger('newSourceLocation', [null]) diff --git a/libs/remix-debug/src/solidity-decoder/internalCallTree.ts b/libs/remix-debug/src/solidity-decoder/internalCallTree.ts index ca5cadae0e..c1f824e3a1 100644 --- a/libs/remix-debug/src/solidity-decoder/internalCallTree.ts +++ b/libs/remix-debug/src/solidity-decoder/internalCallTree.ts @@ -41,6 +41,8 @@ export class InternalCallTree { locationAndOpcodePerVMTraceIndex: { [Key: number]: any } + gasCostPerLine + offsetToLineColumnConverter /** * constructor @@ -51,12 +53,13 @@ export class InternalCallTree { * @param {Object} codeManager - code manager * @param {Object} opts - { includeLocalVariables, debugWithGeneratedSources } */ - constructor (debuggerEvent, traceManager, solidityProxy, codeManager, opts) { + constructor (debuggerEvent, traceManager, solidityProxy, codeManager, opts, offsetToLineColumnConverter?) { this.includeLocalVariables = opts.includeLocalVariables this.debugWithGeneratedSources = opts.debugWithGeneratedSources this.event = new EventManager() this.solidityProxy = solidityProxy this.traceManager = traceManager + this.offsetToLineColumnConverter = offsetToLineColumnConverter this.sourceLocationTracker = new SourceLocationTracker(codeManager, { debugWithGeneratedSources: opts.debugWithGeneratedSources }) debuggerEvent.register('newTraceLoaded', (trace) => { this.reset() @@ -99,6 +102,7 @@ export class InternalCallTree { this.functionCallStack = [] this.functionDefinitionsByScope = {} this.scopeStarts = {} + this.gasCostPerLine = {} this.variableDeclarationByFile = {} this.functionDefinitionByFile = {} this.astWalker = new AstWalker() @@ -181,6 +185,13 @@ export class InternalCallTree { async getValidSourceLocationFromVMTraceIndexFromCache (address: string, step: number, contracts: any) { return await this.sourceLocationTracker.getValidSourceLocationFromVMTraceIndexFromCache(address, step, contracts, this.locationAndOpcodePerVMTraceIndex) } + + async getGasCostPerLine(file: number, line: number) { + if (this.gasCostPerLine[file] && this.gasCostPerLine[file][line]) { + return this.gasCostPerLine[file][line] + } + throw new Error('Could not find gas cost per line') + } } async function buildTree (tree, step, scopeId, isExternalCall, isCreation) { @@ -211,6 +222,7 @@ async function buildTree (tree, step, scopeId, isExternalCall, isCreation) { let newLocation = false try { sourceLocation = await tree.extractSourceLocation(step) + if (!includedSource(sourceLocation, currentSourceLocation)) { tree.reducedTrace.push(step) currentSourceLocation = sourceLocation @@ -229,7 +241,16 @@ async function buildTree (tree, step, scopeId, isExternalCall, isCreation) { } tree.locationAndOpcodePerVMTraceIndex[step] = { sourceLocation, stepDetail } tree.scopes[scopeId].gasCost += stepDetail.gasCost - console.log(step, stepDetail.op, stepDetail.gas, nextStepDetail.gas) + + // gas per line + if (tree.offsetToLineColumnConverter) { + try { + const lineColumnPos = await tree.offsetToLineColumnConverter.offsetToLineColumn(sourceLocation, sourceLocation.file, tree.solidityProxy.sourcesCode, tree.solidityProxy.sources) + if (!tree.gasCostPerLine[sourceLocation.file]) tree.gasCostPerLine[sourceLocation.file] = {} + if (!tree.gasCostPerLine[sourceLocation.file][lineColumnPos.start.line]) tree.gasCostPerLine[sourceLocation.file][lineColumnPos.start.line] = 0 + tree.gasCostPerLine[sourceLocation.file][lineColumnPos.start.line] += stepDetail.gasCost + } catch (e) {} + } const isCallInstrn = isCallInstruction(stepDetail) const isCreateInstrn = isCreateInstruction(stepDetail) diff --git a/libs/remix-debug/src/solidity-decoder/solidityProxy.ts b/libs/remix-debug/src/solidity-decoder/solidityProxy.ts index b9c27dca82..5ea06ddabf 100644 --- a/libs/remix-debug/src/solidity-decoder/solidityProxy.ts +++ b/libs/remix-debug/src/solidity-decoder/solidityProxy.ts @@ -10,6 +10,8 @@ export class SolidityProxy { getCode sources contracts + compilationResult + sourcesCode constructor ({ getCurrentCalledAddressAt, getCode }) { this.cache = new Cache() @@ -23,9 +25,10 @@ export class SolidityProxy { * * @param {Object} compilationResult - result os a compilatiion (diectly returned by the compiler) */ - reset (compilationResult) { - this.sources = compilationResult.sources + reset (compilationResult, sources?) { + this.sources = compilationResult.sources // ast this.contracts = compilationResult.contracts + if (sources) this.sourcesCode = sources this.cache.reset() } diff --git a/libs/remix-ui/app/src/lib/remix-app/components/modals/matomo.tsx b/libs/remix-ui/app/src/lib/remix-app/components/modals/matomo.tsx index 4d4daaed3e..276a3ee47b 100644 --- a/libs/remix-ui/app/src/lib/remix-app/components/modals/matomo.tsx +++ b/libs/remix-ui/app/src/lib/remix-app/components/modals/matomo.tsx @@ -1,6 +1,11 @@ import React, { useContext, useEffect, useState } from 'react' import { AppContext } from '../../context/context' import { useDialogDispatchers } from '../../context/provider' +declare global { + interface Window { + _paq: any + } +} const _paq = window._paq = window._paq || [] const MatomoDialog = (props) => { diff --git a/libs/remix-ui/debugger-ui/src/lib/debugger-ui.tsx b/libs/remix-ui/debugger-ui/src/lib/debugger-ui.tsx index 45bd8a4aae..81f2ea218c 100644 --- a/libs/remix-ui/debugger-ui/src/lib/debugger-ui.tsx +++ b/libs/remix-ui/debugger-ui/src/lib/debugger-ui.tsx @@ -121,7 +121,7 @@ export const DebuggerUI = (props: DebuggerUIProps) => { }) }) - debuggerInstance.event.register('newSourceLocation', async (lineColumnPos, rawLocation, generatedSources, address, stepDetail) => { + debuggerInstance.event.register('newSourceLocation', async (lineColumnPos, rawLocation, generatedSources, address, stepDetail, lineGasCost) => { if (!lineColumnPos) { await debuggerModule.discardHighlight() setState(prevState => { @@ -158,7 +158,7 @@ export const DebuggerUI = (props: DebuggerUIProps) => { return { ...prevState, sourceLocationStatus: '' } }) await debuggerModule.discardHighlight() - await debuggerModule.highlight(lineColumnPos, path, rawLocation, stepDetail) + await debuggerModule.highlight(lineColumnPos, path, rawLocation, stepDetail, lineGasCost) } } }) @@ -266,13 +266,14 @@ export const DebuggerUI = (props: DebuggerUIProps) => { console.log(e.message) } + const localCache = {} const debuggerInstance = new Debugger({ web3, offsetToLineColumnConverter: debuggerModule.offsetToLineColumnConverter, compilationResult: async (address) => { try { - const ret = await debuggerModule.fetchContractAndCompile(address, currentReceipt) - return ret + if (!localCache[address]) localCache[address] = await debuggerModule.fetchContractAndCompile(address, currentReceipt) + return localCache[address] } catch (e) { // debuggerModule.showMessage('Debugging error', 'Unable to fetch a transaction.') console.error(e) diff --git a/libs/remix-ui/debugger-ui/src/lib/idebugger-api.ts b/libs/remix-ui/debugger-ui/src/lib/idebugger-api.ts index e8100dde9b..1b0d19c3d4 100644 --- a/libs/remix-ui/debugger-ui/src/lib/idebugger-api.ts +++ b/libs/remix-ui/debugger-ui/src/lib/idebugger-api.ts @@ -44,7 +44,7 @@ export interface IDebuggerApi { onEditorContentChanged: (listener: onEditorContentChanged) => void onEnvChanged: (listener: onEnvChangedListener) => void discardHighlight: () => Promise - highlight: (lineColumnPos: LineColumnLocation, path: string, rawLocation: any, stepDetail: any) => Promise + highlight: (lineColumnPos: LineColumnLocation, path: string, rawLocation: any, stepDetail: any, highlight: any) => Promise fetchContractAndCompile: (address: string, currentReceipt: TransactionReceipt) => Promise getFile: (path: string) => Promise setFile: (path: string, content: string) => Promise From badb4e1aa9b550602b48062a91e187c7644735a4 Mon Sep 17 00:00:00 2001 From: yann300 Date: Thu, 17 Nov 2022 10:09:04 +0100 Subject: [PATCH 06/40] use validSourceLocation --- .../src/solidity-decoder/internalCallTree.ts | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/libs/remix-debug/src/solidity-decoder/internalCallTree.ts b/libs/remix-debug/src/solidity-decoder/internalCallTree.ts index c1f824e3a1..7dfbdf0dce 100644 --- a/libs/remix-debug/src/solidity-decoder/internalCallTree.ts +++ b/libs/remix-debug/src/solidity-decoder/internalCallTree.ts @@ -161,22 +161,17 @@ export class InternalCallTree { return functions } - async extractSourceLocation (step) { + async extractSourceLocation (step: number, address: string) { try { - const address = this.traceManager.getCurrentCalledAddressAt(step) - const location = await this.sourceLocationTracker.getSourceLocationFromVMTraceIndex(address, step, this.solidityProxy.contracts) - - return location + return await this.sourceLocationTracker.getSourceLocationFromVMTraceIndex(address, step, this.solidityProxy.contracts) } catch (error) { throw new Error('InternalCallTree - Cannot retrieve sourcelocation for step ' + step + ' ' + error) } } - async extractValidSourceLocation (step) { + async extractValidSourceLocation (step: number, address: string) { try { - const address = this.traceManager.getCurrentCalledAddressAt(step) - const location = await this.sourceLocationTracker.getValidSourceLocationFromVMTraceIndex(address, step, this.solidityProxy.contracts) - return location + return await this.sourceLocationTracker.getValidSourceLocationFromVMTraceIndex(address, step, this.solidityProxy.contracts) } catch (error) { throw new Error('InternalCallTree - Cannot retrieve valid sourcelocation for step ' + step + ' ' + error) } @@ -219,10 +214,13 @@ async function buildTree (tree, step, scopeId, isExternalCall, isCreation) { let previousSourceLocation = currentSourceLocation while (step < tree.traceManager.trace.length) { let sourceLocation + let validSourceLocation let newLocation = false try { - sourceLocation = await tree.extractSourceLocation(step) - + const address = this.traceManager.getCurrentCalledAddressAt(step) + sourceLocation = await tree.extractSourceLocation(step, address) + validSourceLocation = await tree.extractValidSourceLocation(step, address) + if (!includedSource(sourceLocation, currentSourceLocation)) { tree.reducedTrace.push(step) currentSourceLocation = sourceLocation @@ -245,11 +243,15 @@ async function buildTree (tree, step, scopeId, isExternalCall, isCreation) { // gas per line if (tree.offsetToLineColumnConverter) { try { - const lineColumnPos = await tree.offsetToLineColumnConverter.offsetToLineColumn(sourceLocation, sourceLocation.file, tree.solidityProxy.sourcesCode, tree.solidityProxy.sources) - if (!tree.gasCostPerLine[sourceLocation.file]) tree.gasCostPerLine[sourceLocation.file] = {} - if (!tree.gasCostPerLine[sourceLocation.file][lineColumnPos.start.line]) tree.gasCostPerLine[sourceLocation.file][lineColumnPos.start.line] = 0 - tree.gasCostPerLine[sourceLocation.file][lineColumnPos.start.line] += stepDetail.gasCost - } catch (e) {} + const lineColumnPos = await tree.offsetToLineColumnConverter.offsetToLineColumn(validSourceLocation, validSourceLocation.file, tree.solidityProxy.sourcesCode, tree.solidityProxy.sources) + if (!tree.gasCostPerLine[validSourceLocation.file]) tree.gasCostPerLine[validSourceLocation.file] = {} + if (!tree.gasCostPerLine[validSourceLocation.file][lineColumnPos.start.line]) { + tree.gasCostPerLine[validSourceLocation.file][lineColumnPos.start.line] = 0 + } + tree.gasCostPerLine[validSourceLocation.file][lineColumnPos.start.line] += stepDetail.gasCost + } catch (e) { + console.log(e) + } } const isCallInstrn = isCallInstruction(stepDetail) From 69cc4eb1a59da52c387e99fb12d0b70d8c6d2970 Mon Sep 17 00:00:00 2001 From: yann300 Date: Thu, 17 Nov 2022 11:51:42 +0100 Subject: [PATCH 07/40] fix getting contract name of struct --- libs/remix-debug/src/solidity-decoder/decodeInfo.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/libs/remix-debug/src/solidity-decoder/decodeInfo.ts b/libs/remix-debug/src/solidity-decoder/decodeInfo.ts index f141a7e1f9..a66434a2b7 100644 --- a/libs/remix-debug/src/solidity-decoder/decodeInfo.ts +++ b/libs/remix-debug/src/solidity-decoder/decodeInfo.ts @@ -241,9 +241,7 @@ function getStructMembers (type, stateDefinitions, contractName, location) { if (type.indexOf('.') === -1) { type = contractName + '.' + type } - if (!contractName) { - contractName = type.split('.')[0] - } + contractName = type.split('.')[0] const state = stateDefinitions[contractName] if (state) { for (const dec of state.stateDefinitions) { From ec1c53a39528717dee8d108fe461a25aa1c48782 Mon Sep 17 00:00:00 2001 From: yann300 Date: Thu, 17 Nov 2022 12:02:24 +0100 Subject: [PATCH 08/40] fix assigning the type property --- libs/remix-debug/src/solidity-decoder/localDecoder.ts | 2 +- libs/remix-debug/src/solidity-decoder/types/StringType.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/remix-debug/src/solidity-decoder/localDecoder.ts b/libs/remix-debug/src/solidity-decoder/localDecoder.ts index 4d697e9cc4..d333622408 100644 --- a/libs/remix-debug/src/solidity-decoder/localDecoder.ts +++ b/libs/remix-debug/src/solidity-decoder/localDecoder.ts @@ -21,7 +21,7 @@ export async function solidityLocals (vmtraceIndex, internalTreeCall, stack, mem locals[name] = await variable.type.decodeFromStack(variable.stackDepth, stack, memory, storageResolver, calldata, cursor, variable) } catch (e) { console.log(e) - locals[name] = { error: '' } + locals[name] = { error: '', type: variable && variable.type && variable.type.typeName || 'unknown' } } } } diff --git a/libs/remix-debug/src/solidity-decoder/types/StringType.ts b/libs/remix-debug/src/solidity-decoder/types/StringType.ts index 849b8f65a3..84c6483e8e 100644 --- a/libs/remix-debug/src/solidity-decoder/types/StringType.ts +++ b/libs/remix-debug/src/solidity-decoder/types/StringType.ts @@ -25,7 +25,7 @@ export class StringType extends DynamicByteArray { return await super.decodeFromStack(stackDepth, stack, memory, storageResolver, calldata, cursor, variableDetails) } catch (e) { console.log(e) - return { error: '' } + return { error: '', type: this.typeName } } } From 096d24eb023f1a8fd08daf8b58478a19a7094c5f Mon Sep 17 00:00:00 2001 From: yann300 Date: Thu, 17 Nov 2022 12:03:33 +0100 Subject: [PATCH 09/40] fix retrieve the current address --- .../remix-debug/src/solidity-decoder/internalCallTree.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/libs/remix-debug/src/solidity-decoder/internalCallTree.ts b/libs/remix-debug/src/solidity-decoder/internalCallTree.ts index 7dfbdf0dce..965ead72d8 100644 --- a/libs/remix-debug/src/solidity-decoder/internalCallTree.ts +++ b/libs/remix-debug/src/solidity-decoder/internalCallTree.ts @@ -6,7 +6,6 @@ import { EventManager } from '../eventManager' import { parseType } from './decodeInfo' import { isContractCreation, isCallInstruction, isCreateInstruction, isJumpDestInstruction } from '../trace/traceHelper' import { extractLocationFromAstVariable } from './types/util' -import { Uint } from './types/Uint' export type StepDetail = { depth: number, @@ -161,16 +160,18 @@ export class InternalCallTree { return functions } - async extractSourceLocation (step: number, address: string) { + async extractSourceLocation (step: number, address?: string) { try { + if (!address) address = this.traceManager.getCurrentCalledAddressAt(step) return await this.sourceLocationTracker.getSourceLocationFromVMTraceIndex(address, step, this.solidityProxy.contracts) } catch (error) { throw new Error('InternalCallTree - Cannot retrieve sourcelocation for step ' + step + ' ' + error) } } - async extractValidSourceLocation (step: number, address: string) { + async extractValidSourceLocation (step: number, address?: string) { try { + if (!address) address = this.traceManager.getCurrentCalledAddressAt(step) return await this.sourceLocationTracker.getValidSourceLocationFromVMTraceIndex(address, step, this.solidityProxy.contracts) } catch (error) { throw new Error('InternalCallTree - Cannot retrieve valid sourcelocation for step ' + step + ' ' + error) @@ -217,7 +218,7 @@ async function buildTree (tree, step, scopeId, isExternalCall, isCreation) { let validSourceLocation let newLocation = false try { - const address = this.traceManager.getCurrentCalledAddressAt(step) + const address = tree.traceManager.getCurrentCalledAddressAt(step) sourceLocation = await tree.extractSourceLocation(step, address) validSourceLocation = await tree.extractValidSourceLocation(step, address) From d05ffe3b821c9baf6140b6ddbe1fd66bace8422f Mon Sep 17 00:00:00 2001 From: yann300 Date: Thu, 17 Nov 2022 12:03:56 +0100 Subject: [PATCH 10/40] add test for getGasCostPerLine and function gasCost --- libs/remix-debug/test/decoder/localDecoder.ts | 2 +- .../test/decoder/localsTests/int.ts | 24 +++++++++++++++---- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/libs/remix-debug/test/decoder/localDecoder.ts b/libs/remix-debug/test/decoder/localDecoder.ts index fc79752e08..fdeb743683 100644 --- a/libs/remix-debug/test/decoder/localDecoder.ts +++ b/libs/remix-debug/test/decoder/localDecoder.ts @@ -23,7 +23,7 @@ tape('solidity', function (t) { async function test (st, privateKey) { var output = compiler.compile(compilerInput(intLocal.contract)) output = JSON.parse(output) - await intLocalTest(st, privateKey, output.contracts['test.sol']['intLocal'].evm.bytecode.object, output) + await intLocalTest(st, privateKey, output.contracts['test.sol']['intLocal'].evm.bytecode.object, output, intLocal.contract) output = compiler.compile(compilerInput(miscLocal.contract)) output = JSON.parse(output) await miscLocalTest(st, privateKey, output.contracts['test.sol']['miscLocal'].evm.bytecode.object, output) diff --git a/libs/remix-debug/test/decoder/localsTests/int.ts b/libs/remix-debug/test/decoder/localsTests/int.ts index 9bf1c4307f..2360c1cf12 100644 --- a/libs/remix-debug/test/decoder/localsTests/int.ts +++ b/libs/remix-debug/test/decoder/localsTests/int.ts @@ -7,9 +7,10 @@ import { contractCreationToken } from '../../../src/trace/traceHelper' import { SolidityProxy } from '../../../src/solidity-decoder/solidityProxy' import { InternalCallTree } from '../../../src/solidity-decoder/internalCallTree' import { EventManager } from '../../../src/eventManager' +import * as sourceMappingDecoder from '../../../src/source/sourceMappingDecoder' import * as helper from './helper' -module.exports = function (st, privateKey, contractBytecode, compilationResult) { +module.exports = function (st, privateKey, contractBytecode, compilationResult, contractCode) { return new Promise(async (resolve) => { let web3 = await (vmCall as any).getWeb3(); (vmCall as any).sendTx(web3, { nonce: 0, privateKey: privateKey }, null, 0, contractBytecode, function (error, hash) { @@ -27,22 +28,37 @@ module.exports = function (st, privateKey, contractBytecode, compilationResult) var solidityProxy = new SolidityProxy({ getCurrentCalledAddressAt: traceManager.getCurrentCalledAddressAt.bind(traceManager), getCode: codeManager.getCode.bind(codeManager) }) solidityProxy.reset(compilationResult) var debuggerEvent = new EventManager() - var callTree = new InternalCallTree(debuggerEvent, traceManager, solidityProxy, codeManager, { includeLocalVariables: true }) + const offsetToLineColumnConverter = { + offsetToLineColumn: (rawLocation) => { + return new Promise((resolve) => { + const lineBreaks = sourceMappingDecoder.getLinebreakPositions(contractCode) + resolve(sourceMappingDecoder.convertOffsetToLineColumn(rawLocation, lineBreaks)) + }) + } + } + var callTree = new InternalCallTree(debuggerEvent, traceManager, solidityProxy, codeManager, { includeLocalVariables: true }, offsetToLineColumnConverter) callTree.event.register('callTreeBuildFailed', (error) => { st.fail(error) }) callTree.event.register('callTreeNotReady', (reason) => { st.fail(reason) }) - callTree.event.register('callTreeReady', (scopes, scopeStarts) => { + callTree.event.register('callTreeReady', async (scopes, scopeStarts) => { try { + + // test gas cost per line + st.equals(await callTree.getGasCostPerLine(0, 16), 11) + st.equals(await callTree.getGasCostPerLine(0, 32), 60) + let functions1 = callTree.retrieveFunctionsStack(102) let functions2 = callTree.retrieveFunctionsStack(115) let functions3 = callTree.retrieveFunctionsStack(13) - + st.equals(functions1.length, 1) st.equals(functions2.length, 2) st.equals(functions3.length, 0) + + st.equal(functions1[0].gasCost, 54) st.equals(Object.keys(functions1[0])[0], 'functionDefinition') st.equals(Object.keys(functions1[0])[1], 'inputs') From 1a7773ca762eb8ceac7725c22e5e727cd1a922e7 Mon Sep 17 00:00:00 2001 From: yann300 Date: Thu, 17 Nov 2022 13:59:51 +0100 Subject: [PATCH 11/40] update e2e test --- apps/remix-ide-e2e/src/commands/waitForElementContainsText.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/remix-ide-e2e/src/commands/waitForElementContainsText.ts b/apps/remix-ide-e2e/src/commands/waitForElementContainsText.ts index 5376da014d..6ce01beaa0 100644 --- a/apps/remix-ide-e2e/src/commands/waitForElementContainsText.ts +++ b/apps/remix-ide-e2e/src/commands/waitForElementContainsText.ts @@ -4,8 +4,10 @@ import EventEmitter from 'events' class WaitForElementContainsText extends EventEmitter { command (this: NightwatchBrowser, id: string, value: string, timeout = 10000): NightwatchBrowser { let waitId // eslint-disable-line + let currentValue const runid = setInterval(() => { this.api.getText(id, (result) => { + currentValue = result.value if (typeof result.value === 'string' && result.value.indexOf(value) !== -1) { clearInterval(runid) clearTimeout(waitId) @@ -17,7 +19,7 @@ class WaitForElementContainsText extends EventEmitter { waitId = setTimeout(() => { clearInterval(runid) - this.api.assert.fail(`TimeoutError: An error occurred while running .waitForElementContainsText() command on ${id} after ${timeout} milliseconds`) + this.api.assert.fail(`TimeoutError: An error occurred while running .waitForElementContainsText() command on ${id} after ${timeout} milliseconds. expected: ${value} - got: ${currentValue}`) }, timeout) return this } From 52db3c7fd97a47096911b6e2e2cd0ef0a292089b Mon Sep 17 00:00:00 2001 From: yann300 Date: Thu, 17 Nov 2022 16:45:51 +0100 Subject: [PATCH 12/40] fix listenning event in debugger --- libs/remix-ui/debugger-ui/src/lib/debugger-ui.tsx | 4 ++-- .../debugger-ui/src/lib/vm-debugger/vm-debugger-head.tsx | 4 ++-- libs/remix-ui/debugger-ui/src/lib/vm-debugger/vm-debugger.tsx | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/libs/remix-ui/debugger-ui/src/lib/debugger-ui.tsx b/libs/remix-ui/debugger-ui/src/lib/debugger-ui.tsx index 81f2ea218c..3233461935 100644 --- a/libs/remix-ui/debugger-ui/src/lib/debugger-ui.tsx +++ b/libs/remix-ui/debugger-ui/src/lib/debugger-ui.tsx @@ -396,8 +396,8 @@ export const DebuggerUI = (props: DebuggerUIProps) => { { state.debugging && }
- { state.debugging && } - { state.debugging && } + { state.debugging && } + { state.debugging && }
) diff --git a/libs/remix-ui/debugger-ui/src/lib/vm-debugger/vm-debugger-head.tsx b/libs/remix-ui/debugger-ui/src/lib/vm-debugger/vm-debugger-head.tsx index 0ffc505833..aa195f0038 100644 --- a/libs/remix-ui/debugger-ui/src/lib/vm-debugger/vm-debugger-head.tsx +++ b/libs/remix-ui/debugger-ui/src/lib/vm-debugger/vm-debugger-head.tsx @@ -5,7 +5,7 @@ import StepDetail from './step-detail' // eslint-disable-line import SolidityState from './solidity-state' // eslint-disable-line import SolidityLocals from './solidity-locals' // eslint-disable-line -export const VmDebuggerHead = ({ vmDebugger: { registerEvent, triggerEvent } }) => { +export const VmDebuggerHead = ({ vmDebugger: { registerEvent, triggerEvent }, debugging }) => { const [functionPanel, setFunctionPanel] = useState(null) const [stepDetail, setStepDetail] = useState({ 'vm trace step': '-', @@ -95,7 +95,7 @@ export const VmDebuggerHead = ({ vmDebugger: { registerEvent, triggerEvent } }) return { ...solidityLocals, message } }) }) - }, [registerEvent]) + }, [debugging]) return (
diff --git a/libs/remix-ui/debugger-ui/src/lib/vm-debugger/vm-debugger.tsx b/libs/remix-ui/debugger-ui/src/lib/vm-debugger/vm-debugger.tsx index 8bf8307f2a..508097d5df 100644 --- a/libs/remix-ui/debugger-ui/src/lib/vm-debugger/vm-debugger.tsx +++ b/libs/remix-ui/debugger-ui/src/lib/vm-debugger/vm-debugger.tsx @@ -8,7 +8,7 @@ import ReturnValuesPanel from './dropdown-panel' // eslint-disable-line import FullStoragesChangesPanel from './full-storages-changes' // eslint-disable-line import GlobalVariables from './global-variables' // eslint-disable-line -export const VmDebugger = ({ vmDebugger: { registerEvent }, currentBlock, currentReceipt, currentTransaction }) => { +export const VmDebugger = ({ vmDebugger: { registerEvent }, currentBlock, currentReceipt, currentTransaction, debugging }) => { const [calldataPanel, setCalldataPanel] = useState(null) const [memoryPanel, setMemoryPanel] = useState(null) const [callStackPanel, setCallStackPanel] = useState(null) @@ -49,7 +49,7 @@ export const VmDebugger = ({ vmDebugger: { registerEvent }, currentBlock, curren registerEvent && registerEvent('traceStorageUpdate', (calldata) => { setFullStoragesChangesPanel(() => calldata) }) - }, [registerEvent]) + }, [debugging]) return (
From 81a74f4fb874f030abb939c16dddbed2868e86d2 Mon Sep 17 00:00:00 2001 From: yann300 Date: Mon, 21 Nov 2022 13:34:52 +0100 Subject: [PATCH 13/40] use ast to identify jump to high level function --- .../src/solidity-decoder/internalCallTree.ts | 103 +++++++++--------- 1 file changed, 51 insertions(+), 52 deletions(-) diff --git a/libs/remix-debug/src/solidity-decoder/internalCallTree.ts b/libs/remix-debug/src/solidity-decoder/internalCallTree.ts index 965ead72d8..8a5756d1ed 100644 --- a/libs/remix-debug/src/solidity-decoder/internalCallTree.ts +++ b/libs/remix-debug/src/solidity-decoder/internalCallTree.ts @@ -190,11 +190,15 @@ export class InternalCallTree { } } -async function buildTree (tree, step, scopeId, isExternalCall, isCreation) { +async function buildTree (tree, step, scopeId, isExternalCall, isCreation, functionDefinition?, contractObj?, sourceLocation?) { let subScope = 1 tree.scopeStarts[step] = scopeId tree.scopes[scopeId] = { firstStep: step, locals: {}, isCreation, gasCost: 0 } + if (functionDefinition) { + await registerFunctionParameters(tree, functionDefinition, step, scopeId, contractObj, sourceLocation) + } + function callDepthChange (step, trace) { if (step + 1 < trace.length) { return trace[step].depth !== trace[step + 1].depth @@ -211,7 +215,7 @@ async function buildTree (tree, step, scopeId, isExternalCall, isCreation) { included.file === source.file) } - let currentSourceLocation = { start: -1, length: -1, file: -1 } + let currentSourceLocation = { start: -1, length: -1, file: -1, jump: '-' } let previousSourceLocation = currentSourceLocation while (step < tree.traceManager.trace.length) { let sourceLocation @@ -255,12 +259,17 @@ async function buildTree (tree, step, scopeId, isExternalCall, isCreation) { } } + const contractObj = await tree.solidityProxy.contractObjectAt(step) + const generatedSources = getGeneratedSources(tree, scopeId, contractObj) + const functionDefinition = resolveFunctionDefinition(tree, sourceLocation, generatedSources) + const isCallInstrn = isCallInstruction(stepDetail) const isCreateInstrn = isCreateInstruction(stepDetail) // we are checking if we are jumping in a new CALL or in an internal function - if (isCallInstrn || sourceLocation.jump === 'i') { + if (isCallInstrn || (previousSourceLocation.jump === 'i' && functionDefinition)) { try { - const externalCallResult = await buildTree(tree, step + 1, scopeId === '' ? subScope.toString() : scopeId + '.' + subScope, isCallInstrn, isCreateInstrn) + const newScopeId = scopeId === '' ? subScope.toString() : scopeId + '.' + subScope + const externalCallResult = await buildTree(tree, step, newScopeId , isCallInstrn, isCreateInstrn, functionDefinition, contractObj, sourceLocation) if (externalCallResult.error) { return { outStep: step, error: 'InternalCallTree - ' + externalCallResult.error } } else { @@ -270,7 +279,7 @@ async function buildTree (tree, step, scopeId, isExternalCall, isCreation) { } catch (e) { return { outStep: step, error: 'InternalCallTree - ' + e.message } } - } else if ((isExternalCall && callDepthChange(step, tree.traceManager.trace)) || (!isExternalCall && sourceLocation.jump === 'o')) { + } else if (callDepthChange(step, tree.traceManager.trace) || (sourceLocation.jump === 'o' && functionDefinition)) { // if not, we might be returning from a CALL or internal function. This is what is checked here. tree.scopes[scopeId].lastStep = step return { outStep: step + 1 } @@ -278,7 +287,7 @@ async function buildTree (tree, step, scopeId, isExternalCall, isCreation) { // if not, we are in the current scope. // We check in `includeVariableDeclaration` if there is a new local variable in scope for this specific `step` if (tree.includeLocalVariables) { - await includeVariableDeclaration(tree, step, sourceLocation, scopeId, newLocation, previousSourceLocation) + await includeVariableDeclaration(tree, step, sourceLocation, scopeId, contractObj, generatedSources) } previousSourceLocation = sourceLocation step++ @@ -299,10 +308,43 @@ function getGeneratedSources (tree, scopeId, contractObj) { return null } -async function includeVariableDeclaration (tree, step, sourceLocation, scopeId, newLocation, previousSourceLocation) { - const contractObj = await tree.solidityProxy.contractObjectAt(step) +async function registerFunctionParameters (tree, functionDefinition, step, scopeId, contractObj, sourceLocation) { + tree.functionCallStack.push(step) + const functionDefinitionAndInputs = { functionDefinition, inputs: [] } + // means: the previous location was a function definition && JUMPDEST + // => we are at the beginning of the function and input/output are setup + + try { + const stack = tree.traceManager.getStackAt(step) + const states = tree.solidityProxy.extractStatesDefinitions() + if (functionDefinition.parameters) { + const inputs = functionDefinition.parameters + const outputs = functionDefinition.returnParameters + // for (const element of functionDefinition.parameters) { + // if (element.nodeType === 'ParameterList') { + // if (!inputs) inputs = element + // else { + // outputs = element + // break + // } + // } + // } + // input params + if (inputs && inputs.parameters) { + functionDefinitionAndInputs.inputs = addParams(inputs, tree, scopeId, states, contractObj, sourceLocation, stack.length, inputs.parameters.length, -1) + } + // output params + if (outputs) addParams(outputs, tree, scopeId, states, contractObj, sourceLocation, stack.length, 0, 1) + } + } catch (error) { + console.log(error) + } + + tree.functionDefinitionsByScope[scopeId] = functionDefinitionAndInputs +} + +async function includeVariableDeclaration (tree, step, sourceLocation, scopeId, contractObj, generatedSources) { let states = null - const generatedSources = getGeneratedSources(tree, scopeId, contractObj) const variableDeclarations = resolveVariableDeclaration(tree, sourceLocation, generatedSources) // using the vm trace step, the current source location and the ast, // we check if the current vm trace step target a new ast node of type VariableDeclaration @@ -332,49 +374,6 @@ async function includeVariableDeclaration (tree, step, sourceLocation, scopeId, } } } - - // we check here if we are at the beginning inside a new function. - // if that is the case, we have to add to locals tree the inputs and output params - const functionDefinition = resolveFunctionDefinition(tree, previousSourceLocation, generatedSources) - if (!functionDefinition) return - - const previousIsJumpDest2 = isJumpDestInstruction(tree.traceManager.trace[step - 2]) - const previousIsJumpDest1 = isJumpDestInstruction(tree.traceManager.trace[step - 1]) - const isConstructor = functionDefinition.kind === 'constructor' - if (newLocation && (previousIsJumpDest1 || previousIsJumpDest2 || isConstructor)) { - tree.functionCallStack.push(step) - const functionDefinitionAndInputs = { functionDefinition, inputs: [] } - // means: the previous location was a function definition && JUMPDEST - // => we are at the beginning of the function and input/output are setup - - try { - const stack = tree.traceManager.getStackAt(step) - states = tree.solidityProxy.extractStatesDefinitions() - if (functionDefinition.parameters) { - const inputs = functionDefinition.parameters - const outputs = functionDefinition.returnParameters - // for (const element of functionDefinition.parameters) { - // if (element.nodeType === 'ParameterList') { - // if (!inputs) inputs = element - // else { - // outputs = element - // break - // } - // } - // } - // input params - if (inputs && inputs.parameters) { - functionDefinitionAndInputs.inputs = addParams(inputs, tree, scopeId, states, contractObj, previousSourceLocation, stack.length, inputs.parameters.length, -1) - } - // output params - if (outputs) addParams(outputs, tree, scopeId, states, contractObj, previousSourceLocation, stack.length, 0, 1) - } - } catch (error) { - console.log(error) - } - - tree.functionDefinitionsByScope[scopeId] = functionDefinitionAndInputs - } } // this extract all the variable declaration for a given ast and file From 83aa1e2ed77e922240e1020335022771fee057cd Mon Sep 17 00:00:00 2001 From: yann300 Date: Mon, 21 Nov 2022 13:39:12 +0100 Subject: [PATCH 14/40] rename --- libs/remix-debug/src/solidity-decoder/internalCallTree.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libs/remix-debug/src/solidity-decoder/internalCallTree.ts b/libs/remix-debug/src/solidity-decoder/internalCallTree.ts index 8a5756d1ed..b58f36c7a0 100644 --- a/libs/remix-debug/src/solidity-decoder/internalCallTree.ts +++ b/libs/remix-debug/src/solidity-decoder/internalCallTree.ts @@ -263,13 +263,13 @@ async function buildTree (tree, step, scopeId, isExternalCall, isCreation, funct const generatedSources = getGeneratedSources(tree, scopeId, contractObj) const functionDefinition = resolveFunctionDefinition(tree, sourceLocation, generatedSources) - const isCallInstrn = isCallInstruction(stepDetail) + const isInternalTxInstrn = isCallInstruction(stepDetail) const isCreateInstrn = isCreateInstruction(stepDetail) // we are checking if we are jumping in a new CALL or in an internal function - if (isCallInstrn || (previousSourceLocation.jump === 'i' && functionDefinition)) { + if (isInternalTxInstrn || (previousSourceLocation.jump === 'i' && functionDefinition)) { try { const newScopeId = scopeId === '' ? subScope.toString() : scopeId + '.' + subScope - const externalCallResult = await buildTree(tree, step, newScopeId , isCallInstrn, isCreateInstrn, functionDefinition, contractObj, sourceLocation) + const externalCallResult = await buildTree(tree, step, newScopeId, isInternalTxInstrn, isCreateInstrn, functionDefinition, contractObj, sourceLocation) if (externalCallResult.error) { return { outStep: step, error: 'InternalCallTree - ' + externalCallResult.error } } else { From b8ed6874af701b1216251c4c7342ee167c1c9fbc Mon Sep 17 00:00:00 2001 From: yann300 Date: Mon, 21 Nov 2022 13:49:06 +0100 Subject: [PATCH 15/40] log debug time --- libs/remix-debug/src/solidity-decoder/internalCallTree.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libs/remix-debug/src/solidity-decoder/internalCallTree.ts b/libs/remix-debug/src/solidity-decoder/internalCallTree.ts index b58f36c7a0..b5c44a9152 100644 --- a/libs/remix-debug/src/solidity-decoder/internalCallTree.ts +++ b/libs/remix-debug/src/solidity-decoder/internalCallTree.ts @@ -61,6 +61,7 @@ export class InternalCallTree { this.offsetToLineColumnConverter = offsetToLineColumnConverter this.sourceLocationTracker = new SourceLocationTracker(codeManager, { debugWithGeneratedSources: opts.debugWithGeneratedSources }) debuggerEvent.register('newTraceLoaded', (trace) => { + let time = Date.now() this.reset() if (!this.solidityProxy.loaded()) { this.event.trigger('callTreeBuildFailed', ['compilation result not loaded. Cannot build internal call tree']) @@ -73,6 +74,7 @@ export class InternalCallTree { this.event.trigger('callTreeBuildFailed', [result.error]) } else { createReducedTrace(this, traceManager.trace.length - 1) + console.log('call tree build lasts ', (Date.now() - time) / 1000) this.event.trigger('callTreeReady', [this.scopes, this.scopeStarts]) } }, (reason) => { From 251fdfd18c301444a61894354d1488fdc8bab87a Mon Sep 17 00:00:00 2001 From: yann300 Date: Mon, 21 Nov 2022 16:51:45 +0100 Subject: [PATCH 16/40] remove need of using extractValidSourceLocation --- .../src/solidity-decoder/internalCallTree.ts | 19 +++++++++++++------ .../src/solidity-decoder/solidityProxy.ts | 12 +++++++++++- .../src/source/sourceLocationTracker.ts | 6 +++++- 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/libs/remix-debug/src/solidity-decoder/internalCallTree.ts b/libs/remix-debug/src/solidity-decoder/internalCallTree.ts index b5c44a9152..8420377492 100644 --- a/libs/remix-debug/src/solidity-decoder/internalCallTree.ts +++ b/libs/remix-debug/src/solidity-decoder/internalCallTree.ts @@ -219,20 +219,26 @@ async function buildTree (tree, step, scopeId, isExternalCall, isCreation, funct let currentSourceLocation = { start: -1, length: -1, file: -1, jump: '-' } let previousSourceLocation = currentSourceLocation + let previousValidSourceLocation = currentSourceLocation while (step < tree.traceManager.trace.length) { let sourceLocation let validSourceLocation - let newLocation = false + let address try { - const address = tree.traceManager.getCurrentCalledAddressAt(step) + address = tree.traceManager.getCurrentCalledAddressAt(step) sourceLocation = await tree.extractSourceLocation(step, address) - validSourceLocation = await tree.extractValidSourceLocation(step, address) - + if (!includedSource(sourceLocation, currentSourceLocation)) { tree.reducedTrace.push(step) currentSourceLocation = sourceLocation - newLocation = true } + + const amountOfSources = tree.sourceLocationTracker.getTotalAmountOfSources(address, tree.solidityProxy.contracts) + if (tree.sourceLocationTracker.isInvalidSourceLocation(currentSourceLocation, amountOfSources)) { // file is -1 or greater than amount of sources + validSourceLocation = previousValidSourceLocation + } else + validSourceLocation = currentSourceLocation + } catch (e) { return { outStep: step, error: 'InternalCallTree - Error resolving source location. ' + step + ' ' + e } } @@ -261,7 +267,7 @@ async function buildTree (tree, step, scopeId, isExternalCall, isCreation, funct } } - const contractObj = await tree.solidityProxy.contractObjectAt(step) + const contractObj = await tree.solidityProxy.contractObjectAtAddress(address) const generatedSources = getGeneratedSources(tree, scopeId, contractObj) const functionDefinition = resolveFunctionDefinition(tree, sourceLocation, generatedSources) @@ -292,6 +298,7 @@ async function buildTree (tree, step, scopeId, isExternalCall, isCreation, funct await includeVariableDeclaration(tree, step, sourceLocation, scopeId, contractObj, generatedSources) } previousSourceLocation = sourceLocation + previousValidSourceLocation = validSourceLocation step++ } } diff --git a/libs/remix-debug/src/solidity-decoder/solidityProxy.ts b/libs/remix-debug/src/solidity-decoder/solidityProxy.ts index 5ea06ddabf..0ec4029020 100644 --- a/libs/remix-debug/src/solidity-decoder/solidityProxy.ts +++ b/libs/remix-debug/src/solidity-decoder/solidityProxy.ts @@ -47,8 +47,18 @@ export class SolidityProxy { * @param {Int} vmTraceIndex - index in the vm trave where to resolve the executed contract name * @param {Function} cb - callback returns (error, contractName) */ - async contractObjectAt (vmTraceIndex) { + async contractObjectAt (vmTraceIndex: number) { const address = this.getCurrentCalledAddressAt(vmTraceIndex) + return this.contractObjectAtAddress(address) + } + + /** + * retrieve the compiled contract name at the @arg vmTraceIndex (cached) + * + * @param {Int} vmTraceIndex - index in the vm trave where to resolve the executed contract name + * @param {Function} cb - callback returns (error, contractName) + */ + async contractObjectAtAddress (address: string) { if (this.cache.contractObjectByAddress[address]) { return this.cache.contractObjectByAddress[address] } diff --git a/libs/remix-debug/src/source/sourceLocationTracker.ts b/libs/remix-debug/src/source/sourceLocationTracker.ts index 94264c5801..c7502db960 100644 --- a/libs/remix-debug/src/source/sourceLocationTracker.ts +++ b/libs/remix-debug/src/source/sourceLocationTracker.ts @@ -87,13 +87,17 @@ export class SourceLocationTracker { (map.file > amountOfSources - 1) this indicates the current file index exceed the total number of files. this happens when generated sources should not be considered. */ - while (vmtraceStepIndex >= 0 && (map.file === -1 || map.file > amountOfSources - 1)) { + while (vmtraceStepIndex >= 0 && this.isInvalidSourceLocation(map, amountOfSources)) { map = await this.getSourceLocationFromVMTraceIndex(address, vmtraceStepIndex, contracts) vmtraceStepIndex = vmtraceStepIndex - 1 } return map } + isInvalidSourceLocation (sourceLocation, amountOfSources) { + return sourceLocation.file === -1 || sourceLocation.file > amountOfSources - 1 + } + async getValidSourceLocationFromVMTraceIndexFromCache (address: string, vmtraceStepIndex: number, contracts: any, cache: Map) { const amountOfSources = this.getTotalAmountOfSources(address, contracts) let map: any = { file: -1 } From 8d88218d214d109a538a041d05343ed3b69d7d36 Mon Sep 17 00:00:00 2001 From: yann300 Date: Mon, 21 Nov 2022 19:36:44 +0100 Subject: [PATCH 17/40] adding cache --- .../src/lib/offset-line-to-column-converter.ts | 13 ++++++++++++- .../src/source/offsetToLineColumnConverter.ts | 12 +++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/libs/remix-core-plugin/src/lib/offset-line-to-column-converter.ts b/libs/remix-core-plugin/src/lib/offset-line-to-column-converter.ts index b6e02bbcdd..0c416b2953 100644 --- a/libs/remix-core-plugin/src/lib/offset-line-to-column-converter.ts +++ b/libs/remix-core-plugin/src/lib/offset-line-to-column-converter.ts @@ -13,9 +13,11 @@ const profile = { export class OffsetToLineColumnConverter extends Plugin { lineBreakPositionsByContent: Record> sourceMappingDecoder: any + offsetConvertion: any constructor () { super(profile) this.lineBreakPositionsByContent = {} + this.offsetConvertion = {} this.sourceMappingDecoder = sourceMappingDecoder } @@ -45,7 +47,15 @@ export class OffsetToLineColumnConverter extends Plugin { } } } - return this.sourceMappingDecoder.convertOffsetToLineColumn(rawLocation, this.lineBreakPositionsByContent[file]) + + const token = `${rawLocation.start}:${rawLocation.length}:${file}` + if (this.offsetConvertion[token]) { + return this.offsetConvertion[token] + } else { + const convertion = this.sourceMappingDecoder.convertOffsetToLineColumn(rawLocation, this.lineBreakPositionsByContent[file]) + this.offsetConvertion[token] = convertion + return convertion + } } /** @@ -64,6 +74,7 @@ export class OffsetToLineColumnConverter extends Plugin { */ clear () { this.lineBreakPositionsByContent = {} + this.offsetConvertion = {} } /** diff --git a/libs/remix-debug/src/source/offsetToLineColumnConverter.ts b/libs/remix-debug/src/source/offsetToLineColumnConverter.ts index 6605b660b9..a1f296c76f 100644 --- a/libs/remix-debug/src/source/offsetToLineColumnConverter.ts +++ b/libs/remix-debug/src/source/offsetToLineColumnConverter.ts @@ -4,9 +4,11 @@ import { getLinebreakPositions, convertOffsetToLineColumn } from './sourceMappin export class OffsetToColumnConverter { lineBreakPositionsByContent sourceMappingDecoder + offsetConvertion constructor (compilerEvent) { this.lineBreakPositionsByContent = {} + this.offsetConvertion = {} if (compilerEvent) { compilerEvent.register('compilationFinished', (success, data, source, input, version) => { this.clear() @@ -26,10 +28,18 @@ export class OffsetToColumnConverter { } } } - return convertOffsetToLineColumn(rawLocation, this.lineBreakPositionsByContent[file]) + const token = `${rawLocation.start}:${rawLocation.length}:${file}` + if (this.offsetConvertion[token]) { + return this.offsetConvertion[token] + } else { + const convertion = convertOffsetToLineColumn(rawLocation, this.lineBreakPositionsByContent[file]) + this.offsetConvertion[token] = convertion + return convertion + } } clear () { this.lineBreakPositionsByContent = {} + this.offsetConvertion = {} } } From 41702522c8cfb8cea3b575ebe2abdcae915b3f07 Mon Sep 17 00:00:00 2001 From: yann300 Date: Tue, 22 Nov 2022 23:17:05 +0100 Subject: [PATCH 18/40] show line number in the opcodes list --- libs/remix-debug/src/debugger/debugger.ts | 30 ++++++++++++++++--- .../src/solidity-decoder/internalCallTree.ts | 8 +++-- .../test/decoder/localsTests/int.ts | 4 +-- .../src/lib/vm-debugger/assembly-items.tsx | 10 ++++++- .../src/reducers/assembly-items.ts | 24 +++++++++++++-- 5 files changed, 64 insertions(+), 12 deletions(-) diff --git a/libs/remix-debug/src/debugger/debugger.ts b/libs/remix-debug/src/debugger/debugger.ts index 8327eb167a..fe15df4051 100644 --- a/libs/remix-debug/src/debugger/debugger.ts +++ b/libs/remix-debug/src/debugger/debugger.ts @@ -14,6 +14,8 @@ export class Debugger { breakPointManager step_manager // eslint-disable-line camelcase vmDebuggerLogic + currentFile = -1 + currentLine = -1 constructor (options) { this.event = new EventManager() @@ -74,6 +76,9 @@ export class Debugger { const compilationResultForAddress = await this.compilationResult(address) if (!compilationResultForAddress) { this.event.trigger('newSourceLocation', [null]) + this.currentFile = -1 + this.currentLine = -1 + this.vmDebuggerLogic.event.trigger('lineGasCostChanged', [null]) return } @@ -92,26 +97,43 @@ export class Debugger { } const lineColumnPos = await this.offsetToLineColumnConverter.offsetToLineColumn(rawLocation, rawLocation.file, sources, astSources) - let lineGasCost = -1 + let lineGasCostObj = null try { - lineGasCost = await this.debugger.callTree.getGasCostPerLine(rawLocation.file, lineColumnPos.start.line) + lineGasCostObj = await this.debugger.callTree.getGasCostPerLine(rawLocation.file, lineColumnPos.start.line) } catch (e) { console.log(e) } - this.event.trigger('newSourceLocation', [lineColumnPos, rawLocation, generatedSources, address, stepDetail, lineGasCost]) + this.event.trigger('newSourceLocation', [lineColumnPos, rawLocation, generatedSources, address, stepDetail, (lineGasCostObj && lineGasCostObj.gasCost) || -1]) this.vmDebuggerLogic.event.trigger('sourceLocationChanged', [rawLocation]) + if (this.currentFile !== rawLocation.file || this.currentLine !== lineColumnPos.start.line) { + const instructionIndexes = lineGasCostObj.indexes.map((index) => { // translate from vmtrace index to instruction index + return this.debugger.codeManager.getInstructionIndex(address, index) + }) + this.vmDebuggerLogic.event.trigger('lineGasCostChanged', [instructionIndexes, lineColumnPos.start.line ]) + this.currentFile = rawLocation.file + this.currentLine = lineColumnPos.start.line + } } else { this.event.trigger('newSourceLocation', [null]) - this.vmDebuggerLogic.event.trigger('sourceLocationChanged', [null]) + this.vmDebuggerLogic.event.trigger('lineGasCostChanged', [null]) + this.currentFile = -1 + this.currentLine = -1 + this.vmDebuggerLogic.event.trigger('lineGasCostChanged', [null]) } }).catch((_error) => { this.event.trigger('newSourceLocation', [null]) this.vmDebuggerLogic.event.trigger('sourceLocationChanged', [null]) + this.currentFile = -1 + this.currentLine = -1 + this.vmDebuggerLogic.event.trigger('lineGasCostChanged', [null]) }) // }) } catch (error) { this.event.trigger('newSourceLocation', [null]) this.vmDebuggerLogic.event.trigger('sourceLocationChanged', [null]) + this.currentFile = -1 + this.currentLine = -1 + this.vmDebuggerLogic.event.trigger('lineGasCostChanged', [null]) return console.log(error) } } diff --git a/libs/remix-debug/src/solidity-decoder/internalCallTree.ts b/libs/remix-debug/src/solidity-decoder/internalCallTree.ts index 8420377492..ef7704b61c 100644 --- a/libs/remix-debug/src/solidity-decoder/internalCallTree.ts +++ b/libs/remix-debug/src/solidity-decoder/internalCallTree.ts @@ -259,9 +259,13 @@ async function buildTree (tree, step, scopeId, isExternalCall, isCreation, funct const lineColumnPos = await tree.offsetToLineColumnConverter.offsetToLineColumn(validSourceLocation, validSourceLocation.file, tree.solidityProxy.sourcesCode, tree.solidityProxy.sources) if (!tree.gasCostPerLine[validSourceLocation.file]) tree.gasCostPerLine[validSourceLocation.file] = {} if (!tree.gasCostPerLine[validSourceLocation.file][lineColumnPos.start.line]) { - tree.gasCostPerLine[validSourceLocation.file][lineColumnPos.start.line] = 0 + tree.gasCostPerLine[validSourceLocation.file][lineColumnPos.start.line] = { + gasCost: 0, + indexes: [] + } } - tree.gasCostPerLine[validSourceLocation.file][lineColumnPos.start.line] += stepDetail.gasCost + tree.gasCostPerLine[validSourceLocation.file][lineColumnPos.start.line].gasCost += stepDetail.gasCost + tree.gasCostPerLine[validSourceLocation.file][lineColumnPos.start.line].indexes.push(step) } catch (e) { console.log(e) } diff --git a/libs/remix-debug/test/decoder/localsTests/int.ts b/libs/remix-debug/test/decoder/localsTests/int.ts index 2360c1cf12..b9ba0ab7f9 100644 --- a/libs/remix-debug/test/decoder/localsTests/int.ts +++ b/libs/remix-debug/test/decoder/localsTests/int.ts @@ -47,8 +47,8 @@ module.exports = function (st, privateKey, contractBytecode, compilationResult, try { // test gas cost per line - st.equals(await callTree.getGasCostPerLine(0, 16), 11) - st.equals(await callTree.getGasCostPerLine(0, 32), 60) + st.equals((await callTree.getGasCostPerLine(0, 16)).gasCost, 11) + st.equals((await callTree.getGasCostPerLine(0, 32)).gasCost, 60) let functions1 = callTree.retrieveFunctionsStack(102) let functions2 = callTree.retrieveFunctionsStack(115) diff --git a/libs/remix-ui/debugger-ui/src/lib/vm-debugger/assembly-items.tsx b/libs/remix-ui/debugger-ui/src/lib/vm-debugger/assembly-items.tsx index 815681529b..b5fb288c48 100644 --- a/libs/remix-ui/debugger-ui/src/lib/vm-debugger/assembly-items.tsx +++ b/libs/remix-ui/debugger-ui/src/lib/vm-debugger/assembly-items.tsx @@ -1,3 +1,4 @@ +import { stateDecoder } from 'dist/libs/remix-debug/src/solidity-decoder' import React, { useState, useRef, useEffect, useReducer } from 'react' // eslint-disable-line import { initialState, reducer } from '../../reducers/assembly-items' import './styles/assembly-items.css' @@ -9,6 +10,7 @@ export const AssemblyItems = ({ registerEvent }) => { const [nextSelectedItems, setNextSelectedItems] = useState([1]) const [returnInstructionIndexes, setReturnInstructionIndexes] = useState([]) const [outOfGasInstructionIndexes, setOutOfGasInstructionIndexes] = useState([]) + const [opcodeTooltipText, setOpcodeTooltipText] = useState('') const refs = useRef({}) const asmItemsRef = useRef(null) @@ -16,6 +18,10 @@ export const AssemblyItems = ({ registerEvent }) => { registerEvent && registerEvent('codeManagerChanged', (code, address, index, nextIndexes, returnInstructionIndexes, outOfGasInstructionIndexes) => { dispatch({ type: 'FETCH_OPCODES_SUCCESS', payload: { code, address, index, nextIndexes, returnInstructionIndexes, outOfGasInstructionIndexes } }) }) + + registerEvent && registerEvent('lineGasCostChanged', (instructionsIndexes: number[], line: []) => { + dispatch({ type: 'FETCH_INDEXES_FOR_NEW_LINE', payload: { currentLineIndexes: instructionsIndexes || [], line } }) + }) }, []) useEffect(() => { @@ -129,7 +135,9 @@ export const AssemblyItems = ({ registerEvent }) => {
{ assemblyItems.display.map((item, i) => { - return
{ refs.current[i] = ref }}>{item}
+ return
{ refs.current[i] = ref }}> + {item}{assemblyItems.currentLineIndexes.includes(i) ? - LINE {assemblyItems.line + 1} : ' - '} +
}) }
diff --git a/libs/remix-ui/debugger-ui/src/reducers/assembly-items.ts b/libs/remix-ui/debugger-ui/src/reducers/assembly-items.ts index a86554f297..4f1e9c307a 100644 --- a/libs/remix-ui/debugger-ui/src/reducers/assembly-items.ts +++ b/libs/remix-ui/debugger-ui/src/reducers/assembly-items.ts @@ -13,6 +13,7 @@ export const initialState = { }, display: [], index: 0, + initialIndex: 0, nextIndexes: [-1], returnInstructionIndexes: [], outOfGasInstructionIndexes: [], @@ -20,7 +21,10 @@ export const initialState = { bottom: 0, isRequesting: false, isSuccessful: false, - hasError: null + hasError: null, + absoluteCurrentLineIndexes: [], + currentLineIndexes: [], + line: -1 } const reducedOpcode = (opCodes, payload) => { @@ -31,6 +35,7 @@ const reducedOpcode = (opCodes, payload) => { return { index: opCodes.index - bottom, nextIndexes: opCodes.nextIndexes.map(index => index - bottom), + currentLineIndexes: (opCodes.absoluteCurrentLineIndexes && opCodes.absoluteCurrentLineIndexes.map(index => index - bottom)) || [], display: opCodes.code.slice(bottom, top), returnInstructionIndexes: payload.returnInstructionIndexes.map((index) => index.instructionIndex - bottom), outOfGasInstructionIndexes: payload.outOfGasInstructionIndexes.map((index) => index.instructionIndex - bottom) @@ -49,20 +54,23 @@ export const reducer = (state = initialState, action: Action) => { } case 'FETCH_OPCODES_SUCCESS': { const opCodes = action.payload.address === state.opCodes.address ? { - ...state.opCodes, index: action.payload.index, nextIndexes: action.payload.nextIndexes + ...state.opCodes, index: action.payload.index, nextIndexes: action.payload.nextIndexes, absoluteCurrentLineIndexes: state.absoluteCurrentLineIndexes } : deepEqual(action.payload.code, state.opCodes.code) ? state.opCodes : action.payload const reduced = reducedOpcode(opCodes, action.payload) return { + ...state, opCodes, display: reduced.display, + initialIndex: action.payload.index, index: reduced.index, nextIndexes: reduced.nextIndexes, isRequesting: false, isSuccessful: true, hasError: null, returnInstructionIndexes: reduced.returnInstructionIndexes, - outOfGasInstructionIndexes: reduced.outOfGasInstructionIndexes + outOfGasInstructionIndexes: reduced.outOfGasInstructionIndexes, + currentLineIndexes: reduced.currentLineIndexes } } case 'FETCH_OPCODES_ERROR': { @@ -73,6 +81,16 @@ export const reducer = (state = initialState, action: Action) => { hasError: action.payload } } + case 'FETCH_INDEXES_FOR_NEW_LINE': { + let bottom = state.initialIndex - 10 + bottom = bottom < 0 ? 0 : bottom + return { + ...state, + absoluteCurrentLineIndexes: action.payload.currentLineIndexes, + currentLineIndexes: action.payload.currentLineIndexes.map(index => index - bottom), + line: action.payload.line + } + } default: throw new Error() } From 17d44abd21cea6ede08432156ac63ac6f6abf148 Mon Sep 17 00:00:00 2001 From: yann300 Date: Tue, 22 Nov 2022 23:22:33 +0100 Subject: [PATCH 19/40] use lineColumnPos from the cache --- libs/remix-debug/src/debugger/debugger.ts | 2 +- .../src/solidity-decoder/internalCallTree.ts | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/libs/remix-debug/src/debugger/debugger.ts b/libs/remix-debug/src/debugger/debugger.ts index fe15df4051..43c45d8b38 100644 --- a/libs/remix-debug/src/debugger/debugger.ts +++ b/libs/remix-debug/src/debugger/debugger.ts @@ -95,7 +95,7 @@ export class Debugger { sources[genSource.name] = { content: genSource.contents } } } - const lineColumnPos = await this.offsetToLineColumnConverter.offsetToLineColumn(rawLocation, rawLocation.file, sources, astSources) + const lineColumnPos = rawLocationAndOpcode.lineColumnPos let lineGasCostObj = null try { diff --git a/libs/remix-debug/src/solidity-decoder/internalCallTree.ts b/libs/remix-debug/src/solidity-decoder/internalCallTree.ts index ef7704b61c..b0f1230be3 100644 --- a/libs/remix-debug/src/solidity-decoder/internalCallTree.ts +++ b/libs/remix-debug/src/solidity-decoder/internalCallTree.ts @@ -250,13 +250,12 @@ async function buildTree (tree, step, scopeId, isExternalCall, isCreation, funct if (stepDetail && nextStepDetail) { stepDetail.gasCost = parseInt(stepDetail.gas as string) - parseInt(nextStepDetail.gas as string) } - tree.locationAndOpcodePerVMTraceIndex[step] = { sourceLocation, stepDetail } - tree.scopes[scopeId].gasCost += stepDetail.gasCost - + // gas per line + let lineColumnPos if (tree.offsetToLineColumnConverter) { try { - const lineColumnPos = await tree.offsetToLineColumnConverter.offsetToLineColumn(validSourceLocation, validSourceLocation.file, tree.solidityProxy.sourcesCode, tree.solidityProxy.sources) + lineColumnPos = await tree.offsetToLineColumnConverter.offsetToLineColumn(validSourceLocation, validSourceLocation.file, tree.solidityProxy.sourcesCode, tree.solidityProxy.sources) if (!tree.gasCostPerLine[validSourceLocation.file]) tree.gasCostPerLine[validSourceLocation.file] = {} if (!tree.gasCostPerLine[validSourceLocation.file][lineColumnPos.start.line]) { tree.gasCostPerLine[validSourceLocation.file][lineColumnPos.start.line] = { @@ -271,6 +270,9 @@ async function buildTree (tree, step, scopeId, isExternalCall, isCreation, funct } } + tree.locationAndOpcodePerVMTraceIndex[step] = { sourceLocation, stepDetail, lineColumnPos } + tree.scopes[scopeId].gasCost += stepDetail.gasCost + const contractObj = await tree.solidityProxy.contractObjectAtAddress(address) const generatedSources = getGeneratedSources(tree, scopeId, contractObj) const functionDefinition = resolveFunctionDefinition(tree, sourceLocation, generatedSources) From f551afa7830e9be353593ac44286ab6134980011 Mon Sep 17 00:00:00 2001 From: yann300 Date: Wed, 23 Nov 2022 09:52:32 +0100 Subject: [PATCH 20/40] fix unit test --- libs/remix-debug/test/decoder/localsTests/int.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/remix-debug/test/decoder/localsTests/int.ts b/libs/remix-debug/test/decoder/localsTests/int.ts index b9ba0ab7f9..f0579ecdb3 100644 --- a/libs/remix-debug/test/decoder/localsTests/int.ts +++ b/libs/remix-debug/test/decoder/localsTests/int.ts @@ -48,7 +48,7 @@ module.exports = function (st, privateKey, contractBytecode, compilationResult, // test gas cost per line st.equals((await callTree.getGasCostPerLine(0, 16)).gasCost, 11) - st.equals((await callTree.getGasCostPerLine(0, 32)).gasCost, 60) + st.equals((await callTree.getGasCostPerLine(0, 32)).gasCost, 84) let functions1 = callTree.retrieveFunctionsStack(102) let functions2 = callTree.retrieveFunctionsStack(115) @@ -58,7 +58,7 @@ module.exports = function (st, privateKey, contractBytecode, compilationResult, st.equals(functions2.length, 2) st.equals(functions3.length, 0) - st.equal(functions1[0].gasCost, 54) + st.equal(functions1[0].gasCost, 55) st.equals(Object.keys(functions1[0])[0], 'functionDefinition') st.equals(Object.keys(functions1[0])[1], 'inputs') From 0a7f563811a8421f24eaf631ebf0ff2d20241e35 Mon Sep 17 00:00:00 2001 From: yann300 Date: Wed, 23 Nov 2022 10:26:00 +0100 Subject: [PATCH 21/40] remove uneeded param --- libs/remix-debug/src/solidity-decoder/internalCallTree.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libs/remix-debug/src/solidity-decoder/internalCallTree.ts b/libs/remix-debug/src/solidity-decoder/internalCallTree.ts index b0f1230be3..0941be4afc 100644 --- a/libs/remix-debug/src/solidity-decoder/internalCallTree.ts +++ b/libs/remix-debug/src/solidity-decoder/internalCallTree.ts @@ -69,7 +69,7 @@ export class InternalCallTree { // each recursive call to buildTree represent a new context (either call, delegatecall, internal function) const calledAddress = traceManager.getCurrentCalledAddressAt(0) const isCreation = isContractCreation(calledAddress) - buildTree(this, 0, '', true, isCreation).then((result) => { + buildTree(this, 0, '', isCreation).then((result) => { if (result.error) { this.event.trigger('callTreeBuildFailed', [result.error]) } else { @@ -192,7 +192,7 @@ export class InternalCallTree { } } -async function buildTree (tree, step, scopeId, isExternalCall, isCreation, functionDefinition?, contractObj?, sourceLocation?) { +async function buildTree (tree, step, scopeId, isCreation, functionDefinition?, contractObj?, sourceLocation?) { let subScope = 1 tree.scopeStarts[step] = scopeId tree.scopes[scopeId] = { firstStep: step, locals: {}, isCreation, gasCost: 0 } @@ -283,7 +283,7 @@ async function buildTree (tree, step, scopeId, isExternalCall, isCreation, funct if (isInternalTxInstrn || (previousSourceLocation.jump === 'i' && functionDefinition)) { try { const newScopeId = scopeId === '' ? subScope.toString() : scopeId + '.' + subScope - const externalCallResult = await buildTree(tree, step, newScopeId, isInternalTxInstrn, isCreateInstrn, functionDefinition, contractObj, sourceLocation) + const externalCallResult = await buildTree(tree, step, newScopeId, isCreateInstrn, functionDefinition, contractObj, sourceLocation) if (externalCallResult.error) { return { outStep: step, error: 'InternalCallTree - ' + externalCallResult.error } } else { From 986583578c4ce3e1c055a399ec2c43c4e8799683 Mon Sep 17 00:00:00 2001 From: yann300 Date: Wed, 23 Nov 2022 11:21:35 +0100 Subject: [PATCH 22/40] fix gas label --- apps/debugger/src/app/debugger-api.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/debugger/src/app/debugger-api.ts b/apps/debugger/src/app/debugger-api.ts index 25530b9d0f..007c42de36 100644 --- a/apps/debugger/src/app/debugger-api.ts +++ b/apps/debugger/src/app/debugger-api.ts @@ -44,7 +44,7 @@ export const DebuggerApiMixin = (Base) => class extends Base { async highlight (lineColumnPos, path, rawLocation, stepDetail, lineGasCost) { await this.call('editor', 'highlight', lineColumnPos, path, '', { focus: true }) - const label = `${stepDetail.op} costs ${stepDetail.gasCost} gas - this line costs ${lineGasCost} gas - ${stepDetail.gas} total gas left` + const label = `${stepDetail.op} costs ${stepDetail.gasCost} gas - this line costs ${lineGasCost} gas - ${stepDetail.gas} gas left` const linetext: lineText = { content: label, position: lineColumnPos, From 84386825b69770190db46193bbd3e876e98300bb Mon Sep 17 00:00:00 2001 From: yann300 Date: Wed, 23 Nov 2022 11:21:49 +0100 Subject: [PATCH 23/40] fix duplicate --- libs/remix-debug/src/debugger/debugger.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/libs/remix-debug/src/debugger/debugger.ts b/libs/remix-debug/src/debugger/debugger.ts index 43c45d8b38..09c8d29185 100644 --- a/libs/remix-debug/src/debugger/debugger.ts +++ b/libs/remix-debug/src/debugger/debugger.ts @@ -115,7 +115,6 @@ export class Debugger { } } else { this.event.trigger('newSourceLocation', [null]) - this.vmDebuggerLogic.event.trigger('lineGasCostChanged', [null]) this.currentFile = -1 this.currentLine = -1 this.vmDebuggerLogic.event.trigger('lineGasCostChanged', [null]) From dd3891ae0300f4fa77587fd04318dde008483177 Mon Sep 17 00:00:00 2001 From: yann300 Date: Wed, 23 Nov 2022 11:22:13 +0100 Subject: [PATCH 24/40] remove commented code --- .../remix-debug/src/solidity-decoder/internalCallTree.ts | 9 --------- 1 file changed, 9 deletions(-) diff --git a/libs/remix-debug/src/solidity-decoder/internalCallTree.ts b/libs/remix-debug/src/solidity-decoder/internalCallTree.ts index 0941be4afc..8271c4b1a5 100644 --- a/libs/remix-debug/src/solidity-decoder/internalCallTree.ts +++ b/libs/remix-debug/src/solidity-decoder/internalCallTree.ts @@ -335,15 +335,6 @@ async function registerFunctionParameters (tree, functionDefinition, step, scope if (functionDefinition.parameters) { const inputs = functionDefinition.parameters const outputs = functionDefinition.returnParameters - // for (const element of functionDefinition.parameters) { - // if (element.nodeType === 'ParameterList') { - // if (!inputs) inputs = element - // else { - // outputs = element - // break - // } - // } - // } // input params if (inputs && inputs.parameters) { functionDefinitionAndInputs.inputs = addParams(inputs, tree, scopeId, states, contractObj, sourceLocation, stack.length, inputs.parameters.length, -1) From d915caf7f93333e9a2d48a6c6e6381773a14fdcb Mon Sep 17 00:00:00 2001 From: yann300 Date: Thu, 24 Nov 2022 09:42:12 +0100 Subject: [PATCH 25/40] fix tracking constructor paramaters --- .../src/solidity-decoder/internalCallTree.ts | 37 +++++++++++++++---- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/libs/remix-debug/src/solidity-decoder/internalCallTree.ts b/libs/remix-debug/src/solidity-decoder/internalCallTree.ts index 8271c4b1a5..4a815e25ab 100644 --- a/libs/remix-debug/src/solidity-decoder/internalCallTree.ts +++ b/libs/remix-debug/src/solidity-decoder/internalCallTree.ts @@ -42,6 +42,10 @@ export class InternalCallTree { } gasCostPerLine offsetToLineColumnConverter + pendingConstructorExecutionAt: number + pendingConstructorId: number + pendingConstructor + constructorsStartExecution /** * constructor @@ -109,6 +113,10 @@ export class InternalCallTree { this.astWalker = new AstWalker() this.reducedTrace = [] this.locationAndOpcodePerVMTraceIndex = {} + this.pendingConstructorExecutionAt = -1 + this.pendingConstructorId = -1 + this.constructorsStartExecution = {} + this.pendingConstructor = null } /** @@ -192,7 +200,7 @@ export class InternalCallTree { } } -async function buildTree (tree, step, scopeId, isCreation, functionDefinition?, contractObj?, sourceLocation?) { +async function buildTree (tree, step, scopeId, isCreation, functionDefinition?, contractObj?, sourceLocation?, validSourceLocation?) { let subScope = 1 tree.scopeStarts[step] = scopeId tree.scopes[scopeId] = { firstStep: step, locals: {}, isCreation, gasCost: 0 } @@ -217,9 +225,9 @@ async function buildTree (tree, step, scopeId, isCreation, functionDefinition?, included.file === source.file) } - let currentSourceLocation = { start: -1, length: -1, file: -1, jump: '-' } + let currentSourceLocation = sourceLocation || { start: -1, length: -1, file: -1, jump: '-' } let previousSourceLocation = currentSourceLocation - let previousValidSourceLocation = currentSourceLocation + let previousValidSourceLocation = validSourceLocation || currentSourceLocation while (step < tree.traceManager.trace.length) { let sourceLocation let validSourceLocation @@ -275,15 +283,31 @@ async function buildTree (tree, step, scopeId, isCreation, functionDefinition?, const contractObj = await tree.solidityProxy.contractObjectAtAddress(address) const generatedSources = getGeneratedSources(tree, scopeId, contractObj) - const functionDefinition = resolveFunctionDefinition(tree, sourceLocation, generatedSources) + let functionDefinition = resolveFunctionDefinition(tree, sourceLocation, generatedSources) const isInternalTxInstrn = isCallInstruction(stepDetail) const isCreateInstrn = isCreateInstruction(stepDetail) // we are checking if we are jumping in a new CALL or in an internal function - if (isInternalTxInstrn || (previousSourceLocation.jump === 'i' && functionDefinition)) { + + const constructorExecutionStarts = tree.pendingConstructorExecutionAt > -1 && tree.pendingConstructorExecutionAt < validSourceLocation.start + if (functionDefinition && functionDefinition.kind === 'constructor' && tree.pendingConstructorExecutionAt === -1 && !tree.constructorsStartExecution[functionDefinition.id]) { + tree.pendingConstructorExecutionAt = validSourceLocation.start + tree.pendingConstructorId = functionDefinition.id + tree.pendingConstructor = functionDefinition + // from now on we'll be waiting for a change in the source location which will mark the beginning of the constructor execution. + // constructorsStartExecution allows to keep track on which constructor has already been executed. + } + if (constructorExecutionStarts || isInternalTxInstrn || (functionDefinition && previousSourceLocation.jump === 'i')) { try { + if (constructorExecutionStarts) { + tree.constructorsStartExecution[tree.pendingConstructorId] = tree.pendingConstructorExecutionAt + functionDefinition = tree.pendingConstructor + tree.pendingConstructorExecutionAt = -1 + tree.pendingConstructorId = -1 + tree.pendingConstructor = null + } const newScopeId = scopeId === '' ? subScope.toString() : scopeId + '.' + subScope - const externalCallResult = await buildTree(tree, step, newScopeId, isCreateInstrn, functionDefinition, contractObj, sourceLocation) + const externalCallResult = await buildTree(tree, step, newScopeId, isCreateInstrn, functionDefinition, contractObj, sourceLocation, validSourceLocation) if (externalCallResult.error) { return { outStep: step, error: 'InternalCallTree - ' + externalCallResult.error } } else { @@ -328,7 +352,6 @@ async function registerFunctionParameters (tree, functionDefinition, step, scope const functionDefinitionAndInputs = { functionDefinition, inputs: [] } // means: the previous location was a function definition && JUMPDEST // => we are at the beginning of the function and input/output are setup - try { const stack = tree.traceManager.getStackAt(step) const states = tree.solidityProxy.extractStatesDefinitions() From 893a8064410ba8d195839e776e85ddb8c1913b1c Mon Sep 17 00:00:00 2001 From: yann300 Date: Thu, 24 Nov 2022 10:17:39 +0100 Subject: [PATCH 26/40] fix linting --- libs/remix-debug/src/solidity-decoder/internalCallTree.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/remix-debug/src/solidity-decoder/internalCallTree.ts b/libs/remix-debug/src/solidity-decoder/internalCallTree.ts index 4a815e25ab..f47db893ac 100644 --- a/libs/remix-debug/src/solidity-decoder/internalCallTree.ts +++ b/libs/remix-debug/src/solidity-decoder/internalCallTree.ts @@ -65,7 +65,7 @@ export class InternalCallTree { this.offsetToLineColumnConverter = offsetToLineColumnConverter this.sourceLocationTracker = new SourceLocationTracker(codeManager, { debugWithGeneratedSources: opts.debugWithGeneratedSources }) debuggerEvent.register('newTraceLoaded', (trace) => { - let time = Date.now() + const time = Date.now() this.reset() if (!this.solidityProxy.loaded()) { this.event.trigger('callTreeBuildFailed', ['compilation result not loaded. Cannot build internal call tree']) From 5535ca77ad7cb60058dada3ab9881425834d424f Mon Sep 17 00:00:00 2001 From: yann300 Date: Thu, 24 Nov 2022 10:18:26 +0100 Subject: [PATCH 27/40] fix decoding function parameters --- libs/remix-debug/src/solidity-decoder/internalCallTree.ts | 5 +++-- libs/remix-debug/src/solidity-decoder/localDecoder.ts | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/libs/remix-debug/src/solidity-decoder/internalCallTree.ts b/libs/remix-debug/src/solidity-decoder/internalCallTree.ts index f47db893ac..cd378c665b 100644 --- a/libs/remix-debug/src/solidity-decoder/internalCallTree.ts +++ b/libs/remix-debug/src/solidity-decoder/internalCallTree.ts @@ -206,7 +206,7 @@ async function buildTree (tree, step, scopeId, isCreation, functionDefinition?, tree.scopes[scopeId] = { firstStep: step, locals: {}, isCreation, gasCost: 0 } if (functionDefinition) { - await registerFunctionParameters(tree, functionDefinition, step, scopeId, contractObj, sourceLocation) + await registerFunctionParameters(tree, functionDefinition, step, scopeId, contractObj, validSourceLocation) } function callDepthChange (step, trace) { @@ -470,7 +470,8 @@ function addParams (parameterList, tree, scopeId, states, contractObj, sourceLoc type: parseType(param.typeDescriptions.typeString, states, contractName, location), stackDepth: stackDepth, sourceLocation: sourceLocation, - abi: contractObj.contract.abi + abi: contractObj.contract.abi, + isParameter: true } params.push(attributesName) } diff --git a/libs/remix-debug/src/solidity-decoder/localDecoder.ts b/libs/remix-debug/src/solidity-decoder/localDecoder.ts index d333622408..d7619ce12b 100644 --- a/libs/remix-debug/src/solidity-decoder/localDecoder.ts +++ b/libs/remix-debug/src/solidity-decoder/localDecoder.ts @@ -11,7 +11,7 @@ export async function solidityLocals (vmtraceIndex, internalTreeCall, stack, mem let anonymousIncr = 1 for (const local in scope.locals) { const variable = scope.locals[local] - if (variable.stackDepth < stack.length && variable.sourceLocation.start <= currentSourceLocation.start) { + if (variable.stackDepth < stack.length && (variable.sourceLocation.start <= currentSourceLocation.start || variable.isParameter)) { let name = variable.name if (name.indexOf('$') !== -1) { name = '<' + anonymousIncr + '>' From 5ff4e3bd6f4c176d8cffcfa936495563bd08601e Mon Sep 17 00:00:00 2001 From: yann300 Date: Thu, 24 Nov 2022 10:18:46 +0100 Subject: [PATCH 28/40] fix display constructor in the function stack --- .../debugger-ui/src/lib/vm-debugger/vm-debugger-head.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/remix-ui/debugger-ui/src/lib/vm-debugger/vm-debugger-head.tsx b/libs/remix-ui/debugger-ui/src/lib/vm-debugger/vm-debugger-head.tsx index aa195f0038..8b2f706015 100644 --- a/libs/remix-ui/debugger-ui/src/lib/vm-debugger/vm-debugger-head.tsx +++ b/libs/remix-ui/debugger-ui/src/lib/vm-debugger/vm-debugger-head.tsx @@ -31,7 +31,7 @@ export const VmDebuggerHead = ({ vmDebugger: { registerEvent, triggerEvent }, de const functions = [] for (const func of stack) { - functions.push(func.functionDefinition.name + '(' + func.inputs.join(', ') + ')' + ' - ' + func.gasCost + ' gas') + functions.push(func.functionDefinition.name || func.functionDefinition.kind + '(' + func.inputs.join(', ') + ')' + ' - ' + func.gasCost + ' gas') } setFunctionPanel(() => functions) }) From 6df1dc27179f78b164535ab7d23fc2ed0c4c02aa Mon Sep 17 00:00:00 2001 From: yann300 Date: Thu, 24 Nov 2022 10:50:35 +0100 Subject: [PATCH 29/40] fix test (the constructor is now included) --- libs/remix-debug/test/decoder/localsTests/int.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libs/remix-debug/test/decoder/localsTests/int.ts b/libs/remix-debug/test/decoder/localsTests/int.ts index f0579ecdb3..dfd15263cb 100644 --- a/libs/remix-debug/test/decoder/localsTests/int.ts +++ b/libs/remix-debug/test/decoder/localsTests/int.ts @@ -54,9 +54,9 @@ module.exports = function (st, privateKey, contractBytecode, compilationResult, let functions2 = callTree.retrieveFunctionsStack(115) let functions3 = callTree.retrieveFunctionsStack(13) - st.equals(functions1.length, 1) - st.equals(functions2.length, 2) - st.equals(functions3.length, 0) + st.equals(functions1.length, 2) + st.equals(functions2.length, 3) + st.equals(functions3.length, 1) st.equal(functions1[0].gasCost, 55) From 3d4f9d91bd337e97346b001ce6ae39653802219c Mon Sep 17 00:00:00 2001 From: yann300 Date: Thu, 24 Nov 2022 11:40:17 +0100 Subject: [PATCH 30/40] set scope id before hand --- .../src/solidity-decoder/internalCallTree.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/libs/remix-debug/src/solidity-decoder/internalCallTree.ts b/libs/remix-debug/src/solidity-decoder/internalCallTree.ts index cd378c665b..fe8e2c7101 100644 --- a/libs/remix-debug/src/solidity-decoder/internalCallTree.ts +++ b/libs/remix-debug/src/solidity-decoder/internalCallTree.ts @@ -73,7 +73,12 @@ export class InternalCallTree { // each recursive call to buildTree represent a new context (either call, delegatecall, internal function) const calledAddress = traceManager.getCurrentCalledAddressAt(0) const isCreation = isContractCreation(calledAddress) - buildTree(this, 0, '', isCreation).then((result) => { + + const scopeId = 1 + this.scopeStarts[0] = scopeId + this.scopes[scopeId] = { firstStep: 0, locals: {}, isCreation, gasCost: 0 } + + buildTree(this, 0, scopeId, isCreation).then((result) => { if (result.error) { this.event.trigger('callTreeBuildFailed', [result.error]) } else { @@ -202,9 +207,6 @@ export class InternalCallTree { async function buildTree (tree, step, scopeId, isCreation, functionDefinition?, contractObj?, sourceLocation?, validSourceLocation?) { let subScope = 1 - tree.scopeStarts[step] = scopeId - tree.scopes[scopeId] = { firstStep: step, locals: {}, isCreation, gasCost: 0 } - if (functionDefinition) { await registerFunctionParameters(tree, functionDefinition, step, scopeId, contractObj, validSourceLocation) } @@ -299,15 +301,18 @@ async function buildTree (tree, step, scopeId, isCreation, functionDefinition?, } if (constructorExecutionStarts || isInternalTxInstrn || (functionDefinition && previousSourceLocation.jump === 'i')) { try { + const newScopeId = scopeId === '' ? subScope.toString() : scopeId + '.' + subScope + tree.scopeStarts[step + 1] = newScopeId + tree.scopes[newScopeId] = { firstStep: step + 1, locals: {}, isCreation, gasCost: 0 } if (constructorExecutionStarts) { tree.constructorsStartExecution[tree.pendingConstructorId] = tree.pendingConstructorExecutionAt functionDefinition = tree.pendingConstructor tree.pendingConstructorExecutionAt = -1 tree.pendingConstructorId = -1 tree.pendingConstructor = null - } - const newScopeId = scopeId === '' ? subScope.toString() : scopeId + '.' + subScope - const externalCallResult = await buildTree(tree, step, newScopeId, isCreateInstrn, functionDefinition, contractObj, sourceLocation, validSourceLocation) + await registerFunctionParameters(tree, functionDefinition, step + 1, newScopeId, contractObj, validSourceLocation) + } + const externalCallResult = await buildTree(tree, step + 1, newScopeId, isCreateInstrn, functionDefinition, contractObj, sourceLocation, validSourceLocation) if (externalCallResult.error) { return { outStep: step, error: 'InternalCallTree - ' + externalCallResult.error } } else { From b35ef7208f0247a24db1dbcc5b4910a51cfa48e9 Mon Sep 17 00:00:00 2001 From: yann300 Date: Thu, 24 Nov 2022 12:01:11 +0100 Subject: [PATCH 31/40] fix tests --- apps/remix-ide-e2e/src/tests/debugger.test.ts | 2 +- .../debugger-ui/src/lib/vm-debugger/vm-debugger-head.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/remix-ide-e2e/src/tests/debugger.test.ts b/apps/remix-ide-e2e/src/tests/debugger.test.ts index 9fdd7fb6c2..7e2e43ec96 100644 --- a/apps/remix-ide-e2e/src/tests/debugger.test.ts +++ b/apps/remix-ide-e2e/src/tests/debugger.test.ts @@ -95,7 +95,7 @@ module.exports = { .waitForElementVisible('#stepdetail') .waitForElementVisible({ locateStrategy: 'xpath', - selector: '//*[@data-id="treeViewLivm trace step" and contains(.,"545")]', + selector: '//*[@data-id="treeViewLivm trace step" and contains(.,"546")]', }) .goToVMTraceStep(10) .waitForElementVisible({ diff --git a/libs/remix-ui/debugger-ui/src/lib/vm-debugger/vm-debugger-head.tsx b/libs/remix-ui/debugger-ui/src/lib/vm-debugger/vm-debugger-head.tsx index 8b2f706015..669a5cf89c 100644 --- a/libs/remix-ui/debugger-ui/src/lib/vm-debugger/vm-debugger-head.tsx +++ b/libs/remix-ui/debugger-ui/src/lib/vm-debugger/vm-debugger-head.tsx @@ -31,7 +31,7 @@ export const VmDebuggerHead = ({ vmDebugger: { registerEvent, triggerEvent }, de const functions = [] for (const func of stack) { - functions.push(func.functionDefinition.name || func.functionDefinition.kind + '(' + func.inputs.join(', ') + ')' + ' - ' + func.gasCost + ' gas') + functions.push((func.functionDefinition.name || func.functionDefinition.kind) + '(' + func.inputs.join(', ') + ')' + ' - ' + func.gasCost + ' gas') } setFunctionPanel(() => functions) }) From 668d5d9e317947bfb0d4cfb1d70435dc42a5178d Mon Sep 17 00:00:00 2001 From: yann300 Date: Thu, 24 Nov 2022 13:05:28 +0100 Subject: [PATCH 32/40] fix decoding ctor parameters --- libs/remix-debug/src/solidity-decoder/internalCallTree.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/libs/remix-debug/src/solidity-decoder/internalCallTree.ts b/libs/remix-debug/src/solidity-decoder/internalCallTree.ts index fe8e2c7101..d52b6dc3b0 100644 --- a/libs/remix-debug/src/solidity-decoder/internalCallTree.ts +++ b/libs/remix-debug/src/solidity-decoder/internalCallTree.ts @@ -306,11 +306,10 @@ async function buildTree (tree, step, scopeId, isCreation, functionDefinition?, tree.scopes[newScopeId] = { firstStep: step + 1, locals: {}, isCreation, gasCost: 0 } if (constructorExecutionStarts) { tree.constructorsStartExecution[tree.pendingConstructorId] = tree.pendingConstructorExecutionAt - functionDefinition = tree.pendingConstructor tree.pendingConstructorExecutionAt = -1 - tree.pendingConstructorId = -1 + tree.pendingConstructorId = -1 + await registerFunctionParameters(tree, tree.pendingConstructor, step, newScopeId, contractObj, previousValidSourceLocation) tree.pendingConstructor = null - await registerFunctionParameters(tree, functionDefinition, step + 1, newScopeId, contractObj, validSourceLocation) } const externalCallResult = await buildTree(tree, step + 1, newScopeId, isCreateInstrn, functionDefinition, contractObj, sourceLocation, validSourceLocation) if (externalCallResult.error) { From f94ecd712b3cabdfd972c2d86d587c7114314c72 Mon Sep 17 00:00:00 2001 From: yann300 Date: Thu, 24 Nov 2022 13:14:46 +0100 Subject: [PATCH 33/40] fix linting --- libs/remix-debug/src/solidity-decoder/internalCallTree.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/remix-debug/src/solidity-decoder/internalCallTree.ts b/libs/remix-debug/src/solidity-decoder/internalCallTree.ts index d52b6dc3b0..0227051833 100644 --- a/libs/remix-debug/src/solidity-decoder/internalCallTree.ts +++ b/libs/remix-debug/src/solidity-decoder/internalCallTree.ts @@ -285,7 +285,7 @@ async function buildTree (tree, step, scopeId, isCreation, functionDefinition?, const contractObj = await tree.solidityProxy.contractObjectAtAddress(address) const generatedSources = getGeneratedSources(tree, scopeId, contractObj) - let functionDefinition = resolveFunctionDefinition(tree, sourceLocation, generatedSources) + const functionDefinition = resolveFunctionDefinition(tree, sourceLocation, generatedSources) const isInternalTxInstrn = isCallInstruction(stepDetail) const isCreateInstrn = isCreateInstruction(stepDetail) From c273ee1812725c5467b40c68d0705222f0d4f048 Mon Sep 17 00:00:00 2001 From: yann300 Date: Thu, 24 Nov 2022 13:28:41 +0100 Subject: [PATCH 34/40] fix test --- apps/remix-ide-e2e/src/tests/debugger.test.ts | 2 +- libs/remix-debug/test/decoder/localsTests/int.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/remix-ide-e2e/src/tests/debugger.test.ts b/apps/remix-ide-e2e/src/tests/debugger.test.ts index 7e2e43ec96..9fdd7fb6c2 100644 --- a/apps/remix-ide-e2e/src/tests/debugger.test.ts +++ b/apps/remix-ide-e2e/src/tests/debugger.test.ts @@ -95,7 +95,7 @@ module.exports = { .waitForElementVisible('#stepdetail') .waitForElementVisible({ locateStrategy: 'xpath', - selector: '//*[@data-id="treeViewLivm trace step" and contains(.,"546")]', + selector: '//*[@data-id="treeViewLivm trace step" and contains(.,"545")]', }) .goToVMTraceStep(10) .waitForElementVisible({ diff --git a/libs/remix-debug/test/decoder/localsTests/int.ts b/libs/remix-debug/test/decoder/localsTests/int.ts index dfd15263cb..55aa009d0f 100644 --- a/libs/remix-debug/test/decoder/localsTests/int.ts +++ b/libs/remix-debug/test/decoder/localsTests/int.ts @@ -50,8 +50,8 @@ module.exports = function (st, privateKey, contractBytecode, compilationResult, st.equals((await callTree.getGasCostPerLine(0, 16)).gasCost, 11) st.equals((await callTree.getGasCostPerLine(0, 32)).gasCost, 84) - let functions1 = callTree.retrieveFunctionsStack(102) - let functions2 = callTree.retrieveFunctionsStack(115) + let functions1 = callTree.retrieveFunctionsStack(103) + let functions2 = callTree.retrieveFunctionsStack(116) let functions3 = callTree.retrieveFunctionsStack(13) st.equals(functions1.length, 2) From adef162317a985a63127245b5c054c36ec6413b8 Mon Sep 17 00:00:00 2001 From: yann300 Date: Thu, 24 Nov 2022 13:58:21 +0100 Subject: [PATCH 35/40] take in account generated sources during debugging --- apps/remix-ide-e2e/src/tests/debugger.test.ts | 10 +++++----- libs/remix-debug/src/debugger/debugger.ts | 9 +-------- .../src/solidity-decoder/internalCallTree.ts | 12 +++++++++++- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/apps/remix-ide-e2e/src/tests/debugger.test.ts b/apps/remix-ide-e2e/src/tests/debugger.test.ts index 9fdd7fb6c2..60408cf1d3 100644 --- a/apps/remix-ide-e2e/src/tests/debugger.test.ts +++ b/apps/remix-ide-e2e/src/tests/debugger.test.ts @@ -97,11 +97,6 @@ module.exports = { locateStrategy: 'xpath', selector: '//*[@data-id="treeViewLivm trace step" and contains(.,"545")]', }) - .goToVMTraceStep(10) - .waitForElementVisible({ - locateStrategy: 'xpath', - selector: '//*[@data-id="treeViewLivm trace step" and contains(.,"10")]', - }) .getEditorValue((content) => { browser.assert.ok(content.indexOf(`constructor (string memory name_, string memory symbol_) { _name = name_; @@ -109,6 +104,11 @@ module.exports = { }`) !== -1, 'current displayed content is not from the ERC20 source code') }) + .goToVMTraceStep(10) + .waitForElementVisible({ + locateStrategy: 'xpath', + selector: '//*[@data-id="treeViewLivm trace step" and contains(.,"10")]', + }) }, 'Should display correct source highlighting while debugging a contract which has ABIEncoderV2 #group2': function (browser: NightwatchBrowser) { diff --git a/libs/remix-debug/src/debugger/debugger.ts b/libs/remix-debug/src/debugger/debugger.ts index 09c8d29185..a96171e196 100644 --- a/libs/remix-debug/src/debugger/debugger.ts +++ b/libs/remix-debug/src/debugger/debugger.ts @@ -87,14 +87,7 @@ export class Debugger { const rawLocation = rawLocationAndOpcode.sourceLocation const stepDetail = rawLocationAndOpcode.stepDetail const generatedSources = this.debugger.callTree.sourceLocationTracker.getGeneratedSourcesFromAddress(address) - const astSources = Object.assign({}, compilationResultForAddress.data.sources) - const sources = Object.assign({}, compilationResultForAddress.source.sources) - if (generatedSources) { - for (const genSource of generatedSources) { - astSources[genSource.name] = { id: genSource.id, ast: genSource.ast } - sources[genSource.name] = { content: genSource.contents } - } - } + const lineColumnPos = rawLocationAndOpcode.lineColumnPos let lineGasCostObj = null diff --git a/libs/remix-debug/src/solidity-decoder/internalCallTree.ts b/libs/remix-debug/src/solidity-decoder/internalCallTree.ts index 0227051833..0e9520ae5d 100644 --- a/libs/remix-debug/src/solidity-decoder/internalCallTree.ts +++ b/libs/remix-debug/src/solidity-decoder/internalCallTree.ts @@ -265,7 +265,17 @@ async function buildTree (tree, step, scopeId, isCreation, functionDefinition?, let lineColumnPos if (tree.offsetToLineColumnConverter) { try { - lineColumnPos = await tree.offsetToLineColumnConverter.offsetToLineColumn(validSourceLocation, validSourceLocation.file, tree.solidityProxy.sourcesCode, tree.solidityProxy.sources) + const generatedSources = tree.sourceLocationTracker.getGeneratedSourcesFromAddress(address) + const astSources = Object.assign({}, tree.solidityProxy.sources) + const sources = Object.assign({}, tree.solidityProxy.sourcesCode) + if (generatedSources) { + for (const genSource of generatedSources) { + astSources[genSource.name] = { id: genSource.id, ast: genSource.ast } + sources[genSource.name] = { content: genSource.contents } + } + } + + lineColumnPos = await tree.offsetToLineColumnConverter.offsetToLineColumn(validSourceLocation, validSourceLocation.file, sources, astSources) if (!tree.gasCostPerLine[validSourceLocation.file]) tree.gasCostPerLine[validSourceLocation.file] = {} if (!tree.gasCostPerLine[validSourceLocation.file][lineColumnPos.start.line]) { tree.gasCostPerLine[validSourceLocation.file][lineColumnPos.start.line] = { From 23c873c16f6dbe800ce55640d82a39deea3c47e1 Mon Sep 17 00:00:00 2001 From: yann300 Date: Thu, 24 Nov 2022 14:09:11 +0100 Subject: [PATCH 36/40] fix test --- apps/remix-ide-e2e/src/tests/solidityUnittests.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/remix-ide-e2e/src/tests/solidityUnittests.test.ts b/apps/remix-ide-e2e/src/tests/solidityUnittests.test.ts index 6a7840c6cd..01d48ef889 100644 --- a/apps/remix-ide-e2e/src/tests/solidityUnittests.test.ts +++ b/apps/remix-ide-e2e/src/tests/solidityUnittests.test.ts @@ -290,7 +290,7 @@ module.exports = { .waitForElementContainsText('*[data-id="sidePanelSwapitTitle"]', 'DEBUGGER', 60000) .waitForElementContainsText('*[data-id="functionPanel"]', 'checkWinningProposalFailed()', 60000) .waitForElementVisible('*[data-id="dropdownPanelSolidityLocals"]').pause(1000) - .waitForElementContainsText('*[data-id="solidityLocals"]', 'no locals', 60000) + .waitForElementContainsText('*[data-id="solidityLocals"]', 'No data available', 60000) .goToVMTraceStep(316) .waitForElementContainsText('*[data-id="functionPanel"]', 'checkWinningProposalFailed()', 60000) .waitForElementContainsText('*[data-id="functionPanel"]', 'vote(proposal)', 60000) From 5ddb35d11fde344f0f78ce08aea5f1c9db2b4eaf Mon Sep 17 00:00:00 2001 From: yann300 Date: Thu, 24 Nov 2022 15:33:01 +0100 Subject: [PATCH 37/40] fix scope type --- libs/remix-debug/src/solidity-decoder/internalCallTree.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/remix-debug/src/solidity-decoder/internalCallTree.ts b/libs/remix-debug/src/solidity-decoder/internalCallTree.ts index 0e9520ae5d..652576a188 100644 --- a/libs/remix-debug/src/solidity-decoder/internalCallTree.ts +++ b/libs/remix-debug/src/solidity-decoder/internalCallTree.ts @@ -74,7 +74,7 @@ export class InternalCallTree { const calledAddress = traceManager.getCurrentCalledAddressAt(0) const isCreation = isContractCreation(calledAddress) - const scopeId = 1 + const scopeId = '1' this.scopeStarts[0] = scopeId this.scopes[scopeId] = { firstStep: 0, locals: {}, isCreation, gasCost: 0 } From 6d7a95549ddb89fbe2e8da3d19db4c19001d16a9 Mon Sep 17 00:00:00 2001 From: yann300 Date: Thu, 24 Nov 2022 15:33:40 +0100 Subject: [PATCH 38/40] need to replay the step if we are parsing the ctor --- .../src/solidity-decoder/internalCallTree.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/libs/remix-debug/src/solidity-decoder/internalCallTree.ts b/libs/remix-debug/src/solidity-decoder/internalCallTree.ts index 652576a188..673de776a0 100644 --- a/libs/remix-debug/src/solidity-decoder/internalCallTree.ts +++ b/libs/remix-debug/src/solidity-decoder/internalCallTree.ts @@ -309,19 +309,22 @@ async function buildTree (tree, step, scopeId, isCreation, functionDefinition?, // from now on we'll be waiting for a change in the source location which will mark the beginning of the constructor execution. // constructorsStartExecution allows to keep track on which constructor has already been executed. } - if (constructorExecutionStarts || isInternalTxInstrn || (functionDefinition && previousSourceLocation.jump === 'i')) { + const internalfunctionCall = functionDefinition && previousSourceLocation.jump === 'i' + if (constructorExecutionStarts || isInternalTxInstrn || internalfunctionCall) { try { const newScopeId = scopeId === '' ? subScope.toString() : scopeId + '.' + subScope - tree.scopeStarts[step + 1] = newScopeId - tree.scopes[newScopeId] = { firstStep: step + 1, locals: {}, isCreation, gasCost: 0 } + tree.scopeStarts[step] = newScopeId + tree.scopes[newScopeId] = { firstStep: step, locals: {}, isCreation, gasCost: 0 } + // for the ctor we we are at the start of its trace, we have to replay this step in order to catch all the locals: + let nextStep = constructorExecutionStarts ? step : step + 1 if (constructorExecutionStarts) { tree.constructorsStartExecution[tree.pendingConstructorId] = tree.pendingConstructorExecutionAt tree.pendingConstructorExecutionAt = -1 - tree.pendingConstructorId = -1 + tree.pendingConstructorId = -1 await registerFunctionParameters(tree, tree.pendingConstructor, step, newScopeId, contractObj, previousValidSourceLocation) tree.pendingConstructor = null - } - const externalCallResult = await buildTree(tree, step + 1, newScopeId, isCreateInstrn, functionDefinition, contractObj, sourceLocation, validSourceLocation) + } + const externalCallResult = await buildTree(tree, nextStep, newScopeId, isCreateInstrn, functionDefinition, contractObj, sourceLocation, validSourceLocation) if (externalCallResult.error) { return { outStep: step, error: 'InternalCallTree - ' + externalCallResult.error } } else { From 71d253d8c6bd022565cbecedf476012f4f0c76dd Mon Sep 17 00:00:00 2001 From: yann300 Date: Thu, 24 Nov 2022 15:34:00 +0100 Subject: [PATCH 39/40] fix tests --- .../test/decoder/localsTests/int.ts | 58 +++++++++---------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/libs/remix-debug/test/decoder/localsTests/int.ts b/libs/remix-debug/test/decoder/localsTests/int.ts index 55aa009d0f..c36a217df3 100644 --- a/libs/remix-debug/test/decoder/localsTests/int.ts +++ b/libs/remix-debug/test/decoder/localsTests/int.ts @@ -58,7 +58,7 @@ module.exports = function (st, privateKey, contractBytecode, compilationResult, st.equals(functions2.length, 3) st.equals(functions3.length, 1) - st.equal(functions1[0].gasCost, 55) + st.equal(functions1[0].gasCost, 54) st.equals(Object.keys(functions1[0])[0], 'functionDefinition') st.equals(Object.keys(functions1[0])[1], 'inputs') @@ -73,34 +73,34 @@ module.exports = function (st, privateKey, contractBytecode, compilationResult, st.equals(functions1[0].functionDefinition.name, 'level11') st.equals(functions2[0].functionDefinition.name, 'level12') st.equals(functions2[1].functionDefinition.name, 'level11') - - st.equals(scopeStarts[0], '') - st.equals(scopeStarts[13], '1') - st.equals(scopeStarts[102], '2') - st.equals(scopeStarts[115], '2.1') - st.equals(scopeStarts[136], '3') - st.equals(scopeStarts[153], '4') - st.equals(scopeStarts[166], '4.1') - st.equals(scopes[''].locals['ui8'].type.typeName, 'uint8') - st.equals(scopes[''].locals['ui16'].type.typeName, 'uint16') - st.equals(scopes[''].locals['ui32'].type.typeName, 'uint32') - st.equals(scopes[''].locals['ui64'].type.typeName, 'uint64') - st.equals(scopes[''].locals['ui128'].type.typeName, 'uint128') - st.equals(scopes[''].locals['ui256'].type.typeName, 'uint256') - st.equals(scopes[''].locals['ui'].type.typeName, 'uint256') - st.equals(scopes[''].locals['i8'].type.typeName, 'int8') - st.equals(scopes[''].locals['i16'].type.typeName, 'int16') - st.equals(scopes[''].locals['i32'].type.typeName, 'int32') - st.equals(scopes[''].locals['i64'].type.typeName, 'int64') - st.equals(scopes[''].locals['i128'].type.typeName, 'int128') - st.equals(scopes[''].locals['i256'].type.typeName, 'int256') - st.equals(scopes[''].locals['i'].type.typeName, 'int256') - st.equals(scopes[''].locals['ishrink'].type.typeName, 'int32') - st.equals(scopes['2'].locals['ui8'].type.typeName, 'uint8') - st.equals(scopes['2.1'].locals['ui81'].type.typeName, 'uint8') - st.equals(scopes['3'].locals['ui81'].type.typeName, 'uint8') - st.equals(scopes['4'].locals['ui8'].type.typeName, 'uint8') - st.equals(scopes['4.1'].locals['ui81'].type.typeName, 'uint8') + + st.equals(scopeStarts[0], '1') + st.equals(scopeStarts[10], '1.1') + st.equals(scopeStarts[102], '1.1.1') + st.equals(scopeStarts[115], '1.1.1.1') + st.equals(scopeStarts[136], '1.1.2') + st.equals(scopeStarts[153], '1.1.3') + st.equals(scopeStarts[166], '1.1.3.1') + st.equals(scopes['1.1'].locals['ui8'].type.typeName, 'uint8') + st.equals(scopes['1.1'].locals['ui16'].type.typeName, 'uint16') + st.equals(scopes['1.1'].locals['ui32'].type.typeName, 'uint32') + st.equals(scopes['1.1'].locals['ui64'].type.typeName, 'uint64') + st.equals(scopes['1.1'].locals['ui128'].type.typeName, 'uint128') + st.equals(scopes['1.1'].locals['ui256'].type.typeName, 'uint256') + st.equals(scopes['1.1'].locals['ui'].type.typeName, 'uint256') + st.equals(scopes['1.1'].locals['i8'].type.typeName, 'int8') + st.equals(scopes['1.1'].locals['i16'].type.typeName, 'int16') + st.equals(scopes['1.1'].locals['i32'].type.typeName, 'int32') + st.equals(scopes['1.1'].locals['i64'].type.typeName, 'int64') + st.equals(scopes['1.1'].locals['i128'].type.typeName, 'int128') + st.equals(scopes['1.1'].locals['i256'].type.typeName, 'int256') + st.equals(scopes['1.1'].locals['i'].type.typeName, 'int256') + st.equals(scopes['1.1'].locals['ishrink'].type.typeName, 'int32') + st.equals(scopes['1.1.1'].locals['ui8'].type.typeName, 'uint8') + st.equals(scopes['1.1.1.1'].locals['ui81'].type.typeName, 'uint8') + st.equals(scopes['1.1.2'].locals['ui81'].type.typeName, 'uint8') + st.equals(scopes['1.1.3'].locals['ui8'].type.typeName, 'uint8') + st.equals(scopes['1.1.3.1'].locals['ui81'].type.typeName, 'uint8') } catch (e) { st.fail(e.message) } From edfeb2c0d11b3956b4b1aa5019c3ee869fb36a02 Mon Sep 17 00:00:00 2001 From: yann300 Date: Thu, 24 Nov 2022 15:42:54 +0100 Subject: [PATCH 40/40] linting --- libs/remix-debug/src/solidity-decoder/internalCallTree.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/remix-debug/src/solidity-decoder/internalCallTree.ts b/libs/remix-debug/src/solidity-decoder/internalCallTree.ts index 673de776a0..ad0e1638f4 100644 --- a/libs/remix-debug/src/solidity-decoder/internalCallTree.ts +++ b/libs/remix-debug/src/solidity-decoder/internalCallTree.ts @@ -316,7 +316,7 @@ async function buildTree (tree, step, scopeId, isCreation, functionDefinition?, tree.scopeStarts[step] = newScopeId tree.scopes[newScopeId] = { firstStep: step, locals: {}, isCreation, gasCost: 0 } // for the ctor we we are at the start of its trace, we have to replay this step in order to catch all the locals: - let nextStep = constructorExecutionStarts ? step : step + 1 + const nextStep = constructorExecutionStarts ? step : step + 1 if (constructorExecutionStarts) { tree.constructorsStartExecution[tree.pendingConstructorId] = tree.pendingConstructorExecutionAt tree.pendingConstructorExecutionAt = -1