diff options
author | Dan Finlay <dan@danfinlay.com> | 2017-03-29 02:46:33 +0800 |
---|---|---|
committer | Dan Finlay <dan@danfinlay.com> | 2017-03-29 02:52:15 +0800 |
commit | 81d3658343bbdf8dd85b175f759c372d9ee00fb8 (patch) | |
tree | df3973c062c7c16ce0c965860c6c630f821d7602 | |
parent | 903d3aeb7a3482f9de1d43d5634bdfec59715288 (diff) | |
download | tangerine-wallet-browser-81d3658343bbdf8dd85b175f759c372d9ee00fb8.tar tangerine-wallet-browser-81d3658343bbdf8dd85b175f759c372d9ee00fb8.tar.gz tangerine-wallet-browser-81d3658343bbdf8dd85b175f759c372d9ee00fb8.tar.bz2 tangerine-wallet-browser-81d3658343bbdf8dd85b175f759c372d9ee00fb8.tar.lz tangerine-wallet-browser-81d3658343bbdf8dd85b175f759c372d9ee00fb8.tar.xz tangerine-wallet-browser-81d3658343bbdf8dd85b175f759c372d9ee00fb8.tar.zst tangerine-wallet-browser-81d3658343bbdf8dd85b175f759c372d9ee00fb8.zip |
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.
-rw-r--r-- | app/scripts/lib/hex-to-bn.js | 7 | ||||
-rw-r--r-- | ui/app/actions.js | 1 | ||||
-rw-r--r-- | ui/app/components/pending-tx.js | 135 | ||||
-rw-r--r-- | ui/app/conf-tx.js | 30 |
4 files changed, 46 insertions, 127 deletions
diff --git a/app/scripts/lib/hex-to-bn.js b/app/scripts/lib/hex-to-bn.js new file mode 100644 index 000000000..184217279 --- /dev/null +++ b/app/scripts/lib/hex-to-bn.js @@ -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) +} + diff --git a/ui/app/actions.js b/ui/app/actions.js index 7288db256..19c0e66ad 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -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) diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index 5bb088af9..1b83f5043 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -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) { diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js index 54c171a8a..3b8618992 100644 --- a/ui/app/conf-tx.js +++ b/ui/app/conf-tx.js @@ -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) { |