Merge pull request #1090 from ethereum/refactor/move_tx_to_remix_lib_2
move most tx code to remix-libpull/1/head
commit
ef10574667
@ -1,123 +0,0 @@ |
|||||||
'use strict' |
|
||||||
var ethJSABI = require('ethereumjs-abi') |
|
||||||
var txHelper = require('../execution/txHelper') |
|
||||||
|
|
||||||
/** |
|
||||||
* 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) { |
|
||||||
if (tx.isCall) return cb(null, { decoded: [], raw: [] }) |
|
||||||
this._api.resolveReceipt(tx, (error, receipt) => { |
|
||||||
if (error) return 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, { decoded: [], raw: [] }) |
|
||||||
} |
|
||||||
this._decodeEvents(tx, receipt.logs, contract, contracts, cb) |
|
||||||
} |
|
||||||
|
|
||||||
_eventABI (contract) { |
|
||||||
var eventABI = {} |
|
||||||
contract.abi.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 |
|
||||||
} |
|
||||||
|
|
||||||
_eventsABI (compiledContracts) { |
|
||||||
var eventsABI = {} |
|
||||||
txHelper.visitContracts(compiledContracts, (contract) => { |
|
||||||
eventsABI[contract.name] = this._eventABI(contract.object) |
|
||||||
}) |
|
||||||
return eventsABI |
|
||||||
} |
|
||||||
|
|
||||||
_event (hash, eventsABI) { |
|
||||||
for (var k in eventsABI) { |
|
||||||
if (eventsABI[k][hash]) { |
|
||||||
return eventsABI[k][hash] |
|
||||||
} |
|
||||||
} |
|
||||||
return null |
|
||||||
} |
|
||||||
|
|
||||||
_decodeEvents (tx, logs, contractName, compiledContracts, cb) { |
|
||||||
var eventsABI = this._eventsABI(compiledContracts) |
|
||||||
var events = [] |
|
||||||
for (var i in logs) { |
|
||||||
// [address, topics, mem]
|
|
||||||
var log = logs[i] |
|
||||||
var topicId = log.topics[0] |
|
||||||
var abi = this._event(topicId.replace('0x', ''), eventsABI) |
|
||||||
if (abi) { |
|
||||||
var event |
|
||||||
try { |
|
||||||
var decoded = new Array(abi.inputs.length) |
|
||||||
event = abi.event |
|
||||||
var indexed = 1 |
|
||||||
var nonindexed = [] |
|
||||||
// decode indexed param
|
|
||||||
abi.inputs.map(function (item, index) { |
|
||||||
if (item.indexed) { |
|
||||||
var encodedData = log.topics[indexed].replace('0x', '') |
|
||||||
try { |
|
||||||
decoded[index] = ethJSABI.rawDecode([item.type], new Buffer(encodedData, 'hex'))[0] |
|
||||||
if (typeof decoded[index] !== 'string') { |
|
||||||
decoded[index] = ethJSABI.stringify([item.type], decoded[index]) |
|
||||||
} |
|
||||||
} catch (e) { |
|
||||||
decoded[index] = encodedData |
|
||||||
} |
|
||||||
indexed++ |
|
||||||
} else { |
|
||||||
nonindexed.push(item.type) |
|
||||||
} |
|
||||||
}) |
|
||||||
// decode non indexed param
|
|
||||||
var nonindexededResult = ethJSABI.rawDecode(nonindexed, new Buffer(log.data.replace('0x', ''), 'hex')) |
|
||||||
nonindexed = ethJSABI.stringify(nonindexed, nonindexededResult) |
|
||||||
// ordering
|
|
||||||
var j = 0 |
|
||||||
abi.inputs.map(function (item, index) { |
|
||||||
if (!item.indexed) { |
|
||||||
decoded[index] = nonindexed[j] |
|
||||||
j++ |
|
||||||
} |
|
||||||
}) |
|
||||||
} catch (e) { |
|
||||||
decoded = log.data |
|
||||||
} |
|
||||||
events.push({ topic: topicId, event: event, args: decoded }) |
|
||||||
} else { |
|
||||||
events.push({ data: log.data, topics: log.topics }) |
|
||||||
} |
|
||||||
} |
|
||||||
cb(null, { decoded: events, raw: logs }) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
module.exports = EventsDecoder |
|
@ -1,75 +0,0 @@ |
|||||||
'use strict' |
|
||||||
|
|
||||||
module.exports = { |
|
||||||
/** |
|
||||||
* deploy the given contract |
|
||||||
* |
|
||||||
* @param {String} data - data to send with the transaction ( return of txFormat.buildData(...) ). |
|
||||||
* @param {Object} udap - udapp. |
|
||||||
* @param {Function} callback - callback. |
|
||||||
*/ |
|
||||||
createContract: function (data, udapp, callback) { |
|
||||||
udapp.runTx({data: data, useCall: false}, (error, txResult) => { |
|
||||||
// see universaldapp.js line 660 => 700 to check possible values of txResult (error case)
|
|
||||||
callback(error, txResult) |
|
||||||
}) |
|
||||||
}, |
|
||||||
|
|
||||||
/** |
|
||||||
* call the current given contract |
|
||||||
* |
|
||||||
* @param {String} to - address of the contract to call. |
|
||||||
* @param {String} data - data to send with the transaction ( return of txFormat.buildData(...) ). |
|
||||||
* @param {Object} funAbi - abi definition of the function to call. |
|
||||||
* @param {Object} udap - udapp. |
|
||||||
* @param {Function} callback - callback. |
|
||||||
*/ |
|
||||||
callFunction: function (to, data, funAbi, udapp, callback) { |
|
||||||
udapp.runTx({to: to, data: data, useCall: funAbi.constant}, (error, txResult) => { |
|
||||||
// see universaldapp.js line 660 => 700 to check possible values of txResult (error case)
|
|
||||||
callback(error, txResult) |
|
||||||
}) |
|
||||||
}, |
|
||||||
|
|
||||||
/** |
|
||||||
* check if the vm has errored |
|
||||||
* |
|
||||||
* @param {Object} txResult - the value returned by the vm |
|
||||||
* @return {Object} - { error: true/false, message: DOMNode } |
|
||||||
*/ |
|
||||||
checkVMError: function (txResult) { |
|
||||||
var errorCode = { |
|
||||||
OUT_OF_GAS: 'out of gas', |
|
||||||
STACK_UNDERFLOW: 'stack underflow', |
|
||||||
STACK_OVERFLOW: 'stack overflow', |
|
||||||
INVALID_JUMP: 'invalid JUMP', |
|
||||||
INVALID_OPCODE: 'invalid opcode', |
|
||||||
REVERT: 'revert', |
|
||||||
STATIC_STATE_CHANGE: 'static state change' |
|
||||||
} |
|
||||||
var ret = { |
|
||||||
error: false, |
|
||||||
message: '' |
|
||||||
} |
|
||||||
if (!txResult.result.vm.exceptionError) { |
|
||||||
return ret |
|
||||||
} |
|
||||||
var error = `VM error: ${txResult.result.vm.exceptionError}.\n` |
|
||||||
var msg |
|
||||||
if (txResult.result.vm.exceptionError === errorCode.INVALID_OPCODE) { |
|
||||||
msg = `\t\n\tThe execution might have thrown.\n` |
|
||||||
ret.error = true |
|
||||||
} else if (txResult.result.vm.exceptionError === errorCode.OUT_OF_GAS) { |
|
||||||
msg = `\tThe transaction ran out of gas. Please increase the Gas Limit.\n` |
|
||||||
ret.error = true |
|
||||||
} else if (txResult.result.vm.exceptionError === errorCode.REVERT) { |
|
||||||
msg = `\tThe transaction has been reverted to the initial state.\nNote: The constructor should be payable if you send value.` |
|
||||||
ret.error = true |
|
||||||
} else if (txResult.result.vm.exceptionError === errorCode.STATIC_STATE_CHANGE) { |
|
||||||
msg = `\tState changes is not allowed in Static Call context\n` |
|
||||||
ret.error = true |
|
||||||
} |
|
||||||
ret.message = `${error}${txResult.result.vm.exceptionError}${msg}\tDebug the transaction to get more information.` |
|
||||||
return ret |
|
||||||
} |
|
||||||
} |
|
@ -1,121 +0,0 @@ |
|||||||
'use strict' |
|
||||||
var ethJSABI = require('ethereumjs-abi') |
|
||||||
|
|
||||||
module.exports = { |
|
||||||
encodeParams: function (funABI, args) { |
|
||||||
var types = [] |
|
||||||
if (funABI.inputs && funABI.inputs.length) { |
|
||||||
for (var i = 0; i < funABI.inputs.length; i++) { |
|
||||||
var type = funABI.inputs[i].type |
|
||||||
types.push(type) |
|
||||||
if (args.length < types.length) { |
|
||||||
args.push('') |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// NOTE: the caller will concatenate the bytecode and this
|
|
||||||
// it could be done here too for consistency
|
|
||||||
return ethJSABI.rawEncode(types, args) |
|
||||||
}, |
|
||||||
|
|
||||||
encodeFunctionId: function (funABI) { |
|
||||||
var types = [] |
|
||||||
if (funABI.inputs && funABI.inputs.length) { |
|
||||||
for (var i = 0; i < funABI.inputs.length; i++) { |
|
||||||
types.push(funABI.inputs[i].type) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return ethJSABI.methodID(funABI.name, types) |
|
||||||
}, |
|
||||||
|
|
||||||
sortAbiFunction: function (contractabi) { |
|
||||||
var abi = contractabi.sort(function (a, b) { |
|
||||||
if (a.name > b.name) { |
|
||||||
return -1 |
|
||||||
} else { |
|
||||||
return 1 |
|
||||||
} |
|
||||||
}).sort(function (a, b) { |
|
||||||
if (a.constant === true) { |
|
||||||
return -1 |
|
||||||
} else { |
|
||||||
return 1 |
|
||||||
} |
|
||||||
}) |
|
||||||
return abi |
|
||||||
}, |
|
||||||
|
|
||||||
getConstructorInterface: function (abi) { |
|
||||||
var funABI = { 'name': '', 'inputs': [], 'type': 'constructor', 'outputs': [] } |
|
||||||
if (typeof abi === 'string') { |
|
||||||
try { |
|
||||||
abi = JSON.parse(abi) |
|
||||||
} catch (e) { |
|
||||||
console.log('exception retrieving ctor abi ' + abi) |
|
||||||
return funABI |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
for (var i = 0; i < abi.length; i++) { |
|
||||||
if (abi[i].type === 'constructor') { |
|
||||||
funABI.inputs = abi[i].inputs || [] |
|
||||||
break |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return funABI |
|
||||||
}, |
|
||||||
|
|
||||||
getFunction: function (abi, fnName) { |
|
||||||
for (var i = 0; i < abi.length; i++) { |
|
||||||
if (abi[i].name === fnName) { |
|
||||||
return abi[i] |
|
||||||
} |
|
||||||
} |
|
||||||
return null |
|
||||||
}, |
|
||||||
|
|
||||||
getFallbackInterface: function (abi) { |
|
||||||
for (var i = 0; i < abi.length; i++) { |
|
||||||
if (abi[i].type === 'fallback') { |
|
||||||
return abi[i] |
|
||||||
} |
|
||||||
} |
|
||||||
}, |
|
||||||
|
|
||||||
/** |
|
||||||
* return the contract obj of the given @arg name. Uses last compilation result. |
|
||||||
* return null if not found |
|
||||||
* @param {String} name - contract name |
|
||||||
* @returns contract obj and associated file: { contract, file } or null |
|
||||||
*/ |
|
||||||
getContract: (contractName, contracts) => { |
|
||||||
for (var file in contracts) { |
|
||||||
if (contracts[file][contractName]) { |
|
||||||
return { object: contracts[file][contractName], file: file } |
|
||||||
} |
|
||||||
} |
|
||||||
return null |
|
||||||
}, |
|
||||||
|
|
||||||
/** |
|
||||||
* call the given @arg cb (function) for all the contracts. Uses last compilation result |
|
||||||
* stop visiting when cb return true |
|
||||||
* @param {Function} cb - callback |
|
||||||
*/ |
|
||||||
visitContracts: (contracts, cb) => { |
|
||||||
for (var file in contracts) { |
|
||||||
for (var name in contracts[file]) { |
|
||||||
if (cb({ name: name, object: contracts[file][name], file: file })) return |
|
||||||
} |
|
||||||
} |
|
||||||
}, |
|
||||||
|
|
||||||
inputParametersDeclarationToString: function (abiinputs) { |
|
||||||
var inputs = (abiinputs || []).map((inp) => inp.type + ' ' + inp.name) |
|
||||||
return inputs.join(', ') |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -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 |
|
@ -1,227 +1,4 @@ |
|||||||
'use strict' |
|
||||||
|
|
||||||
var Web3 = require('web3') |
|
||||||
var remixLib = require('remix-lib') |
var remixLib = require('remix-lib') |
||||||
var EventManager = remixLib.EventManager |
var executionContext = remixLib.execution.executionContext |
||||||
var EthJSVM = require('ethereumjs-vm') |
|
||||||
var ethUtil = require('ethereumjs-util') |
|
||||||
var StateManager = require('ethereumjs-vm/lib/stateManager') |
|
||||||
var Web3VMProvider = remixLib.vm.Web3VMProvider |
|
||||||
var rlp = ethUtil.rlp |
|
||||||
|
|
||||||
var injectedProvider |
|
||||||
|
|
||||||
var web3 |
|
||||||
if (typeof window.web3 !== 'undefined') { |
|
||||||
injectedProvider = window.web3.currentProvider |
|
||||||
web3 = new Web3(injectedProvider) |
|
||||||
} else { |
|
||||||
web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545')) |
|
||||||
} |
|
||||||
|
|
||||||
var blankWeb3 = new Web3() |
|
||||||
|
|
||||||
/* |
|
||||||
extend vm state manager and instanciate VM |
|
||||||
*/ |
|
||||||
|
|
||||||
class StateManagerCommonStorageDump extends StateManager { |
|
||||||
constructor (arg) { |
|
||||||
super(arg) |
|
||||||
this.keyHashes = {} |
|
||||||
} |
|
||||||
|
|
||||||
putContractStorage (address, key, value, cb) { |
|
||||||
this.keyHashes[ethUtil.sha3(key).toString('hex')] = ethUtil.bufferToHex(key) |
|
||||||
super.putContractStorage(address, key, value, cb) |
|
||||||
} |
|
||||||
|
|
||||||
dumpStorage (address, cb) { |
|
||||||
var self = this |
|
||||||
this._getStorageTrie(address, function (err, trie) { |
|
||||||
if (err) { |
|
||||||
return cb(err) |
|
||||||
} |
|
||||||
var storage = {} |
|
||||||
var stream = trie.createReadStream() |
|
||||||
stream.on('data', function (val) { |
|
||||||
var value = rlp.decode(val.value) |
|
||||||
storage['0x' + val.key.toString('hex')] = { |
|
||||||
key: self.keyHashes[val.key.toString('hex')], |
|
||||||
value: '0x' + value.toString('hex') |
|
||||||
} |
|
||||||
}) |
|
||||||
stream.on('end', function () { |
|
||||||
cb(storage) |
|
||||||
}) |
|
||||||
}) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
var stateManager = new StateManagerCommonStorageDump({}) |
|
||||||
var vm = new EthJSVM({ |
|
||||||
enableHomestead: true, |
|
||||||
activatePrecompiles: true |
|
||||||
}) |
|
||||||
|
|
||||||
// FIXME: move state manager in EthJSVM ctr
|
|
||||||
vm.stateManager = stateManager |
|
||||||
vm.blockchain = stateManager.blockchain |
|
||||||
vm.trie = stateManager.trie |
|
||||||
vm.stateManager.checkpoint() |
|
||||||
|
|
||||||
var web3VM = new Web3VMProvider() |
|
||||||
web3VM.setVM(vm) |
|
||||||
|
|
||||||
var mainNetGenesisHash = '0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3' |
|
||||||
|
|
||||||
/* |
|
||||||
trigger contextChanged, web3EndpointChanged |
|
||||||
*/ |
|
||||||
function ExecutionContext () { |
|
||||||
var self = this |
|
||||||
this.event = new EventManager() |
|
||||||
|
|
||||||
var executionContext = null |
|
||||||
|
|
||||||
this.init = function (config) { |
|
||||||
if (config.get('settings/always-use-vm')) { |
|
||||||
executionContext = 'vm' |
|
||||||
} else { |
|
||||||
executionContext = injectedProvider ? 'injected' : 'vm' |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
this.getProvider = function () { |
|
||||||
return executionContext |
|
||||||
} |
|
||||||
|
|
||||||
this.isVM = function () { |
|
||||||
return executionContext === 'vm' |
|
||||||
} |
|
||||||
|
|
||||||
this.web3 = function () { |
|
||||||
return this.isVM() ? web3VM : web3 |
|
||||||
} |
|
||||||
|
|
||||||
this.detectNetwork = function (callback) { |
|
||||||
if (this.isVM()) { |
|
||||||
callback(null, { id: '-', name: 'VM' }) |
|
||||||
} else { |
|
||||||
this.web3().version.getNetwork((err, id) => { |
|
||||||
var name = null |
|
||||||
if (err) name = 'Unknown' |
|
||||||
// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md
|
|
||||||
else if (id === '1') name = 'Main' |
|
||||||
else if (id === '2') name = 'Morden (deprecated)' |
|
||||||
else if (id === '3') name = 'Ropsten' |
|
||||||
else if (id === '4') name = 'Rinkeby' |
|
||||||
else if (id === '42') name = 'Kovan' |
|
||||||
else name = 'Custom' |
|
||||||
|
|
||||||
if (id === '1') { |
|
||||||
this.web3().eth.getBlock(0, (error, block) => { |
|
||||||
if (error) console.log('cant query first block') |
|
||||||
if (block && block.hash !== mainNetGenesisHash) name = 'Custom' |
|
||||||
callback(err, { id, name }) |
|
||||||
}) |
|
||||||
} else { |
|
||||||
callback(err, { id, name }) |
|
||||||
} |
|
||||||
}) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
this.internalWeb3 = function () { |
|
||||||
return web3 |
|
||||||
} |
|
||||||
|
|
||||||
this.blankWeb3 = function () { |
|
||||||
return blankWeb3 |
|
||||||
} |
|
||||||
|
|
||||||
this.vm = function () { |
|
||||||
return vm |
|
||||||
} |
|
||||||
|
|
||||||
this.setContext = function (context, endPointUrl, confirmCb, infoCb) { |
|
||||||
executionContext = context |
|
||||||
this.executionContextChange(context, endPointUrl, confirmCb, infoCb) |
|
||||||
} |
|
||||||
|
|
||||||
this.executionContextChange = function (context, endPointUrl, confirmCb, infoCb, cb) { |
|
||||||
if (!cb) cb = () => {} |
|
||||||
|
|
||||||
if (context === 'vm') { |
|
||||||
executionContext = context |
|
||||||
vm.stateManager.revert(function () { |
|
||||||
vm.stateManager.checkpoint() |
|
||||||
}) |
|
||||||
self.event.trigger('contextChanged', ['vm']) |
|
||||||
return cb() |
|
||||||
} |
|
||||||
|
|
||||||
if (context === 'injected') { |
|
||||||
if (injectedProvider === undefined) { |
|
||||||
var alertMsg = 'No injected Web3 provider found. ' |
|
||||||
alertMsg += 'Make sure your provider (e.g. MetaMask) is active and running ' |
|
||||||
alertMsg += '(when recently activated you may have to reload the page).' |
|
||||||
infoCb(alertMsg) |
|
||||||
return cb() |
|
||||||
} else { |
|
||||||
executionContext = context |
|
||||||
web3.setProvider(injectedProvider) |
|
||||||
self.event.trigger('contextChanged', ['injected']) |
|
||||||
return cb() |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
if (context === 'web3') { |
|
||||||
confirmCb(cb) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
this.currentblockGasLimit = function () { |
|
||||||
return this.blockGasLimit |
|
||||||
} |
|
||||||
|
|
||||||
this.blockGasLimitDefault = 4300000 |
|
||||||
this.blockGasLimit = this.blockGasLimitDefault |
|
||||||
setInterval(() => { |
|
||||||
if (this.getProvider() !== 'vm') { |
|
||||||
web3.eth.getBlock('latest', (err, block) => { |
|
||||||
if (!err) { |
|
||||||
// we can't use the blockGasLimit cause the next blocks could have a lower limit : https://github.com/ethereum/remix/issues/506
|
|
||||||
this.blockGasLimit = (block && block.gasLimit) ? Math.floor(block.gasLimit - (5 * block.gasLimit) / 1024) : this.blockGasLimitDefault |
|
||||||
} else { |
|
||||||
this.blockGasLimit = this.blockGasLimitDefault |
|
||||||
} |
|
||||||
}) |
|
||||||
} |
|
||||||
}, 15000) |
|
||||||
|
|
||||||
// TODO: not used here anymore and needs to be moved
|
|
||||||
function setProviderFromEndpoint (endpoint, context, cb) { |
|
||||||
var oldProvider = web3.currentProvider |
|
||||||
|
|
||||||
if (endpoint === 'ipc') { |
|
||||||
web3.setProvider(new web3.providers.IpcProvider()) |
|
||||||
} else { |
|
||||||
web3.setProvider(new web3.providers.HttpProvider(endpoint)) |
|
||||||
} |
|
||||||
if (web3.isConnected()) { |
|
||||||
executionContext = context |
|
||||||
self.event.trigger('contextChanged', ['web3']) |
|
||||||
self.event.trigger('web3EndpointChanged') |
|
||||||
cb() |
|
||||||
} else { |
|
||||||
web3.setProvider(oldProvider) |
|
||||||
var alertMsg = 'Not possible to connect to the Web3 provider. ' |
|
||||||
alertMsg += 'Make sure the provider is running and a connection is open (via IPC or RPC).' |
|
||||||
cb(alertMsg) |
|
||||||
} |
|
||||||
} |
|
||||||
this.setProviderFromEndpoint = setProviderFromEndpoint |
|
||||||
} |
|
||||||
|
|
||||||
module.exports = new ExecutionContext() |
module.exports = executionContext |
||||||
|
Loading…
Reference in new issue