Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 46 additions & 111 deletions libs/remix-debug/src/Ethdebugger.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,16 @@
'use strict'

const StorageViewer = require('./storage/storageViewer')
const StorageResolver = require('./storage/storageResolver')

const SolidityDecoder = require('./solidity-decoder')
const SolidityProxy = SolidityDecoder.SolidityProxy
const stateDecoder = SolidityDecoder.stateDecoder
const localDecoder = SolidityDecoder.localDecoder
const InternalCallTree = SolidityDecoder.InternalCallTree

const remixLib = require('@remix-project/remix-lib')
const TraceManager = remixLib.trace.TraceManager
const CodeManager = remixLib.code.CodeManager
const traceHelper = remixLib.helpers.trace
const EventManager = remixLib.EventManager

const {SolidityProxy, stateDecoder, localDecoder, InternalCallTree} = require('./solidity-decoder')

const StorageViewer = require('./storage/storageViewer')
const StorageResolver = require('./storage/storageResolver')

/**
* Ethdebugger is a wrapper around a few classes that helps debugging a transaction
*
Expand Down Expand Up @@ -58,23 +54,15 @@ Ethdebugger.prototype.resolveStep = function (index) {
}

Ethdebugger.prototype.setCompilationResult = function (compilationResult) {
if (compilationResult && compilationResult.data) {
this.solidityProxy.reset(compilationResult.data)
} else {
this.solidityProxy.reset({})
}
this.solidityProxy.reset((compilationResult && compilationResult.data) || {})
}

Ethdebugger.prototype.sourceLocationFromVMTraceIndex = function (address, stepIndex, callback) {
this.callTree.sourceLocationTracker.getSourceLocationFromVMTraceIndex(address, stepIndex, this.solidityProxy.contracts).then((rawLocation) => {
callback(null, rawLocation)
}).catch(callback)
Ethdebugger.prototype.sourceLocationFromVMTraceIndex = async function (address, stepIndex) {
return this.callTree.sourceLocationTracker.getSourceLocationFromVMTraceIndex(address, stepIndex, this.solidityProxy.contracts)
}

Ethdebugger.prototype.sourceLocationFromInstructionIndex = function (address, instIndex, callback) {
this.callTree.sourceLocationTracker.getSourceLocationFromInstructionIndex(address, instIndex, this.solidityProxy.contracts).then((rawLocation) => {
callback(null, rawLocation)
}).catch(callback)
Ethdebugger.prototype.sourceLocationFromInstructionIndex = async function (address, instIndex, callback) {
return this.callTree.sourceLocationTracker.getSourceLocationFromInstructionIndex(address, instIndex, this.solidityProxy.contracts)
}

/* breakpoint */
Expand All @@ -83,98 +71,48 @@ Ethdebugger.prototype.setBreakpointManager = function (breakpointManager) {
}

/* decode locals */
Ethdebugger.prototype.extractLocalsAt = function (step, callback) {
callback(null, this.callTree.findScope(step))
Ethdebugger.prototype.extractLocalsAt = function (step) {
return this.callTree.findScope(step)
}

Ethdebugger.prototype.decodeLocalsAt = function (step, sourceLocation, callback) {
const self = this
this.traceManager.waterfall([
function getStackAt (stepIndex, callback) {
try {
const result = self.traceManager.getStackAt(stepIndex)
callback(null, result)
} catch (error) {
callback(error)
}
},
function getMemoryAt (stepIndex, callback) {
try {
const result = self.traceManager.getMemoryAt(stepIndex)
callback(null, result)
} catch (error) {
callback(error)
}
},

function getCurrentCalledAddressAt (stepIndex, next) {
try {
const address = self.traceManager.getCurrentCalledAddressAt(stepIndex)
next(null, address)
} catch (error) {
next(error)
}
}],
step,
(error, result) => {
if (!error) {
const stack = result[0].value
const memory = result[1].value
try {
const storageViewer = new StorageViewer({
stepIndex: step,
tx: this.tx,
address: result[2].value
}, this.storageResolver, this.traceManager)
localDecoder.solidityLocals(step, this.callTree, stack, memory, storageViewer, sourceLocation).then((locals) => {
if (!locals.error) {
callback(null, locals)
} else {
callback(locals.error)
}
})
} catch (e) {
callback(e.message)
}
} else {
callback(error)
Ethdebugger.prototype.decodeLocalsAt = async function (step, sourceLocation, callback) {
try {
const stack = this.traceManager.getStackAt(step)
const memory = this.traceManager.getMemoryAt(step)
const address = this.traceManager.getCurrentCalledAddressAt(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, sourceLocation)
if (locals.error) {
return callback(locals.error)
}
})
return callback(null, locals)
} catch (e) {
callback(e.message)
}
} catch (error) {
callback(error)
}
}

/* decode state */
Ethdebugger.prototype.extractStateAt = function (step, callback) {
this.solidityProxy.extractStateVariablesAt(step).then((stateVars) => {
callback(null, stateVars)
}).catch(callback)
Ethdebugger.prototype.extractStateAt = async function (step) {
return this.solidityProxy.extractStateVariablesAt(step)
}

Ethdebugger.prototype.decodeStateAt = function (step, stateVars, callback) {
Ethdebugger.prototype.decodeStateAt = async function (step, stateVars, callback) {
try {
const address = this.traceManager.getCurrentCalledAddressAt(step)
const storageViewer = new StorageViewer({
stepIndex: step,
tx: this.tx,
address: address
}, this.storageResolver, this.traceManager)
stateDecoder.decodeState(stateVars, storageViewer).then((result) => {
if (!result.error) {
callback(null, result)
} else {
callback(result.error)
}
})
const storageViewer = new StorageViewer({stepIndex: step, tx: this.tx, address: address}, this.storageResolver, this.traceManager)
const result = await stateDecoder.decodeState(stateVars, storageViewer)
return result
} catch (error) {
callback(error)
}
}

Ethdebugger.prototype.storageViewAt = function (step, address) {
return new StorageViewer({
stepIndex: step,
tx: this.tx,
address: address
}, this.storageResolver, this.traceManager)
return new StorageViewer({stepIndex: step, tx: this.tx, address: address}, this.storageResolver, this.traceManager)
}

Ethdebugger.prototype.updateWeb3 = function (web3) {
Expand All @@ -192,21 +130,18 @@ Ethdebugger.prototype.debug = function (tx) {
if (this.traceManager.isLoading) {
return
}
if (!tx.to) {
tx.to = traceHelper.contractCreationToken('0')
}
tx.to = tx.to || traceHelper.contractCreationToken('0')
this.tx = tx
this.traceManager.resolveTrace(tx, async (error, result) => {
if (result) {
this.setCompilationResult(await this.compilationResult(tx.to))
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})
} else {
this.statusMessage = error ? error.message : 'Trace not loaded'

this.traceManager.resolveTrace(tx).then(async (result) => {
this.setCompilationResult(await this.compilationResult(tx.to))
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})
}).catch((error) => {
this.statusMessage = error ? error.message : 'Trace not loaded'
})
}

Expand Down
24 changes: 8 additions & 16 deletions libs/remix-debug/src/solidity-decoder/internalCallTree.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,23 +124,15 @@ class InternalCallTree {
return functions
}

extractSourceLocation (step) {
return new Promise((resolve, reject) => {
try {
const address = this.traceManager.getCurrentCalledAddressAt(step)
try {
this.sourceLocationTracker.getSourceLocationFromVMTraceIndex(address, step, this.solidityProxy.contracts).then(resolve).catch((error) => {
return reject('InternalCallTree - Cannot retrieve sourcelocation for step ' + step + ' ' + error)
})
} catch (error) {
return reject('InternalCallTree - Cannot retrieve address for step ' + step + ' ' + error)
}
} catch (error) {
return reject('InternalCallTree - Cannot retrieve address for step ' + step + ' ' + error)
}
})
async extractSourceLocation (step) {
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)
}
}

}

async function buildTree (tree, step, scopeId, isExternalCall) {
Expand Down
41 changes: 13 additions & 28 deletions libs/remix-debug/src/solidity-decoder/solidityProxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,25 +39,15 @@ class SolidityProxy {
* @param {Int} vmTraceIndex - index in the vm trave where to resolve the executed contract name
* @param {Function} cb - callback returns (error, contractName)
*/
contractNameAt (vmTraceIndex) {
return new Promise((resolve, reject) => {
try {
const address = this.traceManager.getCurrentCalledAddressAt(vmTraceIndex)
if (this.cache.contractNameByAddress[address]) {
return resolve(this.cache.contractNameByAddress[address])
}
this.codeManager.getCode(address, (error, code) => {
if (error) {
return reject(error)
}
const contractName = contractNameFromCode(this.contracts, code.bytecode, address)
this.cache.contractNameByAddress[address] = contractName
resolve(contractName)
})
} catch (error) {
reject(error)
}
})
async contractNameAt (vmTraceIndex) {
const address = this.traceManager.getCurrentCalledAddressAt(vmTraceIndex)
if (this.cache.contractNameByAddress[address]) {
return this.cache.contractNameByAddress[address]
}
const code = await this.codeManager.getCode(address)
const contractName = contractNameFromCode(this.contracts, code.bytecode, address)
this.cache.contractNameByAddress[address] = contractName
return contractName
}

/**
Expand Down Expand Up @@ -95,12 +85,9 @@ class SolidityProxy {
* @param {Int} vmTraceIndex - index in the vm trave where to resolve the state variables
* @return {Object} - returns state variables of @args vmTraceIndex
*/
extractStateVariablesAt (vmtraceIndex) {
return new Promise((resolve, reject) => {
this.contractNameAt(vmtraceIndex).then((contractName) => {
resolve(this.extractStateVariables(contractName))
}).catch(reject)
})
async extractStateVariablesAt (vmtraceIndex) {
const contractName = await this.contractNameAt(vmtraceIndex)
return this.extractStateVariables(contractName)
}

/**
Expand All @@ -113,10 +100,8 @@ class SolidityProxy {
const file = this.fileNameFromIndex(sourceLocation.file)
if (this.sources[file]) {
return this.sources[file].legacyAST
} else {
// console.log('AST not found for file id ' + sourceLocation.file)
return null
}
return null
}

/**
Expand Down
64 changes: 26 additions & 38 deletions libs/remix-debug/src/storage/storageResolver.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,14 @@ class StorageResolver {
* @param {Array} corrections - used in case the calculated sha3 has been modifyed before SSTORE (notably used for struct in mapping).
* @return {Function} - callback
*/
initialPreimagesMappings (tx, stepIndex, address, corrections) {
return new Promise((resolve, reject) => {
if (this.preimagesMappingByAddress[address]) {
return resolve(this.preimagesMappingByAddress[address])
}
this.storageRange(tx, stepIndex, address).then((storage) => {
const mappings = mappingPreimages.decodeMappingsKeys(this.web3, storage, corrections)
this.preimagesMappingByAddress[address] = mappings
resolve(mappings)
}).catch(reject)
})
async initialPreimagesMappings (tx, stepIndex, address, corrections) {
if (this.preimagesMappingByAddress[address]) {
return this.preimagesMappingByAddress[address]
}
const storage = await this.storageRange(tx, stepIndex, address)
const mappings = mappingPreimages.decodeMappingsKeys(this.web3, storage, corrections)
this.preimagesMappingByAddress[address] = mappings
return mappings
}

/**
Expand All @@ -61,12 +58,9 @@ class StorageResolver {
* @param {String} - address - lookup address
* @param {Function} - callback - {key, hashedKey, value} -
*/
storageSlot (slot, tx, stepIndex, address) {
return new Promise((resolve, reject) => {
this.storageRangeInternal(this, slot, tx, stepIndex, address).then((storage) => {
resolve(storage[slot] !== undefined ? storage[slot] : null)
}).catch(reject)
})
async storageSlot (slot, tx, stepIndex, address) {
const storage = await this.storageRangeInternal(this, slot, tx, stepIndex, address)
return (storage[slot] !== undefined ? storage[slot] : null)
}

/**
Expand All @@ -85,27 +79,21 @@ class StorageResolver {
* even if the next 1000 items are not in the cache.
* - If @arg slot is not cached, the corresponding value will be resolved and the next 1000 slots.
*/
storageRangeInternal (self, slotKey, tx, stepIndex, address) {
return new Promise((resolve, reject) => {
var cached = this.fromCache(self, address)
if (cached && cached.storage[slotKey]) { // we have the current slot in the cache and maybe the next 1000...
return resolve(cached.storage)
}
this.storageRangeWeb3Call(tx, address, slotKey, self.maxSize).then((result) => {
const [storage, nextKey] = result
if (!storage[slotKey] && slotKey !== self.zeroSlot) { // we don't cache the zero slot (could lead to inconsistency)
storage[slotKey] = {
key: slotKey,
value: self.zeroSlot
}
}
self.toCache(self, address, storage)
if (slotKey === self.zeroSlot && !nextKey) { // only working if keys are sorted !!
self.storageByAddress[address].complete = true
}
return resolve(storage)
}).catch(reject)
})
async storageRangeInternal (self, slotKey, tx, stepIndex, address) {
var cached = this.fromCache(self, address)
if (cached && cached.storage[slotKey]) { // we have the current slot in the cache and maybe the next 1000...
return cached.storage
}
const result = await this.storageRangeWeb3Call(tx, address, slotKey, self.maxSize)
const [storage, nextKey] = result
if (!storage[slotKey] && slotKey !== self.zeroSlot) { // we don't cache the zero slot (could lead to inconsistency)
storage[slotKey] = {key: slotKey, value: self.zeroSlot}
}
self.toCache(self, address, storage)
if (slotKey === self.zeroSlot && !nextKey) { // only working if keys are sorted !!
self.storageByAddress[address].complete = true
}
return storage
}

/**
Expand Down
Loading