UNPKG

remix-ide

Version:
568 lines (528 loc) 19.4 kB
'use strict' var yo = require('yo-yo') var copyToClipboard = require('./copy-to-clipboard') // -------------- styling ---------------------- var csjs = require('csjs-inject') var remixLib = require('remix-lib') var EventManager = require('../../lib/events') var helper = require('../../lib/helper') var modalDialog = require('./modal-dialog-custom') var typeConversion = remixLib.execution.typeConversion var globlalRegistry = require('../../global/registry') var css = csjs` .log { display: flex; cursor: pointer; align-items: center; cursor: pointer; } .log:hover { opacity: 0.8; } .arrow { color: var(--text-info); font-size: 20px; cursor: pointer; display: flex; margin-left: 10px; } .arrow:hover { color: var(--secondary); } .txLog { } .txStatus { display: flex; font-size: 20px; margin-right: 20px; float: left; } .succeeded { color: var(--success); } .failed { color: var(--danger); } .notavailable { } .call { font-size: 7px; border-radius: 50%; min-width: 20px; min-height: 20px; display: flex; justify-content: center; align-items: center; color: var(--text-info); text-transform: uppercase; font-weight: bold; } .txItem { color: var(--text-info); margin-right: 5px; float: left; } .txItemTitle { font-weight: bold; } .tx { color: var(--text-info); font-weight: bold; float: left; margin-right: 10px; } .txTable, .tr, .td { border-collapse: collapse; font-size: 10px; color: var(--text-info); border: 1px solid var(--text-info); } #txTable { margin-top: 1%; margin-bottom: 5%; align-self: center; width: 85%; } .tr, .td { padding: 4px; vertical-align: baseline; } .td:first-child { min-width: 30%; width: 30%; align-items: baseline; font-weight: bold; } .tableTitle { width: 25%; } .buttons { display: flex; margin-left: auto; } .debug { white-space: nowrap; } .debug:hover { opacity: 0.8; }` /** * This just export a function that register to `newTransaction` and forward them to the logger. * */ class TxLogger { constructor (terminal, blockchain) { this.event = new EventManager() this.seen = {} function filterTx (value, query) { if (value.length) { return helper.find(value, query) } return false } this.eventsDecoder = globlalRegistry.get('eventsDecoder').api this.txListener = globlalRegistry.get('txlistener').api this.terminal = terminal // dependencies this._deps = { compilersArtefacts: globlalRegistry.get('compilersartefacts').api } this.logKnownTX = this.terminal.registerCommand('knownTransaction', (args, cmds, append) => { var data = args[0] var el if (data.tx.isCall) { el = renderCall(this, data) } else { el = renderKnownTransaction(this, data, blockchain) } this.seen[data.tx.hash] = el append(el) }, { activate: true, filterFn: filterTx }) this.logUnknownTX = this.terminal.registerCommand('unknownTransaction', (args, cmds, append) => { // triggered for transaction AND call var data = args[0] var el = renderUnknownTransaction(this, data, blockchain) append(el) }, { activate: false, filterFn: filterTx }) this.logEmptyBlock = this.terminal.registerCommand('emptyBlock', (args, cmds, append) => { var data = args[0] var el = renderEmptyBlock(this, data) append(el) }, { activate: true }) this.txListener.event.register('newBlock', (block) => { if (!block.transactions || block.transactions && !block.transactions.length) { this.logEmptyBlock({ block: block }) } }) this.txListener.event.register('newTransaction', (tx, receipt) => { log(this, tx, receipt) }) this.txListener.event.register('newCall', (tx) => { log(this, tx, null) }) this.terminal.updateJournal({ type: 'select', value: 'unknownTransaction' }) this.terminal.updateJournal({ type: 'select', value: 'knownTransaction' }) } } function debug (e, data, self) { e.stopPropagation() if (data.tx.isCall && data.tx.envMode !== 'vm') { modalDialog.alert('Cannot debug this call. Debugging calls is only possible in JavaScript VM mode.') } else { self.event.trigger('debuggingRequested', [data.tx.hash]) } } function log (self, tx, receipt) { var resolvedTransaction = self.txListener.resolvedTransaction(tx.hash) if (resolvedTransaction) { var compiledContracts = null if (self._deps.compilersArtefacts['__last']) { compiledContracts = self._deps.compilersArtefacts['__last'].getContracts() } self.eventsDecoder.parseLogs(tx, resolvedTransaction.contractName, compiledContracts, (error, logs) => { if (!error) { self.logKnownTX({ tx: tx, receipt: receipt, resolvedData: resolvedTransaction, logs: logs }) } }) } else { // contract unknown - just displaying raw tx. self.logUnknownTX({ tx: tx, receipt: receipt }) } } function renderKnownTransaction (self, data, blockchain) { var from = data.tx.from var to = data.resolvedData.contractName + '.' + data.resolvedData.fn var obj = {from, to} var txType = 'knownTx' var tx = yo` <span id="tx${data.tx.hash}" data-id="txLogger${data.tx.hash}"> <div class="${css.log}" onclick=${e => txDetails(e, tx, data, obj)}> ${checkTxStatus(data.receipt, txType)} ${context(self, {from, to, data}, blockchain)} <div class=${css.buttons}> <button class="${css.debug} btn btn-primary btn-sm" data-shared="txLoggerDebugButton" data-id="txLoggerDebugButton${data.tx.hash}" onclick=${(e) => debug(e, data, self)}>Debug</div> </div> <i class="${css.arrow} fas fa-angle-down"></i> </div> </span> ` return tx } function renderCall (self, data) { var to = data.resolvedData.contractName + '.' + data.resolvedData.fn var from = data.tx.from ? data.tx.from : ' - ' var input = data.tx.input ? helper.shortenHexData(data.tx.input) : '' var obj = {from, to} var txType = 'call' var tx = yo` <span id="tx${data.tx.hash}"> <div class="${css.log}" onclick=${e => txDetails(e, tx, data, obj)}> ${checkTxStatus(data.tx, txType)} <span class=${css.txLog}> <span class=${css.tx}>[call]</span> <div class=${css.txItem}><span class=${css.txItemTitle}>from:</span> ${from}</div> <div class=${css.txItem}><span class=${css.txItemTitle}>to:</span> ${to}</div> <div class=${css.txItem}><span class=${css.txItemTitle}>data:</span> ${input}</div> </span> <div class=${css.buttons}> <div class="${css.debug} btn btn-primary btn-sm" onclick=${(e) => debug(e, data, self)}>Debug</div> </div> <i class="${css.arrow} fas fa-angle-down"></i> </div> </span> ` return tx } function renderUnknownTransaction (self, data, blockchain) { var from = data.tx.from var to = data.tx.to var obj = {from, to} var txType = 'unknown' + (data.tx.isCall ? 'Call' : 'Tx') var tx = yo` <span id="tx${data.tx.hash}"> <div class="${css.log}" onclick=${e => txDetails(e, tx, data, obj)}> ${checkTxStatus(data.receipt || data.tx, txType)} ${context(self, {from, to, data}, blockchain)} <div class=${css.buttons}> <div class="${css.debug} btn btn-primary btn-sm" onclick=${(e) => debug(e, data, self)}>Debug</div> </div> <i class="${css.arrow} fas fa-angle-down"></i> </div> </span> ` return tx } function renderEmptyBlock (self, data) { return yo` <span class=${css.txLog}> <span class='${css.tx}'><div class=${css.txItem}>[<span class=${css.txItemTitle}>block:${data.block.number} - </span> 0 transactions]</span></span> </span>` } function checkTxStatus (tx, type) { if (tx.status === '0x1' || tx.status === true) { return yo`<i class="${css.txStatus} ${css.succeeded} fas fa-check-circle"></i>` } if (type === 'call' || type === 'unknownCall') { return yo`<i class="${css.txStatus} ${css.call}">call</i>` } else if (tx.status === '0x0' || tx.status === false) { return yo`<i class="${css.txStatus} ${css.failed} fas fa-times-circle"></i>` } else { return yo`<i class="${css.txStatus} ${css.notavailable} fas fa-circle-thin" title='Status not available' ></i>` } } function context (self, opts, blockchain) { var data = opts.data || '' var from = opts.from ? helper.shortenHexData(opts.from) : '' var to = opts.to if (data.tx.to) to = to + ' ' + helper.shortenHexData(data.tx.to) var val = data.tx.value var hash = data.tx.hash ? helper.shortenHexData(data.tx.hash) : '' var input = data.tx.input ? helper.shortenHexData(data.tx.input) : '' var logs = data.logs && data.logs.decoded && data.logs.decoded.length ? data.logs.decoded.length : 0 var block = data.receipt ? data.receipt.blockNumber : data.tx.blockNumber || '' var i = data.receipt ? data.receipt.transactionIndex : data.tx.transactionIndex var value = val ? typeConversion.toInt(val) : 0 if (blockchain.getProvider() === 'vm') { return yo` <div> <span class=${css.txLog}> <span class=${css.tx}>[vm]</span> <div class=${css.txItem}><span class=${css.txItemTitle}>from:</span> ${from}</div> <div class=${css.txItem}><span class=${css.txItemTitle}>to:</span> ${to}</div> <div class=${css.txItem}><span class=${css.txItemTitle}>value:</span> ${value} wei</div> <div class=${css.txItem}><span class=${css.txItemTitle}>data:</span> ${input}</div> <div class=${css.txItem}><span class=${css.txItemTitle}>logs:</span> ${logs}</div> <div class=${css.txItem}><span class=${css.txItemTitle}>hash:</span> ${hash}</div> </span> </div>` } else if (blockchain.getProvider() !== 'vm' && data.resolvedData) { return yo` <div> <span class=${css.txLog}> <span class='${css.tx}'>[block:${block} txIndex:${i}]</span> <div class=${css.txItem}><span class=${css.txItemTitle}>from:</span> ${from}</div> <div class=${css.txItem}><span class=${css.txItemTitle}>to:</span> ${to}</div> <div class=${css.txItem}><span class=${css.txItemTitle}>value:</span> ${value} wei</div> <div class=${css.txItem}><span class=${css.txItemTitle}>data:</span> ${input}</div> <div class=${css.txItem}><span class=${css.txItemTitle}>logs:</span> ${logs}</div> <div class=${css.txItem}><span class=${css.txItemTitle}>hash:</span> ${hash}</div> </span> </div>` } else { to = helper.shortenHexData(to) hash = helper.shortenHexData(data.tx.blockHash) return yo` <div> <span class=${css.txLog}> <span class='${css.tx}'>[block:${block} txIndex:${i}]</span> <div class=${css.txItem}><span class=${css.txItemTitle}>from:</span> ${from}</div> <div class=${css.txItem}><span class=${css.txItemTitle}>to:</span> ${to}</div> <div class=${css.txItem}><span class=${css.txItemTitle}>value:</span> ${value} wei</div> </span> </div>` } } module.exports = TxLogger // helpers function txDetails (e, tx, data, obj) { const from = obj.from const to = obj.to const arrowUp = yo`<i class="${css.arrow} fas fa-angle-up"></i>` const arrowDown = yo`<i class="${css.arrow} fas fa-angle-down"></i>` let blockElement = e.target while (true) { // get the parent block element if (blockElement.className.startsWith('block')) break else if (blockElement.parentElement) { blockElement = blockElement.parentElement } else break } let table = blockElement.querySelector(`#${tx.id} [class^="txTable"]`) const log = blockElement.querySelector(`#${tx.id} [class^='log']`) const arrow = blockElement.querySelector(`#${tx.id} [class^='arrow']`) if (table && table.parentNode) { tx.removeChild(table) log.removeChild(arrow) log.appendChild(arrowDown) } else { log.removeChild(arrow) log.appendChild(arrowUp) table = createTable({ hash: data.tx.hash, status: data.receipt ? data.receipt.status : null, isCall: data.tx.isCall, contractAddress: data.tx.contractAddress, data: data.tx, from, to, gas: data.tx.gas, input: data.tx.input, 'decoded input': data.resolvedData && data.resolvedData.params ? JSON.stringify(typeConversion.stringify(data.resolvedData.params), null, '\t') : ' - ', 'decoded output': data.resolvedData && data.resolvedData.decodedReturnValue ? JSON.stringify(typeConversion.stringify(data.resolvedData.decodedReturnValue), null, '\t') : ' - ', logs: data.logs, val: data.tx.value, transactionCost: data.tx.transactionCost, executionCost: data.tx.executionCost }) tx.appendChild(table) } } function createTable (opts) { var table = yo`<table class="${css.txTable}" id="txTable" data-id="txLoggerTable${opts.hash}"></table>` if (!opts.isCall) { var msg = '' if (opts.status !== undefined && opts.status !== null) { if (opts.status === '0x0' || opts.status === false) { msg = ' Transaction mined but execution failed' } else if (opts.status === '0x1' || opts.status === true) { msg = ' Transaction mined and execution succeed' } } else { msg = ' Status not available at the moment' } table.appendChild(yo` <tr class="${css.tr}"> <td class="${css.td}" data-shared="key_${opts.hash}"> status </td> <td class="${css.td}" data-id="txLoggerTableStatus${opts.hash}" data-shared="pair_${opts.hash}">${opts.status}${msg}</td> </tr>`) } var transactionHash = yo` <tr class="${css.tr}"> <td class="${css.td}" data-shared="key_${opts.hash}"> transaction hash </td> <td class="${css.td}" data-id="txLoggerTableHash${opts.hash}" data-shared="pair_${opts.hash}">${opts.hash} ${copyToClipboard(() => opts.hash)} </td> </tr> ` table.appendChild(transactionHash) var contractAddress = yo` <tr class="${css.tr}"> <td class="${css.td}" data-shared="key_${opts.hash}"> contract address </td> <td class="${css.td}" data-id="txLoggerTableContractAddress${opts.hash}" data-shared="pair_${opts.hash}">${opts.contractAddress} ${copyToClipboard(() => opts.contractAddress)} </td> </tr> ` if (opts.contractAddress) table.appendChild(contractAddress) var from = yo` <tr class="${css.tr}"> <td class="${css.td} ${css.tableTitle}" data-shared="key_${opts.hash}"> from </td> <td class="${css.td}" data-id="txLoggerTableFrom${opts.hash}" data-shared="pair_${opts.hash}">${opts.from} ${copyToClipboard(() => opts.from)} </td> </tr> ` if (opts.from) table.appendChild(from) var toHash var data = opts.data // opts.data = data.tx if (data.to) { toHash = opts.to + ' ' + data.to } else { toHash = opts.to } var to = yo` <tr class="${css.tr}"> <td class="${css.td}" data-shared="key_${opts.hash}"> to </td> <td class="${css.td}" data-id="txLoggerTableTo${opts.hash}" data-shared="pair_${opts.hash}">${toHash} ${copyToClipboard(() => data.to ? data.to : toHash)} </td> </tr> ` if (opts.to) table.appendChild(to) var gas = yo` <tr class="${css.tr}"> <td class="${css.td}" data-shared="key_${opts.hash}"> gas </td> <td class="${css.td}" data-id="txLoggerTableGas${opts.hash}" data-shared="pair_${opts.hash}">${opts.gas} gas ${copyToClipboard(() => opts.gas)} </td> </tr> ` if (opts.gas) table.appendChild(gas) var callWarning = '' if (opts.isCall) { callWarning = '(Cost only applies when called by a contract)' } if (opts.transactionCost) { table.appendChild(yo` <tr class="${css.tr}"> <td class="${css.td}" data-shared="key_${opts.hash}"> transaction cost </td> <td class="${css.td}" data-id="txLoggerTableTransactionCost${opts.hash}" data-shared="pair_${opts.hash}">${opts.transactionCost} gas ${callWarning} ${copyToClipboard(() => opts.transactionCost)} </td> </tr>`) } if (opts.executionCost) { table.appendChild(yo` <tr class="${css.tr}"> <td class="${css.td}" data-shared="key_${opts.hash}"> execution cost </td> <td class="${css.td}" data-id="txLoggerTableExecutionHash${opts.hash}" data-shared="pair_${opts.hash}">${opts.executionCost} gas ${callWarning} ${copyToClipboard(() => opts.executionCost)} </td> </tr>`) } var hash = yo` <tr class="${css.tr}"> <td class="${css.td}" data-shared="key_${opts.hash}"> hash </td> <td class="${css.td}" data-id="txLoggerTableHash${opts.hash}" data-shared="pair_${opts.hash}">${opts.hash} ${copyToClipboard(() => opts.hash)} </td> </tr> ` if (opts.hash) table.appendChild(hash) var input = yo` <tr class="${css.tr}"> <td class="${css.td}" data-shared="key_${opts.hash}"> input </td> <td class="${css.td}" data-id="txLoggerTableInput${opts.hash}" data-shared="pair_${opts.hash}">${helper.shortenHexData(opts.input)} ${copyToClipboard(() => opts.input)} </td> </tr> ` if (opts.input) table.appendChild(input) if (opts['decoded input']) { var inputDecoded = yo` <tr class="${css.tr}"> <td class="${css.td}" data-shared="key_${opts.hash}"> decoded input </td> <td class="${css.td}" data-id="txLoggerTableDecodedInput${opts.hash}" data-shared="pair_${opts.hash}">${opts['decoded input']} ${copyToClipboard(() => opts['decoded input'])} </td> </tr>` table.appendChild(inputDecoded) } if (opts['decoded output']) { var outputDecoded = yo` <tr class="${css.tr}"> <td class="${css.td}" data-shared="key_${opts.hash}"> decoded output </td> <td class="${css.td}" id="decodedoutput" data-id="txLoggerTableDecodedOutput${opts.hash}" data-shared="pair_${opts.hash}">${opts['decoded output']} ${copyToClipboard(() => opts['decoded output'])} </td> </tr>` table.appendChild(outputDecoded) } var stringified = ' - ' if (opts.logs && opts.logs.decoded) { stringified = typeConversion.stringify(opts.logs.decoded) } var logs = yo` <tr class="${css.tr}"> <td class="${css.td}" data-shared="key_${opts.hash}"> logs </td> <td class="${css.td}" id="logs" data-id="txLoggerTableLogs${opts.hash}" data-shared="pair_${opts.hash}"> ${JSON.stringify(stringified, null, '\t')} ${copyToClipboard(() => JSON.stringify(stringified, null, '\t'))} ${copyToClipboard(() => JSON.stringify(opts.logs.raw || '0'))} </td> </tr> ` if (opts.logs) table.appendChild(logs) var val = opts.val != null ? typeConversion.toInt(opts.val) : 0 val = yo` <tr class="${css.tr}"> <td class="${css.td}" data-shared="key_${opts.hash}"> value </td> <td class="${css.td}" data-id="txLoggerTableValue${opts.hash}" data-shared="pair_${opts.hash}">${val} wei ${copyToClipboard(() => `${val} wei`)} </td> </tr> ` if (opts.val) table.appendChild(val) return table }