diff --git a/src/assemblyItemsBrowser.js b/src/assemblyItemsBrowser.js index 8674c1ba4c..3b4d920aee 100644 --- a/src/assemblyItemsBrowser.js +++ b/src/assemblyItemsBrowser.js @@ -1,5 +1,6 @@ var React = require('react'); -var BasicPanel = require('./basicPanel'); +var BasicPanel = require('./basicPanel') +var codeUtils = require('./codeUtils') var style = require('./basicStyles') module.exports = React.createClass({ @@ -7,18 +8,20 @@ module.exports = React.createClass({ getInitialState: function() { return { - currentSelected: 0, + currentSelected: 0, // current selected item in the vmTrace + selectedInst: 0, // current selected item in the contract assembly code currentAddress: null, currentStack: null, currentLevels: null, currentStorage: null, currentMemory: null, currentCallData: null, - selectedInst: 0, lastLevels: null, lastStorage: null, lastMemory: null, - lastCallData: null + lastCallData: null, + codes: {}, + codesMap: {} }; }, @@ -33,6 +36,7 @@ module.exports = React.createClass({ { return (
+
{this.state.currentAddress}
@@ -40,7 +44,7 @@ module.exports = React.createClass({
- { this.renderAssemblyItems() }
@@ -48,13 +52,38 @@ module.exports = React.createClass({ - +
); }, + renderMemoryRow: function(data) + { + var ret = [] + if (data) + { + for (var key in data) + { + var memSlot = data[key] + ret.push({memSlot.address}{memSlot.content.raw}{memSlot.content.ascii}) + } + } + return ret + }, + + resolveAddress: function(address) + { + if (!this.state.codes[address]) + { + var hexCode = web3.eth.getCode(address) + var code = codeUtils.nameOpCodes(new Buffer(hexCode.substring(2), 'hex')) + this.state.codes[address] = code[0] + this.state.codesMap[address] = code[1] + } + }, + checkButtonState: function(incr) { if (!this.props.vmTrace) @@ -62,71 +91,82 @@ module.exports = React.createClass({ if (incr === -1) return this.state.currentSelected === 0 ? "disabled" : "" else if (incr === 1) - return this.state.currentSelected >= this.props.vmTrace.vmtrace.length - 1 ? "disabled" : "" + return this.state.currentSelected >= this.props.vmTrace.length - 1 ? "disabled" : "" }, renderAssemblyItems: function() { - if (this.props.vmTrace && this.props.vmTrace.vmtrace) + if (this.props.vmTrace) { - return this.props.vmTrace.vmtrace.map(function(item, i) + return this.state.codes[this.state.currentAddress].map(function(item, i) { - return ; + return ; }); } }, - componentWillReceiveProps: function (nextProps) { + componentWillReceiveProps: function (nextProps) + { this.updateState(nextProps, 0) }, updateState: function(props, vmTraceIndex) { - var previousState = this.state.currentSelected - this.setState({ currentSelected: vmTraceIndex }) - this.state.currentAddress = props.vmTrace.vmtrace[0].address - this.state.selectedInst = props.vmTrace.codesmap[this.state.currentAddress][props.vmTrace.vmtrace[vmTraceIndex].pc] + var previousState = this.state.currentSelected + var stateChanges = {} + + var currentAddress = this.state.currentAddress + if (!currentAddress) + currentAddress = props.vmTrace[vmTraceIndex].address + if (props.vmTrace[vmTraceIndex].address && props.vmTrace[vmTraceIndex].address !== this.state.currentAddress) + { + this.resolveAddress(props.vmTrace[vmTraceIndex].address) + stateChanges["currentAddress"] = props.vmTrace[vmTraceIndex].address + } - if (props.vmTrace.vmtrace[vmTraceIndex].stack) + if (props.vmTrace[vmTraceIndex].stack) { - var stack = props.vmTrace.vmtrace[vmTraceIndex].stack - stack.reverse() - this.setState({ currentStack: stack }) + var stack = props.vmTrace[vmTraceIndex].stack + stack.reverse() + stateChanges["currentStack"] = stack } - if (props.vmTrace.vmtrace[vmTraceIndex].levels) + if (props.vmTrace[vmTraceIndex].levels) { - var levels = props.vmTrace.vmtrace[vmTraceIndex].levels + var levels = props.vmTrace[vmTraceIndex].levels var callStack = [] for (var k in levels) - callStack.push(props.vmTrace.vmtrace[levels[k]].address) - this.setState({ currentCallStack: callStack }) + callStack.push(props.vmTrace[levels[k]].address) + stateChanges["currentCallStack"] = callStack lastCallStack = callStack } var storageIndex = vmTraceIndex if (vmTraceIndex < previousState) - storageIndex = this.retrieveLastSeenProperty(vmTraceIndex, "storage", props.vmTrace.vmtrace) - if (props.vmTrace.vmtrace[storageIndex].storage || storageIndex === 0) + storageIndex = this.retrieveLastSeenProperty(vmTraceIndex, "storage", props.vmTrace) + if (props.vmTrace[storageIndex].storage || storageIndex === 0) { - this.setState({ currentStorage: props.vmTrace.vmtrace[storageIndex].storage }) - lastStorage = props.vmTrace.vmtrace[storageIndex].storage + stateChanges["currentStorage"] = props.vmTrace[storageIndex].storage + lastStorage = props.vmTrace[storageIndex].storage } var memoryIndex = vmTraceIndex if (vmTraceIndex < previousState) - memoryIndex = this.retrieveLastSeenProperty(vmTraceIndex, "memory", props.vmTrace.vmtrace) - if (props.vmTrace.vmtrace[memoryIndex].memory || memoryIndex === 0) + memoryIndex = this.retrieveLastSeenProperty(vmTraceIndex, "memory", props.vmTrace) + if (props.vmTrace[memoryIndex].memory || memoryIndex === 0) { - this.setState({ currentMemory: this.formatMemory(props.vmTrace.vmtrace[memoryIndex].memory, 16) }) - lastMemory = this.formatMemory(props.vmTrace.vmtrace[memoryIndex].memory, 16) + stateChanges["currentMemory"] = this.formatMemory(props.vmTrace[memoryIndex].memory, 16) + lastMemory = this.formatMemory(props.vmTrace[memoryIndex].memory, 16) } - if (props.vmTrace.vmtrace[vmTraceIndex].calldata) + if (props.vmTrace[vmTraceIndex].calldata) { - this.setState({ currentCallData: props.vmTrace.vmtrace[vmTraceIndex].calldata }) - lastCallData = props.vmTrace.vmtrace[vmTraceIndex].calldata + stateChanges["currentCallData"] = props.vmTrace[vmTraceIndex].calldata + lastCallData = props.vmTrace[vmTraceIndex].calldata } + stateChanges["selectedInst"] = this.state.codesMap[currentAddress][props.vmTrace[vmTraceIndex].pc] + stateChanges["currentSelected"] = vmTraceIndex + this.setState(stateChanges) }, retrieveLastSeenProperty: function(currentIndex, propertyName, vmTrace) @@ -185,13 +225,13 @@ module.exports = React.createClass({ isCallInstruction: function(index) { - var state = this.props.vmTrace.vmtrace[index]; - return state.instname === "CALL" || state.instname === "CREATE"; + var state = this.props.vmTrace[index]; + return state.instname === "CALL" || state.instname === "CREATE" || state.instname === "DELEGATECALL" }, isReturnInstruction: function(index) { - var state = this.props.vmTrace.vmtrace[index]; + var state = this.props.vmTrace[index]; return state.instname === "RETURN" }, @@ -219,7 +259,7 @@ module.exports = React.createClass({ { var i = this.state.currentSelected var depth = 0 - while (++i < this.props.vmTrace.vmtrace.length) + while (++i < this.props.vmTrace.length) { if (this.isReturnInstruction(i)) { @@ -241,11 +281,7 @@ module.exports = React.createClass({ selectState: function(index) { - var selectedInst = this.props.vmTrace.codesmap[this.state.currentAddress][this.props.vmTrace.vmtrace[index].pc] this.updateState(this.props, index) - this.refs.itemsList.value = selectedInst - if (this.props.vmTrace.vmtrace[index].address && this.state.currentAddress !== this.props.vmTrace.vmtrace[index].address) - this.state.currentAddress = this.props.vmTrace.vmtrace[index].address }, formatMemory: function(mem, width) diff --git a/src/basicPanel.js b/src/basicPanel.js index 7c4ebc0a1a..1a8fb8f150 100644 --- a/src/basicPanel.js +++ b/src/basicPanel.js @@ -7,7 +7,8 @@ module.exports = React.createClass({ { return { data: null, - name: null + name: null, + renderRow: null }; }, @@ -18,7 +19,7 @@ module.exports = React.createClass({
{this.props.name}
- { this.renderItems() } + {this.renderItems()}
@@ -27,12 +28,16 @@ module.exports = React.createClass({ renderItems: function() { - var ret = [] - if (this.props.data) - { + if (!this.props.data) + return [] + if (!this.props.renderRow) + { + var ret = [] for (var key in this.props.data) - ret.push({JSON.stringify(this.props.data[key])}) + ret.push({this.props.data[key]}) + return ret } - return ret + else + return this.props.renderRow(this.props.data) } }) diff --git a/src/basicStyles.js b/src/basicStyles.js index a4bae4b52a..ffbc37e1f1 100644 --- a/src/basicStyles.js +++ b/src/basicStyles.js @@ -1,16 +1,28 @@ module.exports = { + wrapper: + { + 'fontFamily': "arial,sans-serif" + }, container: { margin: '10px', padding: '5px' }, + address: + { + 'fontStyle': 'italic' + }, + instructions: + { + 'width': '600px' + }, panel: { container: { margin: '10px', border: '1px solid', - width: '800px' + width: '600px' }, table: { @@ -18,7 +30,8 @@ module.exports = { }, title: { - padding: '5px' + padding: '5px', + 'fontStyle': 'italic' } }, hidden: diff --git a/src/codeUtils.js b/src/codeUtils.js new file mode 100644 index 0000000000..c531fc4f88 --- /dev/null +++ b/src/codeUtils.js @@ -0,0 +1,43 @@ +var opcodes = require('./opcodes') + +module.exports = { + + nameOpCodes: function (raw) + { + var pushData = '' + var codeMap = {} + var code = [] + + for (var i = 0; i < raw.length; i++) + { + var pc = i + var curOpCode = opcodes(raw[pc], false).name + codeMap[i] = code.length + // no destinations into the middle of PUSH + if (curOpCode.slice(0, 4) === 'PUSH') + { + var jumpNum = raw[pc] - 0x5f + pushData = raw.slice(pc + 1, pc + jumpNum + 1) + i += jumpNum + } + + code.push(this.pad(pc, this.roundLog(raw.length, 10)) + ' ' + curOpCode + ' ' + pushData.toString('hex')) + pushData = '' + } + return [ code, codeMap ] + }, + + pad: function (num, size) { + var s = num + '' + while (s.length < size) s = '0' + s + return s + }, + + log: function (num, base) { + return Math.log(num) / Math.log(base) + }, + + roundLog: function (num, base) { + return Math.ceil(this.log(num, base)) + } +} \ No newline at end of file diff --git a/src/debugger.js b/src/debugger.js index 5d67b3c160..01345fddf1 100644 --- a/src/debugger.js +++ b/src/debugger.js @@ -11,8 +11,8 @@ module.exports = React.createClass({ render: function() { return ( -
-

Debugger

+
+

Eth Debugger

diff --git a/src/opcodes.js b/src/opcodes.js new file mode 100644 index 0000000000..42355f0793 --- /dev/null +++ b/src/opcodes.js @@ -0,0 +1,178 @@ +const codes = { + // 0x0 range - arithmetic ops + // name, baseCost, off stack, on stack, dynamic + 0x00: ['STOP', 0, 0, 0, false], + 0x01: ['ADD', 3, 2, 1, false], + 0x02: ['MUL', 5, 2, 1, false], + 0x03: ['SUB', 3, 2, 1, false], + 0x04: ['DIV', 5, 2, 1, false], + 0x05: ['SDIV', 5, 2, 1, false], + 0x06: ['MOD', 5, 2, 1, false], + 0x07: ['SMOD', 5, 2, 1, false], + 0x08: ['ADDMOD', 8, 3, 1, false], + 0x09: ['MULMOD', 8, 3, 1, false], + 0x0a: ['EXP', 10, 2, 1, false], + 0x0b: ['SIGNEXTEND', 5, 1, 1, false], + + // 0x10 range - bit ops + 0x10: ['LT', 3, 2, 1, false], + 0x11: ['GT', 3, 2, 1, false], + 0x12: ['SLT', 3, 2, 1, false], + 0x13: ['SGT', 3, 2, 1, false], + 0x14: ['EQ', 3, 2, 1, false], + 0x15: ['ISZERO', 3, 1, 1, false], + 0x16: ['AND', 3, 2, 1, false], + 0x17: ['OR', 3, 2, 1, false], + 0x18: ['XOR', 3, 2, 1, false], + 0x19: ['NOT', 3, 1, 1, false], + 0x1a: ['BYTE', 3, 2, 1, false], + + // 0x20 range - crypto + 0x20: ['SHA3', 30, 2, 1, false], + + // 0x30 range - closure state + 0x30: ['ADDRESS', 2, 0, 1, true], + 0x31: ['BALANCE', 20, 1, 1, true], + 0x32: ['ORIGIN', 2, 0, 1, true], + 0x33: ['CALLER', 2, 0, 1, true], + 0x34: ['CALLVALUE', 2, 0, 1, true], + 0x35: ['CALLDATALOAD', 3, 1, 1, true], + 0x36: ['CALLDATASIZE', 2, 0, 1, true], + 0x37: ['CALLDATACOPY', 3, 3, 0, true], + 0x38: ['CODESIZE', 2, 0, 1, false], + 0x39: ['CODECOPY', 3, 3, 0, false], + 0x3a: ['GASPRICE', 2, 0, 1, false], + 0x3b: ['EXTCODESIZE', 20, 1, 1, true], + 0x3c: ['EXTCODECOPY', 20, 4, 0, true], + + // '0x40' range - block operations + 0x40: ['BLOCKHASH', 20, 1, 1, true], + 0x41: ['COINBASE', 2, 0, 1, true], + 0x42: ['TIMESTAMP', 2, 0, 1, true], + 0x43: ['NUMBER', 2, 0, 1, true], + 0x44: ['DIFFICULTY', 2, 0, 1, true], + 0x45: ['GASLIMIT', 2, 0, 1, true], + + // 0x50 range - 'storage' and execution + 0x50: ['POP', 2, 1, 0, false], + 0x51: ['MLOAD', 3, 1, 1, false], + 0x52: ['MSTORE', 3, 2, 0, false], + 0x53: ['MSTORE8', 3, 2, 0, false], + 0x54: ['SLOAD', 50, 1, 1, true], + 0x55: ['SSTORE', 0, 2, 0, true], + 0x56: ['JUMP', 8, 1, 0, false], + 0x57: ['JUMPI', 10, 2, 0, false], + 0x58: ['PC', 2, 0, 1, false], + 0x59: ['MSIZE', 2, 0, 1, false], + 0x5a: ['GAS', 2, 0, 1, false], + 0x5b: ['JUMPDEST', 1, 0, 0, false], + + // 0x60, range + 0x60: ['PUSH', 3, 0, 1, false], + 0x61: ['PUSH', 3, 0, 1, false], + 0x62: ['PUSH', 3, 0, 1, false], + 0x63: ['PUSH', 3, 0, 1, false], + 0x64: ['PUSH', 3, 0, 1, false], + 0x65: ['PUSH', 3, 0, 1, false], + 0x66: ['PUSH', 3, 0, 1, false], + 0x67: ['PUSH', 3, 0, 1, false], + 0x68: ['PUSH', 3, 0, 1, false], + 0x69: ['PUSH', 3, 0, 1, false], + 0x6a: ['PUSH', 3, 0, 1, false], + 0x6b: ['PUSH', 3, 0, 1, false], + 0x6c: ['PUSH', 3, 0, 1, false], + 0x6d: ['PUSH', 3, 0, 1, false], + 0x6e: ['PUSH', 3, 0, 1, false], + 0x6f: ['PUSH', 3, 0, 1, false], + 0x70: ['PUSH', 3, 0, 1, false], + 0x71: ['PUSH', 3, 0, 1, false], + 0x72: ['PUSH', 3, 0, 1, false], + 0x73: ['PUSH', 3, 0, 1, false], + 0x74: ['PUSH', 3, 0, 1, false], + 0x75: ['PUSH', 3, 0, 1, false], + 0x76: ['PUSH', 3, 0, 1, false], + 0x77: ['PUSH', 3, 0, 1, false], + 0x78: ['PUSH', 3, 0, 1, false], + 0x79: ['PUSH', 3, 0, 1, false], + 0x7a: ['PUSH', 3, 0, 1, false], + 0x7b: ['PUSH', 3, 0, 1, false], + 0x7c: ['PUSH', 3, 0, 1, false], + 0x7d: ['PUSH', 3, 0, 1, false], + 0x7e: ['PUSH', 3, 0, 1, false], + 0x7f: ['PUSH', 3, 0, 1, false], + + 0x80: ['DUP', 3, 0, 1, false], + 0x81: ['DUP', 3, 0, 1, false], + 0x82: ['DUP', 3, 0, 1, false], + 0x83: ['DUP', 3, 0, 1, false], + 0x84: ['DUP', 3, 0, 1, false], + 0x85: ['DUP', 3, 0, 1, false], + 0x86: ['DUP', 3, 0, 1, false], + 0x87: ['DUP', 3, 0, 1, false], + 0x88: ['DUP', 3, 0, 1, false], + 0x89: ['DUP', 3, 0, 1, false], + 0x8a: ['DUP', 3, 0, 1, false], + 0x8b: ['DUP', 3, 0, 1, false], + 0x8c: ['DUP', 3, 0, 1, false], + 0x8d: ['DUP', 3, 0, 1, false], + 0x8e: ['DUP', 3, 0, 1, false], + 0x8f: ['DUP', 3, 0, 1, false], + + 0x90: ['SWAP', 3, 0, 0, false], + 0x91: ['SWAP', 3, 0, 0, false], + 0x92: ['SWAP', 3, 0, 0, false], + 0x93: ['SWAP', 3, 0, 0, false], + 0x94: ['SWAP', 3, 0, 0, false], + 0x95: ['SWAP', 3, 0, 0, false], + 0x96: ['SWAP', 3, 0, 0, false], + 0x97: ['SWAP', 3, 0, 0, false], + 0x98: ['SWAP', 3, 0, 0, false], + 0x99: ['SWAP', 3, 0, 0, false], + 0x9a: ['SWAP', 3, 0, 0, false], + 0x9b: ['SWAP', 3, 0, 0, false], + 0x9c: ['SWAP', 3, 0, 0, false], + 0x9d: ['SWAP', 3, 0, 0, false], + 0x9e: ['SWAP', 3, 0, 0, false], + 0x9f: ['SWAP', 3, 0, 0, false], + + 0xa0: ['LOG', 375, 2, 0, false], + 0xa1: ['LOG', 375, 3, 0, false], + 0xa2: ['LOG', 375, 4, 0, false], + 0xa3: ['LOG', 375, 5, 0, false], + 0xa4: ['LOG', 375, 6, 0, false], + + // '0xf0' range - closures + 0xf0: ['CREATE', 32000, 3, 1, true], + 0xf1: ['CALL', 40, 7, 1, true], + 0xf2: ['CALLCODE', 40, 7, 1, true], + 0xf3: ['RETURN', 0, 2, 0, false], + 0xf4: ['DELEGATECALL', 40, 6, 1, true], + + // '0x70', range - other + 0xff: ['SUICIDE', 0, 1, 0, false] +} + +module.exports = function (op, full) { + var code = codes[op] ? codes[op] : ['INVALID', 0] + var opcode = code[0] + + if (full) { + if (opcode === 'LOG') { + opcode += op - 0xa0 + } + + if (opcode === 'PUSH') { + opcode += op - 0x5f + } + + if (opcode === 'DUP') { + opcode += op - 0x7f + } + + if (opcode === 'SWAP') { + opcode += op - 0x8f + } + } + + return {name: opcode, fee: code[1], in: code[2], out: code[3], dynamic: code[4], async: code[5]} +} \ No newline at end of file diff --git a/src/txBrowser.js b/src/txBrowser.js index 66772bbafb..113a35db9d 100644 --- a/src/txBrowser.js +++ b/src/txBrowser.js @@ -26,7 +26,6 @@ module.exports = React.createClass({ render: function() { return (
-

Transaction details

diff --git a/src/vmTraceManager.js b/src/vmTraceManager.js index 938459b3b3..7cf144884e 100644 --- a/src/vmTraceManager.js +++ b/src/vmTraceManager.js @@ -1,5 +1,5 @@ module.exports = { - retrieveVmTrace: function(blockNumber, txNumber) { + retrieveVmTrace: function(blockNumber, txNumber) { return web3.admin.vmTrace(blockNumber, parseInt(txNumber)); } }