Version 6.7.2 gas limit fix (#6786)

* Introduce delay for eth_estimateGas calls with in test

* Add test that fails when gas estimates of contract method calls without gas are too high.

* Get transaction gas data from unApprovedTxs instead of confirmTransaction

* Fix selection of gas data in gas-modal-page-container.container

* Lint changes related to Version-6.7.2-gasLimitFix

* Fix e2e tests on Version-6.7.2-gasLimitFix

* Fix unit and integration tests for changes from Version-6.7.2-gasLimitFix

* more e2e fixes

* Add assertions for transaction values on confirm screen

* Fix display of transaction amount on confirm screen.
feature/default_network_editable
Dan J Miller 5 years ago committed by Dan Finlay
parent e768ed9b16
commit 632c9b21e2
  1. 18
      app/scripts/controllers/network/createLocalhostClient.js
  2. 22
      development/states/send-edit.json
  3. 26
      test/e2e/beta/metamask-beta-ui.spec.js
  4. 63
      ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.component.js
  5. 41
      ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.container.js
  6. 23
      ui/app/components/app/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-container.test.js
  7. 24
      ui/app/pages/confirm-token-transaction-base/confirm-token-transaction-base.container.js
  8. 81
      ui/app/pages/confirm-transaction-base/confirm-transaction-base.container.js
  9. 60
      ui/app/selectors/confirm-transaction.js

@ -2,9 +2,12 @@ const mergeMiddleware = require('json-rpc-engine/src/mergeMiddleware')
const createFetchMiddleware = require('eth-json-rpc-middleware/fetch') const createFetchMiddleware = require('eth-json-rpc-middleware/fetch')
const createBlockRefRewriteMiddleware = require('eth-json-rpc-middleware/block-ref-rewrite') const createBlockRefRewriteMiddleware = require('eth-json-rpc-middleware/block-ref-rewrite')
const createBlockTrackerInspectorMiddleware = require('eth-json-rpc-middleware/block-tracker-inspector') const createBlockTrackerInspectorMiddleware = require('eth-json-rpc-middleware/block-tracker-inspector')
const createAsyncMiddleware = require('json-rpc-engine/src/createAsyncMiddleware')
const providerFromMiddleware = require('eth-json-rpc-middleware/providerFromMiddleware') const providerFromMiddleware = require('eth-json-rpc-middleware/providerFromMiddleware')
const BlockTracker = require('eth-block-tracker') const BlockTracker = require('eth-block-tracker')
const inTest = process.env.IN_TEST === 'true'
module.exports = createLocalhostClient module.exports = createLocalhostClient
function createLocalhostClient () { function createLocalhostClient () {
@ -13,9 +16,24 @@ function createLocalhostClient () {
const blockTracker = new BlockTracker({ provider: blockProvider, pollingInterval: 1000 }) const blockTracker = new BlockTracker({ provider: blockProvider, pollingInterval: 1000 })
const networkMiddleware = mergeMiddleware([ const networkMiddleware = mergeMiddleware([
createEstimateGasMiddleware(),
createBlockRefRewriteMiddleware({ blockTracker }), createBlockRefRewriteMiddleware({ blockTracker }),
createBlockTrackerInspectorMiddleware({ blockTracker }), createBlockTrackerInspectorMiddleware({ blockTracker }),
fetchMiddleware, fetchMiddleware,
]) ])
return { networkMiddleware, blockTracker } return { networkMiddleware, blockTracker }
} }
function delay (time) {
return new Promise(resolve => setTimeout(resolve, time))
}
function createEstimateGasMiddleware () {
return createAsyncMiddleware(async (req, _, next) => {
if (req.method === 'eth_estimateGas' && inTest) {
await delay(2000)
}
return next()
})
}

@ -1,4 +1,9 @@
{ {
"confirmTransaction": {
"txData": {
"id": 4768706228115573
}
},
"metamask": { "metamask": {
"completedOnboarding": true, "completedOnboarding": true,
"isInitialized": true, "isInitialized": true,
@ -66,7 +71,22 @@
], ],
"tokens": [], "tokens": [],
"transactions": {}, "transactions": {},
"selectedAddressTxList": [], "selectedAddressTxList": [{
"id": 4768706228115573,
"time": 1487363153561,
"status": "unapproved",
"gasMultiplier": 1,
"metamaskNetworkId": "3",
"txParams": {
"from": "0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb",
"to": "0x2f8d4a878cfa04a6e60d46362f5644deab66572d",
"value": "0x1bc16d674ec80000",
"metamaskId": 4768706228115573,
"metamaskNetworkId": "3",
"gas": "0xea60",
"gasPrice": "0xba43b7400"
}
}],
"unapprovedTxs": { "unapprovedTxs": {
"4768706228115573": { "4768706228115573": {
"id": 4768706228115573, "id": 4768706228115573,

@ -340,7 +340,7 @@ describe('MetaMask', function () {
it('confirms the transaction', async function () { it('confirms the transaction', async function () {
const confirmButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`)) const confirmButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`))
await confirmButton.click() await confirmButton.click()
await delay(largeDelayMs) await delay(largeDelayMs * 2)
}) })
it('finds the transaction in the transactions list', async function () { it('finds the transaction in the transactions list', async function () {
@ -428,6 +428,10 @@ describe('MetaMask', function () {
}) })
it('confirms the transaction', async function () { it('confirms the transaction', async function () {
const transactionAmounts = await findElements(driver, By.css('.currency-display-component__text'))
const transactionAmount = transactionAmounts[0]
assert.equal(await transactionAmount.getText(), '1')
const confirmButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`)) const confirmButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`))
await confirmButton.click() await confirmButton.click()
await delay(largeDelayMs) await delay(largeDelayMs)
@ -528,7 +532,7 @@ describe('MetaMask', function () {
await delay(50) await delay(50)
await gasLimitInput.sendKeys('25000') await gasLimitInput.sendKeys('25000')
await delay(tinyDelayMs) await delay(largeDelayMs * 2)
const confirmButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`), 10000) const confirmButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`), 10000)
await confirmButton.click() await confirmButton.click()
@ -685,11 +689,13 @@ describe('MetaMask', function () {
}) })
it('confirms a transaction', async () => { it('confirms a transaction', async () => {
await delay(tinyDelayMs)
const confirmButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`), 10000) const confirmButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`), 10000)
await confirmButton.click() await confirmButton.click()
await delay(regularDelayMs) await delay(largeDelayMs * 2)
const navigationElement = await findElement(driver, By.css('.confirm-page-container-navigation')) const navigationElement = await findElement(driver, By.css('.confirm-page-container-navigation'))
await delay(tinyDelayMs)
const navigationText = await navigationElement.getText() const navigationText = await navigationElement.getText()
assert.equal(navigationText.includes('4'), true, 'transaction confirmed') assert.equal(navigationText.includes('4'), true, 'transaction confirmed')
}) })
@ -792,7 +798,7 @@ describe('MetaMask', function () {
await driver.wait(until.elementTextMatches(contractStatus, /Deposit\sinitiated/), 10000) await driver.wait(until.elementTextMatches(contractStatus, /Deposit\sinitiated/), 10000)
await driver.switchTo().window(extension) await driver.switchTo().window(extension)
await delay(largeDelayMs) await delay(largeDelayMs * 2)
await findElements(driver, By.css('.transaction-list-item')) await findElements(driver, By.css('.transaction-list-item'))
const [txListValue] = await findElements(driver, By.css('.transaction-list-item__amount--primary')) const [txListValue] = await findElements(driver, By.css('.transaction-list-item__amount--primary'))
@ -812,6 +818,8 @@ describe('MetaMask', function () {
await delay(regularDelayMs) await delay(regularDelayMs)
const [gasPriceInput, gasLimitInput] = await findElements(driver, By.css('.advanced-tab__gas-edit-row__input')) const [gasPriceInput, gasLimitInput] = await findElements(driver, By.css('.advanced-tab__gas-edit-row__input'))
const gasLimitValue = await gasLimitInput.getAttribute('value')
assert(Number(gasLimitValue) < 100000, 'Gas Limit too high')
await gasPriceInput.sendKeys(Key.chord(Key.CONTROL, 'a')) await gasPriceInput.sendKeys(Key.chord(Key.CONTROL, 'a'))
await delay(50) await delay(50)
@ -870,7 +878,7 @@ describe('MetaMask', function () {
await delay(regularDelayMs) await delay(regularDelayMs)
await driver.switchTo().window(extension) await driver.switchTo().window(extension)
await delay(regularDelayMs) await delay(largeDelayMs * 2)
const txListItem = await findElement(driver, By.css('.transaction-list-item')) const txListItem = await findElement(driver, By.css('.transaction-list-item'))
await txListItem.click() await txListItem.click()
@ -1102,6 +1110,10 @@ describe('MetaMask', function () {
await txListValue.click() await txListValue.click()
await delay(regularDelayMs) await delay(regularDelayMs)
const transactionAmounts = await findElements(driver, By.css('.currency-display-component__text'))
const transactionAmount = transactionAmounts[0]
assert(await transactionAmount.getText(), '1.5 TST')
// Set the gas limit // Set the gas limit
const configureGas = await driver.wait(until.elementLocated(By.css('.confirm-detail-row__header-text--edit')), 10000) const configureGas = await driver.wait(until.elementLocated(By.css('.confirm-detail-row__header-text--edit')), 10000)
await configureGas.click() await configureGas.click()
@ -1340,10 +1352,10 @@ describe('MetaMask', function () {
}) })
it('submits the transaction', async function () { it('submits the transaction', async function () {
await delay(regularDelayMs) await delay(largeDelayMs * 2)
const confirmButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`)) const confirmButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`))
await confirmButton.click() await confirmButton.click()
await delay(regularDelayMs) await delay(largeDelayMs * 2)
}) })
it('finds the transaction in the transactions list', async function () { it('finds the transaction in the transactions list', async function () {

@ -8,6 +8,17 @@ export default class AdvancedTabContent extends Component {
t: PropTypes.func, t: PropTypes.func,
} }
constructor (props) {
super(props)
this.state = {
gasPrice: this.props.customGasPrice,
gasLimit: this.props.customGasLimit,
}
this.changeGasPrice = debounce(this.changeGasPrice, 500)
this.changeGasLimit = debounce(this.changeGasLimit, 500)
}
static propTypes = { static propTypes = {
updateCustomGasPrice: PropTypes.func, updateCustomGasPrice: PropTypes.func,
updateCustomGasLimit: PropTypes.func, updateCustomGasLimit: PropTypes.func,
@ -20,15 +31,40 @@ export default class AdvancedTabContent extends Component {
showGasLimitInfoModal: PropTypes.func, showGasLimitInfoModal: PropTypes.func,
} }
debouncedGasLimitReset = debounce((dVal) => { componentDidUpdate (prevProps) {
if (dVal < 21000) { const { customGasPrice: prevCustomGasPrice, customGasLimit: prevCustomGasLimit } = prevProps
const { customGasPrice, customGasLimit } = this.props
const { gasPrice, gasLimit } = this.state
if (customGasPrice !== prevCustomGasPrice && customGasPrice !== gasPrice) {
this.setState({ gasPrice: customGasPrice })
}
if (customGasLimit !== prevCustomGasLimit && customGasLimit !== gasLimit) {
this.setState({ gasLimit: customGasLimit })
}
}
onChangeGasLimit = (e) => {
this.setState({ gasLimit: e.target.value })
this.changeGasLimit({ target: { value: e.target.value } })
}
changeGasLimit = (e) => {
if (e.target.value < 21000) {
this.setState({ gasLimit: 21000 })
this.props.updateCustomGasLimit(21000) this.props.updateCustomGasLimit(21000)
} else {
this.props.updateCustomGasLimit(Number(e.target.value))
} }
}, 1000, { trailing: true }) }
onChangeGasPrice = (e) => {
this.setState({ gasPrice: e.target.value })
this.changeGasPrice({ target: { value: e.target.value } })
}
onChangeGasLimit = (val) => { changeGasPrice = (e) => {
this.props.updateCustomGasLimit(val) this.props.updateCustomGasPrice(Number(e.target.value))
this.debouncedGasLimitReset(val)
} }
gasInputError ({ labelKey, insufficientBalance, customPriceIsSafe, isSpeedUp, value }) { gasInputError ({ labelKey, insufficientBalance, customPriceIsSafe, isSpeedUp, value }) {
@ -74,7 +110,7 @@ export default class AdvancedTabContent extends Component {
})} })}
type="number" type="number"
value={value} value={value}
onChange={event => onChange(Number(event.target.value))} onChange={onChange}
/> />
<div className={classnames('advanced-gas-inputs__gas-edit-row__input-arrows', { <div className={classnames('advanced-gas-inputs__gas-edit-row__input-arrows', {
'advanced-gas-inputs__gas-edit-row__input--error': isInError && errorType === 'error', 'advanced-gas-inputs__gas-edit-row__input--error': isInError && errorType === 'error',
@ -82,13 +118,13 @@ export default class AdvancedTabContent extends Component {
})}> })}>
<div <div
className="advanced-gas-inputs__gas-edit-row__input-arrows__i-wrap" className="advanced-gas-inputs__gas-edit-row__input-arrows__i-wrap"
onClick={() => onChange(value + 1)} onClick={() => onChange({ target: { value: value + 1 } })}
> >
<i className="fa fa-sm fa-angle-up" /> <i className="fa fa-sm fa-angle-up" />
</div> </div>
<div <div
className="advanced-gas-inputs__gas-edit-row__input-arrows__i-wrap" className="advanced-gas-inputs__gas-edit-row__input-arrows__i-wrap"
onClick={() => onChange(Math.max(value - 1, 0))} onClick={() => onChange({ target: { value: Math.max(value - 1, 0) } })}
> >
<i className="fa fa-sm fa-angle-down" /> <i className="fa fa-sm fa-angle-down" />
</div> </div>
@ -120,9 +156,6 @@ export default class AdvancedTabContent extends Component {
render () { render () {
const { const {
customGasPrice,
updateCustomGasPrice,
customGasLimit,
insufficientBalance, insufficientBalance,
customPriceIsSafe, customPriceIsSafe,
isSpeedUp, isSpeedUp,
@ -134,8 +167,8 @@ export default class AdvancedTabContent extends Component {
<div className="advanced-gas-inputs__gas-edit-rows"> <div className="advanced-gas-inputs__gas-edit-rows">
{ this.renderGasEditRow({ { this.renderGasEditRow({
labelKey: 'gasPrice', labelKey: 'gasPrice',
value: customGasPrice, value: this.state.gasPrice,
onChange: updateCustomGasPrice, onChange: this.onChangeGasPrice,
insufficientBalance, insufficientBalance,
customPriceIsSafe, customPriceIsSafe,
showGWEI: true, showGWEI: true,
@ -144,7 +177,7 @@ export default class AdvancedTabContent extends Component {
}) } }) }
{ this.renderGasEditRow({ { this.renderGasEditRow({
labelKey: 'gasLimit', labelKey: 'gasLimit',
value: customGasLimit, value: this.state.gasLimit,
onChange: this.onChangeGasLimit, onChange: this.onChangeGasLimit,
insufficientBalance, insufficientBalance,
customPriceIsSafe, customPriceIsSafe,

@ -9,6 +9,7 @@ import {
hideSidebar, hideSidebar,
updateSendAmount, updateSendAmount,
setGasTotal, setGasTotal,
updateTransaction,
} from '../../../../store/actions' } from '../../../../store/actions'
import { import {
setCustomGasPrice, setCustomGasPrice,
@ -22,9 +23,6 @@ import {
hideGasButtonGroup, hideGasButtonGroup,
updateSendErrors, updateSendErrors,
} from '../../../../ducks/send/send.duck' } from '../../../../ducks/send/send.duck'
import {
updateGasAndCalculate,
} from '../../../../ducks/confirm-transaction/confirm-transaction.duck'
import { import {
conversionRateSelector as getConversionRate, conversionRateSelector as getConversionRate,
getCurrentCurrency, getCurrentCurrency,
@ -51,9 +49,6 @@ import {
import { import {
getTokenBalance, getTokenBalance,
} from '../../../../pages/send/send.selectors' } from '../../../../pages/send/send.selectors'
import {
submittedPendingTransactionsSelector,
} from '../../../../selectors/transactions'
import { import {
formatCurrency, formatCurrency,
} from '../../../../helpers/utils/confirm-tx.util' } from '../../../../helpers/utils/confirm-tx.util'
@ -77,11 +72,16 @@ import { getMaxModeOn } from '../../../../pages/send/send-content/send-amount-ro
import { calcMaxAmount } from '../../../../pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.utils' import { calcMaxAmount } from '../../../../pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.utils'
const mapStateToProps = (state, ownProps) => { const mapStateToProps = (state, ownProps) => {
const { selectedAddressTxList } = state.metamask
const { modalState: { props: modalProps } = {} } = state.appState.modal || {}
const { txData = {} } = modalProps || {}
const { transaction = {} } = ownProps const { transaction = {} } = ownProps
const selectedTransaction = selectedAddressTxList.find(({ id }) => id === (transaction.id || txData.id))
const buttonDataLoading = getBasicGasEstimateLoadingStatus(state) const buttonDataLoading = getBasicGasEstimateLoadingStatus(state)
const gasEstimatesLoading = getGasEstimatesLoadingStatus(state) const gasEstimatesLoading = getGasEstimatesLoadingStatus(state)
const { gasPrice: currentGasPrice, gas: currentGasLimit, value } = getTxParams(state, transaction.id) const { gasPrice: currentGasPrice, gas: currentGasLimit, value } = getTxParams(state, selectedTransaction)
const customModalGasPriceInHex = getCustomGasPrice(state) || currentGasPrice const customModalGasPriceInHex = getCustomGasPrice(state) || currentGasPrice
const customModalGasLimitInHex = getCustomGasLimit(state) || currentGasLimit const customModalGasLimitInHex = getCustomGasLimit(state) || currentGasLimit
const customGasTotal = calcGasTotal(customModalGasLimitInHex, customModalGasPriceInHex) const customGasTotal = calcGasTotal(customModalGasLimitInHex, customModalGasPriceInHex)
@ -118,6 +118,7 @@ const mapStateToProps = (state, ownProps) => {
conversionRate, conversionRate,
}) })
return { return {
hideBasic, hideBasic,
isConfirm: isConfirm(state), isConfirm: isConfirm(state),
@ -151,6 +152,7 @@ const mapStateToProps = (state, ownProps) => {
transactionFee: addHexWEIsToRenderableEth('0x0', customGasTotal), transactionFee: addHexWEIsToRenderableEth('0x0', customGasTotal),
sendAmount, sendAmount,
}, },
transaction: txData || transaction,
isSpeedUp: transaction.status === 'submitted', isSpeedUp: transaction.status === 'submitted',
txId: transaction.id, txId: transaction.id,
insufficientBalance, insufficientBalance,
@ -179,10 +181,10 @@ const mapDispatchToProps = dispatch => {
dispatch(setGasLimit(newLimit)) dispatch(setGasLimit(newLimit))
dispatch(setGasPrice(newPrice)) dispatch(setGasPrice(newPrice))
}, },
updateConfirmTxGasAndCalculate: (gasLimit, gasPrice) => { updateConfirmTxGasAndCalculate: (gasLimit, gasPrice, updatedTx) => {
updateCustomGasPrice(gasPrice) updateCustomGasPrice(gasPrice)
dispatch(setCustomGasLimit(addHexPrefix(gasLimit.toString(16)))) dispatch(setCustomGasLimit(addHexPrefix(gasLimit.toString(16))))
return dispatch(updateGasAndCalculate({ gasLimit, gasPrice })) return dispatch(updateTransaction(updatedTx))
}, },
createSpeedUpTransaction: (txId, gasPrice) => { createSpeedUpTransaction: (txId, gasPrice) => {
return dispatch(createSpeedUpTransaction(txId, gasPrice)) return dispatch(createSpeedUpTransaction(txId, gasPrice))
@ -214,6 +216,7 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
selectedToken, selectedToken,
tokenBalance, tokenBalance,
customGasLimit, customGasLimit,
transaction,
} = stateProps } = stateProps
const { const {
updateCustomGasPrice: dispatchUpdateCustomGasPrice, updateCustomGasPrice: dispatchUpdateCustomGasPrice,
@ -234,7 +237,15 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
...ownProps, ...ownProps,
onSubmit: (gasLimit, gasPrice) => { onSubmit: (gasLimit, gasPrice) => {
if (isConfirm) { if (isConfirm) {
dispatchUpdateConfirmTxGasAndCalculate(gasLimit, gasPrice) const updatedTx = {
...transaction,
txParams: {
...transaction.txParams,
gas: gasLimit,
gasPrice,
},
}
dispatchUpdateConfirmTxGasAndCalculate(gasLimit, gasPrice, updatedTx)
dispatchHideModal() dispatchHideModal()
} else if (isSpeedUp) { } else if (isSpeedUp) {
dispatchCreateSpeedUpTransaction(txId, gasPrice) dispatchCreateSpeedUpTransaction(txId, gasPrice)
@ -282,12 +293,10 @@ function calcCustomGasLimit (customGasLimitInHex) {
return parseInt(customGasLimitInHex, 16) return parseInt(customGasLimitInHex, 16)
} }
function getTxParams (state, transactionId) { function getTxParams (state, selectedTransaction = {}) {
const { confirmTransaction: { txData }, metamask: { send } } = state const { metamask: { send } } = state
const pendingTransactions = submittedPendingTransactionsSelector(state) const { txParams } = selectedTransaction
const pendingTransaction = pendingTransactions.find(({ id }) => id === transactionId) return txParams || {
const { txParams: pendingTxParams } = pendingTransaction || {}
return txData.txParams || pendingTxParams || {
from: send.from, from: send.from,
gas: send.gasLimit || '0x5208', gas: send.gasLimit || '0x5208',
gasPrice: send.gasPrice || getFastPriceEstimateInHexWEI(state, true), gasPrice: send.gasPrice || getFastPriceEstimateInHexWEI(state, true),

@ -63,6 +63,9 @@ describe('gas-modal-page-container container', () => {
modalState: { modalState: {
props: { props: {
hideBasic: true, hideBasic: true,
txData: {
id: 34,
},
}, },
}, },
}, },
@ -82,6 +85,14 @@ describe('gas-modal-page-container container', () => {
provider: { provider: {
type: 'mainnet', type: 'mainnet',
}, },
selectedAddressTxList: [{
id: 34,
txParams: {
gas: '0x1600000',
gasPrice: '0x3200000',
value: '0x640000000000000',
},
}],
}, },
gas: { gas: {
basicEstimates: { basicEstimates: {
@ -152,6 +163,9 @@ describe('gas-modal-page-container container', () => {
maxModeOn: false, maxModeOn: false,
selectedToken: null, selectedToken: null,
tokenBalance: '0x0', tokenBalance: '0x0',
transaction: {
id: 34,
},
} }
const baseMockOwnProps = { transaction: { id: 34 } } const baseMockOwnProps = { transaction: { id: 34 } }
const tests = [ const tests = [
@ -168,7 +182,7 @@ describe('gas-modal-page-container container', () => {
mockOwnProps: Object.assign({}, baseMockOwnProps, { mockOwnProps: Object.assign({}, baseMockOwnProps, {
transaction: { id: 34, status: 'submitted' }, transaction: { id: 34, status: 'submitted' },
}), }),
expectedResult: Object.assign({}, baseExpectedResult, { isSpeedUp: true }), expectedResult: Object.assign({}, baseExpectedResult, { isSpeedUp: true, transaction: { id: 34 } }),
}, },
{ {
mockState: Object.assign({}, baseMockState, { mockState: Object.assign({}, baseMockState, {
@ -317,8 +331,10 @@ describe('gas-modal-page-container container', () => {
it('should dispatch a updateGasAndCalculate action with the correct props', () => { it('should dispatch a updateGasAndCalculate action with the correct props', () => {
mapDispatchToPropsObject.updateConfirmTxGasAndCalculate('ffff', 'aaaa') mapDispatchToPropsObject.updateConfirmTxGasAndCalculate('ffff', 'aaaa')
assert.equal(dispatchSpy.callCount, 3) assert.equal(dispatchSpy.callCount, 3)
assert(confirmTransactionActionSpies.updateGasAndCalculate.calledOnce) assert(actionSpies.setGasPrice.calledOnce)
assert.deepEqual(confirmTransactionActionSpies.updateGasAndCalculate.getCall(0).args[0], { gasLimit: 'ffff', gasPrice: 'aaaa' }) assert(actionSpies.setGasLimit.calledOnce)
assert.equal(actionSpies.setGasLimit.getCall(0).args[0], 'ffff')
assert.equal(actionSpies.setGasPrice.getCall(0).args[0], 'aaaa')
}) })
}) })
@ -337,6 +353,7 @@ describe('gas-modal-page-container container', () => {
}, },
isConfirm: true, isConfirm: true,
someOtherStateProp: 'baz', someOtherStateProp: 'baz',
transaction: {},
} }
dispatchProps = { dispatchProps = {
updateCustomGasPrice: sinon.spy(), updateCustomGasPrice: sinon.spy(),

@ -1,7 +1,10 @@
import { connect } from 'react-redux' import { connect } from 'react-redux'
import { compose } from 'recompose'
import { withRouter } from 'react-router-dom'
import ConfirmTokenTransactionBase from './confirm-token-transaction-base.component' import ConfirmTokenTransactionBase from './confirm-token-transaction-base.component'
import { import {
contractExchangeRateSelector, contractExchangeRateSelector,
transactionFeeSelector,
} from '../../selectors/confirm-transaction' } from '../../selectors/confirm-transaction'
import { tokenSelector } from '../../selectors/tokens' import { tokenSelector } from '../../selectors/tokens'
import { import {
@ -14,15 +17,21 @@ import {
} from '../../helpers/utils/token-util' } from '../../helpers/utils/token-util'
const mapStateToProps = (state) => { const mapStateToProps = (state, ownProps) => {
const { confirmTransaction, metamask: { currentCurrency, conversionRate } } = state const { match: { params = {} } } = ownProps
const { id: paramsTransactionId } = params
const { confirmTransaction, metamask: { currentCurrency, conversionRate, selectedAddressTxList } } = state
const { const {
txData: { txParams: { to: tokenAddress, data } = {} } = {}, txData: { id: transactionId, txParams: { to: tokenAddress, data } = {} } = {},
fiatTransactionTotal,
ethTransactionTotal,
} = confirmTransaction } = confirmTransaction
const transaction = selectedAddressTxList.find(({ id }) => id === (Number(paramsTransactionId) || transactionId)) || {}
const {
ethTransactionTotal,
fiatTransactionTotal,
} = transactionFeeSelector(state, transaction)
const tokens = tokenSelector(state) const tokens = tokenSelector(state)
const currentToken = tokens && tokens.find(({ address }) => tokenAddress === address) const currentToken = tokens && tokens.find(({ address }) => tokenAddress === address)
const { decimals, symbol: tokenSymbol } = currentToken || {} const { decimals, symbol: tokenSymbol } = currentToken || {}
@ -46,4 +55,7 @@ const mapStateToProps = (state) => {
} }
} }
export default connect(mapStateToProps)(ConfirmTokenTransactionBase) export default compose(
withRouter,
connect(mapStateToProps)
)(ConfirmTokenTransactionBase)

@ -6,9 +6,9 @@ import contractMap from 'eth-contract-metadata'
import ConfirmTransactionBase from './confirm-transaction-base.component' import ConfirmTransactionBase from './confirm-transaction-base.component'
import { import {
clearConfirmTransaction, clearConfirmTransaction,
updateGasAndCalculate,
} from '../../ducks/confirm-transaction/confirm-transaction.duck' } from '../../ducks/confirm-transaction/confirm-transaction.duck'
import { clearSend, cancelTx, cancelTxs, updateAndApproveTx, showModal, setMetaMetricsSendCount } from '../../store/actions'
import { clearSend, cancelTx, cancelTxs, updateAndApproveTx, showModal, setMetaMetricsSendCount, updateTransaction } from '../../store/actions'
import { import {
INSUFFICIENT_FUNDS_ERROR_KEY, INSUFFICIENT_FUNDS_ERROR_KEY,
GAS_LIMIT_TOO_LOW_ERROR_KEY, GAS_LIMIT_TOO_LOW_ERROR_KEY,
@ -19,6 +19,7 @@ import { conversionGreaterThan } from '../../helpers/utils/conversion-util'
import { MIN_GAS_LIMIT_DEC } from '../send/send.constants' import { MIN_GAS_LIMIT_DEC } from '../send/send.constants'
import { checksumAddress, addressSlicer, valuesFor } from '../../helpers/utils/util' import { checksumAddress, addressSlicer, valuesFor } from '../../helpers/utils/util'
import { getMetaMaskAccounts, getAdvancedInlineGasShown, preferencesSelector, getIsMainnet, getKnownMethodData } from '../../selectors/selectors' import { getMetaMaskAccounts, getAdvancedInlineGasShown, preferencesSelector, getIsMainnet, getKnownMethodData } from '../../selectors/selectors'
import { transactionFeeSelector } from '../../selectors/confirm-transaction'
const casedContractMap = Object.keys(contractMap).reduce((acc, base) => { const casedContractMap = Object.keys(contractMap).reduce((acc, base) => {
return { return {
@ -32,23 +33,26 @@ const mapStateToProps = (state, ownProps) => {
const { id: paramsTransactionId } = params const { id: paramsTransactionId } = params
const { showFiatInTestnets } = preferencesSelector(state) const { showFiatInTestnets } = preferencesSelector(state)
const isMainnet = getIsMainnet(state) const isMainnet = getIsMainnet(state)
const { confirmTransaction, metamask, gas } = state const { confirmTransaction, metamask } = state
const {
conversionRate,
identities,
currentCurrency,
selectedAddress,
selectedAddressTxList,
assetImages,
network,
unapprovedTxs,
metaMetricsSendCount,
} = metamask
const { const {
ethTransactionAmount,
ethTransactionFee,
ethTransactionTotal,
fiatTransactionAmount,
fiatTransactionFee,
fiatTransactionTotal,
hexTransactionAmount,
hexTransactionFee,
hexTransactionTotal,
tokenData, tokenData,
txData, txData,
tokenProps, tokenProps,
nonce, nonce,
} = confirmTransaction } = confirmTransaction
const { txParams = {}, lastGasPrice, id: transactionId, transactionCategory } = txData const { txParams = {}, lastGasPrice, id: transactionId, transactionCategory } = txData
const transaction = R.find(({ id }) => id === (transactionId || Number(paramsTransactionId)))(selectedAddressTxList) || {}
const { const {
from: fromAddress, from: fromAddress,
to: txParamsToAddress, to: txParamsToAddress,
@ -56,26 +60,10 @@ const mapStateToProps = (state, ownProps) => {
gas: gasLimit, gas: gasLimit,
value: amount, value: amount,
data, data,
} = txParams } = transaction && transaction.txParams || txParams
const accounts = getMetaMaskAccounts(state) const accounts = getMetaMaskAccounts(state)
const {
conversionRate,
identities,
currentCurrency,
selectedAddress,
selectedAddressTxList,
assetImages,
network,
unapprovedTxs,
metaMetricsSendCount,
} = metamask
const assetImage = assetImages[txParamsToAddress] const assetImage = assetImages[txParamsToAddress]
const {
customGasLimit,
customGasPrice,
} = gas
const { balance } = accounts[selectedAddress] const { balance } = accounts[selectedAddress]
const { name: fromName } = identities[selectedAddress] const { name: fromName } = identities[selectedAddress]
const toAddress = propsToAddress || txParamsToAddress const toAddress = propsToAddress || txParamsToAddress
@ -88,9 +76,20 @@ const mapStateToProps = (state, ownProps) => {
) )
const isTxReprice = Boolean(lastGasPrice) const isTxReprice = Boolean(lastGasPrice)
const transaction = R.find(({ id }) => id === (transactionId || Number(paramsTransactionId)))(selectedAddressTxList)
const transactionStatus = transaction ? transaction.status : '' const transactionStatus = transaction ? transaction.status : ''
const {
ethTransactionAmount,
ethTransactionFee,
ethTransactionTotal,
fiatTransactionAmount,
fiatTransactionFee,
fiatTransactionTotal,
hexTransactionAmount,
hexTransactionFee,
hexTransactionTotal,
} = transactionFeeSelector(state, transaction)
if (transaction && transaction.simulationFails) { if (transaction && transaction.simulationFails) {
txData.simulationFails = transaction.simulationFails txData.simulationFails = transaction.simulationFails
} }
@ -125,7 +124,7 @@ const mapStateToProps = (state, ownProps) => {
hexTransactionAmount, hexTransactionAmount,
hexTransactionFee, hexTransactionFee,
hexTransactionTotal, hexTransactionTotal,
txData: Object.keys(txData).length ? txData : transaction || {}, txData: { ...txData, ...transaction },
tokenData, tokenData,
methodData, methodData,
tokenProps, tokenProps,
@ -139,8 +138,8 @@ const mapStateToProps = (state, ownProps) => {
unapprovedTxCount, unapprovedTxCount,
currentNetworkUnapprovedTxs, currentNetworkUnapprovedTxs,
customGas: { customGas: {
gasLimit: customGasLimit || gasLimit, gasLimit,
gasPrice: customGasPrice || gasPrice, gasPrice,
}, },
advancedInlineGasShown: getAdvancedInlineGasShown(state), advancedInlineGasShown: getAdvancedInlineGasShown(state),
insufficientBalance, insufficientBalance,
@ -161,8 +160,8 @@ const mapDispatchToProps = dispatch => {
showCustomizeGasModal: ({ txData, onSubmit, validate }) => { showCustomizeGasModal: ({ txData, onSubmit, validate }) => {
return dispatch(showModal({ name: 'CUSTOMIZE_GAS', txData, onSubmit, validate })) return dispatch(showModal({ name: 'CUSTOMIZE_GAS', txData, onSubmit, validate }))
}, },
updateGasAndCalculate: ({ gasLimit, gasPrice }) => { updateGasAndCalculate: (updatedTx) => {
return dispatch(updateGasAndCalculate({ gasLimit, gasPrice })) return dispatch(updateTransaction(updatedTx))
}, },
showRejectTransactionsConfirmationModal: ({ onSubmit, unapprovedTxCount }) => { showRejectTransactionsConfirmationModal: ({ onSubmit, unapprovedTxCount }) => {
return dispatch(showModal({ name: 'REJECT_TRANSACTIONS', onSubmit, unapprovedTxCount })) return dispatch(showModal({ name: 'REJECT_TRANSACTIONS', onSubmit, unapprovedTxCount }))
@ -239,7 +238,17 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
validate: validateEditGas, validate: validateEditGas,
}), }),
cancelAllTransactions: () => dispatchCancelAllTransactions(valuesFor(unapprovedTxs)), cancelAllTransactions: () => dispatchCancelAllTransactions(valuesFor(unapprovedTxs)),
updateGasAndCalculate: dispatchUpdateGasAndCalculate, updateGasAndCalculate: ({ gasLimit, gasPrice }) => {
const updatedTx = {
...txData,
txParams: {
...txData.txParams,
gas: gasLimit,
gasPrice,
},
}
dispatchUpdateGasAndCalculate(updatedTx)
},
} }
} }

@ -1,7 +1,17 @@
import { createSelector } from 'reselect' import { createSelector } from 'reselect'
import txHelper from '../../lib/tx-helper' import txHelper from '../../lib/tx-helper'
import { calcTokenAmount } from '../helpers/utils/token-util' import { calcTokenAmount } from '../helpers/utils/token-util'
import { roundExponential } from '../helpers/utils/confirm-tx.util' import {
roundExponential,
getValueFromWeiHex,
getHexGasTotal,
getTransactionFee,
addFiat,
addEth,
} from '../helpers/utils/confirm-tx.util'
import {
sumHexes,
} from '../helpers/utils/transactions.util'
const unapprovedTxsSelector = state => state.metamask.unapprovedTxs const unapprovedTxsSelector = state => state.metamask.unapprovedTxs
const unapprovedMsgsSelector = state => state.metamask.unapprovedMsgs const unapprovedMsgsSelector = state => state.metamask.unapprovedMsgs
@ -207,3 +217,51 @@ export const contractExchangeRateSelector = createSelector(
tokenAddressSelector, tokenAddressSelector,
(contractExchangeRates, tokenAddress) => contractExchangeRates[tokenAddress] (contractExchangeRates, tokenAddress) => contractExchangeRates[tokenAddress]
) )
export const transactionFeeSelector = function (state, txData) {
const currentCurrency = currentCurrencySelector(state)
const conversionRate = conversionRateSelector(state)
const nativeCurrency = getNativeCurrency(state)
const { txParams: { value = '0x0', gas: gasLimit = '0x0', gasPrice = '0x0' } = {} } = txData
const fiatTransactionAmount = getValueFromWeiHex({
value, fromCurrency: nativeCurrency, toCurrency: currentCurrency, conversionRate, numberOfDecimals: 2,
})
const ethTransactionAmount = getValueFromWeiHex({
value, fromCurrency: nativeCurrency, toCurrency: nativeCurrency, conversionRate, numberOfDecimals: 6,
})
const hexTransactionFee = getHexGasTotal({ gasLimit, gasPrice })
const fiatTransactionFee = getTransactionFee({
value: hexTransactionFee,
fromCurrency: nativeCurrency,
toCurrency: currentCurrency,
numberOfDecimals: 2,
conversionRate,
})
const ethTransactionFee = getTransactionFee({
value: hexTransactionFee,
fromCurrency: nativeCurrency,
toCurrency: nativeCurrency,
numberOfDecimals: 6,
conversionRate,
})
const fiatTransactionTotal = addFiat(fiatTransactionFee, fiatTransactionAmount)
const ethTransactionTotal = addEth(ethTransactionFee, ethTransactionAmount)
const hexTransactionTotal = sumHexes(value, hexTransactionFee)
return {
hexTransactionAmount: value,
fiatTransactionAmount,
ethTransactionAmount,
hexTransactionFee,
fiatTransactionFee,
ethTransactionFee,
fiatTransactionTotal,
ethTransactionTotal,
hexTransactionTotal,
}
}

Loading…
Cancel
Save