commit
6d0de8aad5
@ -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 |
|
||||||
} |
|
||||||
}) |
|
@ -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 |
||||||
|
} |
||||||
|
}) |
@ -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 |
||||||
|
}) |
||||||
|
} |
||||||
|
}) |
@ -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 |
@ -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) |
|
||||||
}) |
|
||||||
} |
|
||||||
} |
|
Loading…
Reference in new issue