commit
9c5f02d103
@ -0,0 +1,65 @@ |
||||
# `remix-debug` |
||||
|
||||
remix-debug wrap other remix-* libraries and can be used to debug Ethereum transactions. |
||||
|
||||
+ [Installation](#installation) |
||||
+ [Development](#development) |
||||
|
||||
## Installation |
||||
|
||||
|
||||
```bash |
||||
npm install remix-debug |
||||
``` |
||||
|
||||
## Development |
||||
|
||||
```bash |
||||
var Debugger = require('remix-debug').EthDebugger |
||||
var BreakpointManager = require('remix-debug').EthDebugger |
||||
|
||||
var debugger = new Debugger({ |
||||
compilationResult: () => { |
||||
return compilationResult // that helps resolving source location |
||||
} |
||||
}) |
||||
|
||||
debugger.addProvider(web3, 'web3') |
||||
debugger.switchProvider('web3') |
||||
|
||||
var breakPointManager = new remixCore.code.BreakpointManager(this.debugger, (sourceLocation) => { |
||||
// return offsetToLineColumn |
||||
}) |
||||
debugger.setBreakpointManager(breakPointManager) |
||||
breakPointManager.add({fileName, row}) |
||||
breakPointManager.add({fileName, row}) |
||||
|
||||
debugger.debug(<tx_hash>) |
||||
|
||||
// this.traceManager.getCurrentCalledAddressAt |
||||
|
||||
debugger.event.register('newTraceLoaded', () => { |
||||
// start doing basic stuff like retrieving step details |
||||
debugger.traceManager.getCallStackAt(34, (error, callstack) => {}) |
||||
}) |
||||
|
||||
debugger.callTree.register('callTreeReady', () => { |
||||
// start doing more complex stuff like resolvng local variables |
||||
breakPointManager.jumpNextBreakpoint(true) |
||||
|
||||
var storageView = debugger.storageViewAt(38, <contract address>, |
||||
storageView.storageSlot(0, (error, storage) => {}) |
||||
storageView.storageRange(error, storage) => {}) // retrieve 0 => 1000 slots |
||||
|
||||
debugger.extractStateAt(23, (error, state) => { |
||||
debugger.decodeStateAt(23, state, (error, decodedState) => {}) |
||||
}) |
||||
|
||||
debugger.sourceLocationFromVMTraceIndex(<contract address>, 23, (error, location) => { |
||||
debugger.decodeLocalsAt(23, location, (error, decodedlocals) => {}) |
||||
}) |
||||
|
||||
debugger.extractLocalsAt(23, (null, locals) => {} |
||||
|
||||
}) |
||||
``` |
@ -0,0 +1,22 @@ |
||||
'use strict' |
||||
var remixCore = require('remix-core') |
||||
var EthDebugger = require('./src/Ethdebugger') |
||||
|
||||
/* |
||||
Use of breakPointManager : |
||||
|
||||
var breakPointManager = new BreakpointManager(this.debugger, (sourceLocation) => { |
||||
return line/column from offset (sourceLocation) |
||||
}) |
||||
this.debugger.setBreakpointManager(breakPointManager) |
||||
*/ |
||||
module.exports = { |
||||
EthDebugger: EthDebugger, |
||||
/** |
||||
* constructor |
||||
* |
||||
* @param {Object} _debugger - type of EthDebugger |
||||
* @return {Function} _locationToRowConverter - function implemented by editor which return a column/line position for a char source location |
||||
*/ |
||||
BreakpointManager: remixCore.code.BreakpointManager |
||||
} |
@ -0,0 +1,96 @@ |
||||
{ |
||||
"name": "remix-debug", |
||||
"version": "0.0.2", |
||||
"description": "Ethereum IDE and tools for the web", |
||||
"contributors": [ |
||||
{ |
||||
"name": "Yann Levreau", |
||||
"email": "yann@ethdev.com" |
||||
}, |
||||
{ |
||||
"name": "Liana Husikyan", |
||||
"email": "liana@ethdev.com" |
||||
} |
||||
], |
||||
"main": "./index.js", |
||||
"devDependencies": { |
||||
"babel-eslint": "^7.1.1", |
||||
"babel-plugin-transform-object-assign": "^6.22.0", |
||||
"babel-plugin-yo-yoify": "^0.3.3", |
||||
"babel-polyfill": "^6.22.0", |
||||
"babel-preset-env": "^1.6.1", |
||||
"babel-preset-es2015": "^6.24.0", |
||||
"babel-preset-stage-0": "^6.24.1", |
||||
"babelify": "^7.3.0", |
||||
"notify-error": "^1.2.0", |
||||
"npm-run-all": "^4.1.2", |
||||
"remix-core": "latest", |
||||
"remix-lib": "latest", |
||||
"remix-solidity": "latest", |
||||
"standard": "^7.0.1", |
||||
"standard-reporter": "^1.0.5" |
||||
}, |
||||
"scripts": { |
||||
"build": "mkdirp build; browserify index.js > build/app.js", |
||||
"lint": "standard | notify-error" |
||||
}, |
||||
"repository": { |
||||
"type": "git", |
||||
"url": "git+https://github.com/ethereum/remix.git" |
||||
}, |
||||
"author": "cpp-ethereum team", |
||||
"license": "MIT", |
||||
"bugs": { |
||||
"url": "https://github.com/ethereum/remix/issues" |
||||
}, |
||||
"homepage": "https://github.com/ethereum/remix#readme", |
||||
"standard": { |
||||
"ignore": [ |
||||
"node_modules/*", |
||||
"build/*", |
||||
"test/resources/*" |
||||
] |
||||
}, |
||||
"babel": { |
||||
"plugins": [ |
||||
"transform-es2015-template-literals", |
||||
"transform-es2015-literals", |
||||
"transform-es2015-function-name", |
||||
"transform-es2015-arrow-functions", |
||||
"transform-es2015-block-scoped-functions", |
||||
"transform-es2015-classes", |
||||
"transform-es2015-object-super", |
||||
"transform-es2015-shorthand-properties", |
||||
"transform-es2015-duplicate-keys", |
||||
"transform-es2015-computed-properties", |
||||
"transform-es2015-for-of", |
||||
"transform-es2015-sticky-regex", |
||||
"transform-es2015-unicode-regex", |
||||
"check-es2015-constants", |
||||
"transform-es2015-spread", |
||||
"transform-es2015-parameters", |
||||
"transform-es2015-destructuring", |
||||
"transform-es2015-block-scoping", |
||||
"transform-object-assign" |
||||
] |
||||
}, |
||||
"browserify": { |
||||
"transform": [ |
||||
[ |
||||
"babelify", |
||||
{ |
||||
"sourceMapsAbsolute": false, |
||||
"sourceMaps": true, |
||||
"plugins": [ |
||||
[ |
||||
"transform-object-assign" |
||||
] |
||||
], |
||||
"presets": [ |
||||
"es2015" |
||||
] |
||||
} |
||||
] |
||||
] |
||||
} |
||||
} |
@ -0,0 +1,224 @@ |
||||
'use strict' |
||||
var remixCore = require('remix-core') |
||||
var TraceManager = remixCore.trace.TraceManager |
||||
var StorageViewer = remixCore.storage.StorageViewer |
||||
var remixLib = require('remix-lib') |
||||
var traceHelper = remixLib.helpers.trace |
||||
var global = remixLib.global |
||||
var init = remixLib.init |
||||
var executionContext = remixLib.execution.executionContext |
||||
var EventManager = remixLib.EventManager |
||||
var Web3Providers = remixLib.vm.Web3Providers |
||||
var DummyProvider = remixLib.vm.DummyProvider |
||||
var CodeManager = remixCore.code.CodeManager |
||||
var remixSolidity = require('remix-solidity') |
||||
var SolidityProxy = remixSolidity.SolidityProxy |
||||
var stateDecoder = remixSolidity.stateDecoder |
||||
var localDecoder = remixSolidity.localDecoder |
||||
var InternalCallTree = remixSolidity.InternalCallTree |
||||
var StorageResolver = remixCore.storage.StorageResolver |
||||
|
||||
/** |
||||
* Ethdebugger is a wrapper around a few classes that helps debugging a transaction |
||||
* |
||||
* - Web3Providers - define which environment (web3) the transaction will be retrieved from |
||||
* - TraceManager - Load / Analyze the trace and retrieve details of specific test |
||||
* - CodeManager - Retrieve loaded byte code and help to resolve AST item from vmtrace index |
||||
* - SolidityProxy - Basically used to extract state variable from AST |
||||
* - Breakpoint Manager - Used to add / remove / jumpto breakpoint |
||||
* - InternalCallTree - Used to retrieved local variables |
||||
* - StorageResolver - Help resolving the storage accross different steps |
||||
* |
||||
* @param {Map} opts - { function compilationResult } //
|
||||
*/ |
||||
function Ethdebugger (opts) { |
||||
this.opts = opts || {} |
||||
if (!this.opts.compilationResult) this.opts.compilationResult = () => { return null } |
||||
|
||||
this.event = new EventManager() |
||||
|
||||
this.tx |
||||
|
||||
this.web3Providers = new Web3Providers() |
||||
this.addProvider('DUMMYWEB3', new DummyProvider()) |
||||
this.switchProvider('DUMMYWEB3') |
||||
|
||||
this.traceManager = new TraceManager() |
||||
this.codeManager = new CodeManager(this.traceManager) |
||||
this.solidityProxy = new SolidityProxy(this.traceManager, this.codeManager) |
||||
this.storageResolver = null |
||||
|
||||
this.callTree = new InternalCallTree(this.event, this.traceManager, this.solidityProxy, this.codeManager, { includeLocalVariables: true }) |
||||
} |
||||
|
||||
Ethdebugger.prototype.resolveStep = function (index) { |
||||
this.codeManager.resolveStep(index, this.tx) |
||||
} |
||||
|
||||
Ethdebugger.prototype.setCompilationResult = function (compilationResult) { |
||||
if (compilationResult && compilationResult.sources && compilationResult.contracts) { |
||||
this.solidityProxy.reset(compilationResult) |
||||
} else { |
||||
this.solidityProxy.reset({}) |
||||
} |
||||
} |
||||
|
||||
/* resolve source location */ |
||||
Ethdebugger.prototype.sourceLocationFromVMTraceIndex = function (address, stepIndex, callback) { |
||||
this.callTree.sourceLocationTracker.getSourceLocationFromVMTraceIndex(address, stepIndex, this.solidityProxy.contracts, (error, rawLocation) => { |
||||
callback(error, rawLocation) |
||||
}) |
||||
} |
||||
|
||||
Ethdebugger.prototype.sourceLocationFromInstructionIndex = function (address, instIndex, callback) { |
||||
this.debugger.callTree.sourceLocationTracker.getSourceLocationFromInstructionIndex(address, instIndex, this.solidityProxy.contracts, function (error, rawLocation) { |
||||
callback(error, rawLocation) |
||||
}) |
||||
} |
||||
|
||||
/* breakpoint */ |
||||
Ethdebugger.prototype.setBreakpointManager = function (breakpointManager) { |
||||
this.breakpointManager = breakpointManager |
||||
} |
||||
|
||||
/* decode locals */ |
||||
Ethdebugger.prototype.extractLocalsAt = function (step, callback) { |
||||
callback(null, this.callTree.findScope(step)) |
||||
} |
||||
|
||||
Ethdebugger.prototype.decodeLocalsAt = function (step, sourceLocation, callback) { |
||||
this.traceManager.waterfall([ |
||||
this.traceManager.getStackAt, |
||||
this.traceManager.getMemoryAt, |
||||
this.traceManager.getCurrentCalledAddressAt], |
||||
step, |
||||
(error, result) => { |
||||
if (!error) { |
||||
var stack = result[0].value |
||||
var memory = result[1].value |
||||
try { |
||||
var storageViewer = new StorageViewer({ |
||||
stepIndex: step, |
||||
tx: this.tx, |
||||
address: result[2].value |
||||
}, this.storageResolver, this.traceManager) |
||||
localDecoder.solidityLocals(step, this.callTree, stack, memory, storageViewer, sourceLocation).then((locals) => { |
||||
if (!locals.error) { |
||||
callback(null, locals) |
||||
} else { |
||||
callback(locals.error) |
||||
} |
||||
}) |
||||
} catch (e) { |
||||
callback(e.message) |
||||
} |
||||
} else { |
||||
callback(error) |
||||
} |
||||
}) |
||||
} |
||||
|
||||
/* decode state */ |
||||
Ethdebugger.prototype.extractStateAt = function (step, callback) { |
||||
this.solidityProxy.extractStateVariablesAt(step, function (error, stateVars) { |
||||
callback(error, stateVars) |
||||
}) |
||||
} |
||||
|
||||
Ethdebugger.prototype.decodeStateAt = function (step, stateVars, callback) { |
||||
this.traceManager.getCurrentCalledAddressAt(step, (error, address) => { |
||||
if (error) return callback(error) |
||||
var storageViewer = new StorageViewer({ |
||||
stepIndex: step, |
||||
tx: this.tx, |
||||
address: address |
||||
}, this.storageResolver, this.traceManager) |
||||
stateDecoder.decodeState(stateVars, storageViewer).then((result) => { |
||||
if (!result.error) { |
||||
callback(null, result) |
||||
} else { |
||||
callback(result.error) |
||||
} |
||||
}) |
||||
}) |
||||
} |
||||
|
||||
Ethdebugger.prototype.storageViewAt = function (step, address) { |
||||
return new StorageViewer({ |
||||
stepIndex: step, |
||||
tx: this.tx, |
||||
address: address |
||||
}, this.storageResolver, this.traceManager) |
||||
} |
||||
/* set env */ |
||||
Ethdebugger.prototype.web3 = function () { |
||||
return global.web3 |
||||
} |
||||
|
||||
Ethdebugger.prototype.addProvider = function (type, obj) { |
||||
this.web3Providers.addProvider(type, obj) |
||||
this.event.trigger('providerAdded', [type]) |
||||
} |
||||
|
||||
Ethdebugger.prototype.switchProvider = function (type) { |
||||
var self = this |
||||
this.web3Providers.get(type, function (error, obj) { |
||||
if (error) { |
||||
console.log('provider ' + type + ' not defined') |
||||
} else { |
||||
global.web3 = obj |
||||
executionContext.detectNetwork((error, network) => { |
||||
if (error || !network) { |
||||
global.web3Debug = obj |
||||
} else { |
||||
var webDebugNode = init.web3DebugNode(network.name) |
||||
global.web3Debug = !webDebugNode ? obj : webDebugNode |
||||
} |
||||
}) |
||||
self.event.trigger('providerChanged', [type]) |
||||
} |
||||
}) |
||||
} |
||||
|
||||
Ethdebugger.prototype.debug = function (tx) { |
||||
this.setCompilationResult(this.opts.compilationResult()) |
||||
if (tx instanceof Object) { |
||||
this.txBrowser.load(tx.hash) |
||||
} else if (tx instanceof String) { |
||||
this.txBrowser.load(tx) |
||||
} |
||||
} |
||||
|
||||
Ethdebugger.prototype.unLoad = function () { |
||||
this.traceManager.init() |
||||
this.codeManager.clear() |
||||
this.stepManager.reset() |
||||
this.event.trigger('traceUnloaded') |
||||
} |
||||
|
||||
Ethdebugger.prototype.debug = function (tx) { |
||||
if (this.traceManager.isLoading) { |
||||
return |
||||
} |
||||
if (!tx.to) { |
||||
tx.to = traceHelper.contractCreationToken('0') |
||||
} |
||||
this.setCompilationResult(this.opts.compilationResult()) |
||||
console.log('loading trace...') |
||||
this.tx = tx |
||||
var self = this |
||||
this.traceManager.resolveTrace(tx, function (error, result) { |
||||
console.log('trace loaded ' + result) |
||||
if (result) { |
||||
self.event.trigger('newTraceLoaded', [self.traceManager.trace]) |
||||
if (self.breakpointManager && self.breakpointManager.hasBreakpoint()) { |
||||
self.breakpointManager.jumpNextBreakpoint(false) |
||||
} |
||||
self.storageResolver = new StorageResolver() |
||||
} else { |
||||
self.statusMessage = error ? error.message : 'Trace not loaded' |
||||
} |
||||
}) |
||||
} |
||||
|
||||
module.exports = Ethdebugger |
Loading…
Reference in new issue