diff --git a/src/app.js b/src/app.js index 5eccf39897..f2f8c6dd66 100644 --- a/src/app.js +++ b/src/app.js @@ -31,8 +31,8 @@ var EditorPanel = require('./app/panels/editor-panel') var RighthandPanel = require('./app/panels/righthand-panel') var examples = require('./app/editor/example-contracts') var modalDialogCustom = require('./app/ui/modal-dialog-custom') -var Txlistener = require('./app/execution/txListener') var TxLogger = require('./app/execution/txLogger') +var Txlistener = remixLib.execution.txListener var EventsDecoder = remixLib.execution.EventsDecoder var handleImports = require('./app/compiler/compiler-imports') var FileManager = require('./app/files/fileManager') diff --git a/src/app/execution/txListener.js b/src/app/execution/txListener.js deleted file mode 100644 index e7139cebed..0000000000 --- a/src/app/execution/txListener.js +++ /dev/null @@ -1,365 +0,0 @@ -'use strict' -var async = require('async') -var ethJSABI = require('ethereumjs-abi') -var ethJSUtil = require('ethereumjs-util') -var remixLib = require('remix-lib') -var EventManager = remixLib.EventManager -var codeUtil = remixLib.util -var executionContext = require('../../execution-context') -var txFormat = require('./txFormat') -var txHelper = require('./txHelper') - -/** - * poll web3 each 2s if web3 - * listen on transaction executed event if VM - * attention: blocks returned by the event `newBlock` have slightly different json properties whether web3 or the VM is used - * trigger 'newBlock' - * - */ -class TxListener { - constructor (opt) { - this.event = new EventManager() - this._api = opt.api - this._resolvedTransactions = {} - this._resolvedContracts = {} - this._isListening = false - this._listenOnNetwork = false - this._loopId = null - this.init() - executionContext.event.register('contextChanged', (context) => { - if (this._isListening) { - this.stopListening() - this.startListening() - } - }) - - opt.event.udapp.register('callExecuted', (error, from, to, data, lookupOnly, txResult) => { - if (error) return - // we go for that case if - // in VM mode - // in web3 mode && listen remix txs only - if (!this._isListening) return // we don't listen - if (this._loopId && executionContext.getProvider() !== 'vm') return // we seems to already listen on a "web3" network - - var call = { - from: from, - to: to, - input: data, - hash: txResult.transactionHash ? txResult.transactionHash : 'call' + (from || '') + to + data, - isCall: true, - returnValue: executionContext.isVM() ? txResult.result.vm.return : ethJSUtil.toBuffer(txResult.result), - envMode: executionContext.getProvider() - } - - addExecutionCosts(txResult, call) - this._resolveTx(call, (error, resolvedData) => { - if (!error) { - this.event.trigger('newCall', [call]) - } - }) - }) - - opt.event.udapp.register('transactionExecuted', (error, from, to, data, lookupOnly, txResult) => { - if (error) return - if (lookupOnly) return - // we go for that case if - // in VM mode - // in web3 mode && listen remix txs only - if (!this._isListening) return // we don't listen - if (this._loopId && executionContext.getProvider() !== 'vm') return // we seems to already listen on a "web3" network - executionContext.web3().eth.getTransaction(txResult.transactionHash, (error, tx) => { - if (error) return console.log(error) - - addExecutionCosts(txResult, tx) - tx.envMode = executionContext.getProvider() - tx.status = txResult.result.status // 0x0 or 0x1 - this._resolve([tx], () => { - }) - }) - }) - - function addExecutionCosts (txResult, tx) { - if (txResult && txResult.result) { - if (txResult.result.vm) { - tx.returnValue = txResult.result.vm.return - if (txResult.result.vm.gasUsed) tx.executionCost = txResult.result.vm.gasUsed.toString(10) - } - if (txResult.result.gasUsed) tx.transactionCost = txResult.result.gasUsed.toString(10) - } - } - } - - /** - * define if txlistener should listen on the network or if only tx created from remix are managed - * - * @param {Bool} type - true if listen on the network - */ - setListenOnNetwork (listenOnNetwork) { - this._listenOnNetwork = listenOnNetwork - if (this._loopId) { - clearInterval(this._loopId) - } - if (this._listenOnNetwork) { - this._startListenOnNetwork() - } - } - - /** - * reset recorded transactions - */ - init () { - this.blocks = [] - this.lastBlock = null - } - - /** - * start listening for incoming transactions - * - * @param {String} type - type/name of the provider to add - * @param {Object} obj - provider - */ - startListening () { - this.init() - this._isListening = true - if (this._listenOnNetwork && executionContext.getProvider() !== 'vm') { - this._startListenOnNetwork() - } - } - - /** - * stop listening for incoming transactions. do not reset the recorded pool. - * - * @param {String} type - type/name of the provider to add - * @param {Object} obj - provider - */ - stopListening () { - if (this._loopId) { - clearInterval(this._loopId) - } - this._loopId = null - this._isListening = false - } - - _startListenOnNetwork () { - this._loopId = setInterval(() => { - var currentLoopId = this._loopId - executionContext.web3().eth.getBlockNumber((error, blockNumber) => { - if (this._loopId === null) return - if (error) return console.log(error) - if (currentLoopId === this._loopId && (!this.lastBlock || blockNumber > this.lastBlock)) { - if (!this.lastBlock) this.lastBlock = blockNumber - 1 - var current = this.lastBlock + 1 - this.lastBlock = blockNumber - while (blockNumber >= current) { - try { - this._manageBlock(current) - } catch (e) { - console.log(e) - } - current++ - } - } - }) - }, 2000) - } - - _manageBlock (blockNumber) { - executionContext.web3().eth.getBlock(blockNumber, true, (error, result) => { - if (!error) { - this._newBlock(Object.assign({type: 'web3'}, result)) - } - }) - } - - /** - * try to resolve the contract name from the given @arg address - * - * @param {String} address - contract address to resolve - * @return {String} - contract name - */ - resolvedContract (address) { - return this._resolvedContracts[address] - } - - /** - * try to resolve the transaction from the given @arg txHash - * - * @param {String} txHash - contract address to resolve - * @return {String} - contract name - */ - resolvedTransaction (txHash) { - return this._resolvedTransactions[txHash] - } - - _newBlock (block) { - this.blocks.push(block) - this._resolve(block.transactions, () => { - this.event.trigger('newBlock', [block]) - }) - } - - _resolve (transactions, callback) { - async.each(transactions, (tx, cb) => { - this._resolveTx(tx, (error, resolvedData) => { - if (error) cb(error) - if (resolvedData) { - this.event.trigger('txResolved', [tx, resolvedData]) - } - this.event.trigger('newTransaction', [tx]) - cb() - }) - }, () => { - callback() - }) - } - - _resolveTx (tx, cb) { - var contracts = this._api.contracts() - if (!contracts) return cb() - var contractName - if (!tx.to || tx.to === '0x0') { // testrpc returns 0x0 in that case - // contract creation / resolve using the creation bytes code - // if web3: we have to call getTransactionReceipt to get the created address - // if VM: created address already included - var code = tx.input - contractName = this._tryResolveContract(code, contracts, true) - if (contractName) { - this._api.resolveReceipt(tx, (error, receipt) => { - if (error) return cb(error) - var address = receipt.contractAddress - this._resolvedContracts[address] = contractName - var fun = this._resolveFunction(contractName, contracts, tx, true) - if (this._resolvedTransactions[tx.hash]) { - this._resolvedTransactions[tx.hash].contractAddress = address - } - return cb(null, {to: null, contractName: contractName, function: fun, creationAddress: address}) - }) - return - } - return cb() - } else { - // first check known contract, resolve against the `runtimeBytecode` if not known - contractName = this._resolvedContracts[tx.to] - if (!contractName) { - executionContext.web3().eth.getCode(tx.to, (error, code) => { - if (error) return cb(error) - if (code) { - var contractName = this._tryResolveContract(code, contracts, false) - if (contractName) { - this._resolvedContracts[tx.to] = contractName - var fun = this._resolveFunction(contractName, contracts, tx, false) - return cb(null, {to: tx.to, contractName: contractName, function: fun}) - } - } - return cb() - }) - return - } - if (contractName) { - var fun = this._resolveFunction(contractName, contracts, tx, false) - return cb(null, {to: tx.to, contractName: contractName, function: fun}) - } - return cb() - } - } - - _resolveFunction (contractName, compiledContracts, tx, isCtor) { - var contract = txHelper.getContract(contractName, compiledContracts) - if (!contract) { - console.log('txListener: cannot resolve ' + contractName) - return - } - var abi = contract.object.abi - var inputData = tx.input.replace('0x', '') - if (!isCtor) { - var methodIdentifiers = contract.object.evm.methodIdentifiers - for (var fn in methodIdentifiers) { - if (methodIdentifiers[fn] === inputData.substring(0, 8)) { - var fnabi = getFunction(abi, fn) - this._resolvedTransactions[tx.hash] = { - contractName: contractName, - to: tx.to, - fn: fn, - params: this._decodeInputParams(inputData.substring(8), fnabi) - } - if (tx.returnValue) { - this._resolvedTransactions[tx.hash].decodedReturnValue = txFormat.decodeResponse(tx.returnValue, fnabi) - } - return this._resolvedTransactions[tx.hash] - } - } - // fallback function - this._resolvedTransactions[tx.hash] = { - contractName: contractName, - to: tx.to, - fn: '(fallback)', - params: null - } - } else { - var bytecode = contract.object.evm.bytecode.object - var params = null - if (bytecode && bytecode.length) { - params = this._decodeInputParams(inputData.substring(bytecode.length), getConstructorInterface(abi)) - } - this._resolvedTransactions[tx.hash] = { - contractName: contractName, - to: null, - fn: '(constructor)', - params: params - } - } - return this._resolvedTransactions[tx.hash] - } - - _tryResolveContract (codeToResolve, compiledContracts, isCreation) { - var found = null - txHelper.visitContracts(compiledContracts, (contract) => { - var bytes = isCreation ? contract.object.evm.bytecode.object : contract.object.evm.deployedBytecode.object - if (codeUtil.compareByteCode(codeToResolve, '0x' + bytes)) { - found = contract.name - return true - } - }) - return found - } - - _decodeInputParams (data, abi) { - data = ethJSUtil.toBuffer('0x' + data) - var inputTypes = [] - for (var i = 0; i < abi.inputs.length; i++) { - inputTypes.push(abi.inputs[i].type) - } - var decoded = ethJSABI.rawDecode(inputTypes, data) - decoded = ethJSABI.stringify(inputTypes, decoded) - var ret = {} - for (var k in abi.inputs) { - ret[abi.inputs[k].type + ' ' + abi.inputs[k].name] = decoded[k] - } - return ret - } -} - -// those function will be duplicate after the merged of the compile and run tabs split -function getConstructorInterface (abi) { - var funABI = { 'name': '', 'inputs': [], 'type': 'constructor', 'outputs': [] } - for (var i = 0; i < abi.length; i++) { - if (abi[i].type === 'constructor') { - funABI.inputs = abi[i].inputs || [] - break - } - } - - return funABI -} - -function getFunction (abi, fnName) { - fnName = fnName.split('(')[0] - for (var i = 0; i < abi.length; i++) { - if (abi[i].name === fnName) { - return abi[i] - } - } - return null -} - -module.exports = TxListener