Merge pull request #25 from yann300/vmTrace

[vmtrace] load storage async
pull/7/head
yann300 9 years ago
commit 6d0de8aad5
  1. 1
      .gitignore
  2. 94
      src/asmCode.js
  3. 370
      src/assemblyItemsBrowser.js
  4. 2
      src/basicPanel.js
  5. 7
      src/basicStyles.js
  6. 6
      src/buttonNavigator.js
  7. 43
      src/calldataPanel.js
  8. 43
      src/callstackPanel.js
  9. 67
      src/codeResolver.js
  10. 64
      src/debugger.js
  11. 94
      src/memoryPanel.js
  12. 10
      src/slider.js
  13. 43
      src/stackPanel.js
  14. 111
      src/stepManager.js
  15. 97
      src/sticker.js
  16. 63
      src/storagePanel.js
  17. 77
      src/traceAnalyser.js
  18. 55
      src/traceCache.js
  19. 150
      src/traceManager.js
  20. 36
      src/traceManagerUtil.js
  21. 25
      src/traceRetriever.js
  22. 66
      src/traceStepManager.js
  23. 32
      src/txBrowser.js
  24. 86
      src/vmDebugger.js
  25. 13
      src/vmTraceBrowser.js
  26. 8
      src/vmTraceManager.js
  27. 6
      src/web3Admin.js

1
.gitignore vendored

@ -2,3 +2,4 @@ build
node_modules node_modules
npm-debug.log npm-debug.log
lint.xml lint.xml
.vscode

@ -0,0 +1,94 @@
'use strict'
var React = require('react')
var style = require('./basicStyles')
var codeResolver = require('./codeResolver')
module.exports = React.createClass({
contextTypes: {
traceManager: React.PropTypes.object,
tx: React.PropTypes.object,
web3: React.PropTypes.object
},
getInitialState: function () {
return {
code: [],
selected: -1,
address: '' // selected instruction in the asm
}
},
getDefaultProps: function () {
return {
currentStepIndex: -1
}
},
render: function () {
return (
<select
size='10'
ref='itemsList'
style={style.instructionsList}
value={this.state.selected}>
{this.renderAssemblyItems()}
</select>
)
},
renderAssemblyItems: function () {
if (this.state.code) {
return this.state.code.map(function (item, i) {
return <option key={i} value={i}>{item}</option>
})
}
},
componentWillReceiveProps: function (nextProps) {
if (nextProps.currentStepIndex < 0) return
codeResolver.setWeb3(this.context.web3)
var self = this
this.context.traceManager.getCurrentCalledAddressAt(nextProps.currentStepIndex, function (error, address) {
if (error) {
console.log(error)
} else {
self.ensureCodeLoaded(address, nextProps.currentStepIndex)
}
})
},
ensureCodeLoaded: function (address, currentStep) {
if (address !== this.state.address) {
this.setState({
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)
}
},
setInstructionIndex: function (address, step) {
var self = this
this.context.traceManager.getCurrentPC(step, function (error, instIndex) {
if (error) {
console.log(error)
} else {
self.setState({
selected: codeResolver.getInstructionIndex(address, instIndex)
})
}
})
}
})

@ -1,370 +0,0 @@
'use strict'
var React = require('react')
var BasicPanel = require('./basicPanel')
var Sticker = require('./sticker')
var ButtonNavigator = require('./vmTraceButtonNavigator')
var codeUtils = require('./codeUtils')
var style = require('./basicStyles')
var Slider = require('./slider')
module.exports = React.createClass({
contextTypes: {
web3: React.PropTypes.object
},
getInitialState: function () {
return {
currentSelected: -1, // current selected item in the vmTrace
selectedInst: -1, // current selected item in the contract assembly code
currentAddress: null,
currentStack: null,
currentLevels: null,
currentStorage: null,
currentMemory: null,
currentCallData: null,
currentStepInfo: null,
codes: {}, // assembly items instructions list by contract addesses
instructionsIndexByBytesOffset: {}, // mapping between bytes offset and instructions index.
callStack: {}
}
},
getDefaultProps: function () {
return {
vmTrace: null
}
},
render: function () {
return (
<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}>
<Slider
ref='slider'
onChange={this.selectState}
min='0'
max={this.props.vmTrace ? this.props.vmTrace.length : 0} />
<ButtonNavigator
vmTraceLength={this.props.vmTrace ? this.props.vmTrace.length : 0}
step={this.state.currentSelected}
stepIntoBack={this.stepIntoBack}
stepIntoForward={this.stepIntoForward}
stepOverBack={this.stepOverBack}
stepOverForward={this.stepOverForward} />
</div>
<div style={style.container}>
<table>
<tbody>
<tr>
<td>
<select
size='10'
ref='itemsList'
style={style.instructionsList}
value={this.state.selectedInst}>
{this.renderAssemblyItems()}
</select>
<div style={Object.assign(style.inline, style.sticker)}>
<Sticker data={this.state.currentStepInfo} />
</div>
</td>
<td>
<BasicPanel name='CallData' data={this.state.currentCallData} />
</td>
</tr>
<tr>
<td>
<BasicPanel name='Stack' data={this.state.currentStack} />
</td>
<td>
<BasicPanel name='CallStack' data={this.state.currentCallStack} />
</td>
</tr>
<tr>
<td>
<BasicPanel name='Storage' data={this.state.currentStorage} renderRow={this.renderStorageRow} />
</td>
<td>
<BasicPanel name='Memory' data={this.state.currentMemory} renderRow={this.renderMemoryRow} />
</td>
</tr>
</tbody>
</table>
</div>
</div>
)
},
renderStorageRow: function (data) {
var ret = []
if (data) {
for (var key in data) {
ret.push(
<tr key={key}>
<td>
{key}
</td>
<td>
{data[key]}
</td>
</tr>)
}
}
return ret
},
renderMemoryRow: function (data) {
var ret = []
if (data) {
for (var key in data) {
var memSlot = data[key]
ret.push(
<tr key={key}>
<td>
{memSlot.address}
</td>
<td>
{memSlot.content.raw}
</td>
<td>
{memSlot.content.ascii}
</td>
</tr>)
}
}
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]
}
},
renderAssemblyItems: function () {
if (this.props.vmTrace) {
return this.state.codes[this.state.currentAddress].map(function (item, i) {
return <option key={i} value={i}>{item}</option>
})
}
},
componentWillReceiveProps: function (nextProps) {
if (!nextProps.vmTrace) {
return
}
this.buildCallStack(nextProps.vmTrace)
this.setState({'currentSelected': -1})
this.updateState(nextProps, 0)
},
buildCallStack: function (vmTrace) {
if (!vmTrace) {
return
}
var callStack = []
var depth = -1
for (var k = 0; k < vmTrace.length; k++) {
var trace = vmTrace[k]
if (trace.depth === undefined || trace.depth === depth) {
continue
}
if (trace.depth > depth) {
callStack.push(trace.address) // new context
} else if (trace.depth < depth) {
callStack.pop() // returning from context
}
depth = trace.depth
this.state.callStack[k] = callStack.slice(0)
}
},
updateState: function (props, vmTraceIndex) {
if (!props.vmTrace || !props.vmTrace[vmTraceIndex]) {
return
}
var previousIndex = this.state.currentSelected
var stateChanges = {}
if (props.vmTrace[vmTraceIndex].stack) { // there's always a stack
var stack = props.vmTrace[vmTraceIndex].stack
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 })
}
var depthIndex = this.shouldUpdateStateProperty('depth', vmTraceIndex, previousIndex, props.vmTrace)
if (depthIndex > -1) {
Object.assign(stateChanges, { 'currentCallStack': this.state.callStack[depthIndex] })
}
var storageIndex = this.shouldUpdateStateProperty('storage', vmTraceIndex, previousIndex, props.vmTrace)
if (storageIndex > -1) {
Object.assign(stateChanges, { 'currentStorage': props.vmTrace[storageIndex].storage })
}
var memoryIndex = this.shouldUpdateStateProperty('memory', vmTraceIndex, previousIndex, props.vmTrace)
if (memoryIndex > -1) {
Object.assign(stateChanges, { 'currentMemory': this.formatMemory(props.vmTrace[memoryIndex].memory, 16) })
}
var callDataIndex = this.shouldUpdateStateProperty('calldata', vmTraceIndex, previousIndex, props.vmTrace)
if (callDataIndex > -1) {
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)
},
shouldUpdateStateProperty: function (vmTraceName, nextIndex, previousIndex, vmTrace) {
var propIndex = -1
if (previousIndex + 1 === nextIndex) {
propIndex = nextIndex
} else {
propIndex = this.retrieveLastSeenProperty(nextIndex, vmTraceName, vmTrace)
}
if (propIndex > -1 && vmTrace[propIndex][vmTraceName] !== undefined) {
return propIndex
} else {
return -1
}
},
retrieveLastSeenProperty: function (currentIndex, propertyName, vmTrace) {
var index = currentIndex
while (index > 0) {
if (vmTrace[index][propertyName]) {
break
}
index--
}
return index
},
stepIntoBack: function () {
this.moveSelection(-1)
},
stepIntoForward: function () {
this.moveSelection(1)
},
stepOverBack: function () {
if (this.isReturnInstruction(this.state.currentSelected - 1)) {
this.stepOutBack()
} else {
this.moveSelection(-1)
}
},
stepOverForward: function () {
if (this.isCallInstruction(this.state.currentSelected)) {
this.stepOutForward()
} else {
this.moveSelection(1)
}
},
isCallInstruction: function (index) {
var state = this.props.vmTrace[index]
return state.instname === 'CALL' || state.instname === 'CALLCODE' || state.instname === 'CREATE' || state.instname === 'DELEGATECALL'
},
isReturnInstruction: function (index) {
var state = this.props.vmTrace[index]
return state.instname === 'RETURN'
},
stepOutBack: function () {
var i = this.state.currentSelected - 1
var depth = 0
while (--i >= 0) {
if (this.isCallInstruction(i)) {
if (depth === 0) {
break
} else {
depth--
}
} else if (this.isReturnInstruction(i)) {
depth++
}
}
this.selectState(i)
},
stepOutForward: function () {
var i = this.state.currentSelected
var depth = 0
while (++i < this.props.vmTrace.length) {
if (this.isReturnInstruction(i)) {
if (depth === 0) {
break
} else {
depth--
}
} else if (this.isCallInstruction(i)) {
depth++
}
}
this.selectState(i + 1)
},
moveSelection: function (incr) {
this.selectState(this.state.currentSelected + incr)
},
selectState: function (index) {
this.updateState(this.props, index)
},
formatMemory: function (mem, width) {
var ret = []
for (var k = 0; k < mem.length; k += (width * 2)) {
var memory = mem.substr(k, width * 2)
ret.push({
address: this.context.web3.toHex(k),
content: this.tryAsciiFormat(memory)
})
}
return ret
},
tryAsciiFormat: function (memorySlot) {
var ret = { ascii: '', raw: '' }
for (var k = 0; k < memorySlot.length; k += 2) {
var raw = memorySlot.substr(k, 2)
var ascii = this.context.web3.toAscii(raw)
if (ascii === String.fromCharCode(0)) {
ret.ascii += '?'
} else {
ret.ascii += ascii
}
ret.raw += ' ' + raw
}
return ret
}
})

@ -38,7 +38,7 @@ module.exports = React.createClass({
ret.push( ret.push(
<tr key={key}> <tr key={key}>
<td> <td>
{this.props.data[key]} <pre style={style.font} >{this.props.data[key]}</pre>
</td> </td>
</tr>) </tr>)
} }

@ -1,6 +1,6 @@
'use strict' 'use strict'
module.exports = { module.exports = {
wrapper: { font: {
'fontFamily': 'arial,sans-serif' 'fontFamily': 'arial,sans-serif'
}, },
container: { container: {
@ -11,7 +11,8 @@ module.exports = {
'fontStyle': 'italic' 'fontStyle': 'italic'
}, },
instructionsList: { instructionsList: {
'width': '320px' 'width': '320px',
'height': '300px'
}, },
transactionInfo: { transactionInfo: {
'marginTop': '5px' 'marginTop': '5px'
@ -22,7 +23,7 @@ module.exports = {
'width': '600px' 'width': '600px'
}, },
tableContainer: { tableContainer: {
'height': '150px', 'height': '300px',
'overflowY': 'auto' 'overflowY': 'auto'
}, },
table: { table: {

@ -2,6 +2,10 @@
var React = require('react') var React = require('react')
module.exports = React.createClass({ module.exports = React.createClass({
contextTypes: {
traceManager: React.PropTypes.object
},
propTypes: { propTypes: {
stepIntoBack: React.PropTypes.func.isRequired, stepIntoBack: React.PropTypes.func.isRequired,
stepIntoForward: React.PropTypes.func.isRequired, stepIntoForward: React.PropTypes.func.isRequired,
@ -32,7 +36,7 @@ module.exports = React.createClass({
if (incr === -1) { if (incr === -1) {
return this.props.step === 0 ? 'disabled' : '' return this.props.step === 0 ? 'disabled' : ''
} else if (incr === 1) { } else if (incr === 1) {
return this.props.step >= this.props.vmTraceLength - 1 ? 'disabled' : '' return this.props.step >= this.props.max - 1 ? 'disabled' : ''
} }
} }
}) })

@ -0,0 +1,43 @@
'use strict'
var React = require('react')
var BasicPanel = require('./basicPanel')
module.exports = React.createClass({
contextTypes: {
traceManager: React.PropTypes.object
},
getDefaultProps: function () {
return {
currentStepIndex: -1
}
},
getInitialState: function () {
return {
data: null
}
},
render: function () {
return (
<BasicPanel name='CallData' data={this.state.data} />
)
},
componentWillReceiveProps: function (nextProps) {
if (nextProps.currentStepIndex < 0) return
if (window.ethDebuggerSelectedItem !== nextProps.currentStepIndex) return
var self = this
this.context.traceManager.getCallDataAt(nextProps.currentStepIndex, function (error, calldata) {
if (error) {
console.log(error)
} else if (window.ethDebuggerSelectedItem === nextProps.currentStepIndex) {
self.setState({
data: calldata
})
}
})
}
})

@ -0,0 +1,43 @@
'use strict'
var React = require('react')
var BasicPanel = require('./basicPanel')
module.exports = React.createClass({
contextTypes: {
traceManager: React.PropTypes.object
},
getDefaultProps: function () {
return {
currentStepIndex: -1
}
},
getInitialState: function () {
return {
data: null
}
},
render: function () {
return (
<BasicPanel name='CallStack' data={this.state.data} />
)
},
componentWillReceiveProps: function (nextProps) {
if (nextProps.currentStepIndex < 0) return
if (window.ethDebuggerSelectedItem !== nextProps.currentStepIndex) return
var self = this
this.context.traceManager.getCallStackAt(nextProps.currentStepIndex, function (error, callstack) {
if (error) {
console.log(error)
} else if (window.ethDebuggerSelectedItem === nextProps.currentStepIndex) {
self.setState({
data: callstack
})
}
})
}
})

@ -0,0 +1,67 @@
'use strict'
var codeUtils = require('./codeUtils')
module.exports = {
web3: null,
codes: {}, // assembly items instructions list by contract addesses
instructionsIndexByBytesOffset: {}, // mapping between bytes offset and instructions index.
setWeb3: function (web3) {
this.web3 = web3
},
resolveCode: function (address, vmTraceIndex, transaction, callBack) {
var cache = this.getExecutingCodeFromCache(address)
if (cache) {
callBack(address, cache.code)
return
}
if (vmTraceIndex === 0 && transaction.to === null) { // start of the trace
callBack(address, this.cacheExecutingCode(address, transaction.input).code)
return
}
var self = this
this.loadCode(address, function (code) {
callBack(address, self.cacheExecutingCode(address, code).code)
})
},
loadCode: function (address, callback) {
console.log('loading new code from web3 ' + address)
this.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.codes[address] = code[0]
this.instructionsIndexByBytesOffset[address] = code[1]
return {
code: code[0],
instructionsIndexByBytesOffset: code[1]
}
},
getExecutingCodeFromCache: function (address) {
if (this.codes[address]) {
return {
code: this.codes[address],
instructionsIndexByBytesOffset: this.instructionsIndexByBytesOffset[address]
}
} else {
return null
}
},
getInstructionIndex: function (address, pc) {
return this.getExecutingCodeFromCache(address).instructionsIndexByBytesOffset[pc]
}
}

@ -1,44 +1,72 @@
'use strict' 'use strict'
var React = require('react') var React = require('react')
var TxBrowser = require('./txBrowser') var TxBrowser = require('./txBrowser')
var VmTraceBrowser = require('./vmTraceBrowser') var StepManager = require('./stepManager')
var AssemblyItemsBrowser = require('./vmDebugger')
var TraceManager = require('./traceManager')
var style = require('./basicStyles') var style = require('./basicStyles')
module.exports = React.createClass({ module.exports = React.createClass({
getInitialState: function () { getInitialState: function () {
return {vmTrace: null, state: '', currentStep: -1} return {
currentStepIndex: -1, // index of the selected item in the vmtrace
tx: null,
traceManager: null
}
}, },
childContextTypes: { childContextTypes: {
web3: React.PropTypes.object web3: React.PropTypes.object,
traceManager: React.PropTypes.object,
tx: React.PropTypes.object
}, },
getChildContext: function () { getChildContext: function () {
return { web3: this.props.web3 } return {
web3: this.props.web3,
traceManager: this.state.traceManager,
tx: this.state.tx
}
},
componentDidMount: function () {
this.setState({
traceManager: new TraceManager(this.props.web3)
})
}, },
render: function () { render: function () {
return ( return (
<div style={style.wrapper}> <div style={style.font}>
<h1 style={style.container}>Eth Debugger</h1> <h1 style={style.container}>Eth Debugger</h1>
<TxBrowser onNewTxRequested={this.retrieveVmTrace} /> <TxBrowser onNewTxRequested={this.startDebugging} />
<div style={style.container}> <StepManager ref='stepManager' onStepChanged={this.stepChanged} />
{this.state.state} <AssemblyItemsBrowser ref='assemblyitemsbrowser' currentStepIndex={this.state.currentStepIndex} />
</div>
<VmTraceBrowser vmTrace={this.state.vmTrace} />
</div> </div>
) )
}, },
retrieveVmTrace: function (blockNumber, txNumber) { stepChanged: function (stepIndex) {
this.setState({state: 'loading...'}) this.setState({
currentStepIndex: stepIndex
})
},
startDebugging: function (blockNumber, txIndex, tx) {
if (this.state.traceManager.isLoading) {
return
}
console.log('loading trace...')
this.setState({
tx: tx
})
var self = this var self = this
this.props.web3.debug.trace(blockNumber, parseInt(txNumber), function (error, result) { this.state.traceManager.resolveTrace(blockNumber, txIndex, function (success) {
if (error) { console.log('trace loaded ' + success)
console.log(error) self.setState({
} else { currentStepIndex: 0
self.setState({vmTrace: result, state: ''}) })
} self.refs.stepManager.newTraceAvailable()
}) })
} }
}) })

@ -0,0 +1,94 @@
'use strict'
var React = require('react')
var BasicPanel = require('./basicPanel')
var style = require('./basicStyles')
module.exports = React.createClass({
contextTypes: {
traceManager: React.PropTypes.object,
web3: React.PropTypes.object
},
getDefaultProps: function () {
return {
currentStepIndex: -1
}
},
getInitialState: function () {
return {
data: null
}
},
render: function () {
return (
<BasicPanel name='Memory' data={this.state.data} renderRow={this.renderMemoryRow} />
)
},
componentWillReceiveProps: function (nextProps) {
if (nextProps.currentStepIndex < 0) return
if (window.ethDebuggerSelectedItem !== nextProps.currentStepIndex) return
var self = this
this.context.traceManager.getMemoryAt(nextProps.currentStepIndex, function (error, memory) {
if (error) {
console.log(error)
} else if (window.ethDebuggerSelectedItem === nextProps.currentStepIndex) {
self.setState({
data: self.formatMemory(memory, 16)
})
}
})
},
renderMemoryRow: function (data) {
var ret = []
if (data) {
for (var key in data) {
var memSlot = data[key]
ret.push(
<tr key={key}>
<td>
<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)) {
var memory = mem.substr(k, width * 2)
ret.push({
address: this.context.web3.toHex(k),
content: this.tryAsciiFormat(memory)
})
}
return ret
},
tryAsciiFormat: function (memorySlot) {
var ret = { ascii: '', raw: '' }
for (var k = 0; k < memorySlot.length; k += 2) {
var raw = memorySlot.substr(k, 2)
var ascii = this.context.web3.toAscii(raw)
if (ascii === String.fromCharCode(0)) {
ret.ascii += '?'
} else {
ret.ascii += ascii
}
ret.raw += ' ' + raw
}
return ret
}
})

@ -3,6 +3,10 @@ var React = require('react')
var style = require('./sliderStyles') var style = require('./sliderStyles')
module.exports = React.createClass({ module.exports = React.createClass({
contextTypes: {
traceManager: React.PropTypes.object
},
propTypes: { propTypes: {
onChange: React.PropTypes.func.isRequired onChange: React.PropTypes.func.isRequired
}, },
@ -10,7 +14,7 @@ module.exports = React.createClass({
getDefaultProps: function () { getDefaultProps: function () {
return { return {
min: 0, min: 0,
max: 500 max: 1
} }
}, },
@ -28,6 +32,10 @@ module.exports = React.createClass({
) )
}, },
componentDidMount: function () {
this.setValue(0)
},
onMouseUp: function (event) { onMouseUp: function (event) {
this.props.onChange(parseInt(this.refs.rule.value)) this.props.onChange(parseInt(this.refs.rule.value))
}, },

@ -0,0 +1,43 @@
'use strict'
var React = require('react')
var BasicPanel = require('./basicPanel')
module.exports = React.createClass({
contextTypes: {
traceManager: React.PropTypes.object
},
getDefaultProps: function () {
return {
currentStepIndex: -1
}
},
getInitialState: function () {
return {
data: null
}
},
render: function () {
return (
<BasicPanel name='Stack' data={this.state.data} />
)
},
componentWillReceiveProps: function (nextProps) {
if (nextProps.currentStepIndex < 0) return
if (window.ethDebuggerSelectedItem !== nextProps.currentStepIndex) return
var self = this
this.context.traceManager.getStackAt(nextProps.currentStepIndex, function (error, stack) {
if (error) {
console.log(error)
} else if (window.ethDebuggerSelectedItem === nextProps.currentStepIndex) {
self.setState({
data: stack
})
}
})
}
})

@ -0,0 +1,111 @@
'use strict'
var React = require('react')
var ButtonNavigator = require('./buttonNavigator')
var Slider = require('./slider')
var style = require('./basicStyles')
module.exports = React.createClass({
propTypes: {
onStepChanged: React.PropTypes.func.isRequired
},
contextTypes: {
traceManager: React.PropTypes.object
},
getInitialState: function () {
return {
currentStepIndex: 0,
traceLength: 0
}
},
render: function () {
return (
<div style={style.container}>
<Slider
ref='slider'
onChange={this.sliderMoved}
min='0'
max={this.state.traceLength} />
<ButtonNavigator
stepIntoBack={this.stepIntoBack}
stepIntoForward={this.stepIntoForward}
stepOverBack={this.stepOverBack}
stepOverForward={this.stepOverForward}
jumpToNextCall={this.jumpToNextCall}
max={this.state.traceLength} />
</div>
)
},
componentDidMount: function () {
this.updateGlobalSelectedItem(0)
},
updateGlobalSelectedItem: function (value) {
window.ethDebuggerSelectedItem = value
},
init: function () {
this.refs.slider.setValue(0)
},
newTraceAvailable: function () {
this.init()
var self = this
this.context.traceManager.getLength(function (error, length) {
if (error) {
console.log(error)
} else {
self.setState({ traceLength: length })
}
})
},
sliderMoved: function (step) {
this.props.onStepChanged(step)
this.changeState(step)
},
stepIntoForward: function () {
var step = this.state.currentStepIndex + 1
this.props.onStepChanged(step)
this.changeState(step)
},
stepIntoBack: function () {
var step = this.state.currentStepIndex - 1
this.props.onStepChanged(step)
this.refs.slider.setValue(step)
this.changeState(step)
},
stepOverForward: function () {
var step = this.context.traceManager.findStepOverForward(this.state.currentStepIndex)
this.props.onStepChanged(step)
this.refs.slider.setValue(step)
this.changeState(step)
},
stepOverBack: function () {
var step = this.context.traceManager.findStepOverBack(this.state.currentStepIndex)
this.props.onStepChanged(step)
this.refs.slider.setValue(step)
this.changeState(step)
},
jumpToNextCall: function () {
var step = this.context.traceManager.findNextCall(this.state.currentStepIndex)
this.props.onStepChanged(step)
this.refs.slider.setValue(step)
this.changeState(step)
},
changeState: function (step) {
this.updateGlobalSelectedItem(step)
this.setState({
currentStepIndex: step
})
}
})

@ -2,9 +2,22 @@
var React = require('react') var React = require('react')
module.exports = React.createClass({ module.exports = React.createClass({
contextTypes: {
traceManager: React.PropTypes.object
},
getDefaultProps: function () { getDefaultProps: function () {
return { return {
data: null currentStepIndex: -1
}
},
getInitialState: function () {
return {
step: '',
addmemory: '',
gas: '',
remainingGas: ''
} }
}, },
@ -13,7 +26,38 @@ module.exports = React.createClass({
<div> <div>
<table> <table>
<tbody> <tbody>
{this.renderItems()} <tr key='step'>
<td>
step
</td>
<td>
{this.state.step}
</td>
</tr>
<tr key='addmemory'>
<td>
add memory
</td>
<td>
{this.state.addmemory}
</td>
</tr>
<tr key='gas'>
<td>
gas
</td>
<td>
{this.state.gas}
</td>
</tr>
<tr key='remaininggas'>
<td>
remaining gas
</td>
<td>
{this.state.remainingGas}
</td>
</tr>
</tbody> </tbody>
</table> </table>
</div> </div>
@ -21,9 +65,9 @@ module.exports = React.createClass({
}, },
renderItems: function () { renderItems: function () {
if (this.props.data) { if (this.state.data) {
var ret = [] var ret = []
for (var key in this.props.data) { for (var key in this.state.data) {
ret.push( ret.push(
<tr key={key}> <tr key={key}>
<td> <td>
@ -34,5 +78,50 @@ module.exports = React.createClass({
return ret return ret
} }
return null return null
},
componentWillReceiveProps: function (nextProps) {
if (nextProps.currentStepIndex < 0) return
var self = this
this.context.traceManager.getCurrentStep(nextProps.currentStepIndex, function (error, step) {
if (error) {
console.log(error)
} else {
self.setState({
step: step
})
}
})
this.context.traceManager.getMemExpand(nextProps.currentStepIndex, function (error, addmem) {
if (error) {
console.log(error)
} else {
self.setState({
addmemory: addmem
})
}
})
this.context.traceManager.getStepCost(nextProps.currentStepIndex, function (error, gas) {
if (error) {
console.log(error)
} else {
self.setState({
gas: gas
})
}
})
this.context.traceManager.getRemainingGas(nextProps.currentStepIndex, function (error, remaingas) {
if (error) {
console.log(error)
} else {
self.setState({
remainingGas: remaingas
})
}
})
} }
}) })

@ -0,0 +1,63 @@
'use strict'
var React = require('react')
var BasicPanel = require('./basicPanel')
var style = require('./basicStyles')
module.exports = React.createClass({
contextTypes: {
traceManager: React.PropTypes.object,
tx: React.PropTypes.object
},
getDefaultProps: function () {
return {
currentStepIndex: -1
}
},
getInitialState: function () {
return {
data: null
}
},
render: function () {
return (
<BasicPanel name='Storage' data={this.state.data} renderRow={this.renderStorageRow} />
)
},
componentWillReceiveProps: function (nextProps) {
if (nextProps.currentStepIndex < 0) return
if (window.ethDebuggerSelectedItem !== nextProps.currentStepIndex) return
var self = this
this.context.traceManager.getStorageAt(nextProps.currentStepIndex, this.context.tx.blockNumber.toString(), this.context.tx.transactionIndex, function (error, storage) {
if (error) {
console.log(error)
} else if (window.ethDebuggerSelectedItem === nextProps.currentStepIndex) {
self.setState({
data: storage
})
}
})
},
renderStorageRow: function (data) {
var ret = []
if (data) {
for (var key in data) {
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
}
})

@ -0,0 +1,77 @@
'use strict'
function TraceAnalyser (_cache) {
this.traceCache = _cache
this.trace = null
}
TraceAnalyser.prototype.analyse = function (trace, callback) {
this.trace = trace
var currentDepth = 0
var context = {
currentStorageAddress: trace[0].address,
previousStorageAddress: trace[0].address
}
var callStack = []
for (var k in this.trace) {
var step = this.trace[k]
this.buildCalldata(k, step)
this.buildMemory(k, step)
var depth = this.buildDepth(k, step, currentDepth, callStack)
if (depth) {
currentDepth = depth
}
context = this.buildStorage(k, step, context)
}
callback(null, true)
}
TraceAnalyser.prototype.buildCalldata = function (index, step) {
if (step.calldata) {
this.traceCache.pushCallDataChanges(index)
}
}
TraceAnalyser.prototype.buildMemory = function (index, step) {
if (step.memory) {
this.traceCache.pushMemoryChanges(index)
}
}
TraceAnalyser.prototype.buildStorage = function (index, step, context) {
if (step.address) {
// new context
context.currentStorageAddress = step.address
this.traceCache.pushStoreChanges(index, context.currentStorageAddress)
} else if (step.inst === 'SSTORE') {
this.traceCache.pushStoreChanges(index, context.currentStorageAddress, step.stack[step.stack.length - 1], step.stack[step.stack.length - 2])
} else if (!step.address && step.depth) {
// returned from context
context.currentStorageAddress = context.previousStorageAddress
this.traceCache.pushStoreChanges(index, context.currentStorageAddress)
}
return context
}
TraceAnalyser.prototype.buildDepth = function (index, step, currentDepth, callStack) {
if (step.depth === undefined) return
if (step.depth > currentDepth) {
if (index === 0) {
callStack.push('0x' + step.address) // new context
} else {
// getting the address from the stack
var callTrace = this.trace[index - 1]
var address = callTrace.stack[callTrace.stack.length - 2]
callStack.push(address) // new context
}
} else if (step.depth < currentDepth) {
callStack.pop() // returning from context
}
this.traceCache.pushCallStack(index, {
stack: callStack.slice(0),
depth: step.depth
})
this.traceCache.pushDepthChanges(index)
return step.depth
}
module.exports = TraceAnalyser

@ -0,0 +1,55 @@
'use strict'
function TraceCache () {
this.init()
}
TraceCache.prototype.init = function () {
// ...Changes contains index in the vmtrace of the corresponding changes
this.depthChanges = []
this.memoryChanges = []
this.callDataChanges = []
this.storageChanges = []
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)
}
TraceCache.prototype.pushCallDataChanges = function (value) {
this.callDataChanges.push(value)
}
TraceCache.prototype.pushMemoryChanges = function (value) {
this.memoryChanges.push(value)
}
TraceCache.prototype.pushDepthChanges = function (value) {
this.depthChanges.push(value)
}
TraceCache.prototype.pushCallStack = function (index, callStack) {
this.callStack[index] = callStack
}
TraceCache.prototype.pushStoreChanges = function (index, address, key, value) {
this.sstore[index] = {
'address': address,
'key': key,
'value': value
}
this.storageChanges.push(index)
}
TraceCache.prototype.rebuildStorage = function (address, storage, index) {
for (var k in this.storageChanges) {
var changesIndex = this.storageChanges[k]
if (changesIndex > index) {
return storage
}
var sstore = this.sstore[changesIndex]
if (sstore.address === address && sstore.key) {
storage[sstore.key] = sstore.value
}
}
return storage
}
module.exports = TraceCache

@ -0,0 +1,150 @@
'use strict'
var TraceAnalyser = require('./traceAnalyser')
var TraceRetriever = require('./traceRetriever')
var TraceCache = require('./traceCache')
var TraceStepManager = require('./traceStepManager')
var traceManagerUtil = require('./traceManagerUtil')
function TraceManager (_web3) {
this.web3 = _web3
this.isLoading = false
this.trace = null
this.traceCache = new TraceCache()
this.traceAnalyser = new TraceAnalyser(this.traceCache)
this.traceRetriever = new TraceRetriever(_web3)
this.traceStepManager = new TraceStepManager(this.traceAnalyser)
}
// init section
TraceManager.prototype.resolveTrace = function (blockNumber, txNumber, callback) {
this.isLoading = true
this.init()
if (!this.web3) callback(false)
var self = this
this.traceRetriever.getTrace(blockNumber, parseInt(txNumber), function (error, result) {
self.trace = result
if (error) {
console.log(error)
} else {
self.traceAnalyser.analyse(result, function (error, result) {
if (error) {
console.log(error)
callback(false)
} else {
callback(true)
}
})
}
})
}
TraceManager.prototype.init = function () {
this.trace = null
this.traceCache.init()
}
// API section
TraceManager.prototype.getLength = function (callback) {
if (!this.trace) callback('no trace available', null)
callback(null, this.trace.length)
}
TraceManager.prototype.getStorageAt = function (stepIndex, blockNumber, txIndex, callback) {
var stoChange = traceManagerUtil.findLowerBound(stepIndex, this.traceCache.storageChanges)
var address = this.traceCache.sstore[stoChange].address
var self = this
this.traceRetriever.getStorage(blockNumber, txIndex, address, function (error, result) {
if (error) {
console.log(error)
callback(error, null)
} else {
var storage = self.traceCache.rebuildStorage(address, result, stepIndex)
callback(null, storage)
}
})
}
TraceManager.prototype.getCallDataAt = function (stepIndex, callback) {
var callDataChange = traceManagerUtil.findLowerBound(stepIndex, this.traceCache.callDataChanges)
if (!callDataChange) return callback('no calldata found', null)
callback(null, [this.trace[callDataChange].calldata])
}
TraceManager.prototype.getCallStackAt = function (stepIndex, callback) {
var callStackChange = traceManagerUtil.findLowerBound(stepIndex, this.traceCache.depthChanges)
if (!callStackChange) return callback('no callstack found', null)
callback(null, this.traceCache.callStack[callStackChange].stack)
}
TraceManager.prototype.getStackAt = function (stepIndex, callback) {
var stack
if (this.trace[stepIndex].stack) { // there's always a stack
stack = this.trace[stepIndex].stack.slice(0)
stack.reverse()
callback(null, stack)
} else {
callback('no stack found', null)
}
}
TraceManager.prototype.getLastDepthIndexChangeSince = function (stepIndex, callback) {
var depthIndex = traceManagerUtil.findLowerBound(stepIndex, this.traceCache.depthChanges)
callback(null, depthIndex)
}
TraceManager.prototype.getCurrentCalledAddressAt = function (stepIndex, callback) {
var self = this
this.getLastDepthIndexChangeSince(stepIndex, function (error, addressIndex) {
if (error) {
callback(error, null)
} else {
callback(null, traceManagerUtil.resolveCalledAddress(addressIndex, self.trace))
}
})
}
TraceManager.prototype.getMemoryAt = function (stepIndex, callback) {
var lastChanges = traceManagerUtil.findLowerBound(stepIndex, this.traceCache.memoryChanges)
if (!lastChanges) return callback('no memory found', null)
callback(null, this.trace[lastChanges].memory)
}
TraceManager.prototype.getCurrentPC = function (stepIndex, callback) {
callback(null, this.trace[stepIndex].pc)
}
TraceManager.prototype.getCurrentStep = function (stepIndex, callback) {
callback(null, this.trace[stepIndex].steps)
}
TraceManager.prototype.getMemExpand = function (stepIndex, callback) {
callback(null, this.trace[stepIndex].memexpand ? this.trace[stepIndex].memexpand : '')
}
TraceManager.prototype.getStepCost = function (stepIndex, callback) {
callback(null, this.trace[stepIndex].gascost)
}
TraceManager.prototype.getRemainingGas = function (stepIndex, callback) {
callback(null, this.trace[stepIndex].gas)
}
// step section
TraceManager.prototype.findStepOverBack = function (currentStep) {
return this.traceStepManager.findStepOverBack(currentStep)
}
TraceManager.prototype.findStepOverForward = function (currentStep) {
return this.traceStepManager.findStepOverForward(currentStep)
}
TraceManager.prototype.findStepOutBack = function (currentStep) {
return this.traceStepManager.findStepOutBack(currentStep)
}
TraceManager.prototype.findStepOutForward = function (currentStep) {
return this.traceStepManager.findStepOutForward(currentStep)
}
module.exports = TraceManager

@ -0,0 +1,36 @@
module.exports = {
// util section
findLowerBound: function (target, changes) {
if (changes.length === 0) {
return undefined
}
if (changes.length === 1) {
if (changes[0] > target) {
// we only a closest maximum, returning O
return 0
} else {
return changes[0]
}
}
var middle = Math.floor(changes.length / 2)
if (changes[middle] > target) {
return this.findLowerBound(target, changes.slice(0, middle))
} else if (changes[middle] < target) {
return this.findLowerBound(target, changes.slice(middle, changes.length))
} else {
return changes[middle]
}
},
resolveCalledAddress: function (vmTraceIndex, trace) {
var address = trace[vmTraceIndex].address
if (vmTraceIndex > 0) {
var stack = trace[vmTraceIndex - 1].stack // callcode, delegatecall, ...
address = stack[stack.length - 2]
}
return address
}
}

@ -0,0 +1,25 @@
'use strict'
function TraceRetriever (_web3) {
this.web3 = _web3
this.storages = {} // contains all intial storage (by addresses)
}
TraceRetriever.prototype.getTrace = function (blockNumber, txNumber, callback) {
this.web3.debug.trace(blockNumber, parseInt(txNumber), function (error, result) {
callback(error, result)
})
}
TraceRetriever.prototype.getStorage = function (blockNumber, txIndex, address, callback) {
if (this.storages[address]) {
callback(null, this.storages[address])
} else {
var self = this
this.web3.debug.storageAt(blockNumber, txIndex, address, function (error, result) {
self.storages[address] = result
callback(error, result)
})
}
}
module.exports = TraceRetriever

@ -0,0 +1,66 @@
'use strict'
function TraceStepManager (_traceAnalyser) {
this.traceAnalyser = _traceAnalyser
}
TraceStepManager.prototype.isCallInstruction = function (index) {
var state = this.traceAnalyser.trace[index]
return state.instname === 'CALL' || state.instname === 'CALLCODE' || state.instname === 'CREATE' || state.instname === 'DELEGATECALL'
}
TraceStepManager.prototype.isReturnInstruction = function (index) {
var state = this.traceAnalyser.trace[index]
return state.instname === 'RETURN'
}
TraceStepManager.prototype.findStepOverBack = function (currentStep) {
if (this.isReturnInstruction(currentStep - 1)) {
return this.findStepOutBack(currentStep)
} else {
return currentStep - 1
}
}
TraceStepManager.prototype.findStepOverForward = function (currentStep) {
if (this.isCallInstruction(currentStep)) {
return this.findStepOutForward(currentStep)
} else {
return currentStep + 1
}
}
TraceStepManager.prototype.findStepOutBack = function (currentStep) {
var i = currentStep - 1
var depth = 0
while (--i >= 0) {
if (this.isCallInstruction(i)) {
if (depth === 0) {
break
} else {
depth--
}
} else if (this.isReturnInstruction(i)) {
depth++
}
}
return i
}
TraceStepManager.prototype.findStepOutForward = function (currentStep) {
var i = currentStep
var depth = 0
while (++i < this.traceAnalyser.length) {
if (this.isReturnInstruction(i)) {
if (depth === 0) {
break
} else {
depth--
}
} else if (this.isCallInstruction(i)) {
depth++
}
}
return i + 1
}
module.exports = TraceStepManager

@ -19,8 +19,10 @@ module.exports = React.createClass({
var tx = this.context.web3.eth.getTransactionFromBlock(this.state.blockNumber, this.state.txNumber) var tx = this.context.web3.eth.getTransactionFromBlock(this.state.blockNumber, this.state.txNumber)
if (tx) { if (tx) {
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)
} else {
console.log('cannot find ' + this.state.blockNumber + ' ' + this.state.txNumber)
} }
this.props.onNewTxRequested(this.state.blockNumber, parseInt(this.state.txNumber))
}, },
updateBlockN: function (ev) { updateBlockN: function (ev) {
@ -40,18 +42,22 @@ module.exports = React.createClass({
Get Get
</button> </button>
<div style={style.transactionInfo}> <div style={style.transactionInfo}>
<div> <table>
Hash: <tbody>
{this.state.hash} <tr>
</div> <td>Hash: </td>
<div> <td>{this.state.hash}</td>
From: </tr>
{this.state.from} <tr>
</div> <td>From: </td>
<div> <td>{this.state.from}</td>
To: </tr>
{this.state.to} <tr>
</div> <td>To: </td>
<td>{this.state.to}</td>
</tr>
</tbody>
</table>
</div> </div>
</div> </div>
) )

@ -0,0 +1,86 @@
'use strict'
var React = require('react')
var Sticker = require('./sticker')
var style = require('./basicStyles')
var ASMCode = require('./asmCode')
var CalldataPanel = require('./calldataPanel')
var MemoryPanel = require('./memoryPanel')
var CallstackPanel = require('./callstackPanel')
var StackPanel = require('./stackPanel')
var StoragePanel = require('./storagePanel')
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 () {
return (
<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}>
<table>
<tbody>
<tr>
<td>
<ASMCode currentStepIndex={this.props.currentStepIndex} />
<div style={Object.assign(style.inline, style.sticker)}>
<Sticker currentStepIndex={this.props.currentStepIndex} />
</div>
</td>
<td>
<CalldataPanel currentStepIndex={this.props.currentStepIndex} />
</td>
</tr>
<tr>
<td>
<StackPanel currentStepIndex={this.props.currentStepIndex} />
</td>
<td>
<CallstackPanel currentStepIndex={this.props.currentStepIndex} />
</td>
</tr>
<tr>
<td>
<StoragePanel currentStepIndex={this.props.currentStepIndex} />
</td>
<td>
<MemoryPanel currentStepIndex={this.props.currentStepIndex} />
</td>
</tr>
</tbody>
</table>
</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
})
}
})
}
})

@ -1,13 +0,0 @@
'use strict'
var React = require('react')
var AssemblyItemsBrowser = require('./assemblyItemsBrowser')
module.exports = React.createClass({
render: function () {
return (
<div>
<AssemblyItemsBrowser vmTrace={this.props.vmTrace} />
</div>
)
}
})

@ -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)
})
}
}

@ -89,6 +89,12 @@ module.exports = {
web3._extend({ web3._extend({
property: 'debug', property: 'debug',
methods: [ methods: [
new web3._extend.Method({
name: 'storageAt',
call: 'debug_storageAt',
inputFormatter: [null, null, null],
params: 3
}),
new web3._extend.Method({ new web3._extend.Method({
name: 'trace', name: 'trace',
call: 'debug_trace', call: 'debug_trace',

Loading…
Cancel
Save