diff --git a/src/assemblyItemsBrowser.js b/src/assemblyItemsBrowser.js
index 266d88babc..b66f14661b 100644
--- a/src/assemblyItemsBrowser.js
+++ b/src/assemblyItemsBrowser.js
@@ -6,12 +6,13 @@ var ButtonNavigator = require('./vmTraceButtonNavigator')
var codeUtils = require('./codeUtils')
var style = require('./basicStyles')
var Slider = require('./slider')
+var StorageResolver = require('./storageResolver.js')
module.exports = React.createClass({
contextTypes: {
web3: React.PropTypes.object
},
-
+
getInitialState: function () {
return {
currentSelected: -1, // current selected item in the vmTrace
@@ -24,14 +25,17 @@ module.exports = React.createClass({
currentCallData: null,
currentStepInfo: null,
codes: {}, // assembly items instructions list by contract addesses
+ executingCode: [], // code currently loaded in the debugger
instructionsIndexByBytesOffset: {}, // mapping between bytes offset and instructions index.
- callStack: {}
+ callStack: {},
+ storageStates: {}
}
},
getDefaultProps: function () {
return {
- vmTrace: null
+ vmTrace: null,
+ transaction: null
}
},
@@ -53,8 +57,10 @@ module.exports = React.createClass({
stepIntoBack={this.stepIntoBack}
stepIntoForward={this.stepIntoForward}
stepOverBack={this.stepOverBack}
- stepOverForward={this.stepOverForward} />
+ stepOverForward={this.stepOverForward}
+ jumpToNextCall={this.jumpToNextCall} />
+
@@ -138,84 +144,131 @@ module.exports = React.createClass({
return ret
},
- resolveAddress: function (address) {
- if (!this.state.codes[address]) {
- var hexCode = this.context.web3.eth.getCode(address)
- var code = codeUtils.nameOpCodes(new Buffer(hexCode.substring(2), 'hex'))
- this.state.codes[address] = code[0]
- this.state.instructionsIndexByBytesOffset[address] = code[1]
+ loadCode: function (address, callback) {
+ console.log('loading new code from web3 ' + address)
+ this.context.web3.eth.getCode(address, function (error, result) {
+ if (error) {
+ console.log(error)
+ } else {
+ callback(result)
+ }
+ })
+ },
+
+ cacheExecutingCode: function (address, hexCode) {
+ var code = codeUtils.nameOpCodes(new Buffer(hexCode.substring(2), 'hex'))
+ this.state.codes[address] = code[0]
+ this.state.instructionsIndexByBytesOffset[address] = code[1]
+ return {
+ code: code[0],
+ instructionsIndexByBytesOffset: code[1]
+ }
+ },
+
+ getExecutingCodeFromCache: function (address) {
+ if (this.state.codes[address]) {
+ return {
+ code: this.state.codes[address],
+ instructionsIndexByBytesOffset: this.state.instructionsIndexByBytesOffset[address]
+ }
+ } else {
+ return null
}
},
renderAssemblyItems: function () {
- if (this.props.vmTrace) {
- return this.state.codes[this.state.currentAddress].map(function (item, i) {
+ if (this.props.vmTrace && this.state.executingCode) {
+ return this.state.executingCode.map(function (item, i) {
return
})
}
},
componentWillReceiveProps: function (nextProps) {
+ this.setState(this.getInitialState())
if (!nextProps.vmTrace) {
return
}
this.buildCallStack(nextProps.vmTrace)
- this.setState({'currentSelected': -1})
this.updateState(nextProps, 0)
},
buildCallStack: function (vmTrace) {
- if (!vmTrace) {
- return
- }
+ if (!vmTrace) return
var callStack = []
+ var callStackFrame = {}
var depth = -1
+ this.refs.storageResolver.init(this.props.transaction)
for (var k = 0; k < vmTrace.length; k++) {
var trace = vmTrace[k]
- if (trace.depth === undefined || trace.depth === depth) {
- continue
- }
+ if (trace.depth === undefined || trace.depth === depth) continue
if (trace.depth > depth) {
- callStack.push(trace.address) // new context
+ if (k === 0) {
+ callStack.push('0x' + vmTrace[k].address) // new context
+ } else {
+ // getting the address from the stack
+ var callTrace = vmTrace[k - 1]
+ var address = callTrace.stack[callTrace.stack.length - 2]
+ callStack.push(address) // new context
+ }
} else if (trace.depth < depth) {
callStack.pop() // returning from context
}
depth = trace.depth
- this.state.callStack[k] = callStack.slice(0)
+ callStackFrame[k] = callStack.slice(0)
+
+ this.refs.storageResolver.trackStorageChange(k, trace)
}
+ this.setState({'callStack': callStackFrame})
},
updateState: function (props, vmTraceIndex) {
- if (!props.vmTrace || !props.vmTrace[vmTraceIndex]) {
- return
- }
+ if (!props.vmTrace || !props.vmTrace[vmTraceIndex]) return
var previousIndex = this.state.currentSelected
var stateChanges = {}
+ var stack
if (props.vmTrace[vmTraceIndex].stack) { // there's always a stack
- var stack = props.vmTrace[vmTraceIndex].stack
+ stack = props.vmTrace[vmTraceIndex].stack.slice(0)
stack.reverse()
- stateChanges['currentStack'] = stack
- }
-
- var currentAddress = this.state.currentAddress
- var addressIndex = this.shouldUpdateStateProperty('address', vmTraceIndex, previousIndex, props.vmTrace)
- if (addressIndex > -1) {
- currentAddress = props.vmTrace[addressIndex].address
- this.resolveAddress(currentAddress)
- Object.assign(stateChanges, { 'currentAddress': currentAddress })
+ Object.assign(stateChanges, { 'currentStack': stack })
}
+ var newContextLoaded = false
var depthIndex = this.shouldUpdateStateProperty('depth', vmTraceIndex, previousIndex, props.vmTrace)
if (depthIndex > -1) {
- Object.assign(stateChanges, { 'currentCallStack': this.state.callStack[depthIndex] })
+ Object.assign(stateChanges, {'currentCallStack': this.state.callStack[depthIndex]})
+ // updating exectution context:
+ var address = this.resolveAddress(depthIndex, props)
+ if (address !== this.state.currentAddress) {
+ var self = this
+ this.ensureExecutingCodeUpdated(address, vmTraceIndex, props, function (code) {
+ if (self.state.currentAddress !== address) {
+ console.log('updating executing code ' + self.state.currentAddress + ' -> ' + address)
+ self.setState(
+ {
+ 'selectedInst': code.instructionsIndexByBytesOffset[props.vmTrace[vmTraceIndex].pc],
+ 'executingCode': code.code,
+ 'currentAddress': address
+ })
+ }
+ })
+ newContextLoaded = true
+ }
}
-
- var storageIndex = this.shouldUpdateStateProperty('storage', vmTraceIndex, previousIndex, props.vmTrace)
- if (storageIndex > -1) {
- Object.assign(stateChanges, { 'currentStorage': props.vmTrace[storageIndex].storage })
+ if (!newContextLoaded) {
+ Object.assign(stateChanges,
+ {
+ 'selectedInst': this.getExecutingCodeFromCache(this.state.currentAddress).instructionsIndexByBytesOffset[props.vmTrace[vmTraceIndex].pc]
+ })
}
+ Object.assign(stateChanges, { 'currentSelected': vmTraceIndex })
+
+ this.refs.storageResolver.rebuildStorageAt(vmTraceIndex, props.transaction, function (storage) {
+ Object.assign(stateChanges, { 'currentStorage': storage })
+ })
+
var memoryIndex = this.shouldUpdateStateProperty('memory', vmTraceIndex, previousIndex, props.vmTrace)
if (memoryIndex > -1) {
Object.assign(stateChanges, { 'currentMemory': this.formatMemory(props.vmTrace[memoryIndex].memory, 16) })
@@ -226,19 +279,50 @@ module.exports = React.createClass({
Object.assign(stateChanges, { 'currentCallData': [props.vmTrace[callDataIndex].calldata] })
}
- stateChanges['selectedInst'] = this.state.instructionsIndexByBytesOffset[currentAddress][props.vmTrace[vmTraceIndex].pc]
- stateChanges['currentSelected'] = vmTraceIndex
-
stateChanges['currentStepInfo'] = [
'Current Step: ' + props.vmTrace[vmTraceIndex].steps,
'Adding Memory: ' + (props.vmTrace[vmTraceIndex].memexpand ? props.vmTrace[vmTraceIndex].memexpand : ''),
'Step Cost: ' + props.vmTrace[vmTraceIndex].gascost,
'Remaining Gas: ' + props.vmTrace[vmTraceIndex].gas
]
+
this.refs.slider.setValue(vmTraceIndex)
this.setState(stateChanges)
},
+ ensureExecutingCodeUpdated: function (address, vmTraceIndex, props, callBack) {
+ this.resolveCode(address, vmTraceIndex, props, function (address, code) {
+ callBack(code)
+ })
+ },
+
+ resolveAddress: function (vmTraceIndex, props) {
+ var address = props.vmTrace[vmTraceIndex].address
+ if (vmTraceIndex > 0) {
+ var stack = this.state.callStack[vmTraceIndex] // callcode, delegatecall, ...
+ address = stack[stack.length - 1]
+ }
+ return address
+ },
+
+ resolveCode: function (address, vmTraceIndex, props, callBack) {
+ var cache = this.getExecutingCodeFromCache(address)
+ if (cache) {
+ callBack(address, cache)
+ return
+ }
+
+ if (vmTraceIndex === 0 && props.transaction.to === null) { // start of the trace
+ callBack(address, this.cacheExecutingCode(address, props.transaction.input))
+ return
+ }
+
+ var self = this
+ this.loadCode(address, function (code) {
+ callBack(address, self.cacheExecutingCode(address, code))
+ })
+ },
+
shouldUpdateStateProperty: function (vmTraceName, nextIndex, previousIndex, vmTrace) {
var propIndex = -1
if (previousIndex + 1 === nextIndex) {
@@ -265,6 +349,16 @@ module.exports = React.createClass({
return index
},
+ jumpToNextCall: function () {
+ var i = this.state.currentSelected
+ while (++i < this.props.vmTrace.length) {
+ if (this.isCallInstruction(i)) {
+ this.selectState(i + 1)
+ break
+ }
+ }
+ },
+
stepIntoBack: function () {
this.moveSelection(-1)
},
diff --git a/src/debugger.js b/src/debugger.js
index ff7109ea59..d56348f33d 100644
--- a/src/debugger.js
+++ b/src/debugger.js
@@ -1,12 +1,16 @@
'use strict'
var React = require('react')
var TxBrowser = require('./txBrowser')
-var VmTraceBrowser = require('./vmTraceBrowser')
+var AssemblyItemsBrowser = require('./assemblyItemsBrowser')
var style = require('./basicStyles')
module.exports = React.createClass({
getInitialState: function () {
- return {vmTrace: null, state: '', currentStep: -1}
+ return {
+ vmTrace: null,
+ state: '',
+ currentStep: -1
+ }
},
childContextTypes: {
@@ -25,19 +29,22 @@ module.exports = React.createClass({
{this.state.state}
-
+
)
},
- retrieveVmTrace: function (blockNumber, txNumber) {
- this.setState({state: 'loading...'})
+ retrieveVmTrace: function (blockNumber, txNumber, tx) {
+ if (this.state.state !== '') return
+
var self = this
+ this.setState({state: 'loading...'})
+
this.props.web3.debug.trace(blockNumber, parseInt(txNumber), function (error, result) {
if (error) {
console.log(error)
} else {
- self.setState({vmTrace: result, state: ''})
+ self.setState({vmTrace: result, transaction: tx, state: ''})
}
})
}
diff --git a/src/storageResolver.js b/src/storageResolver.js
new file mode 100644
index 0000000000..8935f793a9
--- /dev/null
+++ b/src/storageResolver.js
@@ -0,0 +1,113 @@
+'use strict'
+var React = require('react')
+
+module.exports = React.createClass({
+ contextTypes: {
+ web3: React.PropTypes.object
+ },
+
+ getInitialState: function () {
+ return {
+ storage: {},
+ storageChanges: [],
+ vmTraceIndexByStorageChange: {},
+ vmTraceChangesRef: []
+ }
+ },
+
+ init: function () {
+ var defaultState = this.getInitialState()
+ this.state.storage = defaultState.storage
+ this.state.storageChanges = defaultState.storageChanges
+ this.state.vmTraceIndexByStorageChange = defaultState.vmTraceIndexByStorageChange
+ this.state.vmTraceChangesRef = defaultState.vmTraceChangesRef
+ },
+
+ render: function () {
+ return null
+ },
+
+ // retrieve the storage of an account just after the execution of txHash
+ retrieveStorage: function (address, transaction, callBack) {
+ if (this.state.storage[address]) {
+ callBack(this.state.storage[address])
+ }
+ var self = this
+ if (transaction) {
+ this.context.web3.debug.storageAt(transaction.blockNumber.toString(), transaction.transactionIndex, address, function (error, result) {
+ if (error) {
+ console.log(error)
+ } else {
+ self.state.storage[address] = result
+ callBack(result)
+ }
+ })
+ } else {
+ console.log('transaction is not defined')
+ }
+ },
+
+ trackStorageChange: function (vmTraceIndex, trace) {
+ var change = false
+ if (trace.address) {
+ // new context
+ this.state.storageChanges.push({ address: trace.address, changes: [] })
+ change = true
+ } else if (trace.depth && !trace.address) {
+ // returned from context
+ this.state.storageChanges.push({ address: this.state.storageChanges[this.state.storageChanges.length - 1].address, changes: [] })
+ change = true
+ } else if (trace.inst === 'SSTORE') {
+ this.state.storageChanges[this.state.storageChanges.length - 1].changes.push(
+ {
+ 'key': trace.stack[trace.stack.length - 1],
+ 'value': trace.stack[trace.stack.length - 2]
+ })
+ change = true
+ }
+
+ if (change) {
+ this.state.vmTraceIndexByStorageChange[vmTraceIndex] = {
+ context: this.state.storageChanges.length - 1,
+ changes: this.state.storageChanges[this.state.storageChanges.length - 1].changes.length - 1
+ }
+ this.state.vmTraceChangesRef.push(vmTraceIndex)
+ }
+ },
+
+ rebuildStorageAt: function (vmTraceIndex, transaction, callBack) {
+ var changesLocation = this.retrieveLastChange(vmTraceIndex)
+ if (!changesLocation) {
+ console.log('unable to build storage')
+ callBack({})
+ } else {
+ var address = this.state.storageChanges[changesLocation.context].address
+ this.retrieveStorage(address, transaction, function (storage) {
+ for (var k = 0; k < changesLocation.context; k++) {
+ var context = this.state.storageChanges[k]
+ if (context.address === address) {
+ for (var i = 0; i < context.changes.length; i++) {
+ if (i > changesLocation.changes) break
+ var change = context.changes[i]
+ storage[change.key] = change.value
+ }
+ }
+ }
+ callBack(storage)
+ })
+ }
+ },
+
+ retrieveLastChange: function (vmTraceIndex) {
+ var change = this.state.vmTraceIndexByStorageChange[vmTraceIndex]
+ if (change) {
+ return change
+ } else {
+ for (var k in this.state.vmTraceChangesRef) {
+ if (this.state.vmTraceChangesRef[k] > vmTraceIndex) {
+ return this.state.vmTraceIndexByStorageChange[k - 1]
+ }
+ }
+ }
+ }
+})
diff --git a/src/txBrowser.js b/src/txBrowser.js
index 8bb3eced04..0bba5312c4 100644
--- a/src/txBrowser.js
+++ b/src/txBrowser.js
@@ -19,8 +19,10 @@ module.exports = React.createClass({
var tx = this.context.web3.eth.getTransactionFromBlock(this.state.blockNumber, this.state.txNumber)
if (tx) {
this.setState({from: tx.from, to: tx.to, hash: tx.hash})
+ this.props.onNewTxRequested(this.state.blockNumber, parseInt(this.state.txNumber), tx)
+ } else {
+ console.log('cannot find ' + this.state.blockNumber + ' ' + this.state.txNumber)
}
- this.props.onNewTxRequested(this.state.blockNumber, parseInt(this.state.txNumber))
},
updateBlockN: function (ev) {
diff --git a/src/vmTraceBrowser.js b/src/vmTraceBrowser.js
deleted file mode 100644
index eee6492b82..0000000000
--- a/src/vmTraceBrowser.js
+++ /dev/null
@@ -1,13 +0,0 @@
-'use strict'
-var React = require('react')
-var AssemblyItemsBrowser = require('./assemblyItemsBrowser')
-
-module.exports = React.createClass({
- render: function () {
- return (
-
- )
- }
-})
diff --git a/src/vmTraceManager.js b/src/vmTraceManager.js
deleted file mode 100644
index 137d4721e2..0000000000
--- a/src/vmTraceManager.js
+++ /dev/null
@@ -1,8 +0,0 @@
-'use strict'
-module.exports = {
- retrieveVmTrace: function (blockNumber, txNumber, callBack) {
- this.context.web3.debug.trace(blockNumber, parseInt(txNumber), function (error, result) {
- callBack(error, result)
- })
- }
-}
diff --git a/src/web3Admin.js b/src/web3Admin.js
index cebe2f64f7..a3a37661c9 100644
--- a/src/web3Admin.js
+++ b/src/web3Admin.js
@@ -89,6 +89,12 @@ module.exports = {
web3._extend({
property: 'debug',
methods: [
+ new web3._extend.Method({
+ name: 'storageAt',
+ call: 'debug_storageAt',
+ inputFormatter: [null, null, null],
+ params: 3
+ }),
new web3._extend.Method({
name: 'trace',
call: 'debug_trace',