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.
325 lines
8.3 KiB
325 lines
8.3 KiB
'use strict'
|
|
import { util, execution } from '@remix-project/remix-lib'
|
|
const { toHexPaddedString } = util
|
|
import { TraceAnalyser } from './traceAnalyser'
|
|
import { TraceCache } from './traceCache'
|
|
import { TraceStepManager } from './traceStepManager'
|
|
import { isCreateInstruction } from './traceHelper'
|
|
|
|
export class TraceManager {
|
|
web3
|
|
fork: string
|
|
isLoading: boolean
|
|
trace
|
|
traceCache
|
|
traceAnalyser
|
|
traceStepManager
|
|
tx
|
|
|
|
constructor (options) {
|
|
this.web3 = options.web3
|
|
this.isLoading = false
|
|
this.trace = null
|
|
this.traceCache = new TraceCache()
|
|
this.traceAnalyser = new TraceAnalyser(this.traceCache)
|
|
this.traceStepManager = new TraceStepManager(this.traceAnalyser)
|
|
}
|
|
|
|
// init section
|
|
async resolveTrace (tx) {
|
|
this.tx = tx
|
|
this.init()
|
|
if (!this.web3) throw new Error('web3 not loaded')
|
|
this.isLoading = true
|
|
const result = await this.getTrace(tx.hash)
|
|
try {
|
|
if (result['structLogs'].length > 0) {
|
|
this.trace = result['structLogs']
|
|
|
|
try {
|
|
const networkId = await this.web3.eth.net.getId()
|
|
this.fork = execution.forkAt(networkId, tx.blockNumber)
|
|
} catch (e) {
|
|
this.fork = 'merge'
|
|
console.log(`unable to detect fork, defaulting to ${this.fork}..`)
|
|
console.error(e)
|
|
}
|
|
|
|
this.traceAnalyser.analyse(result['structLogs'], tx)
|
|
this.isLoading = false
|
|
return true
|
|
}
|
|
const mes = tx.hash + ' is not a contract invocation or contract creation.'
|
|
console.log(mes)
|
|
this.isLoading = false
|
|
throw new Error(mes)
|
|
} catch (error) {
|
|
console.log(error)
|
|
this.isLoading = false
|
|
throw new Error(error)
|
|
}
|
|
}
|
|
|
|
getTrace (txHash) {
|
|
return new Promise((resolve, reject) => {
|
|
const options = {
|
|
disableStorage: true,
|
|
enableMemory: true,
|
|
disableStack: false,
|
|
fullStorage: false
|
|
}
|
|
this.web3.debug.traceTransaction(txHash, options, function (error, result) {
|
|
if (error) return reject(error)
|
|
resolve(result)
|
|
})
|
|
})
|
|
}
|
|
|
|
init () {
|
|
this.trace = null
|
|
this.traceCache.init()
|
|
}
|
|
|
|
getCurrentFork () {
|
|
return this.fork
|
|
}
|
|
|
|
// API section
|
|
inRange (step) {
|
|
return this.isLoaded() && step >= 0 && step < this.trace.length
|
|
}
|
|
|
|
isLoaded () {
|
|
return !this.isLoading && this.trace !== null
|
|
}
|
|
|
|
getLength (callback) {
|
|
if (!this.trace) {
|
|
callback(new Error('no trace available'), null)
|
|
} else {
|
|
callback(null, this.trace.length)
|
|
}
|
|
}
|
|
|
|
accumulateStorageChanges (index, address, storageOrigin) {
|
|
return this.traceCache.accumulateStorageChanges(index, address, storageOrigin)
|
|
}
|
|
|
|
getAddresses () {
|
|
return this.traceCache.addresses
|
|
}
|
|
|
|
getCallDataAt (stepIndex) {
|
|
try {
|
|
this.checkRequestedStep(stepIndex)
|
|
} catch (check) {
|
|
throw new Error(check)
|
|
}
|
|
const callDataChange = util.findLowerBoundValue(stepIndex, this.traceCache.callDataChanges)
|
|
if (callDataChange === null) {
|
|
throw new Error('no calldata found')
|
|
}
|
|
return [this.traceCache.callsData[callDataChange]]
|
|
}
|
|
|
|
async buildCallPath (stepIndex) {
|
|
try {
|
|
this.checkRequestedStep(stepIndex)
|
|
} catch (check) {
|
|
throw new Error(check)
|
|
}
|
|
const callsPath = util.buildCallPath(stepIndex, this.traceCache.callsTree.call)
|
|
if (callsPath === null) throw new Error('no call path built')
|
|
return callsPath
|
|
}
|
|
|
|
getCallStackAt (stepIndex) {
|
|
try {
|
|
this.checkRequestedStep(stepIndex)
|
|
} catch (check) {
|
|
throw new Error(check)
|
|
}
|
|
const call = util.findCall(stepIndex, this.traceCache.callsTree.call)
|
|
if (call === null) {
|
|
throw new Error('no callstack found')
|
|
}
|
|
return call.callStack
|
|
}
|
|
|
|
getStackAt (stepIndex) {
|
|
this.checkRequestedStep(stepIndex)
|
|
if (this.trace[stepIndex] && this.trace[stepIndex].stack) { // there's always a stack
|
|
if (Array.isArray(this.trace[stepIndex].stack)) {
|
|
const stack = this.trace[stepIndex].stack.slice(0)
|
|
stack.reverse()
|
|
return stack.map(el => toHexPaddedString(el))
|
|
} else {
|
|
// it's an object coming from the VM.
|
|
// for performance reasons,
|
|
// we don't turn the stack coming from the VM into an array when the tx is executed
|
|
// but now when the app needs it.
|
|
const stack = []
|
|
for (const prop in this.trace[stepIndex].stack) {
|
|
if (prop !== 'length') {
|
|
stack.push(toHexPaddedString(this.trace[stepIndex].stack[prop]))
|
|
}
|
|
}
|
|
stack.reverse()
|
|
return stack
|
|
}
|
|
} else {
|
|
throw new Error('no stack found')
|
|
}
|
|
}
|
|
|
|
getLastCallChangeSince (stepIndex) {
|
|
try {
|
|
this.checkRequestedStep(stepIndex)
|
|
} catch (check) {
|
|
throw new Error(check)
|
|
}
|
|
|
|
const callChange = util.findCall(stepIndex, this.traceCache.callsTree.call)
|
|
if (callChange === null) {
|
|
return 0
|
|
}
|
|
return callChange
|
|
}
|
|
|
|
getCurrentCalledAddressAt (stepIndex) {
|
|
try {
|
|
this.checkRequestedStep(stepIndex)
|
|
const resp = this.getLastCallChangeSince(stepIndex)
|
|
if (!resp) {
|
|
throw new Error('unable to get current called address. ' + stepIndex + ' does not match with a CALL')
|
|
}
|
|
return resp.address
|
|
} catch (error) {
|
|
throw new Error(error)
|
|
}
|
|
}
|
|
|
|
getContractCreationCode (token) {
|
|
if (!this.traceCache.contractCreation[token]) {
|
|
throw new Error('no contract creation named ' + token)
|
|
}
|
|
return this.traceCache.contractCreation[token]
|
|
}
|
|
|
|
getMemoryAt (stepIndex, format = true) {
|
|
this.checkRequestedStep(stepIndex)
|
|
const lastChanges = util.findLowerBoundValue(stepIndex, this.traceCache.memoryChanges)
|
|
if (lastChanges === null) {
|
|
throw new Error('no memory found')
|
|
}
|
|
if (!format) {
|
|
return this.trace[lastChanges].memory
|
|
}
|
|
if (this.traceCache.formattedMemory[lastChanges]) {
|
|
return this.traceCache.formattedMemory[lastChanges]
|
|
}
|
|
const memory = util.formatMemory(this.trace[lastChanges].memory)
|
|
this.traceCache.setFormattedMemory(lastChanges, memory)
|
|
return memory
|
|
}
|
|
|
|
getCurrentPC (stepIndex) {
|
|
try {
|
|
this.checkRequestedStep(stepIndex)
|
|
} catch (check) {
|
|
throw new Error(check)
|
|
}
|
|
return this.trace[stepIndex].pc
|
|
}
|
|
|
|
getAllStopIndexes () {
|
|
return this.traceCache.stopIndexes
|
|
}
|
|
|
|
getAllOutofGasIndexes () {
|
|
return this.traceCache.outofgasIndexes
|
|
}
|
|
|
|
getReturnValue (stepIndex) {
|
|
try {
|
|
this.checkRequestedStep(stepIndex)
|
|
} catch (check) {
|
|
throw new Error(check)
|
|
}
|
|
if (!this.traceCache.returnValues[stepIndex]) {
|
|
throw new Error('current step is not a return step')
|
|
}
|
|
return this.traceCache.returnValues[stepIndex]
|
|
}
|
|
|
|
getCurrentStep (stepIndex) {
|
|
try {
|
|
this.checkRequestedStep(stepIndex)
|
|
} catch (check) {
|
|
throw new Error(check)
|
|
}
|
|
return this.traceCache.steps[stepIndex]
|
|
}
|
|
|
|
getMemExpand (stepIndex) {
|
|
return (this.getStepProperty(stepIndex, 'memexpand') || '')
|
|
}
|
|
|
|
getStepCost (stepIndex) {
|
|
return this.getStepProperty(stepIndex, 'gasCost')
|
|
}
|
|
|
|
getRemainingGas (stepIndex) {
|
|
return this.getStepProperty(stepIndex, 'gas')
|
|
}
|
|
|
|
getStepProperty (stepIndex, property) {
|
|
try {
|
|
this.checkRequestedStep(stepIndex)
|
|
} catch (check) {
|
|
throw new Error(check)
|
|
}
|
|
return this.trace[stepIndex][property]
|
|
}
|
|
|
|
isCreationStep (stepIndex) {
|
|
return isCreateInstruction(this.trace[stepIndex])
|
|
}
|
|
|
|
// step section
|
|
findStepOverBack (currentStep) {
|
|
return this.traceStepManager.findStepOverBack(currentStep)
|
|
}
|
|
|
|
findStepOverForward (currentStep) {
|
|
return this.traceStepManager.findStepOverForward(currentStep)
|
|
}
|
|
|
|
findNextCall (currentStep) {
|
|
return this.traceStepManager.findNextCall(currentStep)
|
|
}
|
|
|
|
findStepOut (currentStep) {
|
|
return this.traceStepManager.findStepOut(currentStep)
|
|
}
|
|
|
|
checkRequestedStep (stepIndex) {
|
|
if (!this.trace) {
|
|
throw new Error('trace not loaded')
|
|
} else if (stepIndex >= this.trace.length) {
|
|
throw new Error('trace smaller than requested')
|
|
}
|
|
}
|
|
|
|
waterfall (calls, stepindex, cb) {
|
|
const ret = []
|
|
let retError = null
|
|
for (const call in calls) {
|
|
calls[call].apply(this, [stepindex, function (error, result) {
|
|
retError = error
|
|
ret.push({ error: error, value: result })
|
|
}])
|
|
}
|
|
cb(retError, ret)
|
|
}
|
|
}
|
|
|