diff --git a/src/app.js b/src/app.js index 987b8410a4..9e390565de 100644 --- a/src/app.js +++ b/src/app.js @@ -7,7 +7,8 @@ var base64 = require('js-base64').Base64 var swarmgw = require('swarmgw') var csjs = require('csjs-inject') var yo = require('yo-yo') -var EventManager = require('ethereum-remix').lib.EventManager +var remix = require('ethereum-remix') +var EventManager = remix.lib.EventManager var UniversalDApp = require('./universal-dapp.js') var Remixd = require('./lib/remixd') @@ -30,7 +31,9 @@ var EditorPanel = require('./app/editor-panel') var RighthandPanel = require('./app/righthand-panel') var examples = require('./app/example-contracts') var modalDialogCustom = require('./app/modal-dialog-custom') -// var Txlistener = require('./app/txListener') +var Txlistener = require('./app/txListener') +var EventsDecoder = require('./app/eventsDecoder') +var Web3VMProvider = remix.web3.web3VMProvider var css = csjs` html { box-sizing: border-box; } @@ -748,26 +751,62 @@ function run () { node.insertBefore(staticanalysis.render(), node.childNodes[0]) // ----------------- Tx listener ----------------- - // Commented for now. will be used later. - /* + // not used right now + + // TODO the following should be put in execution context + var web3VM = new Web3VMProvider() + web3VM.setVM(executionContext.vm()) + + var currentWeb3 = function () { + return executionContext.isVM() ? web3VM : executionContext.web3() + } + + var transactionReceiptResolver = { + _transactionReceipts: {}, + resolve: function (tx, cb) { + if (this._transactionReceipts[tx.hash]) { + return cb(null, this._transactionReceipts[tx.hash]) + } + currentWeb3().eth.getTransactionReceipt(tx.hash, (error, receipt) => { + if (!error) { + this._transactionReceipts[tx.hash] = receipt + cb(null, receipt) + } else { + cb(error) + } + }) + } + } + + var compiledContracts = function () { + if (compiler.lastCompilationResult && compiler.lastCompilationResult.data) { + return compiler.lastCompilationResult.data.contracts + } + return null + } + var txlistener = new Txlistener({ api: { - web3: function () { return executionContext.web3() }, + web3: function () { return currentWeb3() }, isVM: function () { return executionContext.isVM() }, - vm: function () { return executionContext.vm() }, - contracts: function () { - if (compiler.lastCompilationResult && compiler.lastCompilationResult.data) { - return compiler.lastCompilationResult.data.contracts - } - return null - }, + contracts: compiledContracts, context: function () { return executionContext.getProvider() + }, + resolveReceipt: function (tx, cb) { + transactionReceiptResolver.resolve(tx, cb) } }, event: { executionContext: executionContext.event, udapp: udapp.event + }}) + + var eventsDecoder = new EventsDecoder({ + api: { + resolveReceipt: function (tx, cb) { + transactionReceiptResolver.resolve(tx, cb) + } } }) @@ -777,15 +816,31 @@ function run () { var resolvedTransaction = txlistener.resolvedTransaction(tx.hash) var address = null if (resolvedTransaction) { + var resolvedContract address = resolvedTransaction.contractAddress ? resolvedTransaction.contractAddress : tx.to + resolvedContract = txlistener.resolvedContract(address) + if (resolvedContract) { + eventsDecoder.parseLogs(tx, resolvedContract, compiledContracts(), (error, log) => { + console.log({ + tx: tx, + resolvedTransaction: resolvedTransaction, + resolvedContract: resolvedContract, + resolvedEvents: (!error ? log : error) + }) + }) + } else { + console.log({ + tx: tx, + resolvedTransaction: resolvedTransaction + }) + } + } else { + // contract unknown - just displaying raw tx. + console.log({ + tx: tx + }) } - console.log({ - tx: tx, - resolvedContract: txlistener.resolvedContract(address), - resolvedTransaction: resolvedTransaction - }) }) - */ // ----------------- autoCompile ----------------- var autoCompile = document.querySelector('#autoCompile').checked diff --git a/src/app/eventsDecoder.js b/src/app/eventsDecoder.js new file mode 100644 index 0000000000..e3e9a99ca5 --- /dev/null +++ b/src/app/eventsDecoder.js @@ -0,0 +1,77 @@ +'use strict' +var ethJSABI = require('ethereumjs-abi') + +/** + * Register to txListener and extract events + * + */ +class EventsDecoder { + constructor (opt = {}) { + this._api = opt.api + } + +/** + * use Transaction Receipt to decode logs. assume that the transaction as already been resolved by txListener. + * logs are decoded only if the contract if known by remix. + * + * @param {Object} tx - transaction object + * @param {Function} cb - callback + */ + parseLogs (tx, contractName, compiledContracts, cb) { + this._api.resolveReceipt(tx, (error, receipt) => { + if (error) cb(error) + this._decodeLogs(tx, receipt, contractName, compiledContracts, cb) + }) + } + + _decodeLogs (tx, receipt, contract, contracts, cb) { + if (!contract || !receipt) { + return cb('cannot decode logs - contract or receipt not resolved ') + } + if (!receipt.logs) { + return cb(null, []) + } + this._decodeEvents(tx, receipt.logs, contract, contracts, cb) + } + + _eventABI (contractName, compiledContracts) { + var contractabi = JSON.parse(compiledContracts[contractName].interface) + var eventABI = {} + contractabi.forEach(function (funABI, i) { + if (funABI.type !== 'event') { + return + } + var hash = ethJSABI.eventID(funABI.name, funABI.inputs.map(function (item) { return item.type })) + eventABI[hash.toString('hex')] = { event: funABI.name, inputs: funABI.inputs } + }) + return eventABI + } + + _decodeEvents (tx, logs, contractName, compiledContracts, cb) { + var eventABI = this._eventABI(contractName, compiledContracts) + // FIXME: support indexed events + var events = [] + for (var i in logs) { + // [address, topics, mem] + var log = logs[i] + var event + var decoded + + try { + var abi = eventABI[log.topics[0].replace('0x', '')] + event = abi.event + var types = abi.inputs.map(function (item) { + return item.type + }) + decoded = ethJSABI.rawDecode(types, new Buffer(log.data.replace('0x', ''), 'hex')) + decoded = ethJSABI.stringify(types, decoded) + } catch (e) { + decoded = log.data + } + events.push({ event: event, args: decoded }) + } + cb(null, events) + } +} + +module.exports = EventsDecoder diff --git a/src/app/txListener.js b/src/app/txListener.js index 751aadb902..d2bc3a8711 100644 --- a/src/app/txListener.js +++ b/src/app/txListener.js @@ -5,7 +5,6 @@ var ethJSUtil = require('ethereumjs-util') var EventManager = require('ethereum-remix').lib.EventManager var remix = require('ethereum-remix') var codeUtil = remix.util.code -var Web3VMProvider = remix.web3.web3VMProvider /** * poll web3 each 2s if web3 @@ -18,8 +17,6 @@ class TxListener { constructor (opt) { this.event = new EventManager() this._api = opt.api - this._web3VMProvider = new Web3VMProvider() // TODO this should maybe be put in app.js - this._web3VMProvider.setVM(opt.api.vm()) this._resolvedTransactions = {} this._resolvedContracts = {} this.init() @@ -30,7 +27,7 @@ class TxListener { }) opt.event.udapp.register('transactionExecuted', (to, data, lookupOnly, txResult) => { if (this.loopId && this._api.isVM()) { - this._web3VMProvider.getTransaction(txResult.transactionHash, (error, tx) => { + this._api.web3().eth.getTransaction(txResult.transactionHash, (error, tx) => { if (error) return console.log(error) this._newBlock({ type: 'VM', @@ -64,6 +61,7 @@ class TxListener { } else { this.loopId = setInterval(() => { this._api.web3().eth.getBlockNumber((error, blockNumber) => { + if (this.loopId === null || this.loopId === 'vm-listener') return if (error) return console.log(error) if (!this.lastBlock || blockNumber > this.lastBlock) { this.lastBlock = blockNumber @@ -78,10 +76,6 @@ class TxListener { } } - currentWeb3 () { // TODO this should maybe be put in app.js - return this._api.isVM() ? this._web3VMProvider : this._api.web3() - } - /** * stop listening for incoming transactions. do not reset the recorded pool. * @@ -124,7 +118,9 @@ class TxListener { _resolve (block, callback) { async.each(block.transactions, (tx, cb) => { - this._resolveTx(tx, () => { + this._resolveTx(tx, (error, resolvedData) => { + if (error) cb(error) + if (resolvedData) this.event.trigger('txResolved', [tx, resolvedData]) this.event.trigger('newTransaction', [tx]) cb() }) @@ -144,14 +140,15 @@ class TxListener { var code = tx.input contractName = this._tryResolveContract(code, contracts, 'bytecode') if (contractName) { - this._resolveCreationAddress(tx, (error, address) => { + this._api.resolveReceipt(tx, (error, receipt) => { if (error) return cb(error) + var address = receipt.contractAddress this._resolvedContracts[address] = contractName - this._resolveFunction(contractName, contracts, tx, true) + var fun = this._resolveFunction(contractName, contracts, tx, true) if (this._resolvedTransactions[tx.hash]) { this._resolvedTransactions[tx.hash].contractAddress = address } - return cb() + return cb(null, {to: null, contractName: contractName, function: fun, creationAddress: address}) }) return } @@ -160,13 +157,14 @@ class TxListener { // first check known contract, resolve against the `runtimeBytecode` if not known contractName = this._resolvedContracts[tx.to] if (!contractName) { - this.currentWeb3().eth.getCode(tx.to, (error, code) => { + this._api.web3().eth.getCode(tx.to, (error, code) => { if (error) return cb(error) if (code) { var contractName = this._tryResolveContract(code, contracts, 'runtimeBytecode') if (contractName) { this._resolvedContracts[tx.to] = contractName - this._resolveFunction(contractName, contracts, tx, false) + var fun = this._resolveFunction(contractName, contracts, tx, false) + return cb(null, {to: tx.to, contractName: contractName, function: fun}) } } return cb() @@ -174,22 +172,13 @@ class TxListener { return } if (contractName) { - this._resolveFunction(contractName, contracts, tx, false) + var fun = this._resolveFunction(contractName, contracts, tx, false) + return cb(null, {to: tx.to, contractName: contractName, function: fun}) } return cb() } } - _resolveCreationAddress (tx, cb) { - this.currentWeb3().eth.getTransactionReceipt(tx.hash, (error, receipt) => { - if (!error) { - cb(null, receipt.contractAddress) - } else { - cb(error) - } - }) - } - _resolveFunction (contractName, compiledContracts, tx, isCtor) { var abi = JSON.parse(compiledContracts[contractName].interface) var inputData = tx.input.replace('0x', '') @@ -222,6 +211,7 @@ class TxListener { params: params } } + return this._resolvedTransactions[tx.hash] } _tryResolveContract (codeToResolve, compiledContracts, type) {