You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
201 lines
7.3 KiB
201 lines
7.3 KiB
'use strict'
|
|
|
|
import { StorageViewer } from './storage/storageViewer'
|
|
import { StorageResolver } from './storage/storageResolver'
|
|
import { TraceManager } from './trace/traceManager'
|
|
import { CodeManager } from './code/codeManager'
|
|
import { contractCreationToken } from './trace/traceHelper'
|
|
import { EventManager } from './eventManager'
|
|
import { SolidityProxy, stateDecoder, localDecoder, InternalCallTree } from './solidity-decoder'
|
|
import { extractStateVariables } from './solidity-decoder/stateDecoder'
|
|
|
|
/**
|
|
* Ethdebugger is a wrapper around a few classes that helps debugging a transaction
|
|
*
|
|
* - TraceManager - Load / Analyze the trace and retrieve details of specific test
|
|
* - CodeManager - Retrieve loaded byte code and help to resolve AST item from vmtrace index
|
|
* - SolidityProxy - Basically used to extract state variable from AST
|
|
* - Breakpoint Manager - Used to add / remove / jumpto breakpoint
|
|
* - InternalCallTree - Used to retrieved local variables
|
|
* - StorageResolver - Help resolving the storage across different steps
|
|
*
|
|
* @param {Map} opts - { function compilationResult } //
|
|
*/
|
|
export class Ethdebugger {
|
|
compilationResult
|
|
web3
|
|
opts
|
|
event
|
|
tx
|
|
traceManager
|
|
codeManager
|
|
solidityProxy
|
|
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
|
|
|
|
this.event = new EventManager()
|
|
this.traceManager = new TraceManager({ web3: this.web3 })
|
|
this.codeManager = new CodeManager(this.traceManager)
|
|
this.solidityProxy = new SolidityProxy({
|
|
getCurrentCalledAddressAt: this.traceManager.getCurrentCalledAddressAt.bind(this.traceManager),
|
|
getCode: this.codeManager.getCode.bind(this.codeManager),
|
|
compilationResult: this.compilationResult
|
|
})
|
|
this.storageResolver = null
|
|
|
|
const includeLocalVariables = true
|
|
this.callTree = new InternalCallTree(this.event,
|
|
this.traceManager,
|
|
this.solidityProxy,
|
|
this.codeManager,
|
|
{ ...opts, includeLocalVariables },
|
|
this.offsetToLineColumnConverter)
|
|
}
|
|
|
|
setManagers () {
|
|
this.traceManager = new TraceManager({ web3: this.web3 })
|
|
this.codeManager = new CodeManager(this.traceManager)
|
|
this.solidityProxy = new SolidityProxy({
|
|
getCurrentCalledAddressAt: this.traceManager.getCurrentCalledAddressAt.bind(this.traceManager),
|
|
getCode: this.codeManager.getCode.bind(this.codeManager),
|
|
compilationResult: this.compilationResult
|
|
})
|
|
this.storageResolver = null
|
|
const includeLocalVariables = true
|
|
|
|
this.callTree = new InternalCallTree(this.event,
|
|
this.traceManager,
|
|
this.solidityProxy,
|
|
this.codeManager,
|
|
{ ...this.opts, includeLocalVariables },
|
|
this.offsetToLineColumnConverter)
|
|
}
|
|
|
|
resolveStep (index) {
|
|
this.codeManager.resolveStep(index, this.tx)
|
|
}
|
|
|
|
async sourceLocationFromVMTraceIndex (address, stepIndex) {
|
|
const compilationResult = await this.compilationResult(address)
|
|
return this.callTree.sourceLocationTracker.getSourceLocationFromVMTraceIndex(address, stepIndex, compilationResult.data.contracts)
|
|
}
|
|
|
|
async getValidSourceLocationFromVMTraceIndex (address, stepIndex) {
|
|
const compilationResult = await this.compilationResult(address)
|
|
return this.callTree.sourceLocationTracker.getValidSourceLocationFromVMTraceIndex(address, stepIndex, compilationResult.data.contracts)
|
|
}
|
|
|
|
async sourceLocationFromInstructionIndex (address, instIndex, callback) {
|
|
const compilationResult = await this.compilationResult(address)
|
|
return this.callTree.sourceLocationTracker.getSourceLocationFromInstructionIndex(address, instIndex, compilationResult.data.contracts)
|
|
}
|
|
|
|
/* breakpoint */
|
|
setBreakpointManager (breakpointManager) {
|
|
this.breakpointManager = breakpointManager
|
|
}
|
|
|
|
/* decode locals */
|
|
extractLocalsAt (step) {
|
|
return this.callTree.findScope(step)
|
|
}
|
|
|
|
async decodeLocalVariableByIdAtCurrentStep (step: number, id: number) {
|
|
const variable = this.callTree.getLocalVariableById(id)
|
|
if (!variable) return null
|
|
const stack = this.traceManager.getStackAt(step)
|
|
const memory = this.traceManager.getMemoryAt(step)
|
|
const address = this.traceManager.getCurrentCalledAddressAt(step)
|
|
const calldata = this.traceManager.getCallDataAt(step)
|
|
const storageViewer = new StorageViewer({ stepIndex: step, tx: this.tx, address: address }, this.storageResolver, this.traceManager)
|
|
return await variable.type.decodeFromStack(variable.stackDepth, stack, memory, storageViewer, calldata, null, variable)
|
|
}
|
|
|
|
async decodeStateVariableByIdAtCurrentStep (step: number, id: number) {
|
|
const stateVars = await this.solidityProxy.extractStateVariablesAt(step)
|
|
const variable = stateVars.filter((el) => el.variable.id === id)
|
|
if (variable && variable.length) {
|
|
const state = await this.decodeStateAt(step, variable)
|
|
return state[variable[0].name]
|
|
}
|
|
return null
|
|
}
|
|
|
|
async decodeLocalsAt (step, sourceLocation, callback) {
|
|
try {
|
|
const stack = this.traceManager.getStackAt(step)
|
|
const memory = this.traceManager.getMemoryAt(step)
|
|
const address = this.traceManager.getCurrentCalledAddressAt(step)
|
|
const calldata = this.traceManager.getCallDataAt(step)
|
|
try {
|
|
const storageViewer = new StorageViewer({ stepIndex: step, tx: this.tx, address: address }, this.storageResolver, this.traceManager)
|
|
const locals = await localDecoder.solidityLocals(step, this.callTree, stack, memory, storageViewer, calldata, sourceLocation, null)
|
|
if (locals['error']) {
|
|
return callback(locals['error'])
|
|
}
|
|
return callback(null, locals)
|
|
} catch (e) {
|
|
callback(e.message)
|
|
}
|
|
} catch (error) {
|
|
callback(error)
|
|
}
|
|
}
|
|
|
|
/* decode state */
|
|
async extractStateAt (step) {
|
|
return await this.solidityProxy.extractStateVariablesAt(step)
|
|
}
|
|
|
|
async decodeStateAt (step, stateVars, callback?) {
|
|
try {
|
|
callback = callback || (() => {})
|
|
const address = this.traceManager.getCurrentCalledAddressAt(step)
|
|
const storageViewer = new StorageViewer({ stepIndex: step, tx: this.tx, address: address }, this.storageResolver, this.traceManager)
|
|
const result = await stateDecoder.decodeState(stateVars, storageViewer)
|
|
callback(result)
|
|
return result
|
|
} catch (error) {
|
|
callback(error)
|
|
}
|
|
}
|
|
|
|
storageViewAt (step, address) {
|
|
return new StorageViewer({ stepIndex: step, tx: this.tx, address: address }, this.storageResolver, this.traceManager)
|
|
}
|
|
|
|
updateWeb3 (web3) {
|
|
this.web3 = web3
|
|
this.setManagers()
|
|
}
|
|
|
|
unLoad () {
|
|
this.traceManager.init()
|
|
this.codeManager.clear()
|
|
this.solidityProxy.reset()
|
|
this.event.trigger('traceUnloaded')
|
|
}
|
|
|
|
async debug (tx) {
|
|
if (this.traceManager.isLoading) {
|
|
return
|
|
}
|
|
tx.to = tx.to || contractCreationToken('0')
|
|
this.tx = tx
|
|
|
|
await this.traceManager.resolveTrace(tx)
|
|
this.event.trigger('newTraceLoaded', [this.traceManager.trace])
|
|
if (this.breakpointManager && this.breakpointManager.hasBreakpoint()) {
|
|
this.breakpointManager.jumpNextBreakpoint(false)
|
|
}
|
|
this.storageResolver = new StorageResolver({ web3: this.traceManager.web3 })
|
|
}
|
|
}
|
|
|