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