trace analyser, cache and helper updated

pull/697/head
aniket-engg 4 years ago committed by Aniket
parent 1187d4adcd
commit d2161740a8
  1. 229
      libs/remix-debug/src/trace/traceAnalyser.ts
  2. 199
      libs/remix-debug/src/trace/traceCache.ts
  3. 2
      libs/remix-debug/src/trace/traceHelper.ts

@ -1,139 +1,144 @@
'use strict'
const traceHelper = require('./traceHelper')
function TraceAnalyser (_cache) {
this.traceCache = _cache
this.trace = null
}
export class TraceAnalyser {
TraceAnalyser.prototype.analyse = function (trace, tx) {
this.trace = trace
this.traceCache.pushStoreChanges(0, tx.to)
let context = {
storageContext: [tx.to],
currentCallIndex: 0,
lastCallIndex: 0
}
const callStack = [tx.to]
this.traceCache.pushCall(trace[0], 0, callStack[0], callStack.slice(0))
if (traceHelper.isContractCreation(tx.to)) {
this.traceCache.pushContractCreation(tx.to, tx.input)
traceCache
trace
constructor (_cache) {
this.traceCache = _cache
this.trace = null
}
this.buildCalldata(0, this.trace[0], tx, true)
for (let k = 0; k < this.trace.length; k++) {
const step = this.trace[k]
this.buildMemory(k, step)
context = this.buildDepth(k, step, tx, callStack, context)
context = this.buildStorage(k, step, context)
this.buildReturnValues(k, step)
}
return true
}
TraceAnalyser.prototype.buildReturnValues = function (index, step) {
if (traceHelper.isReturnInstruction(step)) {
let offset = 2 * parseInt(step.stack[step.stack.length - 1], 16)
const size = 2 * parseInt(step.stack[step.stack.length - 2], 16)
const memory = this.trace[this.traceCache.memoryChanges[this.traceCache.memoryChanges.length - 1]].memory
const noOfReturnParams = size / 64
const memoryInString = memory.join('')
let returnParamsObj = []
for (let i = 0; i < noOfReturnParams; i++) {
returnParamsObj.push('0x' + memoryInString.substring(offset, offset + 64))
offset += 64
analyse (trace, tx) {
this.trace = trace
this.traceCache.pushStoreChanges(0, tx.to)
let context = {
storageContext: [tx.to],
currentCallIndex: 0,
lastCallIndex: 0
}
const callStack = [tx.to]
this.traceCache.pushCall(trace[0], 0, callStack[0], callStack.slice(0))
if (traceHelper.isContractCreation(tx.to)) {
this.traceCache.pushContractCreation(tx.to, tx.input)
}
this.buildCalldata(0, this.trace[0], tx, true)
for (let k = 0; k < this.trace.length; k++) {
const step = this.trace[k]
this.buildMemory(k, step)
context = this.buildDepth(k, step, tx, callStack, context)
context = this.buildStorage(k, step, context)
this.buildReturnValues(k, step)
}
return true
}
this.traceCache.pushReturnValue(index, returnParamsObj)
buildReturnValues (index, step) {
if (traceHelper.isReturnInstruction(step)) {
let offset = 2 * parseInt(step.stack[step.stack.length - 1], 16)
const size = 2 * parseInt(step.stack[step.stack.length - 2], 16)
const memory = this.trace[this.traceCache.memoryChanges[this.traceCache.memoryChanges.length - 1]].memory
const noOfReturnParams = size / 64
const memoryInString = memory.join('')
let returnParamsObj = []
for (let i = 0; i < noOfReturnParams; i++) {
returnParamsObj.push('0x' + memoryInString.substring(offset, offset + 64))
offset += 64
}
this.traceCache.pushReturnValue(index, returnParamsObj)
}
}
}
TraceAnalyser.prototype.buildCalldata = function (index, step, tx, newContext) {
let calldata = ''
if (index === 0) {
calldata = tx.input
this.traceCache.pushCallDataChanges(index, calldata)
} else if (!newContext) {
const lastCall = this.traceCache.callsData[this.traceCache.callDataChanges[this.traceCache.callDataChanges.length - 2]]
this.traceCache.pushCallDataChanges(index + 1, lastCall)
} else {
const memory = this.trace[this.traceCache.memoryChanges[this.traceCache.memoryChanges.length - 1]].memory
const callStep = this.trace[index]
const stack = callStep.stack
let offset = ''
let size = ''
if (callStep.op === 'DELEGATECALL') {
offset = 2 * parseInt(stack[stack.length - 3], 16)
size = 2 * parseInt(stack[stack.length - 4], 16)
buildCalldata (index, step, tx, newContext) {
let calldata = ''
if (index === 0) {
calldata = tx.input
this.traceCache.pushCallDataChanges(index, calldata)
} else if (!newContext) {
const lastCall = this.traceCache.callsData[this.traceCache.callDataChanges[this.traceCache.callDataChanges.length - 2]]
this.traceCache.pushCallDataChanges(index + 1, lastCall)
} else {
offset = 2 * parseInt(stack[stack.length - 4], 16)
size = 2 * parseInt(stack[stack.length - 5], 16)
const memory = this.trace[this.traceCache.memoryChanges[this.traceCache.memoryChanges.length - 1]].memory
const callStep = this.trace[index]
const stack = callStep.stack
let offset = 0
let size = 0
if (callStep.op === 'DELEGATECALL') {
offset = 2 * parseInt(stack[stack.length - 3], 16)
size = 2 * parseInt(stack[stack.length - 4], 16)
} else {
offset = 2 * parseInt(stack[stack.length - 4], 16)
size = 2 * parseInt(stack[stack.length - 5], 16)
}
calldata = '0x' + memory.join('').substr(offset, size)
this.traceCache.pushCallDataChanges(index + 1, calldata)
}
calldata = '0x' + memory.join('').substr(offset, size)
this.traceCache.pushCallDataChanges(index + 1, calldata)
}
}
TraceAnalyser.prototype.buildMemory = function (index, step) {
if (step.memory) {
this.traceCache.pushMemoryChanges(index)
buildMemory (index, step) {
if (step.memory) {
this.traceCache.pushMemoryChanges(index)
}
}
}
TraceAnalyser.prototype.buildStorage = function (index, step, context) {
if (traceHelper.newContextStorage(step) && !traceHelper.isCallToPrecompiledContract(index, this.trace)) {
const calledAddress = traceHelper.resolveCalledAddress(index, this.trace)
if (calledAddress) {
context.storageContext.push(calledAddress)
} else {
console.log('unable to build storage changes. ' + index + ' does not match with a CALL. storage changes will be corrupted')
buildStorage (index, step, context) {
if (traceHelper.newContextStorage(step) && !traceHelper.isCallToPrecompiledContract(index, this.trace)) {
const calledAddress = traceHelper.resolveCalledAddress(index, this.trace)
if (calledAddress) {
context.storageContext.push(calledAddress)
} else {
console.log('unable to build storage changes. ' + index + ' does not match with a CALL. storage changes will be corrupted')
}
this.traceCache.pushStoreChanges(index + 1, context.storageContext[context.storageContext.length - 1])
} else if (traceHelper.isSSTOREInstruction(step)) {
this.traceCache.pushStoreChanges(index + 1, context.storageContext[context.storageContext.length - 1], step.stack[step.stack.length - 1], step.stack[step.stack.length - 2])
} else if (traceHelper.isReturnInstruction(step) || traceHelper.isStopInstruction(step)) {
context.storageContext.pop()
this.traceCache.pushStoreChanges(index + 1, context.storageContext[context.storageContext.length - 1])
} else if (traceHelper.isRevertInstruction(step)) {
context.storageContext.pop()
this.traceCache.resetStoreChanges()
}
this.traceCache.pushStoreChanges(index + 1, context.storageContext[context.storageContext.length - 1])
} else if (traceHelper.isSSTOREInstruction(step)) {
this.traceCache.pushStoreChanges(index + 1, context.storageContext[context.storageContext.length - 1], step.stack[step.stack.length - 1], step.stack[step.stack.length - 2])
} else if (traceHelper.isReturnInstruction(step) || traceHelper.isStopInstruction(step)) {
context.storageContext.pop()
this.traceCache.pushStoreChanges(index + 1, context.storageContext[context.storageContext.length - 1])
} else if (traceHelper.isRevertInstruction(step)) {
context.storageContext.pop()
this.traceCache.resetStoreChanges()
return context
}
return context
}
TraceAnalyser.prototype.buildDepth = function (index, step, tx, callStack, context) {
if (traceHelper.isCallInstruction(step) && !traceHelper.isCallToPrecompiledContract(index, this.trace)) {
let newAddress
if (traceHelper.isCreateInstruction(step)) {
newAddress = traceHelper.contractCreationToken(index)
callStack.push(newAddress)
const lastMemoryChange = this.traceCache.memoryChanges[this.traceCache.memoryChanges.length - 1]
this.traceCache.pushContractCreationFromMemory(index, newAddress, this.trace, lastMemoryChange)
} else {
newAddress = traceHelper.resolveCalledAddress(index, this.trace)
if (newAddress) {
buildDepth (index, step, tx, callStack, context) {
if (traceHelper.isCallInstruction(step) && !traceHelper.isCallToPrecompiledContract(index, this.trace)) {
let newAddress
if (traceHelper.isCreateInstruction(step)) {
newAddress = traceHelper.contractCreationToken(index)
callStack.push(newAddress)
const lastMemoryChange = this.traceCache.memoryChanges[this.traceCache.memoryChanges.length - 1]
this.traceCache.pushContractCreationFromMemory(index, newAddress, this.trace, lastMemoryChange)
} else {
console.log('unable to build depth changes. ' + index + ' does not match with a CALL. depth changes will be corrupted')
newAddress = traceHelper.resolveCalledAddress(index, this.trace)
if (newAddress) {
callStack.push(newAddress)
} else {
console.log('unable to build depth changes. ' + index + ' does not match with a CALL. depth changes will be corrupted')
}
}
}
this.traceCache.pushCall(step, index + 1, newAddress, callStack.slice(0))
this.buildCalldata(index, step, tx, true)
this.traceCache.pushSteps(index, context.currentCallIndex)
context.lastCallIndex = context.currentCallIndex
context.currentCallIndex = 0
} else if (traceHelper.isReturnInstruction(step) || traceHelper.isStopInstruction(step) || step.error || step.invalidDepthChange) {
if (index < this.trace.length) {
callStack.pop()
this.traceCache.pushCall(step, index + 1, null, callStack.slice(0), step.error || step.invalidDepthChange)
this.buildCalldata(index, step, tx, false)
this.traceCache.pushCall(step, index + 1, newAddress, callStack.slice(0))
this.buildCalldata(index, step, tx, true)
this.traceCache.pushSteps(index, context.currentCallIndex)
context.lastCallIndex = context.currentCallIndex
context.currentCallIndex = 0
} else if (traceHelper.isReturnInstruction(step) || traceHelper.isStopInstruction(step) || step.error || step.invalidDepthChange) {
if (index < this.trace.length) {
callStack.pop()
this.traceCache.pushCall(step, index + 1, null, callStack.slice(0), step.error || step.invalidDepthChange)
this.buildCalldata(index, step, tx, false)
this.traceCache.pushSteps(index, context.currentCallIndex)
context.currentCallIndex = context.lastCallIndex + 1
}
} else {
this.traceCache.pushSteps(index, context.currentCallIndex)
context.currentCallIndex = context.lastCallIndex + 1
context.currentCallIndex++
}
} else {
this.traceCache.pushSteps(index, context.currentCallIndex)
context.currentCallIndex++
return context
}
return context
}
module.exports = TraceAnalyser

@ -2,116 +2,129 @@
const remixLib = require('@remix-project/remix-lib')
const helper = remixLib.util
function TraceCache () {
this.init()
}
export class TraceCache {
TraceCache.prototype.init = function () {
// ...Changes contains index in the vmtrace of the corresponding changes
this.returnValues = {}
this.currentCall = null
this.callsTree = null
this.callsData = {}
this.contractCreation = {}
this.steps = {}
this.addresses = []
this.callDataChanges = []
this.memoryChanges = []
this.storageChanges = []
this.sstore = {} // all sstore occurence in the trace
}
returnValues
currentCall
callsTree
callsData
contractCreation
steps
addresses
callDataChanges
memoryChanges
storageChanges
sstore
TraceCache.prototype.pushSteps = function (index, currentCallIndex) {
this.steps[index] = currentCallIndex
}
constructor() {
this.init()
}
TraceCache.prototype.pushCallDataChanges = function (value, calldata) {
this.callDataChanges.push(value)
this.callsData[value] = calldata
}
TraceCache.prototype.pushMemoryChanges = function (value) {
this.memoryChanges.push(value)
}
init () {
// ...Changes contains index in the vmtrace of the corresponding changes
// outOfGas has been removed because gas left logging is apparently made differently
// in the vm/geth/eth. TODO add the error property (with about the error in all clients)
TraceCache.prototype.pushCall = function (step, index, address, callStack, reverted) {
let validReturnStep = step.op === 'RETURN' || step.op === 'STOP'
if ((validReturnStep || reverted) && (this.currentCall)) {
this.currentCall.call.return = index - 1
if (!validReturnStep) {
this.currentCall.call.reverted = reverted
}
var parent = this.currentCall.parent
this.currentCall = parent ? { call: parent.call, parent: parent.parent } : null
return
this.returnValues = {}
this.currentCall = null
this.callsTree = null
this.callsData = {}
this.contractCreation = {}
this.steps = {}
this.addresses = []
this.callDataChanges = []
this.memoryChanges = []
this.storageChanges = []
this.sstore = {} // all sstore occurence in the trace
}
let call = {
op: step.op,
address: address,
callStack: callStack,
calls: {},
start: index
pushSteps (index, currentCallIndex) {
this.steps[index] = currentCallIndex
}
this.addresses.push(address)
if (this.currentCall) {
this.currentCall.call.calls[index] = call
} else {
this.callsTree = { call: call }
pushCallDataChanges (value, calldata) {
this.callDataChanges.push(value)
this.callsData[value] = calldata
}
this.currentCall = { call: call, parent: this.currentCall }
}
TraceCache.prototype.pushReturnValue = function (step, value) {
this.returnValues[step] = value
}
pushMemoryChanges (value) {
this.memoryChanges.push(value)
}
TraceCache.prototype.pushContractCreationFromMemory = function (index, token, trace, lastMemoryChange) {
const memory = trace[lastMemoryChange].memory
const stack = trace[index].stack
const offset = 2 * parseInt(stack[stack.length - 2], 16)
const size = 2 * parseInt(stack[stack.length - 3], 16)
this.contractCreation[token] = '0x' + memory.join('').substr(offset, size)
}
// outOfGas has been removed because gas left logging is apparently made differently
// in the vm/geth/eth. TODO add the error property (with about the error in all clients)
pushCall (step, index, address, callStack, reverted) {
let validReturnStep = step.op === 'RETURN' || step.op === 'STOP'
if ((validReturnStep || reverted) && (this.currentCall)) {
this.currentCall.call.return = index - 1
if (!validReturnStep) {
this.currentCall.call.reverted = reverted
}
var parent = this.currentCall.parent
this.currentCall = parent ? { call: parent.call, parent: parent.parent } : null
return
}
let call = {
op: step.op,
address: address,
callStack: callStack,
calls: {},
start: index
}
this.addresses.push(address)
if (this.currentCall) {
this.currentCall.call.calls[index] = call
} else {
this.callsTree = { call: call }
}
this.currentCall = { call: call, parent: this.currentCall }
}
TraceCache.prototype.pushContractCreation = function (token, code) {
this.contractCreation[token] = code
}
pushReturnValue (step, value) {
this.returnValues[step] = value
}
TraceCache.prototype.resetStoreChanges = function (index, address, key, value) {
this.sstore = {}
this.storageChanges = []
}
pushContractCreationFromMemory (index, token, trace, lastMemoryChange) {
const memory = trace[lastMemoryChange].memory
const stack = trace[index].stack
const offset = 2 * parseInt(stack[stack.length - 2], 16)
const size = 2 * parseInt(stack[stack.length - 3], 16)
this.contractCreation[token] = '0x' + memory.join('').substr(offset, size)
}
TraceCache.prototype.pushStoreChanges = function (index, address, key, value) {
this.sstore[index] = {
'address': address,
'key': key,
'value': value,
'hashedKey': helper.sha3_256(key)
pushContractCreation (token, code) {
this.contractCreation[token] = code
}
resetStoreChanges (index, address, key, value) {
this.sstore = {}
this.storageChanges = []
}
this.storageChanges.push(index)
}
TraceCache.prototype.accumulateStorageChanges = function (index, address, storage) {
const ret = Object.assign({}, storage)
for (var k in this.storageChanges) {
const changesIndex = this.storageChanges[k]
if (changesIndex > index) {
return ret
pushStoreChanges (index, address, key, value) {
this.sstore[index] = {
'address': address,
'key': key,
'value': value,
'hashedKey': helper.sha3_256(key)
}
var sstore = this.sstore[changesIndex]
if (sstore.address === address && sstore.key) {
ret[sstore.hashedKey] = {
key: sstore.key,
value: sstore.value
this.storageChanges.push(index)
}
accumulateStorageChanges (index, address, storage) {
const ret = Object.assign({}, storage)
for (var k in this.storageChanges) {
const changesIndex = this.storageChanges[k]
if (changesIndex > index) {
return ret
}
var sstore = this.sstore[changesIndex]
if (sstore.address === address && sstore.key) {
ret[sstore.hashedKey] = {
key: sstore.key,
value: sstore.value
}
}
}
return ret
}
return ret
}
module.exports = TraceCache

@ -2,7 +2,7 @@
const remixLib = require('@remix-project/remix-lib')
const ui = remixLib.helpers.ui
module.exports = {
export = {
// vmTraceIndex has to point to a CALL, CODECALL, ...
resolveCalledAddress: function (vmTraceIndex, trace) {
const step = trace[vmTraceIndex]

Loading…
Cancel
Save