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',