modularisation

pull/7/head
yann300 9 years ago
parent cfe1903a05
commit 79ae4bf993
  1. 83
      src/asmCode.js
  2. 357
      src/assemblyItemsBrowser.js
  3. 6
      src/buttonNavigator.js
  4. 67
      src/codeResolver.js
  5. 59
      src/debugger.js
  6. 10
      src/slider.js
  7. 98
      src/stepManager.js
  8. 81
      src/sticker.js
  9. 113
      src/storageResolver.js
  10. 340
      src/traceManager.js
  11. 28
      src/txBrowser.js

@ -0,0 +1,83 @@
'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) {
console.log('asm' + JSON.stringify(nextProps))
if (nextProps.currentStepIndex < 0) return
codeResolver.setWeb3(this.context.web3)
var self = this
this.context.traceManager.getCurrentCalledAddressAt(nextProps.currentStepIndex, function (address) {
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) {
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 (instIndex) {
self.setState({
selected: codeResolver.getInstructionIndex(address, instIndex)
})
})
}
})

@ -2,40 +2,28 @@
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')
var StorageResolver = require('./storageResolver.js')
var ASMCode = require('./asmCode')
module.exports = React.createClass({
contextTypes: {
traceManager: React.PropTypes.object,
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
executingCode: [], // code currently loaded in the debugger
instructionsIndexByBytesOffset: {}, // mapping between bytes offset and instructions index.
callStack: {},
storageStates: {}
currentCallData: null
}
},
getDefaultProps: function () {
return {
vmTrace: null,
transaction: null
currentStepIndex: -1 // index of the selected item in the vmtrace
}
},
@ -45,36 +33,14 @@ module.exports = React.createClass({
<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}
jumpToNextCall={this.jumpToNextCall} />
</div>
<StorageResolver ref='storageResolver' transaction={this.props.transaction} />
<div style={style.container}>
<table>
<tbody>
<tr>
<td>
<select
size='10'
ref='itemsList'
style={style.instructionsList}
value={this.state.selectedInst}>
{this.renderAssemblyItems()}
</select>
<ASMCode currentStepIndex={this.props.currentStepIndex} />
<div style={Object.assign(style.inline, style.sticker)}>
<Sticker data={this.state.currentStepInfo} />
<Sticker currentStepIndex={this.props.currentStepIndex} />
</div>
</td>
<td>
@ -144,295 +110,46 @@ module.exports = React.createClass({
return ret
},
loadCode: function (address, callback) {
console.log('loading new code from web3 ' + address)
this.context.web3.eth.getCode(address, function (error, result) {
if (error) {
console.log(error)
} else {
callback(result)
}
})
},
cacheExecutingCode: function (address, hexCode) {
var code = codeUtils.nameOpCodes(new Buffer(hexCode.substring(2), 'hex'))
this.state.codes[address] = code[0]
this.state.instructionsIndexByBytesOffset[address] = code[1]
return {
code: code[0],
instructionsIndexByBytesOffset: code[1]
}
},
getExecutingCodeFromCache: function (address) {
if (this.state.codes[address]) {
return {
code: this.state.codes[address],
instructionsIndexByBytesOffset: this.state.instructionsIndexByBytesOffset[address]
}
} else {
return null
}
},
renderAssemblyItems: function () {
if (this.props.vmTrace && this.state.executingCode) {
return this.state.executingCode.map(function (item, i) {
return <option key={i} value={i}>{item}</option>
})
}
},
componentWillReceiveProps: function (nextProps) {
this.setState(this.getInitialState())
if (!nextProps.vmTrace) {
return
}
this.buildCallStack(nextProps.vmTrace)
this.updateState(nextProps, 0)
},
console.log("asse " + JSON.stringify(nextProps))
if (nextProps.currentStepIndex < 0) return
buildCallStack: function (vmTrace) {
if (!vmTrace) return
var callStack = []
var callStackFrame = {}
var depth = -1
this.refs.storageResolver.init(this.props.transaction)
for (var k = 0; k < vmTrace.length; k++) {
var trace = vmTrace[k]
if (trace.depth === undefined || trace.depth === depth) continue
if (trace.depth > depth) {
if (k === 0) {
callStack.push('0x' + vmTrace[k].address) // new context
} else {
// getting the address from the stack
var callTrace = vmTrace[k - 1]
var address = callTrace.stack[callTrace.stack.length - 2]
callStack.push(address) // new context
}
} else if (trace.depth < depth) {
callStack.pop() // returning from context
}
depth = trace.depth
callStackFrame[k] = callStack.slice(0)
this.refs.storageResolver.trackStorageChange(k, trace)
}
this.setState({'callStack': callStackFrame})
},
updateState: function (props, vmTraceIndex) {
if (!props.vmTrace || !props.vmTrace[vmTraceIndex]) return
var previousIndex = this.state.currentSelected
var stateChanges = {}
var stack
if (props.vmTrace[vmTraceIndex].stack) { // there's always a stack
stack = props.vmTrace[vmTraceIndex].stack.slice(0)
stack.reverse()
Object.assign(stateChanges, { 'currentStack': stack })
}
var newContextLoaded = false
var depthIndex = this.shouldUpdateStateProperty('depth', vmTraceIndex, previousIndex, props.vmTrace)
if (depthIndex > -1) {
Object.assign(stateChanges, {'currentCallStack': this.state.callStack[depthIndex]})
// updating exectution context:
var address = this.resolveAddress(depthIndex, props)
if (address !== this.state.currentAddress) {
var self = this
this.ensureExecutingCodeUpdated(address, vmTraceIndex, props, function (code) {
if (self.state.currentAddress !== address) {
console.log('updating executing code ' + self.state.currentAddress + ' -> ' + address)
self.setState(
{
'selectedInst': code.instructionsIndexByBytesOffset[props.vmTrace[vmTraceIndex].pc],
'executingCode': code.code,
'currentAddress': address
})
}
})
newContextLoaded = true
}
}
if (!newContextLoaded) {
Object.assign(stateChanges,
{
'selectedInst': this.getExecutingCodeFromCache(this.state.currentAddress).instructionsIndexByBytesOffset[props.vmTrace[vmTraceIndex].pc]
})
}
Object.assign(stateChanges, { 'currentSelected': vmTraceIndex })
this.refs.storageResolver.rebuildStorageAt(vmTraceIndex, props.transaction, function (storage) {
Object.assign(stateChanges, { 'currentStorage': storage })
var self = this
this.context.traceManager.getCallDataAt(nextProps.currentStepIndex, function (calldata) {
self.setState({
currentCallData: calldata
})
})
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['currentStepInfo'] = [
'Current Step: ' + props.vmTrace[vmTraceIndex].steps,
'Adding Memory: ' + (props.vmTrace[vmTraceIndex].memexpand ? props.vmTrace[vmTraceIndex].memexpand : ''),
'Step Cost: ' + props.vmTrace[vmTraceIndex].gascost,
'Remaining Gas: ' + props.vmTrace[vmTraceIndex].gas
]
this.refs.slider.setValue(vmTraceIndex)
this.setState(stateChanges)
},
ensureExecutingCodeUpdated: function (address, vmTraceIndex, props, callBack) {
this.resolveCode(address, vmTraceIndex, props, function (address, code) {
callBack(code)
this.context.traceManager.getCallStackAt(nextProps.currentStepIndex, function (callstack) {
self.setState({
currentCallStack: callstack
})
})
},
resolveAddress: function (vmTraceIndex, props) {
var address = props.vmTrace[vmTraceIndex].address
if (vmTraceIndex > 0) {
var stack = this.state.callStack[vmTraceIndex] // callcode, delegatecall, ...
address = stack[stack.length - 1]
}
return address
},
resolveCode: function (address, vmTraceIndex, props, callBack) {
var cache = this.getExecutingCodeFromCache(address)
if (cache) {
callBack(address, cache)
return
}
if (vmTraceIndex === 0 && props.transaction.to === null) { // start of the trace
callBack(address, this.cacheExecutingCode(address, props.transaction.input))
return
}
var self = this
this.loadCode(address, function (code) {
callBack(address, self.cacheExecutingCode(address, code))
this.context.traceManager.getMemoryAt(nextProps.currentStepIndex, function (memory) {
self.setState({
currentMemory: self.formatMemory(memory, 16)
})
})
},
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
},
jumpToNextCall: function () {
var i = this.state.currentSelected
while (++i < this.props.vmTrace.length) {
if (this.isCallInstruction(i)) {
this.selectState(i + 1)
break
}
}
},
stepIntoBack: function () {
this.moveSelection(-1)
},
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)
},
this.context.traceManager.getStorageAt(nextProps.currentStepIndex, function (storage) {
self.setState({
currentStorage: storage
})
})
moveSelection: function (incr) {
this.selectState(this.state.currentSelected + incr)
},
this.context.traceManager.getStackAt(nextProps.currentStepIndex, function (stack) {
self.setState({
currentStack: stack
})
})
selectState: function (index) {
this.updateState(this.props, index)
this.context.traceManager.getCurrentCalledAddressAt(nextProps.currentStepIndex, function (address) {
self.setState({
currentAddress: address
})
})
},
formatMemory: function (mem, width) {

@ -2,6 +2,10 @@
var React = require('react')
module.exports = React.createClass({
contextTypes: {
traceManager: React.PropTypes.object
},
propTypes: {
stepIntoBack: React.PropTypes.func.isRequired,
stepIntoForward: React.PropTypes.func.isRequired,
@ -32,7 +36,7 @@ module.exports = React.createClass({
if (incr === -1) {
return this.props.step === 0 ? 'disabled' : ''
} 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,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,51 +1,70 @@
'use strict'
var React = require('react')
var TxBrowser = require('./txBrowser')
var StepManager = require('./stepManager')
var AssemblyItemsBrowser = require('./assemblyItemsBrowser')
var traceManager = require('./traceManager')
var style = require('./basicStyles')
module.exports = React.createClass({
getInitialState: function () {
return {
vmTrace: null,
state: '',
currentStep: -1
tx: null,
currentStepIndex: -1 // index of the selected item in the vmtrace
}
},
childContextTypes: {
web3: React.PropTypes.object
web3: React.PropTypes.object,
traceManager: React.PropTypes.object,
tx: React.PropTypes.object
},
getChildContext: function () {
return { web3: this.props.web3 }
return {
web3: this.props.web3,
traceManager: traceManager,
tx: this.state.tx
}
},
componentDidMount: function () {
traceManager.setWeb3(this.props.web3)
},
render: function () {
return (
<div style={style.wrapper}>
<h1 style={style.container}>Eth Debugger</h1>
<TxBrowser onNewTxRequested={this.retrieveVmTrace} />
<div style={style.container}>
{this.state.state}
</div>
<AssemblyItemsBrowser vmTrace={this.state.vmTrace} transaction={this.state.transaction} />
<TxBrowser onNewTxRequested={this.startDebugging} />
<StepManager ref='stepManager' onStepChanged={this.stepChanged} />
<AssemblyItemsBrowser currentStepIndex={this.state.currentStepIndex} />
</div>
)
},
retrieveVmTrace: function (blockNumber, txNumber, tx) {
if (this.state.state !== '') return
stepChanged: function (stepIndex) {
this.setState({
currentStepIndex: stepIndex
})
},
startDebugging: function (blockNumber, txIndex, tx) {
if (traceManager.isLoading) {
return
}
console.log('loading trace...')
this.setState({
tx: tx
})
traceManager.setTransaction(tx)
var self = this
this.setState({state: 'loading...'})
this.props.web3.debug.trace(blockNumber, parseInt(txNumber), function (error, result) {
if (error) {
console.log(error)
} else {
self.setState({vmTrace: result, transaction: tx, state: ''})
}
traceManager.resolveTrace(blockNumber, txIndex, function (success) {
console.log('trace loaded ' + success)
self.setState({
currentStepIndex: 0
})
self.refs.stepManager.newTraceAvailable()
})
}
})

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

@ -0,0 +1,98 @@
'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>
)
},
init: function () {
this.refs.slider.setValue(0)
},
newTraceAvailable: function () {
this.init()
var self = this
this.context.traceManager.getLength(function (length) {
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.setState({
currentStepIndex: step
})
}
})

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

@ -1,113 +0,0 @@
'use strict'
var React = require('react')
module.exports = React.createClass({
contextTypes: {
web3: React.PropTypes.object
},
getInitialState: function () {
return {
storage: {},
storageChanges: [],
vmTraceIndexByStorageChange: {},
vmTraceChangesRef: []
}
},
init: function () {
var defaultState = this.getInitialState()
this.state.storage = defaultState.storage
this.state.storageChanges = defaultState.storageChanges
this.state.vmTraceIndexByStorageChange = defaultState.vmTraceIndexByStorageChange
this.state.vmTraceChangesRef = defaultState.vmTraceChangesRef
},
render: function () {
return null
},
// retrieve the storage of an account just after the execution of txHash
retrieveStorage: function (address, transaction, callBack) {
if (this.state.storage[address]) {
callBack(this.state.storage[address])
}
var self = this
if (transaction) {
this.context.web3.debug.storageAt(transaction.blockNumber.toString(), transaction.transactionIndex, address, function (error, result) {
if (error) {
console.log(error)
} else {
self.state.storage[address] = result
callBack(result)
}
})
} else {
console.log('transaction is not defined')
}
},
trackStorageChange: function (vmTraceIndex, trace) {
var change = false
if (trace.address) {
// new context
this.state.storageChanges.push({ address: trace.address, changes: [] })
change = true
} else if (trace.depth && !trace.address) {
// returned from context
this.state.storageChanges.push({ address: this.state.storageChanges[this.state.storageChanges.length - 1].address, changes: [] })
change = true
} else if (trace.inst === 'SSTORE') {
this.state.storageChanges[this.state.storageChanges.length - 1].changes.push(
{
'key': trace.stack[trace.stack.length - 1],
'value': trace.stack[trace.stack.length - 2]
})
change = true
}
if (change) {
this.state.vmTraceIndexByStorageChange[vmTraceIndex] = {
context: this.state.storageChanges.length - 1,
changes: this.state.storageChanges[this.state.storageChanges.length - 1].changes.length - 1
}
this.state.vmTraceChangesRef.push(vmTraceIndex)
}
},
rebuildStorageAt: function (vmTraceIndex, transaction, callBack) {
var changesLocation = this.retrieveLastChange(vmTraceIndex)
if (!changesLocation) {
console.log('unable to build storage')
callBack({})
} else {
var address = this.state.storageChanges[changesLocation.context].address
this.retrieveStorage(address, transaction, function (storage) {
for (var k = 0; k < changesLocation.context; k++) {
var context = this.state.storageChanges[k]
if (context.address === address) {
for (var i = 0; i < context.changes.length; i++) {
if (i > changesLocation.changes) break
var change = context.changes[i]
storage[change.key] = change.value
}
}
}
callBack(storage)
})
}
},
retrieveLastChange: function (vmTraceIndex) {
var change = this.state.vmTraceIndexByStorageChange[vmTraceIndex]
if (change) {
return change
} else {
for (var k in this.state.vmTraceChangesRef) {
if (this.state.vmTraceChangesRef[k] > vmTraceIndex) {
return this.state.vmTraceIndexByStorageChange[k - 1]
}
}
}
}
})

@ -0,0 +1,340 @@
'use strict'
module.exports = {
isLoading: false,
web3: null,
transaction: null,
trace: null,
// vmtrace changes section
depthChanges: [],
callStack: {},
memoryChanges: [],
callDataChanges: [],
// storage section
storageChanges: [],
vmTraceIndexByStorageChange: {},
vmTraceChangesRef: [],
storages: {},
// init section
setWeb3: function (web3) {
this.web3 = web3
},
setTransaction: function (tx) {
this.transaction = tx
},
resolveTrace: function (blockNumber, txNumber, callback) {
this.isLoading = true
this.init()
if (!this.web3) callback(false)
var self = this
this.web3.debug.trace(blockNumber, parseInt(txNumber), function (error, result) {
if (!error) {
self.computeTrace(result)
callback(true)
} else {
console.log(error)
callback(false)
}
this.isLoading = false
})
},
init: function () {
this.trace = null
this.depthChanges = []
this.memoryChanges = []
this.callDataChanges = []
this.storageChanges = []
this.vmTraceIndexByStorageChange = {}
this.vmTraceChangesRef = []
this.callStack = {}
},
computeTrace: function (trace) {
this.trace = trace
var currentDepth = 0
var currentStorageAddress
var callStack = []
for (var k in this.trace) {
var step = this.trace[k]
this.calldata(k, step)
this.memory(k, step)
currentStorageAddress = this.storage(k, step, currentStorageAddress)
var depth = this.depth(k, step, currentDepth, callStack)
if (depth) {
currentDepth = depth
}
}
},
// compute trace section
calldata: function (index, step) {
if (step.calldata) {
this.callDataChanges.push(index)
}
},
memory: function (index, step) {
if (step.memory) {
this.memoryChanges.push(index)
}
},
storage: function (index, step, currentAddress) {
var change = false
if (step.address) {
// new context
this.storageChanges.push({ address: step.address, changes: [] })
change = true
} else if (step.inst === 'SSTORE') {
this.storageChanges[this.storageChanges.length - 1].changes.push(
{
'key': step.stack[step.stack.length - 1],
'value': step.stack[step.stack.length - 2]
})
change = true
} else if (!step.address && step.depth) {
// returned from context
var address = this.storageChanges[this.storageChanges.length - 2].address
this.storageChanges.push({ address: address, changes: [] })
change = true
}
if (change) {
this.vmTraceIndexByStorageChange[index] = {
context: this.storageChanges.length - 1,
changes: this.storageChanges[this.storageChanges.length - 1].changes.length - 1
}
this.vmTraceChangesRef.push(index)
}
return currentAddress
},
depth: 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.callStack[index] = {
stack: callStack.slice(0),
depth: step.depth,
address: step.address
}
this.depthChanges.push(index)
return step.depth
},
// API section
getLength: function (callback) {
if (!this.trace) callback(0)
callback(this.trace.length)
},
getStorageAt: function (stepIndex, callback) {
var stoChange = this.lastPropertyChange(stepIndex, this.vmTraceChangesRef)
if (!stoChange) {
return {}
}
var changeRefs = this.vmTraceIndexByStorageChange[stoChange]
var address = this.storageChanges[changeRefs.context].address
var self = this
this.retrieveStorage(address, function (storage) {
for (var k = 0; k < changeRefs.context; k++) {
var context = self.storageChanges[k]
if (context.address === address) {
for (var i = 0; i < context.changes.length; i++) {
if (i > changeRefs.changes) break
var change = context.changes[i]
storage[change.key] = change.value
}
}
}
callback(storage)
})
},
getCallDataAt: function (stepIndex, callback) {
var callDataChange = this.lastPropertyChange(stepIndex, this.callDataChanges)
if (!callDataChange) return ['']
callback([this.trace[callDataChange].calldata])
},
getCallStackAt: function (stepIndex, callback) {
var callStackChange = this.lastPropertyChange(stepIndex, this.depthChanges)
if (!callStackChange) return ''
callback(this.callStack[callStackChange].stack)
},
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(stack)
}
},
getLastDepthIndexChangeSince: function (stepIndex, callback) {
var depthIndex = this.lastPropertyChange(stepIndex, this.depthChanges)
callback(depthIndex)
},
getCurrentCalledAddressAt: function (stepIndex, callback) {
var self = this
this.getLastDepthIndexChangeSince(stepIndex, function (addressIndex) {
callback(self.resolveAddress(addressIndex))
})
},
getMemoryAt: function (stepIndex, callback) {
var lastChanges = this.lastPropertyChange(stepIndex, this.memoryChanges)
if (!lastChanges) return ''
callback(this.trace[lastChanges].memory)
},
getCurrentPC: function (stepIndex, callback) {
callback(this.trace[stepIndex].pc)
},
getCurrentStep: function (stepIndex, callback) {
callback(this.trace[stepIndex].steps)
},
getMemExpand: function (stepIndex, callback) {
callback(this.trace[stepIndex].memexpand ? this.trace[stepIndex].memexpand : '')
},
getStepCost: function (stepIndex, callback) {
callback(this.trace[stepIndex].gascost)
},
getRemainingGas: function (stepIndex, callback) {
callback(this.trace[stepIndex].gas)
},
// step section
isCallInstruction: function (index) {
var state = this.trace[index]
return state.instname === 'CALL' || state.instname === 'CALLCODE' || state.instname === 'CREATE' || state.instname === 'DELEGATECALL'
},
isReturnInstruction: function (index) {
var state = this.trace[index]
return state.instname === 'RETURN'
},
findStepOverBack: function (currentStep) {
if (this.isReturnInstruction(currentStep - 1)) {
return this.findStepOutBack(currentStep)
} else {
return currentStep - 1
}
},
findStepOverForward: function (currentStep) {
if (this.isCallInstruction(currentStep)) {
return this.findStepOutForward(currentStep)
} else {
return currentStep + 1
}
},
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
},
findStepOutForward: function (currentStep) {
var i = currentStep
var depth = 0
while (++i < this.trace.length) {
if (this.isReturnInstruction(i)) {
if (depth === 0) {
break
} else {
depth--
}
} else if (this.isCallInstruction(i)) {
depth++
}
}
return i + 1
},
// util section
lastPropertyChange: function (target, changes) {
if (changes.length === 1) {
if (changes[0] > target) {
// we only a closest maximum, returning 0
return null
} else {
return changes[0]
}
}
var middle = Math.floor(changes.length / 2)
if (changes[middle] > target) {
return this.lastPropertyChange(target, changes.slice(0, middle))
} else if (changes[middle] < target) {
return this.lastPropertyChange(target, changes.slice(middle, changes.length))
} else {
return changes[middle]
}
},
resolveAddress: function (vmTraceIndex) {
var address = this.trace[vmTraceIndex].address
if (vmTraceIndex > 0) {
var stack = this.trace[vmTraceIndex - 1].stack // callcode, delegatecall, ...
address = stack[stack.length - 2]
}
return address
},
// retrieve the storage of an account just after the execution of tx
retrieveStorage: function (address, callBack) {
if (this.storages[address]) {
callBack(this.storages[address])
}
var self = this
if (this.transaction) {
this.web3.debug.storageAt(this.transaction.blockNumber.toString(), this.transaction.transactionIndex, address, function (error, result) {
if (error) {
console.log(error)
} else {
self.storages[address] = result
callBack(result)
}
})
} else {
console.log('transaction is not defined')
}
}
}

@ -42,18 +42,22 @@ module.exports = React.createClass({
Get
</button>
<div style={style.transactionInfo}>
<div>
Hash:
{this.state.hash}
</div>
<div>
From:
{this.state.from}
</div>
<div>
To:
{this.state.to}
</div>
<table>
<tbody>
<tr>
<td>Hash: </td>
<td>{this.state.hash}</td>
</tr>
<tr>
<td>From: </td>
<td>{this.state.from}</td>
</tr>
<tr>
<td>To: </td>
<td>{this.state.to}</td>
</tr>
</tbody>
</table>
</div>
</div>
)

Loading…
Cancel
Save