/ lib / modules / debugger / index.js
index.js
  1  var RemixDebug = require('remix-debug-debugtest');
  2  var CmdLine = RemixDebug.CmdLine;
  3  var DebuggerManager = require('./debugger_manager.js');
  4  
  5  class TransactionDebugger {
  6    constructor(embark, _options) {
  7      const self = this
  8      this.embark = embark
  9  
 10      this.debugger_manager = new DebuggerManager("http://localhost:8545")
 11      embark.events.on('contracts:compile:solc', this.debugger_manager.setInputJson.bind(this.debugger_manager))
 12      embark.events.on('contracts:compiled:solc', this.debugger_manager.setOutputJson.bind(this.debugger_manager))
 13  
 14      this.tx_tracker = {}
 15      this.last_tx = ""
 16  
 17      this.isDebugging = false
 18      this.listenToEvents()
 19      this.listenToCommands()
 20      this.listentoAPI()
 21    }
 22  
 23    listenToEvents() {
 24      const self = this
 25      this.embark.events.on('blockchain:tx', (tx) => {
 26        this.embark.events.request("contracts:contract", tx.name, (contract) => {
 27          self.tx_tracker[tx.transactionHash] = {tx: tx, contract: contract}
 28          self.last_tx = tx.transactionHash
 29          if (tx.status !== '0x0') return
 30  
 31          self.embark.logger.info("Transaction failed");
 32  
 33          self.debugger_manager.getLastLine(tx.transactionHash, contract.filename, (lines, line, known_vars) => {
 34            lines.forEach((line) => {
 35              self.embark.logger.error(line)
 36            })
 37  
 38  					self.find_vars_in_line(tx.transactionHash, line, known_vars, (found_vars) => {
 39  						if (!found_vars) return
 40  						self.embark.logger.info("vars:")
 41  						found_vars.forEach((variable) => {
 42  							self.embark.logger.info(`${variable.name}: ${variable.value}`)
 43  						})
 44  					});
 45          })
 46        })
 47      })
 48    }
 49  
 50  	find_vars_in_line(txHash, line, known_vars, cb) {
 51  		let found_vars = [];
 52  		this.getGlobals(txHash, (err, globals) => {
 53  			if (err) return cb([]);
 54  			for (let variable in globals) {
 55          let value = globals[variable]
 56  				if (line.indexOf(variable) >= 0) {
 57            found_vars.push({name: variable, value: value});
 58  				}
 59  			}
 60  
 61  			for (let variable in known_vars.locals) {
 62          let value = known_vars.locals[variable];
 63          let variable_name = variable.split(' ')[0]
 64  				if (line.indexOf(variable_name) >= 0) {
 65            found_vars.push({name: variable, value: value});
 66  				}
 67  			}
 68  
 69  			for (let variable in known_vars.contract) {
 70          let value = known_vars.contract[variable];
 71          let variable_name = variable.split(' ')[0]
 72  				if (line.indexOf(variable_name) >= 0) {
 73            found_vars.push({name: variable, value: value});
 74  				}
 75  			}
 76  
 77        cb(found_vars);
 78      })
 79    }
 80  
 81    listentoAPI() {
 82      this.debuggerData = {}
 83      this.apiDebugger = false
 84  
 85      this.embark.registerAPICall('post', '/embark-api/debugger/start', (req, res) => {
 86        let txHash = req.body.params.txHash
 87  
 88        this.embark.events.request("contracts:contract:byTxHash", txHash, (err, contract) => {
 89          if (err) {
 90            this.embark.logger.error(err);
 91            return res.send({error: err})
 92          }
 93  
 94          let filename = contract.filename
 95  
 96          this.apiDebugger = this.debugger_manager.createDebuggerSession(txHash, filename, () => {
 97  					this.getGlobals(txHash, (err, globals) => {
 98  						if (err) return res.send({ok: false});
 99  						this.debuggerData.globals = globals;
100  						res.send({ok :true})
101  					})
102          })
103        })
104      });
105  
106      this.embark.registerAPICall('post', '/embark-api/debugger/JumpBack', (req, res) => {
107        this.apiDebugger.stepJumpNextBreakpoint()
108        res.send({ok :true})
109      })
110      this.embark.registerAPICall('post', '/embark-api/debugger/JumpForward', (req, res) => {
111        this.apiDebugger.stepJumpPreviousBreakpoint()
112        res.send({ok :true})
113      })
114      this.embark.registerAPICall('post', '/embark-api/debugger/StepOverForward', (req, res) => {
115        this.apiDebugger.stepOverForward(true)
116        res.send({ok :true})
117      })
118      this.embark.registerAPICall('post', '/embark-api/debugger/StepOverBackward', (req, res) => {
119        this.apiDebugger.stepOverBack(true)
120        res.send({ok :true})
121      })
122      this.embark.registerAPICall('post', '/embark-api/debugger/StepIntoForward', (req, res) => {
123        this.apiDebugger.stepIntoForward(true)
124        res.send({ok :true})
125      })
126      this.embark.registerAPICall('post', '/embark-api/debugger/StepIntoBackward', (req, res) => {
127        this.apiDebugger.stepIntoBack(true)
128        res.send({ok :true})
129      });
130      this.embark.registerAPICall('post', '/embark-api/debugger/breakpoint', (req, res) => {
131        console.dir("new breakpoint")
132        res.send({ok :true})
133      });
134  
135      this.embark.registerAPICall('ws', '/embark-api/debugger', (ws, _req) => {
136        if (!this.apiDebugger) return
137  
138        this.apiDebugger.events.on("source", (lineColumnPos, rawLocation) => {
139          this.debuggerData.sources = {lineColumnPos, rawLocation}
140          ws.send(JSON.stringify(this.debuggerData), () => {})
141        })
142  
143  			this.apiDebugger.events.on("locals", (data) => {
144  				this.debuggerData.locals = this.simplifyDebuggerVars(data)
145  				ws.send(JSON.stringify(this.debuggerData), () => {})
146  			});
147  
148        this.apiDebugger.events.on("globals", (data) => {
149          this.debuggerData.contract = this.simplifyDebuggerVars(data)
150          ws.send(JSON.stringify(this.debuggerData), () => {})
151        })
152      });
153    }
154  
155    simplifyDebuggerVars(data) {
156      let new_data = {};
157  
158      for (let key in data) {
159        let field = data[key];
160        new_data[`${key} (${field.type})`] = field.value
161      }
162  
163      return new_data
164    }
165  
166    listenToCommands() {
167      const self = this
168      this.cmdDebugger = false
169  		this.currentCmdTxHash = ""
170  
171      this.embark.registerConsoleCommand((cmd, _options) => {
172        let cmdName = cmd.split(" ")[0]
173        let txHash = cmd.split(" ")[1]
174        return {
175          match: () => cmdName === 'debug',
176          process: (cb) => {
177            if (txHash) {
178              this.embark.events.request("contracts:contract:byTxHash", txHash, (err, contract) => {
179                if (err) {
180                  this.embark.logger.error(err);
181                  return;
182                }
183                let filename = contract.filename
184  							self.currentCmdTxHash = txHash
185                self.cmdDebugger = self.debugger_manager.createDebuggerSession(txHash, filename, () => {
186                  self.cmdDebugger.getSource().forEach((line) => {
187                    console.dir(line)
188                  })
189                })
190              });
191              return
192            }
193  					self.currentCmdTxHash = self.last_tx
194            let filename = self.tx_tracker[self.last_tx].contract.filename
195            self.cmdDebugger = self.debugger_manager.createDebuggerSession(self.last_tx, filename, () => {
196              self.cmdDebugger.getSource().forEach((line) => {
197                console.dir(line)
198              })
199            })
200          }
201        };
202      })
203  
204      this.embark.registerConsoleCommand((cmd, _options) => {
205        return {
206          match: () => (cmd === 'next' || cmd === 'n'),
207          process: (cb) => {
208            if (!self.cmdDebugger.currentStep()) {
209              console.dir("end of execution reached")
210              return self.cmdDebugger.unload()
211            }
212            self.cmdDebugger.stepOverForward(true)
213            self.cmdDebugger.getSource().forEach((line) => {
214              console.dir(line)
215            })
216          }
217        };
218      })
219  
220      this.embark.registerConsoleCommand((cmd, _options) => {
221        return {
222          match: () => (cmd === 'previous' || cmd === 'p'),
223          process: (cb) => {
224            if (!self.cmdDebugger.currentStep()) {
225              console.dir("end of execution reached")
226              return self.cmdDebugger.unload()
227            }
228            self.cmdDebugger.stepOverBack(true)
229            self.cmdDebugger.getSource().forEach((line) => {
230              console.dir(line)
231            })
232          }
233        };
234      })
235  
236      this.embark.registerConsoleCommand((cmd, _options) => {
237        return {
238          match: () => (cmd === 'var local' || cmd === 'v l' || cmd === 'vl'),
239          process: (cb) => {
240            self.cmdDebugger.displayLocals()
241          }
242        };
243      })
244  
245      this.embark.registerConsoleCommand((cmd, _options) => {
246        return {
247          match: () => (cmd === 'var global' || cmd === 'v g' || cmd === 'vg'),
248          process: (cb) => {
249            self.cmdDebugger.displayGlobals()
250          }
251        };
252      })
253  
254      this.embark.registerConsoleCommand((cmd, _options) => {
255        return {
256          match: () => (cmd === 'var all' || cmd === 'v a' || cmd === 'va'),
257          process: (cb) => {
258  
259  					self.getGlobals((err, globals) => {
260  						if (err) return self.embark.logger.error(err);
261  	          console.dir(globals);
262  					});
263          }
264        };
265      })
266    }
267  
268  	getGlobals(txHash, cb) {
269  		let globals = {}
270  		this.embark.events.request("blockchain:getTransaction", txHash, (err, tx) => {
271  			if (err) return cb(err);
272  
273  			this.embark.events.request("blockchain:block:byHash", tx.blockHash, (err, block) => {
274  				if (err) return cb(err);
275  
276  				globals["block.blockHash"] = tx.blockHash;
277  				globals["block.number"] = tx.blockNumber;
278  				globals["block.coinbase"] = block.miner;
279  				globals["block.difficulty"] = block.difficulty;
280  				globals["block.gaslimit"] = block.gasLimit;
281  				globals["block.timestamp"] = block.timestamp;
282  				globals["msg.sender"] = tx.from;
283  				globals["msg.gas"] = tx.gas;
284  				globals["msg.gasPrice"] = tx.gasPrice;
285  				globals["msg.value"] = tx.value;
286  				globals["now"] = block.timestamp;
287  
288  				cb(null, globals);
289  			})
290  		})
291  	}
292  
293  }
294  
295  module.exports = TransactionDebugger