Merge pull request #1100 from ethereum/refactor/txRunner_confirmation_dialog_3

refactor txRunner
pull/3094/head
yann300 7 years ago committed by GitHub
commit 15f06493ef
  1. 66
      src/app/execution/confirmDialog.js
  2. 350
      src/app/execution/txRunner.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`
<div>
<div>You are creating a transaction on the main network. Click confirm if you are sure to continue.</div>
<div class=${css.txInfoBox}>
<div>From: ${tx.from}</div>
<div>To: ${tx.to ? tx.to : '(Contract Creation)'}</div>
<div>Amount: ${amount} Ether</div>
<div>Gas estimation: ${gasEstimation}</div>
<div>Gas limit: ${tx.gas}</div>
<div>Gas price: <input id='gasprice' oninput=${onGasPriceChange} /> Gwei <span> (visit <a target='_blank' href='https://ethgasstation.info'>ethgasstation.info</a> to get more info about gas price)</span></div>
<div>Max transaction fee:<span id='txfee'></span></div>
<div>Data:</div>
<pre class=${css.wrapword}>${tx.data}</pre>
</div>
<div class=${css.checkbox}>
<input id='confirmsetting' type="checkbox">
<i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Do not ask for confirmation again. (the setting will not be persisted for the next page reload)
</div>
</div>
`
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

@ -5,26 +5,10 @@ var ethJSUtil = require('ethereumjs-util')
var BN = ethJSUtil.BN var BN = ethJSUtil.BN
var executionContext = require('../../execution-context') var executionContext = require('../../execution-context')
var modalDialog = require('../ui/modaldialog') 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 modal = require('../ui/modal-dialog-custom')
var typeConversion = require('../../lib/typeConversion')
var css = csjs` var confirmDialog = require('./confirmDialog')
.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 TxRunner (vmaccounts, api) { function TxRunner (vmaccounts, api) {
this._api = api this._api = api
@ -43,150 +27,179 @@ TxRunner.prototype.rawRun = function (args, cb) {
run(this, args, Date.now(), cb) run(this, args, Date.now(), cb)
} }
TxRunner.prototype.execute = function (args, callback) { function executeTx (tx, gasPrice, api, callback) {
var self = this if (gasPrice) tx.gasPrice = executionContext.web3().toHex(gasPrice)
function execute (gasPrice) {
if (gasPrice) tx.gasPrice = executionContext.web3().toHex(gasPrice)
if (self._api.personalMode()) { if (api.personalMode()) {
modal.promptPassphrase(null, 'Personal mode is enabled. Please provide passphrase of account ' + tx.from, '', (value) => { modal.promptPassphrase(null, 'Personal mode is enabled. Please provide passphrase of account ' + tx.from, '', (value) => {
sendTransaction(executionContext.web3().personal.sendTransaction, tx, value, callback) sendTransaction(executionContext.web3().personal.sendTransaction, tx, value, callback)
}, () => { }, () => {
return callback('Canceled by user.') return callback('Canceled by user.')
}) })
} else { } else {
sendTransaction(executionContext.web3().eth.sendTransaction, tx, null, callback) 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 var data = args.data
if (data.slice(0, 2) !== '0x') { if (data.slice(0, 2) !== '0x') {
data = '0x' + data data = '0x' + data
} }
var value = args.value
var gasLimit = args.gasLimit
var tx
if (!executionContext.isVM()) { if (!executionContext.isVM()) {
tx = { self.runInNode(args.from, args.to, data, args.value, args.gasLimit, args.useCall, callback)
from: from, } else {
to: to, try {
data: data, self.runInVm(args.from, args.to, data, args.value, args.gasLimit, args.useCall, callback)
value: value } catch (e) {
callback(e, null)
} }
}
}
if (args.useCall) { TxRunner.prototype.runInVm = function (from, to, data, value, gasLimit, useCall, callback) {
tx.gas = gasLimit const self = this
executionContext.web3().eth.call(tx, function (error, result) { var account = self.vmaccounts[from]
callback(error, { if (!account) {
result: result, return callback('Invalid account selected')
transactionHash: result.transactionHash }
}) var tx = new EthJSTX({
}) nonce: new BN(account.nonce++),
} else { gasPrice: new BN(1),
executionContext.web3().eth.estimateGas(tx, function (err, gasEstimation) { gasLimit: new BN(gasLimit, 10),
if (err) { to: to,
return callback(err, gasEstimation) value: new BN(value, 10),
} data: new Buffer(data.slice(2), 'hex')
var blockGasLimit = executionContext.currentblockGasLimit() })
// NOTE: estimateGas very likely will return a large limit if execution of the code failed tx.sign(account.privateKey)
// 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 (!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)." executionContext.vm().runTx({block: block, tx: tx, skipBalance: true, skipNonce: true}, function (err, result) {
if (gasEstimation > gasLimit) { if (useCall) {
return callback('Gas required exceeds limit: ' + gasLimit + '. ' + warnEstimation) executionContext.vm().stateManager.revert(function () {})
} }
if (gasEstimation > blockGasLimit) { err = err ? err.message : err
return callback('Gas required exceeds block gas limit: ' + gasLimit + '. ' + warnEstimation) 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')) { if (useCall) {
self._api.detectNetwork((err, network) => { tx.gas = gasLimit
if (err) { return executionContext.web3().eth.call(tx, function (error, result) {
console.log(err) callback(error, {
} else { result: result,
if (network.name === 'Main') { transactionHash: result.transactionHash
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()
}
}) })
})
}
executionContext.web3().eth.estimateGas(tx, function (err, gasEstimation) {
if (err) {
return callback(err, gasEstimation)
} }
} else { var blockGasLimit = executionContext.currentblockGasLimit()
try { // NOTE: estimateGas very likely will return a large limit if execution of the code failed
var account = self.vmaccounts[from] // we want to be able to run the code in order to debug and find the cause for the failure
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)
const coinbases = [ '0x0e9281e9c6a0808672eaba6bd1220e144c9bb07a', '0x8945a1288dc78a6d8952a92c77aee6730b414778', '0x94d76e24f818426ae84aa404140e8d5f60e10e7e' ] 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)."
const difficulties = [ new BN('69762765929000', 10), new BN('70762765929000', 10), new BN('71762765929000', 10) ] if (gasEstimation > gasLimit) {
var block = new EthJSBlock({ return callback('Gas required exceeds limit: ' + gasLimit + '. ' + warnEstimation)
header: { }
timestamp: new Date().getTime() / 1000 | 0, if (gasEstimation > blockGasLimit) {
number: self.blockNumber, return callback('Gas required exceeds block gas limit: ' + gasLimit + '. ' + warnEstimation)
coinbase: coinbases[self.blockNumber % coinbases.length], }
difficulty: difficulties[self.blockNumber % difficulties.length],
gasLimit: new BN(gasLimit, 10).imuln(2) tx.gas = gasEstimation
},
transactions: [], if (self._api.config.getUnpersistedProperty('doNotShowTransactionConfirmationAgain')) {
uncleHeaders: [] return executeTx(tx, null, self._api, callback)
}) }
if (!args.useCall) { self._api.detectNetwork((err, network) => {
++self.blockNumber if (err) {
} else { console.log(err)
executionContext.vm().stateManager.checkpoint() 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) { var amount = executionContext.web3().fromWei(typeConversion.toInt(tx.value), 'ether')
if (args.useCall) { var content = confirmDialog(tx, amount, gasEstimation, self,
executionContext.vm().stateManager.revert(function () {}) (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, { modalDialog('Confirm transaction', content,
result: result, { label: 'Confirm',
transactionHash: ethJSUtil.bufferToHex(new Buffer(tx.hash())) fn: () => {
}) self._api.config.setUnpersistedProperty('doNotShowTransactionConfirmationAgain', content.querySelector('input#confirmsetting').checked)
}) if (!content.gasPriceStatus) {
} catch (e) { return callback('Given gas grice is not correct')
callback(e, null) }
} 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) { 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`<input id='confirmsetting' type="checkbox">`
var el = yo`
<div>
<div>You are creating a transaction on the main network. Click confirm if you are sure to continue.</div>
<div class=${css.txInfoBox}>
<div>From: ${tx.from}</div>
<div>To: ${tx.to ? tx.to : '(Contract Creation)'}</div>
<div>Amount: ${amount} Ether</div>
<div>Gas estimation: ${gasEstimation}</div>
<div>Gas limit: ${tx.gas}</div>
<div>Gas price: <input id='gasprice' oninput=${gasPriceChanged} /> Gwei <span> (visit <a target='_blank' href='https://ethgasstation.info'>ethgasstation.info</a> to get more info about gas price)</span></div>
<div>Max transaction fee:<span id='txfee'></span></div>
<div>Data:</div>
<pre class=${css.wrapword}>${tx.data}</pre>
</div>
<div class=${css.checkbox}>
${input}
<i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Do not ask for confirmation again. (the setting will not be persisted for the next page reload)
</div>
</div>
`
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 module.exports = TxRunner

Loading…
Cancel
Save