commit
ec30476a27
@ -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