From ef4e0965541b8b4dad7156259e7cd9f4db38e393 Mon Sep 17 00:00:00 2001 From: yann300 Date: Sun, 13 Nov 2022 15:40:36 +0100 Subject: [PATCH] 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