Merge pull request #28 from yann300/vmTrace

align trace with geth trace
pull/7/head
yann300 9 years ago committed by GitHub
commit b9cac79586
  1. 98
      src/asmCode.js
  2. 29
      src/basicPanel.js
  3. 41
      src/buttonNavigator.js
  4. 44
      src/calldataPanel.js
  5. 44
      src/callstackPanel.js
  6. 101
      src/codeManager.js
  7. 14
      src/codeResolver.js
  8. 44
      src/debugger.js
  9. 32
      src/eventManager.js
  10. 13
      src/index.js
  11. 85
      src/memoryPanel.js
  12. 4
      src/slider.js
  13. 44
      src/stackPanel.js
  14. 48
      src/stepManager.js
  15. 88
      src/sticker.js
  16. 57
      src/storagePanel.js
  17. 88
      src/traceAnalyser.js
  18. 32
      src/traceCache.js
  19. 186
      src/traceManager.js
  20. 53
      src/traceManagerUtil.js
  21. 23
      src/traceRetriever.js
  22. 43
      src/traceStepManager.js
  23. 51
      src/txBrowser.js
  24. 8
      src/util.js
  25. 48
      src/vmDebugger.js
  26. 4
      src/web3Admin.js

@ -1,94 +1,64 @@
'use strict' 'use strict'
var React = require('react') var React = require('react')
var style = require('./basicStyles') var style = require('./basicStyles')
var codeResolver = require('./codeResolver')
module.exports = React.createClass({ module.exports = React.createClass({
contextTypes: { contextTypes: {
traceManager: React.PropTypes.object, codeManager: React.PropTypes.object,
tx: React.PropTypes.object, root: React.PropTypes.object,
web3: React.PropTypes.object tx: React.PropTypes.object
}, },
getInitialState: function () { getInitialState: function () {
return { return {
code: [], code: '',
selected: -1, address: ''
address: '' // selected instruction in the asm
}
},
getDefaultProps: function () {
return {
currentStepIndex: -1
} }
}, },
render: function () { render: function () {
return ( return (
<select <select size='10' ref='itemsList' style={style.instructionsList}>
size='10'
ref='itemsList'
style={style.instructionsList}
value={this.state.selected}>
{this.renderAssemblyItems()} {this.renderAssemblyItems()}
</select> </select>
) )
}, },
renderAssemblyItems: function () { componentDidMount: function () {
if (this.state.code) { var self = this
return this.state.code.map(function (item, i) { this.context.codeManager.register('indexChanged', this, this.indexChanged)
return <option key={i} value={i}>{item}</option> this.context.codeManager.register('codeChanged', this, this.codeChanged)
}) this.context.codeManager.register('loadingCode', this, function (address) {
} })
this.context.root.register('indexChanged', this, function (index) {
self.context.codeManager.resolveStep(index, self.context.tx)
})
}, },
componentWillReceiveProps: function (nextProps) { indexChanged: function (index) {
if (nextProps.currentStepIndex < 0) return this.refs.itemsList.value = index
codeResolver.setWeb3(this.context.web3) },
var self = this
this.context.traceManager.getCurrentCalledAddressAt(nextProps.currentStepIndex, function (error, address) { codeChanged: function (code, address, index) {
if (error) { this.setState({
console.log(error) code: code,
} else { address: address
self.ensureCodeLoaded(address, nextProps.currentStepIndex)
}
}) })
this.refs.itemsList.value = index
}, },
ensureCodeLoaded: function (address, currentStep) { shouldComponentUpdate: function (nextProps, nextState) {
if (address !== this.state.address) { if (nextState.address === this.state.address) {
this.setState({ return false
code: ['loading...']
})
var self = this
codeResolver.resolveCode(address, currentStep, this.context.tx, function (address, code) {
if (window.ethDebuggerSelectedItem !== currentStep) {
console.log(currentStep + ' discarded. current is ' + window.ethDebuggerSelectedItem)
return
}
self.setState({
code: code,
address: address
})
self.setInstructionIndex(address, currentStep)
})
} else {
this.setInstructionIndex(this.state.address, currentStep)
} }
return true
}, },
setInstructionIndex: function (address, step) { renderAssemblyItems: function () {
var self = this if (this.state && this.state.code) {
this.context.traceManager.getCurrentPC(step, function (error, instIndex) { return this.state.code.map(function (item, i) {
if (error) { return <option key={i} value={i}>{item}</option>
console.log(error) })
} else { }
self.setState({
selected: codeResolver.getInstructionIndex(address, instIndex)
})
}
})
} }
}) })

@ -6,8 +6,7 @@ module.exports = React.createClass({
getDefaultProps: function () { getDefaultProps: function () {
return { return {
data: null, data: null,
name: null, name: null
renderRow: null
} }
}, },
@ -18,33 +17,9 @@ module.exports = React.createClass({
{this.props.name} {this.props.name}
</div> </div>
<div style={style.panel.tableContainer}> <div style={style.panel.tableContainer}>
<table style={style.panel.table}> <pre style={Object.assign(style.panel.table, style.font)} >{this.props.data}</pre>
<tbody>
{this.renderItems()}
</tbody>
</table>
</div> </div>
</div> </div>
) )
},
renderItems: function () {
if (!this.props.data) {
return []
}
if (!this.props.renderRow) {
var ret = []
for (var key in this.props.data) {
ret.push(
<tr key={key}>
<td>
<pre style={style.font} >{this.props.data[key]}</pre>
</td>
</tr>)
}
return ret
} else {
return this.props.renderRow(this.props.data)
}
} }
}) })

@ -10,33 +10,54 @@ module.exports = React.createClass({
stepIntoBack: React.PropTypes.func.isRequired, stepIntoBack: React.PropTypes.func.isRequired,
stepIntoForward: React.PropTypes.func.isRequired, stepIntoForward: React.PropTypes.func.isRequired,
stepOverBack: React.PropTypes.func.isRequired, stepOverBack: React.PropTypes.func.isRequired,
stepOverForward: React.PropTypes.func.isRequired stepOverForward: React.PropTypes.func.isRequired,
jumpNextCall: React.PropTypes.func.isRequired
}, },
render: function () { render: function () {
return ( return (
<div> <div>
<button onClick={this.props.stepIntoBack} disabled={this.checkButtonState(-1)}> <button ref='intoback' onClick={this.props.stepIntoBack}>
Step Into Back Step Into Back
</button> </button>
<button onClick={this.props.stepOverBack} disabled={this.checkButtonState(-1)}> <button ref='overback' onClick={this.props.stepOverBack}>
Step Over Back Step Over Back
</button> </button>
<button onClick={this.props.stepOverForward} disabled={this.checkButtonState(1)}> <button ref='overforward' onClick={this.props.stepOverForward}>
Step Over Forward Step Over Forward
</button> </button>
<button onClick={this.props.stepIntoForward} disabled={this.checkButtonState(1)}> <button ref='intoforward' onClick={this.props.stepIntoForward}>
Step Into Forward Step Into Forward
</button> </button>
<button ref='nextcall' onClick={this.props.jumpNextCall}>
Jump Next Call
</button>
</div> </div>
) )
}, },
checkButtonState: function (incr) { shouldComponentUpdate: function () {
if (incr === -1) { return false
return this.props.step === 0 ? 'disabled' : '' },
} else if (incr === 1) {
return this.props.step >= this.props.max - 1 ? 'disabled' : '' stepChanged: function (step) {
this.refs.intoback.disabled = step <= 0
this.refs.overback.disabled = step <= 0
if (!this.context.traceManager) {
this.refs.intoforward.disabled = true
this.refs.overforward.disabled = true
this.refs.nextcall.disabled = true
} else {
var self = this
this.context.traceManager.getLength(function (error, length) {
if (error) {
console.log(error)
} else {
self.refs.intoforward.disabled = step >= length - 1
self.refs.overforward.disabled = step >= length - 1
self.refs.nextcall.disabled = step >= length - 1
}
})
} }
} }
}) })

@ -4,13 +4,9 @@ var BasicPanel = require('./basicPanel')
module.exports = React.createClass({ module.exports = React.createClass({
contextTypes: { contextTypes: {
traceManager: React.PropTypes.object traceManager: React.PropTypes.object,
}, codeManager: React.PropTypes.object,
root: React.PropTypes.object
getDefaultProps: function () {
return {
currentStepIndex: -1
}
}, },
getInitialState: function () { getInitialState: function () {
@ -25,19 +21,29 @@ module.exports = React.createClass({
) )
}, },
componentWillReceiveProps: function (nextProps) { componentDidMount: function () {
if (nextProps.currentStepIndex < 0) return
if (window.ethDebuggerSelectedItem !== nextProps.currentStepIndex) return
var self = this var self = this
this.context.traceManager.getCallDataAt(nextProps.currentStepIndex, function (error, calldata) { this.context.root.register('indexChanged', this, function (index) {
if (error) { if (index < 0) return
console.log(error) if (self.context.root.ethDebuggerSelectedItem !== index) return
} else if (window.ethDebuggerSelectedItem === nextProps.currentStepIndex) {
self.setState({ self.context.traceManager.getCallDataAt(index, function (error, calldata) {
data: calldata if (error) {
}) console.log(error)
} } else if (self.context.root.ethDebuggerSelectedItem === index) {
self.setState({
data: self.format(calldata)
})
}
})
}) })
},
format: function (calldata) {
var ret = ''
for (var key in calldata) {
ret += calldata[key] + '\n'
}
return ret
} }
}) })

@ -4,13 +4,9 @@ var BasicPanel = require('./basicPanel')
module.exports = React.createClass({ module.exports = React.createClass({
contextTypes: { contextTypes: {
traceManager: React.PropTypes.object traceManager: React.PropTypes.object,
}, codeManager: React.PropTypes.object,
root: React.PropTypes.object
getDefaultProps: function () {
return {
currentStepIndex: -1
}
}, },
getInitialState: function () { getInitialState: function () {
@ -25,19 +21,29 @@ module.exports = React.createClass({
) )
}, },
componentWillReceiveProps: function (nextProps) { componentDidMount: function () {
if (nextProps.currentStepIndex < 0) return
if (window.ethDebuggerSelectedItem !== nextProps.currentStepIndex) return
var self = this var self = this
this.context.traceManager.getCallStackAt(nextProps.currentStepIndex, function (error, callstack) { this.context.root.register('indexChanged', this, function (index) {
if (error) { if (index < 0) return
console.log(error) if (self.context.root.ethDebuggerSelectedItem !== index) return
} else if (window.ethDebuggerSelectedItem === nextProps.currentStepIndex) {
self.setState({ self.context.traceManager.getCallStackAt(index, function (error, callstack) {
data: callstack if (error) {
}) console.log(error)
} } else if (self.context.root.ethDebuggerSelectedItem === index) {
self.setState({
data: self.format(callstack)
})
}
})
}) })
},
format: function (callstack) {
var ret = ''
for (var key in callstack) {
ret += callstack[key] + '\n'
}
return ret
} }
}) })

@ -0,0 +1,101 @@
'use strict'
var traceManagerUtil = require('./traceManagerUtil')
var codeResolver = require('./codeResolver')
var util = require('./util')
var EventManager = require('./eventManager')
/*
resolve contract code referenced by vmtrace in order to be used by asm listview.
events:
- indexChanged: triggered when an item is selected
- codeChanged: triggered when an item (in a different context) is selected
- loadingCode: triggerred when loading new code
- resolvingStep: when CodeManager resolves code/selected instruction of a new step
*/
function CodeManager (_web3, _traceManager) {
util.extend(this, new EventManager())
this.web3 = _web3
this.isLoading = false
this.traceManager = _traceManager
this.currentAddress = ''
codeResolver.setWeb3(_web3)
}
CodeManager.prototype.resolveStep = function (stepIndex, tx) {
if (stepIndex < 0) return
this.trigger('resolvingStep')
var self = this
if (stepIndex === 0) {
self.ensureCodeLoaded(tx.to, stepIndex, tx)
} else {
this.traceManager.getCurrentCalledAddressAt(stepIndex, function (error, address) {
if (error) {
console.log(error)
} else {
self.ensureCodeLoaded(address, stepIndex, tx)
}
})
}
}
CodeManager.prototype.ensureCodeLoaded = function (address, currentStep, tx) {
var self = this
if (address !== this.currentAddress) {
if (traceManagerUtil.isContractCreation(address)) {
this.traceManager.getContractCreationCode(address, function (error, hexCode) {
// contract creation
if (error) {
console.log(error)
} else {
var codes = codeResolver.cacheExecutingCode(address, hexCode)
self.trigger('loadingCode', [address])
self.getInstructionIndex(address, currentStep, function (error, result) {
if (!error) {
self.trigger('codeChanged', [codes.code, address, result])
self.trigger('indexChanged', [result])
self.currentAddress = address
} else {
console.log(error)
}
})
}
})
} else {
codeResolver.resolveCode(address, currentStep, tx, function (address, code) {
// resoling code from stack
self.trigger('loadingCode', [address])
self.getInstructionIndex(address, currentStep, function (error, result) {
if (!error) {
self.trigger('codeChanged', [code, address, result])
self.trigger('indexChanged', [result])
self.currentAddress = address
} else {
console.log(error)
}
})
})
}
} else {
// only set selected item
this.getInstructionIndex(this.currentAddress, currentStep, function (error, result) {
if (!error) {
self.trigger('indexChanged', [result])
}
})
}
}
CodeManager.prototype.getInstructionIndex = function (address, step, callback) {
this.traceManager.getCurrentPC(step, function (error, instIndex) {
if (error) {
console.log(error)
callback('Cannot retrieve current PC for ' + step, null)
} else {
var itemIndex = codeResolver.getInstructionIndex(address, instIndex)
callback(null, itemIndex)
}
})
}
module.exports = CodeManager

@ -18,11 +18,6 @@ module.exports = {
return return
} }
if (vmTraceIndex === 0 && transaction.to === null) { // start of the trace
callBack(address, this.cacheExecutingCode(address, transaction.input).code)
return
}
var self = this var self = this
this.loadCode(address, function (code) { this.loadCode(address, function (code) {
callBack(address, self.cacheExecutingCode(address, code).code) callBack(address, self.cacheExecutingCode(address, code).code)
@ -41,9 +36,14 @@ module.exports = {
}, },
cacheExecutingCode: function (address, hexCode) { cacheExecutingCode: function (address, hexCode) {
var codes = this.formatCode(hexCode)
this.codes[address] = codes.code
this.instructionsIndexByBytesOffset[address] = codes.instructionsIndexByBytesOffset
return codes
},
formatCode: function (hexCode) {
var code = codeUtils.nameOpCodes(new Buffer(hexCode.substring(2), 'hex')) var code = codeUtils.nameOpCodes(new Buffer(hexCode.substring(2), 'hex'))
this.codes[address] = code[0]
this.instructionsIndexByBytesOffset[address] = code[1]
return { return {
code: code[0], code: code[0],
instructionsIndexByBytesOffset: code[1] instructionsIndexByBytesOffset: code[1]

@ -2,58 +2,63 @@
var React = require('react') var React = require('react')
var TxBrowser = require('./txBrowser') var TxBrowser = require('./txBrowser')
var StepManager = require('./stepManager') var StepManager = require('./stepManager')
var AssemblyItemsBrowser = require('./vmDebugger') var VmDebugger = require('./vmDebugger')
var TraceManager = require('./traceManager')
var style = require('./basicStyles') var style = require('./basicStyles')
var util = require('./util')
var EventManager = require('./eventManager')
module.exports = React.createClass({ module.exports = React.createClass({
ethDebuggerSelectedItem: -1,
getInitialState: function () { getInitialState: function () {
return { return {
currentStepIndex: -1, // index of the selected item in the vmtrace currentStepIndex: -1, // index of the selected item in the vmtrace
tx: null, tx: null
traceManager: null
} }
}, },
childContextTypes: { childContextTypes: {
web3: React.PropTypes.object, web3: React.PropTypes.object,
traceManager: React.PropTypes.object, traceManager: React.PropTypes.object,
codeManager: React.PropTypes.object,
root: React.PropTypes.object,
tx: React.PropTypes.object tx: React.PropTypes.object
}, },
getChildContext: function () { getChildContext: function () {
return { return {
web3: this.props.web3, web3: this.props.context.web3,
traceManager: this.state.traceManager, traceManager: this.props.context.traceManager,
codeManager: this.props.context.codeManager,
root: this,
tx: this.state.tx tx: this.state.tx
} }
}, },
componentDidMount: function () {
this.setState({
traceManager: new TraceManager(this.props.web3)
})
},
render: function () { render: function () {
return ( return (
<div style={style.font}> <div style={style.font}>
<h1 style={style.container}>Eth Debugger</h1> <h1 style={style.container}>Eth Debugger</h1>
<TxBrowser onNewTxRequested={this.startDebugging} /> <TxBrowser onNewTxRequested={this.startDebugging} />
<StepManager ref='stepManager' onStepChanged={this.stepChanged} /> <StepManager ref='stepManager' onStepChanged={this.stepChanged} />
<AssemblyItemsBrowser ref='assemblyitemsbrowser' currentStepIndex={this.state.currentStepIndex} /> <VmDebugger ref='assemblyitemsbrowser' currentStepIndex={this.state.currentStepIndex} />
</div> </div>
) )
}, },
stepChanged: function (stepIndex) { stepChanged: function (stepIndex) {
this.trigger('indexChanged', [stepIndex])
this.setState({ this.setState({
currentStepIndex: stepIndex currentStepIndex: stepIndex
}) })
}, },
componentWillMount: function () {
util.extend(this, new EventManager())
},
startDebugging: function (blockNumber, txIndex, tx) { startDebugging: function (blockNumber, txIndex, tx) {
if (this.state.traceManager.isLoading) { if (this.props.context.traceManager.isLoading) {
return return
} }
console.log('loading trace...') console.log('loading trace...')
@ -61,12 +66,13 @@ module.exports = React.createClass({
tx: tx tx: tx
}) })
var self = this var self = this
this.state.traceManager.resolveTrace(blockNumber, txIndex, function (success) { this.props.context.traceManager.resolveTrace(tx, function (success) {
console.log('trace loaded ' + success) console.log('trace loaded ' + success)
self.setState({ if (success) {
currentStepIndex: 0 self.trigger('newTraceLoaded')
}) } else {
self.refs.stepManager.newTraceAvailable() console.log('trace not loaded')
}
}) })
} }
}) })

@ -0,0 +1,32 @@
'use strict'
function EventManager () {
this.registered = {}
}
EventManager.prototype.unregister = function (eventName, obj) {
for (var reg in this.registered[eventName]) {
if (this.registered[eventName][reg] && this.registered[eventName][reg].obj === obj) {
this.registered[eventName].splice(reg, 1)
return
}
}
}
EventManager.prototype.register = function (eventName, obj, func) {
if (!this.registered[eventName]) {
this.registered[eventName] = []
}
this.registered[eventName].push({
obj: obj,
func: func
})
}
EventManager.prototype.trigger = function (eventName, args) {
for (var listener in this.registered[eventName]) {
var l = this.registered[eventName][listener]
l.func.apply(l.obj, args)
}
}
module.exports = EventManager

@ -3,17 +3,24 @@ var ReactDOM = require('react-dom')
var React = require('react') var React = require('react')
var Web3 = require('web3') var Web3 = require('web3')
var Web3Admin = require('./web3Admin') var Web3Admin = require('./web3Admin')
var TraceManager = require('./traceManager')
var CodeManager = require('./codeManager')
function loadWeb3 () { function loadContext () {
var web3 = new Web3() var web3 = new Web3()
web3.setProvider(new web3.providers.HttpProvider('http://localhost:8545')) web3.setProvider(new web3.providers.HttpProvider('http://localhost:8545'))
Web3Admin.extend(web3) Web3Admin.extend(web3)
return web3 var traceManager = new TraceManager(web3)
return {
web3: web3,
traceManager: traceManager,
codeManager: new CodeManager(web3, traceManager)
}
} }
var Debugger = require('./debugger') var Debugger = require('./debugger')
ReactDOM.render( ReactDOM.render(
<Debugger web3={loadWeb3()} />, <Debugger context={loadContext()} />,
document.getElementById('app') document.getElementById('app')
) )

@ -1,18 +1,13 @@
'use strict' 'use strict'
var React = require('react') var React = require('react')
var BasicPanel = require('./basicPanel') var BasicPanel = require('./basicPanel')
var style = require('./basicStyles')
module.exports = React.createClass({ module.exports = React.createClass({
contextTypes: { contextTypes: {
traceManager: React.PropTypes.object, traceManager: React.PropTypes.object,
web3: React.PropTypes.object web3: React.PropTypes.object,
}, codeManager: React.PropTypes.object,
root: React.PropTypes.object
getDefaultProps: function () {
return {
currentStepIndex: -1
}
}, },
getInitialState: function () { getInitialState: function () {
@ -23,56 +18,42 @@ module.exports = React.createClass({
render: function () { render: function () {
return ( return (
<BasicPanel name='Memory' data={this.state.data} renderRow={this.renderMemoryRow} /> <BasicPanel name='Memory' data={this.state.data} />
) )
}, },
componentWillReceiveProps: function (nextProps) { componentDidMount: function () {
if (nextProps.currentStepIndex < 0) return
if (window.ethDebuggerSelectedItem !== nextProps.currentStepIndex) return
var self = this var self = this
this.context.traceManager.getMemoryAt(nextProps.currentStepIndex, function (error, memory) { this.context.root.register('indexChanged', this, function (index) {
if (error) { if (index < 0) return
console.log(error) if (self.context.root.ethDebuggerSelectedItem !== index) return
} else if (window.ethDebuggerSelectedItem === nextProps.currentStepIndex) {
self.setState({ self.context.traceManager.getMemoryAt(index, function (error, memory) {
data: self.formatMemory(memory, 16) if (error) {
}) console.log(error)
} } else if (self.context.root.ethDebuggerSelectedItem === index) {
self.setState({
data: self.formatMemory(memory, 16)
})
}
})
}) })
}, },
renderMemoryRow: function (data) { formatMemory: function (mem, width) {
var ret = [] var ret = ''
if (data) { if (!mem) {
for (var key in data) { return ret
var memSlot = data[key] }
ret.push(
<tr key={key}> if (!mem.substr) {
<td> mem = mem.join('') // geth returns an array, eth return raw string
<pre style={style.font}>{memSlot.address}</pre>
</td>
<td>
<pre style={style.font}>{memSlot.content.raw}</pre>
</td>
<td>
<pre style={style.font}>{memSlot.content.ascii}</pre>
</td>
</tr>)
}
} }
return ret
},
formatMemory: function (mem, width) {
var ret = []
for (var k = 0; k < mem.length; k += (width * 2)) { for (var k = 0; k < mem.length; k += (width * 2)) {
var memory = mem.substr(k, width * 2) var memory = mem.substr(k, width * 2)
ret.push({ var content = this.tryAsciiFormat(memory)
address: this.context.web3.toHex(k), ret += this.context.web3.toHex(k) + ' ' + content.raw + ' ' + content.ascii + '\n'
content: this.tryAsciiFormat(memory)
})
} }
return ret return ret
}, },
@ -81,12 +62,12 @@ module.exports = React.createClass({
var ret = { ascii: '', raw: '' } var ret = { ascii: '', raw: '' }
for (var k = 0; k < memorySlot.length; k += 2) { for (var k = 0; k < memorySlot.length; k += 2) {
var raw = memorySlot.substr(k, 2) var raw = memorySlot.substr(k, 2)
var ascii = this.context.web3.toAscii(raw) var ascii = String.fromCharCode(parseInt(raw, 16))
if (ascii === String.fromCharCode(0)) { ascii = ascii.replace(/\W/g, '?')
ret.ascii += '?' if (ascii === '') {
} else { ascii = '?'
ret.ascii += ascii
} }
ret.ascii += ascii
ret.raw += ' ' + raw ret.raw += ' ' + raw
} }
return ret return ret

@ -32,6 +32,10 @@ module.exports = React.createClass({
) )
}, },
shouldComponentUpdate: function (nextProps, nextState) {
return (nextProps.max !== this.props.max || nextProps.min !== this.props.min)
},
componentDidMount: function () { componentDidMount: function () {
this.setValue(0) this.setValue(0)
}, },

@ -4,13 +4,9 @@ var BasicPanel = require('./basicPanel')
module.exports = React.createClass({ module.exports = React.createClass({
contextTypes: { contextTypes: {
traceManager: React.PropTypes.object traceManager: React.PropTypes.object,
}, codeManager: React.PropTypes.object,
root: React.PropTypes.object
getDefaultProps: function () {
return {
currentStepIndex: -1
}
}, },
getInitialState: function () { getInitialState: function () {
@ -25,19 +21,29 @@ module.exports = React.createClass({
) )
}, },
componentWillReceiveProps: function (nextProps) { componentDidMount: function () {
if (nextProps.currentStepIndex < 0) return
if (window.ethDebuggerSelectedItem !== nextProps.currentStepIndex) return
var self = this var self = this
this.context.traceManager.getStackAt(nextProps.currentStepIndex, function (error, stack) { this.context.root.register('indexChanged', this, function (index) {
if (error) { if (index < 0) return
console.log(error) if (self.context.root.ethDebuggerSelectedItem !== index) return
} else if (window.ethDebuggerSelectedItem === nextProps.currentStepIndex) {
self.setState({ self.context.traceManager.getStackAt(index, function (error, stack) {
data: stack if (error) {
}) console.log(error)
} } else if (self.context.root.ethDebuggerSelectedItem === index) {
self.setState({
data: self.format(stack)
})
}
})
}) })
},
format: function (stack) {
var ret = ''
for (var key in stack) {
ret += stack[key] + '\n'
}
return ret
} }
}) })

@ -10,7 +10,8 @@ module.exports = React.createClass({
}, },
contextTypes: { contextTypes: {
traceManager: React.PropTypes.object traceManager: React.PropTypes.object,
root: React.PropTypes.object
}, },
getInitialState: function () { getInitialState: function () {
@ -29,26 +30,32 @@ module.exports = React.createClass({
min='0' min='0'
max={this.state.traceLength} /> max={this.state.traceLength} />
<ButtonNavigator <ButtonNavigator
ref='buttons'
stepIntoBack={this.stepIntoBack} stepIntoBack={this.stepIntoBack}
stepIntoForward={this.stepIntoForward} stepIntoForward={this.stepIntoForward}
stepOverBack={this.stepOverBack} stepOverBack={this.stepOverBack}
stepOverForward={this.stepOverForward} stepOverForward={this.stepOverForward}
jumpToNextCall={this.jumpToNextCall} jumpNextCall={this.jumpToNextCall}
max={this.state.traceLength} /> max={this.state.traceLength} />
</div> </div>
) )
}, },
componentDidMount: function () { componentDidMount: function () {
this.updateGlobalSelectedItem(0) var self = this
this.context.root.register('newTraceLoaded', this, function () {
self.newTraceAvailable()
})
this.changeState(-1)
}, },
updateGlobalSelectedItem: function (value) { updateGlobalSelectedItem: function (value) {
window.ethDebuggerSelectedItem = value this.context.root.ethDebuggerSelectedItem = value
}, },
init: function () { init: function () {
this.refs.slider.setValue(0) this.refs.slider.setValue(0)
this.changeState(0)
}, },
newTraceAvailable: function () { newTraceAvailable: function () {
@ -64,40 +71,59 @@ module.exports = React.createClass({
}, },
sliderMoved: function (step) { sliderMoved: function (step) {
this.props.onStepChanged(step) if (!this.context.traceManager.inRange(step)) {
return
}
this.changeState(step) this.changeState(step)
}, },
stepIntoForward: function () { stepIntoForward: function () {
if (!this.context.traceManager.isLoaded()) {
return
}
var step = this.state.currentStepIndex + 1 var step = this.state.currentStepIndex + 1
this.props.onStepChanged(step) if (!this.context.traceManager.inRange(step)) {
return
}
this.refs.slider.setValue(step)
this.changeState(step) this.changeState(step)
}, },
stepIntoBack: function () { stepIntoBack: function () {
if (!this.context.traceManager.isLoaded()) {
return
}
var step = this.state.currentStepIndex - 1 var step = this.state.currentStepIndex - 1
this.props.onStepChanged(step) if (!this.context.traceManager.inRange(step)) {
return
}
this.refs.slider.setValue(step) this.refs.slider.setValue(step)
this.changeState(step) this.changeState(step)
}, },
stepOverForward: function () { stepOverForward: function () {
if (!this.context.traceManager.isLoaded()) {
return
}
var step = this.context.traceManager.findStepOverForward(this.state.currentStepIndex) var step = this.context.traceManager.findStepOverForward(this.state.currentStepIndex)
this.props.onStepChanged(step)
this.refs.slider.setValue(step) this.refs.slider.setValue(step)
this.changeState(step) this.changeState(step)
}, },
stepOverBack: function () { stepOverBack: function () {
if (!this.context.traceManager.isLoaded()) {
return
}
var step = this.context.traceManager.findStepOverBack(this.state.currentStepIndex) var step = this.context.traceManager.findStepOverBack(this.state.currentStepIndex)
this.props.onStepChanged(step)
this.refs.slider.setValue(step) this.refs.slider.setValue(step)
this.changeState(step) this.changeState(step)
}, },
jumpToNextCall: function () { jumpToNextCall: function () {
if (!this.context.traceManager.isLoaded()) {
return
}
var step = this.context.traceManager.findNextCall(this.state.currentStepIndex) var step = this.context.traceManager.findNextCall(this.state.currentStepIndex)
this.props.onStepChanged(step)
this.refs.slider.setValue(step) this.refs.slider.setValue(step)
this.changeState(step) this.changeState(step)
}, },
@ -107,5 +133,7 @@ module.exports = React.createClass({
this.setState({ this.setState({
currentStepIndex: step currentStepIndex: step
}) })
this.refs.buttons.stepChanged(step)
this.props.onStepChanged(step)
} }
}) })

@ -3,13 +3,9 @@ var React = require('react')
module.exports = React.createClass({ module.exports = React.createClass({
contextTypes: { contextTypes: {
traceManager: React.PropTypes.object traceManager: React.PropTypes.object,
}, codeManager: React.PropTypes.object,
root: React.PropTypes.object
getDefaultProps: function () {
return {
currentStepIndex: -1
}
}, },
getInitialState: function () { getInitialState: function () {
@ -80,48 +76,50 @@ module.exports = React.createClass({
return null return null
}, },
componentWillReceiveProps: function (nextProps) { componentDidMount: function () {
if (nextProps.currentStepIndex < 0) return
var self = this var self = this
this.context.traceManager.getCurrentStep(nextProps.currentStepIndex, function (error, step) { this.context.root.register('indexChanged', this, function (index) {
if (error) { if (index < 0) return
console.log(error)
} else {
self.setState({
step: step
})
}
})
this.context.traceManager.getMemExpand(nextProps.currentStepIndex, function (error, addmem) { self.context.traceManager.getCurrentStep(index, function (error, step) {
if (error) { if (error) {
console.log(error) console.log(error)
} else { } else {
self.setState({ self.setState({
addmemory: addmem step: step
}) })
} }
}) })
this.context.traceManager.getStepCost(nextProps.currentStepIndex, function (error, gas) { self.context.traceManager.getMemExpand(index, function (error, addmem) {
if (error) { if (error) {
console.log(error) console.log(error)
} else { } else {
self.setState({ self.setState({
gas: gas addmemory: addmem
}) })
} }
}) })
this.context.traceManager.getRemainingGas(nextProps.currentStepIndex, function (error, remaingas) { self.context.traceManager.getStepCost(index, function (error, gas) {
if (error) { if (error) {
console.log(error) console.log(error)
} else { } else {
self.setState({ self.setState({
remainingGas: remaingas gas: gas
}) })
} }
})
self.context.traceManager.getRemainingGas(index, function (error, remaingas) {
if (error) {
console.log(error)
} else {
self.setState({
remainingGas: remaingas
})
}
})
}) })
} }
}) })

@ -1,18 +1,13 @@
'use strict' 'use strict'
var React = require('react') var React = require('react')
var BasicPanel = require('./basicPanel') var BasicPanel = require('./basicPanel')
var style = require('./basicStyles')
module.exports = React.createClass({ module.exports = React.createClass({
contextTypes: { contextTypes: {
traceManager: React.PropTypes.object, traceManager: React.PropTypes.object,
tx: React.PropTypes.object tx: React.PropTypes.object,
}, codeManager: React.PropTypes.object,
root: React.PropTypes.object
getDefaultProps: function () {
return {
currentStepIndex: -1
}
}, },
getInitialState: function () { getInitialState: function () {
@ -23,40 +18,32 @@ module.exports = React.createClass({
render: function () { render: function () {
return ( return (
<BasicPanel name='Storage' data={this.state.data} renderRow={this.renderStorageRow} /> <BasicPanel name='Storage' data={this.state.data} />
) )
}, },
componentWillReceiveProps: function (nextProps) { componentDidMount: function () {
if (nextProps.currentStepIndex < 0) return
if (window.ethDebuggerSelectedItem !== nextProps.currentStepIndex) return
var self = this var self = this
this.context.traceManager.getStorageAt(nextProps.currentStepIndex, this.context.tx.blockNumber.toString(), this.context.tx.transactionIndex, function (error, storage) { this.context.root.register('indexChanged', this, function (index) {
if (error) { if (index < 0) return
console.log(error) if (self.context.root.ethDebuggerSelectedItem !== index) return
} else if (window.ethDebuggerSelectedItem === nextProps.currentStepIndex) {
self.setState({ self.context.traceManager.getStorageAt(index, self.context.tx, function (error, storage) {
data: storage if (error) {
}) console.log(error)
} } else if (self.context.root.ethDebuggerSelectedItem === index) {
self.setState({
data: self.formatStorage(storage)
})
}
})
}) })
}, },
renderStorageRow: function (data) { formatStorage: function (storage) {
var ret = [] var ret = ''
if (data) { for (var key in storage) {
for (var key in data) { ret += key + ' ' + storage[key] + '\n'
ret.push(
<tr key={key}>
<td>
<pre style={style.font} >{key}</pre>
</td>
<td>
<pre style={style.font}>{data[key]}</pre>
</td>
</tr>)
}
} }
return ret return ret
} }

@ -1,25 +1,32 @@
'use strict' 'use strict'
var traceManagerUtil = require('./traceManagerUtil')
function TraceAnalyser (_cache) { function TraceAnalyser (_cache) {
this.traceCache = _cache this.traceCache = _cache
this.trace = null this.trace = null
} }
TraceAnalyser.prototype.analyse = function (trace, callback) { TraceAnalyser.prototype.analyse = function (trace, tx, callback) {
this.trace = trace this.trace = trace
var currentDepth = 0 this.traceCache.pushStoreChanges(0, tx.to)
var context = { var context = {
currentStorageAddress: trace[0].address, currentStorageAddress: tx.to,
previousStorageAddress: trace[0].address previousStorageAddress: tx.to
} }
var callStack = [] var callStack = [tx.to]
for (var k in this.trace) { this.traceCache.pushCallStack(0, {
callStack: callStack.slice(0)
})
if (traceManagerUtil.isContractCreation(tx.to)) {
this.traceCache.pushContractCreation(tx.to, tx.input)
}
for (var k = 0; k < this.trace.length; k++) {
var step = this.trace[k] var step = this.trace[k]
this.buildCalldata(k, step) this.buildCalldata(k, step)
this.buildMemory(k, step) this.buildMemory(k, step)
var depth = this.buildDepth(k, step, currentDepth, callStack) this.buildDepth(k, step, callStack)
if (depth) {
currentDepth = depth
}
context = this.buildStorage(k, step, context) context = this.buildStorage(k, step, context)
} }
callback(null, true) callback(null, true)
@ -38,40 +45,49 @@ TraceAnalyser.prototype.buildMemory = function (index, step) {
} }
TraceAnalyser.prototype.buildStorage = function (index, step, context) { TraceAnalyser.prototype.buildStorage = function (index, step, context) {
if (step.address) { if (traceManagerUtil.newContextStorage(step)) {
// new context var calledAddress = traceManagerUtil.resolveCalledAddress(index, this.trace)
context.currentStorageAddress = step.address if (calledAddress) {
this.traceCache.pushStoreChanges(index, context.currentStorageAddress) context.currentStorageAddress = calledAddress
} else if (step.inst === 'SSTORE') { } else {
this.traceCache.pushStoreChanges(index, context.currentStorageAddress, step.stack[step.stack.length - 1], step.stack[step.stack.length - 2]) console.log('unable to build storage changes. ' + index + ' does not match with a CALL. storage changes will be corrupted')
} else if (!step.address && step.depth) { }
// returned from context this.traceCache.pushStoreChanges(index + 1, context.currentStorageAddress)
} else if (traceManagerUtil.isSSTOREInstruction(step)) {
this.traceCache.pushStoreChanges(index + 1, context.currentStorageAddress, step.stack[step.stack.length - 1], step.stack[step.stack.length - 2])
} else if (traceManagerUtil.isReturnInstruction(step)) {
context.currentStorageAddress = context.previousStorageAddress context.currentStorageAddress = context.previousStorageAddress
this.traceCache.pushStoreChanges(index, context.currentStorageAddress) this.traceCache.pushStoreChanges(index + 1, context.currentStorageAddress)
} }
return context return context
} }
TraceAnalyser.prototype.buildDepth = function (index, step, currentDepth, callStack) { TraceAnalyser.prototype.buildDepth = function (index, step, callStack) {
if (step.depth === undefined) return if (traceManagerUtil.isCallInstruction(step) && !traceManagerUtil.isCallToPrecompiledContract(index, this.trace)) {
if (step.depth > currentDepth) { if (traceManagerUtil.isCreateInstruction(step)) {
if (index === 0) { var contractToken = traceManagerUtil.contractCreationToken(index)
callStack.push('0x' + step.address) // new context callStack.push(contractToken)
var lastMemoryChange = this.traceCache.memoryChanges[this.traceCache.memoryChanges.length - 1]
this.traceCache.pushContractCreationFromMemory(index, contractToken, this.trace, lastMemoryChange)
} else { } else {
// getting the address from the stack var newAddress = traceManagerUtil.resolveCalledAddress(index, this.trace)
var callTrace = this.trace[index - 1] if (newAddress) {
var address = callTrace.stack[callTrace.stack.length - 2] callStack.push(newAddress)
callStack.push(address) // new context } else {
console.log('unable to build depth changes. ' + index + ' does not match with a CALL. depth changes will be corrupted')
}
} }
} else if (step.depth < currentDepth) { this.traceCache.pushCallChanges(step, index + 1)
callStack.pop() // returning from context this.traceCache.pushCallStack(index + 1, {
callStack: callStack.slice(0)
})
} else if (traceManagerUtil.isReturnInstruction(step)) {
callStack.pop()
this.traceCache.pushCallChanges(step, index + 1)
this.traceCache.pushCallStack(index + 1, {
callStack: callStack.slice(0)
})
} }
this.traceCache.pushCallStack(index, {
stack: callStack.slice(0),
depth: step.depth
})
this.traceCache.pushDepthChanges(index)
return step.depth
} }
module.exports = TraceAnalyser module.exports = TraceAnalyser

@ -5,9 +5,14 @@ function TraceCache () {
TraceCache.prototype.init = function () { TraceCache.prototype.init = function () {
// ...Changes contains index in the vmtrace of the corresponding changes // ...Changes contains index in the vmtrace of the corresponding changes
this.depthChanges = []
this.memoryChanges = [] this.callChanges = []
this.returnChanges = []
this.calls = {}
this.contractCreation = {}
this.callDataChanges = [] this.callDataChanges = []
this.memoryChanges = []
this.storageChanges = [] this.storageChanges = []
this.sstore = {} // all sstore occurence in the trace this.sstore = {} // all sstore occurence in the trace
this.callStack = {} // contains all callStack by vmtrace index (we need to rebuild it, callstack is not included in the vmtrace) this.callStack = {} // contains all callStack by vmtrace index (we need to rebuild it, callstack is not included in the vmtrace)
@ -21,8 +26,27 @@ TraceCache.prototype.pushMemoryChanges = function (value) {
this.memoryChanges.push(value) this.memoryChanges.push(value)
} }
TraceCache.prototype.pushDepthChanges = function (value) { TraceCache.prototype.pushCallChanges = function (step, value) {
this.depthChanges.push(value) this.callChanges.push(value)
this.calls[value] = {
op: step.op
}
}
TraceCache.prototype.pushContractCreationFromMemory = function (index, token, trace, lastMemoryChange) {
var memory = trace[lastMemoryChange].memory
var stack = trace[index].stack
var offset = 2 * parseInt(stack[stack.length - 2], 16)
var size = 2 * parseInt(stack[stack.length - 3], 16)
this.contractCreation[token] = '0x' + memory.join('').substr(offset, size)
}
TraceCache.prototype.pushContractCreation = function (token, code) {
this.contractCreation[token] = code
}
TraceCache.prototype.pushReturnChanges = function (value) {
this.returnChanges.push(value)
} }
TraceCache.prototype.pushCallStack = function (index, callStack) { TraceCache.prototype.pushCallStack = function (index, callStack) {

@ -13,27 +13,36 @@ function TraceManager (_web3) {
this.traceAnalyser = new TraceAnalyser(this.traceCache) this.traceAnalyser = new TraceAnalyser(this.traceCache)
this.traceRetriever = new TraceRetriever(_web3) this.traceRetriever = new TraceRetriever(_web3)
this.traceStepManager = new TraceStepManager(this.traceAnalyser) this.traceStepManager = new TraceStepManager(this.traceAnalyser)
this.tx
} }
// init section // init section
TraceManager.prototype.resolveTrace = function (blockNumber, txNumber, callback) { TraceManager.prototype.resolveTrace = function (tx, callback) {
this.isLoading = true this.tx = tx
this.init() this.init()
if (!this.web3) callback(false) if (!this.web3) callback(false)
this.isLoading = true
var self = this var self = this
this.traceRetriever.getTrace(blockNumber, parseInt(txNumber), function (error, result) { this.traceRetriever.getTrace(tx.hash, function (error, result) {
self.trace = result
if (error) { if (error) {
console.log(error) console.log(error)
self.isLoading = false
} else { } else {
self.traceAnalyser.analyse(result, function (error, result) { if (result.structLogs.length > 0) {
if (error) { self.trace = result.structLogs
console.log(error) self.traceAnalyser.analyse(result.structLogs, tx, function (error, result) {
callback(false) if (error) {
} else { console.log(error)
callback(true) callback(false)
} } else {
}) callback(true)
}
self.isLoading = false
})
} else {
console.log(tx.hash + ' is not a contract invokation or contract creation.')
self.isLoading = false
}
} }
}) })
} }
@ -44,40 +53,71 @@ TraceManager.prototype.init = function () {
} }
// API section // API section
TraceManager.prototype.inRange = function (step) {
return this.isLoaded() && step >= 0 && step < this.trace.length
}
TraceManager.prototype.isLoaded = function () {
return !this.isLoading && this.trace !== null
}
TraceManager.prototype.getLength = function (callback) { TraceManager.prototype.getLength = function (callback) {
if (!this.trace) callback('no trace available', null) if (!this.trace) {
callback(null, this.trace.length) callback('no trace available', null)
} else {
callback(null, this.trace.length)
}
} }
TraceManager.prototype.getStorageAt = function (stepIndex, blockNumber, txIndex, callback) { TraceManager.prototype.getStorageAt = function (stepIndex, tx, callback) {
var check = this.checkRequestedStep(stepIndex)
if (check) {
return callback(check, null)
}
var stoChange = traceManagerUtil.findLowerBound(stepIndex, this.traceCache.storageChanges) var stoChange = traceManagerUtil.findLowerBound(stepIndex, this.traceCache.storageChanges)
if (stoChange === undefined) return callback('no storage found', null)
var address = this.traceCache.sstore[stoChange].address
var self = this var self = this
this.traceRetriever.getStorage(blockNumber, txIndex, address, function (error, result) { if (this.traceRetriever.debugStorageAtAvailable()) {
if (error) { var address = this.traceCache.sstore[stoChange].address
console.log(error) this.traceRetriever.getStorage(tx, address, function (error, result) {
callback(error, null) if (error) {
} else { console.log(error)
var storage = self.traceCache.rebuildStorage(address, result, stepIndex) callback(error, null)
callback(null, storage) } else {
} var storage = self.traceCache.rebuildStorage(address, result, stepIndex)
}) callback(null, storage)
}
})
} else {
callback(null, this.trace[stoChange].storage)
}
} }
TraceManager.prototype.getCallDataAt = function (stepIndex, callback) { TraceManager.prototype.getCallDataAt = function (stepIndex, callback) {
var check = this.checkRequestedStep(stepIndex)
if (check) {
return callback(check, null)
}
var callDataChange = traceManagerUtil.findLowerBound(stepIndex, this.traceCache.callDataChanges) var callDataChange = traceManagerUtil.findLowerBound(stepIndex, this.traceCache.callDataChanges)
if (!callDataChange) return callback('no calldata found', null) if (callDataChange === undefined) return callback('no calldata found', null)
callback(null, [this.trace[callDataChange].calldata]) callback(null, [this.trace[callDataChange].calldata])
} }
TraceManager.prototype.getCallStackAt = function (stepIndex, callback) { TraceManager.prototype.getCallStackAt = function (stepIndex, callback) {
var callStackChange = traceManagerUtil.findLowerBound(stepIndex, this.traceCache.depthChanges) var check = this.checkRequestedStep(stepIndex)
if (!callStackChange) return callback('no callstack found', null) if (check) {
callback(null, this.traceCache.callStack[callStackChange].stack) return callback(check, null)
}
var callStackChange = traceManagerUtil.findLowerBound(stepIndex, this.traceCache.callChanges)
if (callStackChange === undefined) return callback('no callstack found', null)
callback(null, this.traceCache.callStack[callStackChange].callStack)
} }
TraceManager.prototype.getStackAt = function (stepIndex, callback) { TraceManager.prototype.getStackAt = function (stepIndex, callback) {
var check = this.checkRequestedStep(stepIndex)
if (check) {
return callback(check, null)
}
var stack var stack
if (this.trace[stepIndex].stack) { // there's always a stack if (this.trace[stepIndex].stack) { // there's always a stack
stack = this.trace[stepIndex].stack.slice(0) stack = this.trace[stepIndex].stack.slice(0)
@ -88,48 +128,106 @@ TraceManager.prototype.getStackAt = function (stepIndex, callback) {
} }
} }
TraceManager.prototype.getLastDepthIndexChangeSince = function (stepIndex, callback) { TraceManager.prototype.getLastCallChangeSince = function (stepIndex, callback) {
var depthIndex = traceManagerUtil.findLowerBound(stepIndex, this.traceCache.depthChanges) var check = this.checkRequestedStep(stepIndex)
callback(null, depthIndex) if (check) {
return callback(check, null)
}
var callChange = traceManagerUtil.findLowerBound(stepIndex, this.traceCache.callChanges)
if (callChange === undefined) {
callback(null, 0)
} else {
callback(null, callChange)
}
} }
TraceManager.prototype.getCurrentCalledAddressAt = function (stepIndex, callback) { TraceManager.prototype.getCurrentCalledAddressAt = function (stepIndex, callback) {
var check = this.checkRequestedStep(stepIndex)
if (check) {
return callback(check, null)
}
var self = this var self = this
this.getLastDepthIndexChangeSince(stepIndex, function (error, addressIndex) { this.getLastCallChangeSince(stepIndex, function (error, addressIndex) {
if (error) { if (error) {
callback(error, null) callback(error, null)
} else { } else {
callback(null, traceManagerUtil.resolveCalledAddress(addressIndex, self.trace)) if (addressIndex === 0) {
callback(null, self.tx.to)
} else {
var callStack = self.traceCache.callStack[addressIndex].callStack
var calledAddress = callStack[callStack.length - 1]
if (calledAddress) {
callback(null, calledAddress)
} else {
callback('unable to get current called address. ' + stepIndex + ' does not match with a CALL', null)
}
}
} }
}) })
} }
TraceManager.prototype.getContractCreationCode = function (token, callback) {
if (this.traceCache.contractCreation[token]) {
callback(null, this.traceCache.contractCreation[token])
} else {
callback('no contract creation named ' + token, null)
}
}
TraceManager.prototype.getMemoryAt = function (stepIndex, callback) { TraceManager.prototype.getMemoryAt = function (stepIndex, callback) {
var check = this.checkRequestedStep(stepIndex)
if (check) {
return callback(check, null)
}
var lastChanges = traceManagerUtil.findLowerBound(stepIndex, this.traceCache.memoryChanges) var lastChanges = traceManagerUtil.findLowerBound(stepIndex, this.traceCache.memoryChanges)
if (!lastChanges) return callback('no memory found', null) if (lastChanges === undefined) return callback('no memory found', null)
callback(null, this.trace[lastChanges].memory) callback(null, this.trace[lastChanges].memory)
} }
TraceManager.prototype.getCurrentPC = function (stepIndex, callback) { TraceManager.prototype.getCurrentPC = function (stepIndex, callback) {
var check = this.checkRequestedStep(stepIndex)
if (check) {
return callback(check, null)
}
callback(null, this.trace[stepIndex].pc) callback(null, this.trace[stepIndex].pc)
} }
TraceManager.prototype.getCurrentStep = function (stepIndex, callback) { TraceManager.prototype.getCurrentStep = function (stepIndex, callback) {
var check = this.checkRequestedStep(stepIndex)
if (check) {
return callback(check, null)
}
callback(null, this.trace[stepIndex].steps) callback(null, this.trace[stepIndex].steps)
} }
TraceManager.prototype.getMemExpand = function (stepIndex, callback) { TraceManager.prototype.getMemExpand = function (stepIndex, callback) {
var check = this.checkRequestedStep(stepIndex)
if (check) {
return callback(check, null)
}
callback(null, this.trace[stepIndex].memexpand ? this.trace[stepIndex].memexpand : '') callback(null, this.trace[stepIndex].memexpand ? this.trace[stepIndex].memexpand : '')
} }
TraceManager.prototype.getStepCost = function (stepIndex, callback) { TraceManager.prototype.getStepCost = function (stepIndex, callback) {
callback(null, this.trace[stepIndex].gascost) var check = this.checkRequestedStep(stepIndex)
if (check) {
return callback(check, null)
}
callback(null, this.trace[stepIndex].gasCost)
} }
TraceManager.prototype.getRemainingGas = function (stepIndex, callback) { TraceManager.prototype.getRemainingGas = function (stepIndex, callback) {
var check = this.checkRequestedStep(stepIndex)
if (check) {
return callback(check, null)
}
callback(null, this.trace[stepIndex].gas) callback(null, this.trace[stepIndex].gas)
} }
TraceManager.prototype.isCreationStep = function (stepIndex) {
return traceManagerUtil.isCreateInstruction(stepIndex, this.trace)
}
// step section // step section
TraceManager.prototype.findStepOverBack = function (currentStep) { TraceManager.prototype.findStepOverBack = function (currentStep) {
return this.traceStepManager.findStepOverBack(currentStep) return this.traceStepManager.findStepOverBack(currentStep)
@ -147,4 +245,18 @@ TraceManager.prototype.findStepOutForward = function (currentStep) {
return this.traceStepManager.findStepOutForward(currentStep) return this.traceStepManager.findStepOutForward(currentStep)
} }
TraceManager.prototype.findNextCall = function (currentStep) {
return this.traceStepManager.findNextCall(currentStep)
}
// util
TraceManager.prototype.checkRequestedStep = function (stepIndex) {
if (!this.trace) {
return 'trace not loaded'
} else if (stepIndex >= this.trace.length) {
return 'trace smaller than requested'
}
return undefined
}
module.exports = TraceManager module.exports = TraceManager

@ -1,3 +1,4 @@
'use strict'
module.exports = { module.exports = {
// util section // util section
findLowerBound: function (target, changes) { findLowerBound: function (target, changes) {
@ -24,13 +25,53 @@ module.exports = {
} }
}, },
// vmTraceIndex has to point to a CALL, CODECALL, ...
resolveCalledAddress: function (vmTraceIndex, trace) { resolveCalledAddress: function (vmTraceIndex, trace) {
var address = trace[vmTraceIndex].address var step = trace[vmTraceIndex]
if (vmTraceIndex > 0) { if (this.isCreateInstruction(step)) {
var stack = trace[vmTraceIndex - 1].stack // callcode, delegatecall, ... return this.contractCreationToken(vmTraceIndex)
address = stack[stack.length - 2] } else if (this.isCallInstruction(step)) {
var stack = step.stack // callcode, delegatecall, ...
return stack[stack.length - 2]
} }
return address return undefined
} },
isCallInstruction: function (step) {
return step.op === 'CALL' || step.op === 'CALLCODE' || step.op === 'CREATE' || step.op === 'DELEGATECALL'
},
isCreateInstruction: function (step) {
return step.op === 'CREATE'
},
isReturnInstruction: function (step) {
return step.op === 'RETURN'
},
isSSTOREInstruction: function (step) {
return step.op === 'SSTORE'
},
newContextStorage: function (step) {
return step.op === 'CREATE' || step.op === 'CALL'
},
isCallToPrecompiledContract: function (index, trace) {
// if stack empty => this is not a precompiled contract
var step = trace[index]
if (this.isCallInstruction(step)) {
return trace[index + 1].stack.length !== 0
} else {
return false
}
},
contractCreationToken: function (index) {
return '(Contract Creation - Step ' + index + ')'
},
isContractCreation: function (address) {
return address.indexOf('(Contract Creation - Step') !== -1
}
} }

@ -1,25 +1,38 @@
'use strict' 'use strict'
var traceManagerUtil = require('./traceManagerUtil')
function TraceRetriever (_web3) { function TraceRetriever (_web3) {
this.web3 = _web3 this.web3 = _web3
this.storages = {} // contains all intial storage (by addresses) this.storages = {} // contains all intial storage (by addresses)
} }
TraceRetriever.prototype.getTrace = function (blockNumber, txNumber, callback) { TraceRetriever.prototype.getTrace = function (txHash, callback) {
this.web3.debug.trace(blockNumber, parseInt(txNumber), function (error, result) { var options = {
disableStorage: this.debugStorageAtAvailable(),
disableMemory: false,
disableStack: false,
fullStorage: !this.debugStorageAtAvailable()
}
this.web3.debug.traceTransaction(txHash, options, function (error, result) {
callback(error, result) callback(error, result)
}) })
} }
TraceRetriever.prototype.getStorage = function (blockNumber, txIndex, address, callback) { TraceRetriever.prototype.getStorage = function (tx, address, callback) {
if (this.storages[address]) { if (traceManagerUtil.isContractCreation(address)) {
callback(null, {})
} else if (this.storages[address]) {
callback(null, this.storages[address]) callback(null, this.storages[address])
} else { } else {
var self = this var self = this
this.web3.debug.storageAt(blockNumber, txIndex, address, function (error, result) { this.web3.debug.storageAt(tx.blockNumber.toString(), tx.transactionIndex, address, function (error, result) {
self.storages[address] = result self.storages[address] = result
callback(error, result) callback(error, result)
}) })
} }
} }
TraceRetriever.prototype.debugStorageAtAvailable = function () {
return true // storageAt not available if using geth
}
module.exports = TraceRetriever module.exports = TraceRetriever

@ -1,35 +1,34 @@
'use strict' 'use strict'
var traceManagerUtil = require('./traceManagerUtil')
function TraceStepManager (_traceAnalyser) { function TraceStepManager (_traceAnalyser) {
this.traceAnalyser = _traceAnalyser this.traceAnalyser = _traceAnalyser
} }
TraceStepManager.prototype.isCallInstruction = function (index) { TraceStepManager.prototype.isCallInstruction = function (index) {
var state = this.traceAnalyser.trace[index] var state = this.traceAnalyser.trace[index]
return state.instname === 'CALL' || state.instname === 'CALLCODE' || state.instname === 'CREATE' || state.instname === 'DELEGATECALL' return traceManagerUtil.isCallInstruction(state)
} }
TraceStepManager.prototype.isReturnInstruction = function (index) { TraceStepManager.prototype.isReturnInstruction = function (index) {
var state = this.traceAnalyser.trace[index] var state = this.traceAnalyser.trace[index]
return state.instname === 'RETURN' return traceManagerUtil.isReturnInstruction(state)
} }
TraceStepManager.prototype.findStepOverBack = function (currentStep) { TraceStepManager.prototype.findStepOverBack = function (currentStep) {
if (this.isReturnInstruction(currentStep - 1)) { if (currentStep === 0) return 0
return this.findStepOutBack(currentStep) return this.findStepOutBack(currentStep)
} else {
return currentStep - 1
}
} }
TraceStepManager.prototype.findStepOverForward = function (currentStep) { TraceStepManager.prototype.findStepOverForward = function (currentStep) {
if (this.isCallInstruction(currentStep)) { if (currentStep === this.traceAnalyser.trace.length - 1) return currentStep
return this.findStepOutForward(currentStep) return this.findStepOutForward(currentStep)
} else {
return currentStep + 1
}
} }
TraceStepManager.prototype.findStepOutBack = function (currentStep) { TraceStepManager.prototype.findStepOutBack = function (currentStep) {
if (!this.traceAnalyser.trace) {
return currentStep
}
var i = currentStep - 1 var i = currentStep - 1
var depth = 0 var depth = 0
while (--i >= 0) { while (--i >= 0) {
@ -47,9 +46,12 @@ TraceStepManager.prototype.findStepOutBack = function (currentStep) {
} }
TraceStepManager.prototype.findStepOutForward = function (currentStep) { TraceStepManager.prototype.findStepOutForward = function (currentStep) {
if (!this.traceAnalyser.trace) {
return currentStep
}
var i = currentStep var i = currentStep
var depth = 0 var depth = 0
while (++i < this.traceAnalyser.length) { while (++i < this.traceAnalyser.trace.length) {
if (this.isReturnInstruction(i)) { if (this.isReturnInstruction(i)) {
if (depth === 0) { if (depth === 0) {
break break
@ -60,7 +62,20 @@ TraceStepManager.prototype.findStepOutForward = function (currentStep) {
depth++ depth++
} }
} }
return i + 1 return i
}
TraceStepManager.prototype.findNextCall = function (currentStep) {
if (!this.traceAnalyser.trace) {
return currentStep
}
var i = currentStep
while (++i < this.traceAnalyser.trace.length) {
if (this.isCallInstruction(i)) {
return i
}
}
return currentStep
} }
module.exports = TraceStepManager module.exports = TraceStepManager

@ -1,6 +1,7 @@
'use strict' 'use strict'
var React = require('react') var React = require('react')
var style = require('./basicStyles') var style = require('./basicStyles')
var traceManagerUtil = require('./traceManagerUtil')
module.exports = React.createClass({ module.exports = React.createClass({
contextTypes: { contextTypes: {
@ -12,12 +13,26 @@ module.exports = React.createClass({
}, },
getInitialState: function () { getInitialState: function () {
return {blockNumber: '1382256', txNumber: '1', from: '', to: '', hash: ''} return {blockNumber: '1000110', txNumber: '0x20ef65b8b186ca942fcccd634f37074dde49b541c27994fc7596740ef44cfd51', from: '', to: '', hash: ''}
}, },
// creation 0xa9619e1d0a35b2c1d686f5b661b3abd87f998d2844e8e9cc905edb57fc9ce349
// invokation 0x71a6d583d16d142c5c3e8903060e8a4ee5a5016348a9448df6c3e63b68076ec4
// test:
// creation: 0x72908de76f99fca476f9e3a3b5d352f350a98cd77d09cebfc59ffe32a6ecaa0b
// invokation: 0x20ef65b8b186ca942fcccd634f37074dde49b541c27994fc7596740ef44cfd51
submit: function () { submit: function () {
var tx = this.context.web3.eth.getTransactionFromBlock(this.state.blockNumber, this.state.txNumber) var tx
if (this.state.txNumber.indexOf('0x') !== -1) {
tx = this.context.web3.eth.getTransaction(this.state.txNumber)
} else {
tx = this.context.web3.eth.getTransactionFromBlock(this.state.blockNumber, this.state.txNumber)
}
if (tx) { if (tx) {
if (!tx.to) {
tx.to = traceManagerUtil.contractCreationToken('0')
}
this.setState({from: tx.from, to: tx.to, hash: tx.hash}) this.setState({from: tx.from, to: tx.to, hash: tx.hash})
this.props.onNewTxRequested(this.state.blockNumber, parseInt(this.state.txNumber), tx) this.props.onNewTxRequested(this.state.blockNumber, parseInt(this.state.txNumber), tx)
} else { } else {
@ -25,6 +40,10 @@ module.exports = React.createClass({
} }
}, },
updateTxhash: function (ev) {
this.state.hash = ev.target.value
},
updateBlockN: function (ev) { updateBlockN: function (ev) {
this.state.blockNumber = ev.target.value this.state.blockNumber = ev.target.value
}, },
@ -36,8 +55,8 @@ module.exports = React.createClass({
render: function () { render: function () {
return ( return (
<div style={style.container}> <div style={style.container}>
<input onChange={this.updateBlockN} type='text' placeholder={'Block number or hash (default 1382256)' + this.state.blockNumber}></input> <input onChange={this.updateBlockN} type='text' placeholder={'Block number (default 1000110)' + this.state.blockNumber}></input>
<input onChange={this.updateTxN} type='text' placeholder={'Transaction Number (default 1) ' + this.state.txNumber}></input> <input onChange={this.updateTxN} type='text' placeholder={'Transaction Number or hash (default 2) ' + this.state.txNumber}></input>
<button onClick={this.submit}> <button onClick={this.submit}>
Get Get
</button> </button>
@ -45,16 +64,28 @@ module.exports = React.createClass({
<table> <table>
<tbody> <tbody>
<tr> <tr>
<td>Hash: </td> <td>
<td>{this.state.hash}</td> Hash:
</td>
<td>
{this.state.hash}
</td>
</tr> </tr>
<tr> <tr>
<td>From: </td> <td>
<td>{this.state.from}</td> From:
</td>
<td>
{this.state.from}
</td>
</tr> </tr>
<tr> <tr>
<td>To: </td> <td>
<td>{this.state.to}</td> To:
</td>
<td>
{this.state.to}
</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

@ -0,0 +1,8 @@
'use strict'
module.exports = {
extend: function (destination, source) {
for (var property in source) {
destination[property] = source[property]
}
}
}

@ -10,56 +10,37 @@ var StackPanel = require('./stackPanel')
var StoragePanel = require('./storagePanel') var StoragePanel = require('./storagePanel')
module.exports = React.createClass({ module.exports = React.createClass({
contextTypes: {
traceManager: React.PropTypes.object
},
getInitialState: function () {
return {
currentAddress: null
}
},
getDefaultProps: function () {
return {
currentStepIndex: -1 // index of the selected item in the vmtrace
}
},
render: function () { render: function () {
return ( return (
<div style={this.props.vmTrace === null ? style.hidden : style.display}> <div style={this.props.vmTrace === null ? style.hidden : style.display}>
<div style={style.container}>
<span style={style.address}>Current code: {this.state.currentAddress}</span>
</div>
<div style={style.container}> <div style={style.container}>
<table> <table>
<tbody> <tbody>
<tr> <tr>
<td> <td>
<ASMCode currentStepIndex={this.props.currentStepIndex} /> <ASMCode />
<div style={Object.assign(style.inline, style.sticker)}> <div style={Object.assign(style.inline, style.sticker)}>
<Sticker currentStepIndex={this.props.currentStepIndex} /> <Sticker />
</div> </div>
</td> </td>
<td> <td>
<CalldataPanel currentStepIndex={this.props.currentStepIndex} /> <StackPanel />
</td> </td>
</tr> </tr>
<tr> <tr>
<td> <td>
<StackPanel currentStepIndex={this.props.currentStepIndex} /> <StoragePanel />
</td> </td>
<td> <td>
<CallstackPanel currentStepIndex={this.props.currentStepIndex} /> <MemoryPanel />
</td> </td>
</tr> </tr>
<tr> <tr>
<td> <td>
<StoragePanel currentStepIndex={this.props.currentStepIndex} /> <CalldataPanel />
</td> </td>
<td> <td>
<MemoryPanel currentStepIndex={this.props.currentStepIndex} /> <CallstackPanel />
</td> </td>
</tr> </tr>
</tbody> </tbody>
@ -67,20 +48,5 @@ module.exports = React.createClass({
</div> </div>
</div> </div>
) )
},
componentWillReceiveProps: function (nextProps) {
if (nextProps.currentStepIndex < 0) return
if (window.ethDebuggerSelectedItem !== nextProps.currentStepIndex) return
var self = this
this.context.traceManager.getCurrentCalledAddressAt(nextProps.currentStepIndex, function (error, address) {
if (error) {
console.log(error)
} else if (window.ethDebuggerSelectedItem === nextProps.currentStepIndex) {
self.setState({
currentAddress: address
})
}
})
} }
}) })

@ -96,8 +96,8 @@ module.exports = {
params: 3 params: 3
}), }),
new web3._extend.Method({ new web3._extend.Method({
name: 'trace', name: 'traceTransaction',
call: 'debug_trace', call: 'debug_traceTransaction',
inputFormatter: [null, null], inputFormatter: [null, null],
params: 2 params: 2
}), }),

Loading…
Cancel
Save