diff --git a/src/app/execution/confirmDialog.js b/src/app/execution/confirmDialog.js new file mode 100644 index 0000000000..60654b8092 --- /dev/null +++ b/src/app/execution/confirmDialog.js @@ -0,0 +1,66 @@ +var yo = require('yo-yo') +var csjs = require('csjs-inject') +var remixLib = require('remix-lib') +var styleGuide = remixLib.ui.styleGuide +var styles = styleGuide() + +var css = csjs` + .txInfoBox { + ${styles.rightPanel.compileTab.box_CompileContainer}; // add askToConfirmTXContainer to Remix and then replace this styling + } + .wrapword { + white-space: pre-wrap; /* Since CSS 2.1 */ + white-space: -moz-pre-wrap; /* Mozilla, since 1999 */ + white-space: -pre-wrap; /* Opera 4-6 */ + white-space: -o-pre-wrap; /* Opera 7 */ + word-wrap: break-word; /* Internet Explorer 5.5+ */ + } +` + +function confirmDialog (tx, amount, gasEstimation, self, newGasPriceCb, initialParamsCb) { + var onGasPriceChange = function () { + var gasPrice = el.querySelector('#gasprice').value + newGasPriceCb(gasPrice, (priceStatus, txFeeText) => { + el.querySelector('#txfee').innerHTML = txFeeText + el.gasPriceStatus = priceStatus + }) + } + + var el = yo` +
+
You are creating a transaction on the main network. Click confirm if you are sure to continue.
+
+
From: ${tx.from}
+
To: ${tx.to ? tx.to : '(Contract Creation)'}
+
Amount: ${amount} Ether
+
Gas estimation: ${gasEstimation}
+
Gas limit: ${tx.gas}
+
Gas price: Gwei (visit ethgasstation.info to get more info about gas price)
+
Max transaction fee:
+
Data:
+
${tx.data}
+
+
+ + Do not ask for confirmation again. (the setting will not be persisted for the next page reload) +
+
+ ` + + initialParamsCb((txFeeText, gasPriceValue, gasPriceStatus) => { + if (txFeeText) { + el.querySelector('#txfee').innerHTML = txFeeText + } + if (gasPriceValue) { + el.querySelector('#gasprice').value = gasPriceValue + onGasPriceChange() + } + if (gasPriceStatus !== undefined) { + el.gasPriceStatus = gasPriceStatus + } + }) + + return el +} + +module.exports = confirmDialog diff --git a/src/app/execution/txRunner.js b/src/app/execution/txRunner.js index 0d48c8e485..e109881860 100644 --- a/src/app/execution/txRunner.js +++ b/src/app/execution/txRunner.js @@ -5,26 +5,10 @@ var ethJSUtil = require('ethereumjs-util') var BN = ethJSUtil.BN var executionContext = require('../../execution-context') var modalDialog = require('../ui/modaldialog') -var yo = require('yo-yo') -var typeConversion = require('../../lib/typeConversion') -var csjs = require('csjs-inject') -var remixLib = require('remix-lib') -var styleGuide = remixLib.ui.styleGuide -var styles = styleGuide() var modal = require('../ui/modal-dialog-custom') +var typeConversion = require('../../lib/typeConversion') -var css = csjs` - .txInfoBox { - ${styles.rightPanel.compileTab.box_CompileContainer}; // add askToConfirmTXContainer to Remix and then replace this styling - } - .wrapword { - white-space: pre-wrap; /* Since CSS 2.1 */ - white-space: -moz-pre-wrap; /* Mozilla, since 1999 */ - white-space: -pre-wrap; /* Opera 4-6 */ - white-space: -o-pre-wrap; /* Opera 7 */ - word-wrap: break-word; /* Internet Explorer 5.5+ */ - } -` +var confirmDialog = require('./confirmDialog') function TxRunner (vmaccounts, api) { this._api = api @@ -43,150 +27,179 @@ TxRunner.prototype.rawRun = function (args, cb) { run(this, args, Date.now(), cb) } -TxRunner.prototype.execute = function (args, callback) { - var self = this - function execute (gasPrice) { - if (gasPrice) tx.gasPrice = executionContext.web3().toHex(gasPrice) +function executeTx (tx, gasPrice, api, callback) { + if (gasPrice) tx.gasPrice = executionContext.web3().toHex(gasPrice) - if (self._api.personalMode()) { - modal.promptPassphrase(null, 'Personal mode is enabled. Please provide passphrase of account ' + tx.from, '', (value) => { - sendTransaction(executionContext.web3().personal.sendTransaction, tx, value, callback) - }, () => { - return callback('Canceled by user.') - }) - } else { - sendTransaction(executionContext.web3().eth.sendTransaction, tx, null, callback) - } + if (api.personalMode()) { + modal.promptPassphrase(null, 'Personal mode is enabled. Please provide passphrase of account ' + tx.from, '', (value) => { + sendTransaction(executionContext.web3().personal.sendTransaction, tx, value, callback) + }, () => { + return callback('Canceled by user.') + }) + } else { + sendTransaction(executionContext.web3().eth.sendTransaction, tx, null, callback) } +} + +TxRunner.prototype.execute = function (args, callback) { + var self = this - var from = args.from - var to = args.to var data = args.data if (data.slice(0, 2) !== '0x') { data = '0x' + data } - var value = args.value - var gasLimit = args.gasLimit - var tx if (!executionContext.isVM()) { - tx = { - from: from, - to: to, - data: data, - value: value + self.runInNode(args.from, args.to, data, args.value, args.gasLimit, args.useCall, callback) + } else { + try { + self.runInVm(args.from, args.to, data, args.value, args.gasLimit, args.useCall, callback) + } catch (e) { + callback(e, null) } + } +} - if (args.useCall) { - tx.gas = gasLimit - executionContext.web3().eth.call(tx, function (error, result) { - callback(error, { - result: result, - transactionHash: result.transactionHash - }) - }) - } else { - executionContext.web3().eth.estimateGas(tx, function (err, gasEstimation) { - if (err) { - return callback(err, gasEstimation) - } - var blockGasLimit = executionContext.currentblockGasLimit() - // NOTE: estimateGas very likely will return a large limit if execution of the code failed - // we want to be able to run the code in order to debug and find the cause for the failure +TxRunner.prototype.runInVm = function (from, to, data, value, gasLimit, useCall, callback) { + const self = this + var account = self.vmaccounts[from] + if (!account) { + return callback('Invalid account selected') + } + var tx = new EthJSTX({ + nonce: new BN(account.nonce++), + gasPrice: new BN(1), + gasLimit: new BN(gasLimit, 10), + to: to, + value: new BN(value, 10), + data: new Buffer(data.slice(2), 'hex') + }) + tx.sign(account.privateKey) + + const coinbases = [ '0x0e9281e9c6a0808672eaba6bd1220e144c9bb07a', '0x8945a1288dc78a6d8952a92c77aee6730b414778', '0x94d76e24f818426ae84aa404140e8d5f60e10e7e' ] + const difficulties = [ new BN('69762765929000', 10), new BN('70762765929000', 10), new BN('71762765929000', 10) ] + var block = new EthJSBlock({ + header: { + timestamp: new Date().getTime() / 1000 | 0, + number: self.blockNumber, + coinbase: coinbases[self.blockNumber % coinbases.length], + difficulty: difficulties[self.blockNumber % difficulties.length], + gasLimit: new BN(gasLimit, 10).imuln(2) + }, + transactions: [], + uncleHeaders: [] + }) + if (!useCall) { + ++self.blockNumber + } else { + executionContext.vm().stateManager.checkpoint() + } - var warnEstimation = " An important gas estimation might also be the sign of a problem in the contract code. Please check loops and be sure you did not sent value to a non payable function (that's also the reason of strong gas estimation)." - if (gasEstimation > gasLimit) { - return callback('Gas required exceeds limit: ' + gasLimit + '. ' + warnEstimation) - } - if (gasEstimation > blockGasLimit) { - return callback('Gas required exceeds block gas limit: ' + gasLimit + '. ' + warnEstimation) - } + executionContext.vm().runTx({block: block, tx: tx, skipBalance: true, skipNonce: true}, function (err, result) { + if (useCall) { + executionContext.vm().stateManager.revert(function () {}) + } + err = err ? err.message : err + result.status = '0x' + result.vm.exception.toString(16) + callback(err, { + result: result, + transactionHash: ethJSUtil.bufferToHex(new Buffer(tx.hash())) + }) + }) +} - tx.gas = gasEstimation +TxRunner.prototype.runInNode = function (from, to, data, value, gasLimit, useCall, callback) { + const self = this + var tx = { from: from, to: to, data: data, value: value } - if (!self._api.config.getUnpersistedProperty('doNotShowTransactionConfirmationAgain')) { - self._api.detectNetwork((err, network) => { - if (err) { - console.log(err) - } else { - if (network.name === 'Main') { - var content = confirmDialog(tx, gasEstimation, self) - modalDialog('Confirm transaction', content, - { label: 'Confirm', - fn: () => { - self._api.config.setUnpersistedProperty('doNotShowTransactionConfirmationAgain', content.querySelector('input#confirmsetting').checked) - if (!content.gasPriceStatus) { - callback('Given gas grice is not correct') - } else { - var gasPrice = executionContext.web3().toWei(content.querySelector('#gasprice').value, 'gwei') - execute(gasPrice) - } - }}, { - label: 'Cancel', - fn: () => { - return callback('Transaction canceled by user.') - } - }) - } else { - execute() - } - } - }) - } else { - execute() - } + if (useCall) { + tx.gas = gasLimit + return executionContext.web3().eth.call(tx, function (error, result) { + callback(error, { + result: result, + transactionHash: result.transactionHash }) + }) + } + executionContext.web3().eth.estimateGas(tx, function (err, gasEstimation) { + if (err) { + return callback(err, gasEstimation) } - } else { - try { - var account = self.vmaccounts[from] - if (!account) { - return callback('Invalid account selected') - } - tx = new EthJSTX({ - nonce: new BN(account.nonce++), - gasPrice: new BN(1), - gasLimit: new BN(gasLimit, 10), - to: to, - value: new BN(value, 10), - data: new Buffer(data.slice(2), 'hex') - }) - tx.sign(account.privateKey) + var blockGasLimit = executionContext.currentblockGasLimit() + // NOTE: estimateGas very likely will return a large limit if execution of the code failed + // we want to be able to run the code in order to debug and find the cause for the failure - const coinbases = [ '0x0e9281e9c6a0808672eaba6bd1220e144c9bb07a', '0x8945a1288dc78a6d8952a92c77aee6730b414778', '0x94d76e24f818426ae84aa404140e8d5f60e10e7e' ] - const difficulties = [ new BN('69762765929000', 10), new BN('70762765929000', 10), new BN('71762765929000', 10) ] - var block = new EthJSBlock({ - header: { - timestamp: new Date().getTime() / 1000 | 0, - number: self.blockNumber, - coinbase: coinbases[self.blockNumber % coinbases.length], - difficulty: difficulties[self.blockNumber % difficulties.length], - gasLimit: new BN(gasLimit, 10).imuln(2) - }, - transactions: [], - uncleHeaders: [] - }) - if (!args.useCall) { - ++self.blockNumber - } else { - executionContext.vm().stateManager.checkpoint() + var warnEstimation = " An important gas estimation might also be the sign of a problem in the contract code. Please check loops and be sure you did not sent value to a non payable function (that's also the reason of strong gas estimation)." + if (gasEstimation > gasLimit) { + return callback('Gas required exceeds limit: ' + gasLimit + '. ' + warnEstimation) + } + if (gasEstimation > blockGasLimit) { + return callback('Gas required exceeds block gas limit: ' + gasLimit + '. ' + warnEstimation) + } + + tx.gas = gasEstimation + + if (self._api.config.getUnpersistedProperty('doNotShowTransactionConfirmationAgain')) { + return executeTx(tx, null, self._api, callback) + } + self._api.detectNetwork((err, network) => { + if (err) { + console.log(err) + return + } + if (network.name !== 'Main') { + return executeTx(tx, null, self._api, callback) } - executionContext.vm().runTx({block: block, tx: tx, skipBalance: true, skipNonce: true}, function (err, result) { - if (args.useCall) { - executionContext.vm().stateManager.revert(function () {}) + var amount = executionContext.web3().fromWei(typeConversion.toInt(tx.value), 'ether') + var content = confirmDialog(tx, amount, gasEstimation, self, + (gasPrice, cb) => { + let txFeeText, priceStatus + // TODO: this try catch feels like an anti pattern, can/should be + // removed, but for now keeping the original logic + try { + var fee = executionContext.web3().toBigNumber(tx.gas).mul(executionContext.web3().toBigNumber(executionContext.web3().toWei(gasPrice.toString(10), 'gwei'))) + txFeeText = ' ' + executionContext.web3().fromWei(fee.toString(10), 'ether') + ' Ether' + priceStatus = true + } catch (e) { + txFeeText = ' Please fix this issue before sending any transaction. ' + e.message + priceStatus = false + } + cb(priceStatus, txFeeText) + }, + (cb) => { + executionContext.web3().eth.getGasPrice((error, gasPrice) => { + var warnMessage = ' Please fix this issue before sending any transaction. ' + if (error) { + return cb('Unable to retrieve the current network gas price.' + warnMessage + error) + } + try { + var gasPriceValue = executionContext.web3().fromWei(gasPrice.toString(10), 'gwei') + cb(null, gasPriceValue) + } catch (e) { + cb(warnMessage + e.message, null, false) + } + }) } - err = err ? err.message : err - result.status = '0x' + result.vm.exception.toString(16) - callback(err, { - result: result, - transactionHash: ethJSUtil.bufferToHex(new Buffer(tx.hash())) - }) - }) - } catch (e) { - callback(e, null) - } - } + ) + + modalDialog('Confirm transaction', content, + { label: 'Confirm', + fn: () => { + self._api.config.setUnpersistedProperty('doNotShowTransactionConfirmationAgain', content.querySelector('input#confirmsetting').checked) + if (!content.gasPriceStatus) { + return callback('Given gas grice is not correct') + } + var gasPrice = executionContext.web3().toWei(content.querySelector('#gasprice').value, 'gwei') + executeTx(tx, gasPrice, self._api, callback) + }}, { + label: 'Cancel', + fn: () => { + return callback('Transaction canceled by user.') + } + }) + }) + }) } function tryTillResponse (txhash, done) { @@ -234,57 +247,4 @@ function run (self, tx, stamp, callback) { } } -function confirmDialog (tx, gasEstimation, self) { - var amount = executionContext.web3().fromWei(typeConversion.toInt(tx.value), 'ether') - var input = yo`` - var el = yo` -
-
You are creating a transaction on the main network. Click confirm if you are sure to continue.
-
-
From: ${tx.from}
-
To: ${tx.to ? tx.to : '(Contract Creation)'}
-
Amount: ${amount} Ether
-
Gas estimation: ${gasEstimation}
-
Gas limit: ${tx.gas}
-
Gas price: Gwei (visit ethgasstation.info to get more info about gas price)
-
Max transaction fee:
-
Data:
-
${tx.data}
-
-
- ${input} - Do not ask for confirmation again. (the setting will not be persisted for the next page reload) -
-
- ` - - var warnMessage = ' Please fix this issue before sending any transaction. ' - function gasPriceChanged () { - try { - var gasPrice = el.querySelector('#gasprice').value - var fee = executionContext.web3().toBigNumber(tx.gas).mul(executionContext.web3().toBigNumber(executionContext.web3().toWei(gasPrice.toString(10), 'gwei'))) - el.querySelector('#txfee').innerHTML = ' ' + executionContext.web3().fromWei(fee.toString(10), 'ether') + ' Ether' - el.gasPriceStatus = true - } catch (e) { - el.querySelector('#txfee').innerHTML = warnMessage + e.message - el.gasPriceStatus = false - } - } - - executionContext.web3().eth.getGasPrice((error, gasPrice) => { - if (error) { - el.querySelector('#txfee').innerHTML = 'Unable to retrieve the current network gas price.' + warnMessage + error - } else { - try { - el.querySelector('#gasprice').value = executionContext.web3().fromWei(gasPrice.toString(10), 'gwei') - gasPriceChanged() - } catch (e) { - el.querySelector('#txfee').innerHTML = warnMessage + e.message - el.gasPriceStatus = false - } - } - }) - return el -} - module.exports = TxRunner