mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Improve UI gas calculation logic
- Now striping hex prefixed gas values, which may have been causing mis-estimation. - Unified calculation logic to be entirely functional. - Greatly simplified how the pending-tx form keeps updated form state. Still needs a commit from @kumavis to ensure the background passes in a txMeta.txParams.gasPrice value.
This commit is contained in:
parent
903d3aeb7a
commit
81d3658343
7
app/scripts/lib/hex-to-bn.js
Normal file
7
app/scripts/lib/hex-to-bn.js
Normal file
@ -0,0 +1,7 @@
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const BN = ethUtil.BN
|
||||
|
||||
module.exports = function hexToBn (hex) {
|
||||
return new BN(ethUtil.stripHexPrefix(hex), 16)
|
||||
}
|
||||
|
@ -421,6 +421,7 @@ function updateAndApproveTx (txData) {
|
||||
return (dispatch) => {
|
||||
log.debug(`actions calling background.updateAndApproveTx`)
|
||||
background.updateAndApproveTransaction(txData, (err) => {
|
||||
dispatch(actions.hideLoadingIndication())
|
||||
if (err) {
|
||||
dispatch(actions.txError(err))
|
||||
return console.error(err.message)
|
||||
|
@ -2,11 +2,11 @@ const Component = require('react').Component
|
||||
const connect = require('react-redux').connect
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const extend = require('xtend')
|
||||
const actions = require('../actions')
|
||||
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const BN = ethUtil.BN
|
||||
const hexToBn = require('../../../app/scripts/lib/hex-to-bn')
|
||||
|
||||
const MiniAccountPanel = require('./mini-account-panel')
|
||||
const EthBalance = require('./eth-balance')
|
||||
@ -29,42 +29,43 @@ function PendingTx () {
|
||||
Component.call(this)
|
||||
this.state = {
|
||||
valid: true,
|
||||
gas: null,
|
||||
gasPrice: null,
|
||||
txData: null,
|
||||
}
|
||||
}
|
||||
|
||||
PendingTx.prototype.render = function () {
|
||||
const props = this.props
|
||||
const state = this.state
|
||||
|
||||
const txData = state.txData || props.txData
|
||||
const txParams = txData.txParams || {}
|
||||
const txMeta = this.gatherTxMeta()
|
||||
const txParams = txMeta.txParams || {}
|
||||
|
||||
const address = txParams.from || props.selectedAddress
|
||||
const identity = props.identities[address] || { address: address }
|
||||
const account = props.accounts[address]
|
||||
const balance = account ? account.balance : '0x0'
|
||||
|
||||
const gas = state.gas || txParams.gas
|
||||
const gasPrice = state.gasPrice || txData.gasPrice
|
||||
const gasBn = new BN(gas, 16)
|
||||
const gasPriceBn = new BN(gasPrice, 16)
|
||||
const gas = txParams.gas
|
||||
const gasPrice = txParams.gasPrice
|
||||
|
||||
const gasBn = hexToBn(gas)
|
||||
const gasPriceBn = hexToBn(gasPrice)
|
||||
|
||||
const txFeeBn = gasBn.mul(gasPriceBn)
|
||||
const valueBn = new BN(ethUtil.stripHexPrefix(txParams.value), 16)
|
||||
const valueBn = hexToBn(txParams.value)
|
||||
const maxCost = txFeeBn.add(valueBn)
|
||||
|
||||
const dataLength = txParams.data ? (txParams.data.length - 2) / 2 : 0
|
||||
const imageify = props.imageifyIdenticons === undefined ? true : props.imageifyIdenticons
|
||||
|
||||
const balanceBn = hexToBn(balance)
|
||||
const insufficientBalance = balanceBn.lt(maxCost)
|
||||
|
||||
this.inputs = []
|
||||
|
||||
return (
|
||||
|
||||
h('div', {
|
||||
key: txData.id,
|
||||
key: txMeta.id,
|
||||
}, [
|
||||
|
||||
h('form#pending-tx-form', {
|
||||
@ -73,9 +74,8 @@ PendingTx.prototype.render = function () {
|
||||
const form = document.querySelector('form#pending-tx-form')
|
||||
const valid = form.checkValidity()
|
||||
this.setState({ valid })
|
||||
|
||||
if (valid && this.verifyGasParams()) {
|
||||
props.sendTransaction(txData, event)
|
||||
props.sendTransaction(txMeta, event)
|
||||
} else {
|
||||
this.props.dispatch(actions.displayWarning('Invalid Gas Parameters'))
|
||||
}
|
||||
@ -162,7 +162,8 @@ PendingTx.prototype.render = function () {
|
||||
h(HexInput, {
|
||||
name: 'Gas Limit',
|
||||
value: gas,
|
||||
min: MIN_GAS_LIMIT_BN.toString(10), // The hard lower limit for gas.
|
||||
// The hard lower limit for gas.
|
||||
min: MIN_GAS_LIMIT_BN.toString(10),
|
||||
suffix: 'UNITS',
|
||||
style: {
|
||||
position: 'relative',
|
||||
@ -170,7 +171,9 @@ PendingTx.prototype.render = function () {
|
||||
},
|
||||
onChange: (newHex) => {
|
||||
log.info(`Gas limit changed to ${newHex}`)
|
||||
this.setState({ gas: newHex })
|
||||
const txMeta = this.gatherTxMeta()
|
||||
txMeta.txParams.gas = newHex
|
||||
this.setState({ txData: txMeta })
|
||||
},
|
||||
ref: (hexInput) => { this.inputs.push(hexInput) },
|
||||
}),
|
||||
@ -193,7 +196,9 @@ PendingTx.prototype.render = function () {
|
||||
},
|
||||
onChange: (newHex) => {
|
||||
log.info(`Gas price changed to: ${newHex}`)
|
||||
this.setState({ gasPrice: newHex })
|
||||
const txMeta = this.gatherTxMeta()
|
||||
txMeta.txParams.gasPrice = newHex
|
||||
this.setState({ txData: txMeta })
|
||||
},
|
||||
ref: (hexInput) => { this.inputs.push(hexInput) },
|
||||
}),
|
||||
@ -255,7 +260,7 @@ PendingTx.prototype.render = function () {
|
||||
}
|
||||
`),
|
||||
|
||||
txData.simulationFails ?
|
||||
txMeta.simulationFails ?
|
||||
h('.error', {
|
||||
style: {
|
||||
marginLeft: 50,
|
||||
@ -264,7 +269,7 @@ PendingTx.prototype.render = function () {
|
||||
}, 'Transaction Error. Exception thrown in contract code.')
|
||||
: null,
|
||||
|
||||
props.insufficientBalance ?
|
||||
insufficientBalance ?
|
||||
h('span.error', {
|
||||
style: {
|
||||
marginLeft: 50,
|
||||
@ -283,7 +288,7 @@ PendingTx.prototype.render = function () {
|
||||
}, [
|
||||
|
||||
|
||||
props.insufficientBalance ?
|
||||
insufficientBalance ?
|
||||
h('button', {
|
||||
onClick: props.buyEth,
|
||||
}, 'Buy Ether')
|
||||
@ -301,7 +306,7 @@ PendingTx.prototype.render = function () {
|
||||
type: 'submit',
|
||||
value: 'ACCEPT',
|
||||
style: { marginLeft: '10px' },
|
||||
disabled: props.insufficientBalance || !this.state.valid,
|
||||
disabled: insufficientBalance || !this.state.valid,
|
||||
}),
|
||||
|
||||
h('button.cancel.btn-red', {
|
||||
@ -313,10 +318,6 @@ PendingTx.prototype.render = function () {
|
||||
)
|
||||
}
|
||||
|
||||
PendingTx.prototype.validChanged = function (newValid) {
|
||||
this.setState({ valid: newValid })
|
||||
}
|
||||
|
||||
PendingTx.prototype.miniAccountPanelForRecipient = function () {
|
||||
const props = this.props
|
||||
const txData = props.txData
|
||||
@ -358,63 +359,8 @@ PendingTx.prototype.miniAccountPanelForRecipient = function () {
|
||||
}
|
||||
}
|
||||
|
||||
PendingTx.prototype.componentDidUpdate = function (prevProps, previousState) {
|
||||
log.debug(`pending-tx componentDidUpdate`)
|
||||
const state = this.state || {}
|
||||
const prevState = previousState || {}
|
||||
const { gas, gasPrice } = state
|
||||
|
||||
// Only if gas or gasPrice changed:
|
||||
if (!prevState ||
|
||||
(gas !== prevState.gas ||
|
||||
gasPrice !== prevState.gasPrice)) {
|
||||
log.debug(`recalculating gas since prev state change: ${JSON.stringify({ prevState, state })}`)
|
||||
this.calculateGas()
|
||||
}
|
||||
}
|
||||
|
||||
PendingTx.prototype.calculateGas = function () {
|
||||
const state = this.state
|
||||
const props = this.props
|
||||
const txData = props.txData
|
||||
|
||||
const txMeta = this.gatherParams()
|
||||
log.debug(`pending-tx calculating gas for ${JSON.stringify(txMeta)}`)
|
||||
|
||||
const txParams = txMeta.txParams
|
||||
const gasLimit = new BN(ethUtil.stripHexPrefix(txParams.gas || txMeta.estimatedGas), 16)
|
||||
const gasPriceHex = state.gasPrice || txData.gasPrice
|
||||
const gasPrice = new BN(ethUtil.stripHexPrefix(gasPriceHex), 16)
|
||||
|
||||
const valid = !gasPrice.lt(MIN_GAS_PRICE_BN) && !gasLimit.lt(MIN_GAS_LIMIT_BN)
|
||||
this.validChanged(valid)
|
||||
|
||||
const txFee = gasLimit.mul(gasPrice)
|
||||
const txValue = new BN(ethUtil.stripHexPrefix(txParams.value || '0x0'), 16)
|
||||
const maxCost = txValue.add(txFee)
|
||||
|
||||
const txFeeHex = '0x' + txFee.toString('hex')
|
||||
const maxCostHex = '0x' + maxCost.toString('hex')
|
||||
|
||||
txMeta.txFee = txFeeHex
|
||||
txMeta.maxCost = maxCostHex
|
||||
txMeta.txParams.gasPrice = gasPriceHex
|
||||
|
||||
const newState = {
|
||||
txFee: '0x' + txFee.toString('hex'),
|
||||
maxCost: '0x' + maxCost.toString('hex'),
|
||||
}
|
||||
log.info(`tx form updating local state with ${JSON.stringify(newState)}`)
|
||||
this.setState(newState)
|
||||
|
||||
if (this.props.onTxChange) {
|
||||
this.props.onTxChange(txMeta)
|
||||
}
|
||||
}
|
||||
|
||||
PendingTx.prototype.resetGasFields = function () {
|
||||
log.debug(`pending-tx resetGasFields`)
|
||||
const txData = this.props.txData
|
||||
|
||||
this.inputs.forEach((hexInput) => {
|
||||
if (hexInput) {
|
||||
@ -423,36 +369,29 @@ PendingTx.prototype.resetGasFields = function () {
|
||||
})
|
||||
|
||||
this.setState({
|
||||
gas: txData.txParams.gas,
|
||||
gasPrice: txData.gasPrice,
|
||||
txData: null,
|
||||
valid: true,
|
||||
})
|
||||
}
|
||||
|
||||
// After a customizable state value has been updated,
|
||||
PendingTx.prototype.gatherParams = function () {
|
||||
log.debug(`pending-tx gatherParams`)
|
||||
PendingTx.prototype.gatherTxMeta = function () {
|
||||
log.debug(`pending-tx gatherTxMeta`)
|
||||
const props = this.props
|
||||
const state = this.state || {}
|
||||
const state = this.state
|
||||
const txData = state.txData || props.txData
|
||||
const txParams = txData.txParams
|
||||
const gas = state.gas || txParams.gas
|
||||
const gasPrice = state.gasPrice || txParams.gasPrice
|
||||
const resultTx = extend(txParams, {
|
||||
gas,
|
||||
gasPrice,
|
||||
})
|
||||
const resultTxMeta = extend(txData, {
|
||||
txParams: resultTx,
|
||||
})
|
||||
log.debug(`UI has computed tx params ${JSON.stringify(resultTx)}`)
|
||||
return resultTxMeta
|
||||
|
||||
log.debug(`UI has defaulted to tx meta ${JSON.stringify(txData)}`)
|
||||
return txData
|
||||
}
|
||||
|
||||
PendingTx.prototype.verifyGasParams = function () {
|
||||
// We call this in case the gas has not been modified at all
|
||||
if (!this.state) { return true }
|
||||
return this._notZeroOrEmptyString(this.state.gas) && this._notZeroOrEmptyString(this.state.gasPrice)
|
||||
return (
|
||||
this._notZeroOrEmptyString(this.state.gas) &&
|
||||
this._notZeroOrEmptyString(this.state.gasPrice)
|
||||
)
|
||||
}
|
||||
|
||||
PendingTx.prototype._notZeroOrEmptyString = function (obj) {
|
||||
|
@ -7,8 +7,6 @@ const actions = require('./actions')
|
||||
const NetworkIndicator = require('./components/network')
|
||||
const txHelper = require('../lib/tx-helper')
|
||||
const isPopupOrNotification = require('../../app/scripts/lib/is-popup-or-notification')
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const BN = ethUtil.BN
|
||||
|
||||
const PendingTx = require('./components/pending-tx')
|
||||
const PendingMsg = require('./components/pending-msg')
|
||||
@ -104,9 +102,6 @@ ConfirmTxScreen.prototype.render = function () {
|
||||
selectedAddress: props.selectedAddress,
|
||||
accounts: props.accounts,
|
||||
identities: props.identities,
|
||||
insufficientBalance: this.checkBalanceAgainstTx(txData),
|
||||
// State actions
|
||||
onTxChange: this.onTxChange.bind(this),
|
||||
// Actions
|
||||
buyEth: this.buyEth.bind(this, txParams.from || props.selectedAddress),
|
||||
sendTransaction: this.sendTransaction.bind(this, txData),
|
||||
@ -145,37 +140,14 @@ function currentTxView (opts) {
|
||||
}
|
||||
}
|
||||
|
||||
ConfirmTxScreen.prototype.checkBalanceAgainstTx = function (txData) {
|
||||
if (!txData.txParams) return false
|
||||
var props = this.props
|
||||
var address = txData.txParams.from || props.selectedAddress
|
||||
var account = props.accounts[address]
|
||||
var balance = account ? account.balance : '0x0'
|
||||
var maxCost = new BN(txData.maxCost, 16)
|
||||
|
||||
var balanceBn = new BN(ethUtil.stripHexPrefix(balance), 16)
|
||||
return maxCost.gt(balanceBn)
|
||||
}
|
||||
|
||||
ConfirmTxScreen.prototype.buyEth = function (address, event) {
|
||||
this.stopPropagation(event)
|
||||
this.props.dispatch(actions.buyEthView(address))
|
||||
}
|
||||
|
||||
// Allows the detail view to update the gas calculations,
|
||||
// for manual gas controls.
|
||||
ConfirmTxScreen.prototype.onTxChange = function (txData) {
|
||||
log.debug(`conf-tx onTxChange triggered with ${JSON.stringify(txData)}`)
|
||||
this.setState({ txData })
|
||||
}
|
||||
|
||||
// Must default to any local state txData,
|
||||
// to allow manual override of gas calculations.
|
||||
ConfirmTxScreen.prototype.sendTransaction = function (txData, event) {
|
||||
this.stopPropagation(event)
|
||||
const state = this.state || {}
|
||||
const txMeta = state.txData
|
||||
this.props.dispatch(actions.updateAndApproveTx(txMeta || txData))
|
||||
this.props.dispatch(actions.updateAndApproveTx(txData))
|
||||
}
|
||||
|
||||
ConfirmTxScreen.prototype.cancelTransaction = function (txData, event) {
|
||||
|
Loading…
Reference in New Issue
Block a user