Simplify gas estimate actions and add local estimateGasPriceFromRecentBlocks method.

feature/default_network_editable
Dan 7 years ago
parent 17909465f2
commit 166fda5877
  1. 51
      ui/app/actions.js
  2. 6
      ui/app/components/customize-gas-modal/index.js
  3. 6
      ui/app/components/send/currency-display.js
  4. 3
      ui/app/components/send_/send.component.js
  5. 8
      ui/app/components/send_/send.constants.js
  6. 6
      ui/app/components/send_/send.container.js
  7. 13
      ui/app/components/send_/send.selectors.js
  8. 37
      ui/app/components/send_/send.utils.js
  9. 2
      ui/app/components/send_/tests/send-component.test.js
  10. 9
      ui/app/components/send_/tests/send-container.test.js
  11. 1
      ui/app/components/send_/tests/send-selectors-test-data.js
  12. 10
      ui/app/components/send_/tests/send-selectors.test.js
  13. 106
      ui/app/components/send_/tests/send-utils.test.js

@ -6,6 +6,8 @@ const {
calcGasTotal,
getParamsForGasEstimate,
calcTokenBalance,
estimateGas,
estimateGasPriceFromRecentBlocks,
} = require('./components/send_/send.utils')
const ethUtil = require('ethereumjs-util')
const { fetchLocale } = require('../i18n-helper')
@ -160,9 +162,6 @@ var actions = {
updateTransactionParams,
UPDATE_TRANSACTION_PARAMS: 'UPDATE_TRANSACTION_PARAMS',
// send screen
estimateGas,
getGasEstimate,
getGasPrice,
UPDATE_GAS_LIMIT: 'UPDATE_GAS_LIMIT',
UPDATE_GAS_PRICE: 'UPDATE_GAS_PRICE',
UPDATE_GAS_TOTAL: 'UPDATE_GAS_TOTAL',
@ -705,22 +704,6 @@ function signTx (txData) {
}
}
function estimateGas (params = {}) {
return (dispatch) => {
return new Promise((resolve, reject) => {
global.ethQuery.estimateGas(params, (err, data) => {
if (err) {
dispatch(actions.displayWarning(err.message))
return reject(err)
}
dispatch(actions.hideWarning())
dispatch(actions.setGasLimit(data))
return resolve(data)
})
})
}
}
function setGasLimit (gasLimit) {
return {
type: actions.UPDATE_GAS_LIMIT,
@ -728,22 +711,6 @@ function setGasLimit (gasLimit) {
}
}
function getGasPrice () {
return (dispatch) => {
return new Promise((resolve, reject) => {
global.ethQuery.gasPrice((err, data) => {
if (err) {
dispatch(actions.displayWarning(err.message))
return reject(err)
}
dispatch(actions.hideWarning())
dispatch(actions.setGasPrice(data))
return resolve(data)
})
})
}
}
function setGasPrice (gasPrice) {
return {
type: actions.UPDATE_GAS_PRICE,
@ -758,22 +725,18 @@ function setGasTotal (gasTotal) {
}
}
function getGasEstimate ({ selectedAddress, selectedToken, data }) {
function updateGasData ({ recentBlocks, selectedAddress, selectedToken, data }) {
return (dispatch) => {
const estimateGasParams = getParamsForGasEstimate(selectedAddress, selectedToken, data)
return Promise.all([
dispatch(actions.getGasPrice()),
dispatch(actions.estimateGas(estimateGasParams)),
Promise.resolve(estimateGasPriceFromRecentBlocks(recentBlocks)),
estimateGas(estimateGasParams),
])
.then(([gasPrice, gas]) => {
dispatch(actions.setGasPrice(gasPrice))
dispatch(actions.setGasLimit(gas))
return calcGasTotal(gas, gasPrice)
})
}
}
function updateGasData ({ selectedAddress, selectedToken, data }) {
return (dispatch) => {
return dispatch(actions.getGasEstimate({ selectedAddress, selectedToken, data }))
.then((gasEstimate) => {
dispatch(actions.setGasTotal(gasEstimate))
dispatch(updateSendErrors({ gasLoadingError: null }))

@ -67,7 +67,7 @@ function mapDispatchToProps (dispatch) {
hideModal: () => dispatch(actions.hideModal()),
setGasPrice: newGasPrice => dispatch(actions.setGasPrice(newGasPrice)),
setGasLimit: newGasLimit => dispatch(actions.setGasLimit(newGasLimit)),
updateGasData: newGasTotal => dispatch(actions.setGasTotal(newGasTotal)),
setGasTotal: newGasTotal => dispatch(actions.setGasTotal(newGasTotal)),
updateSendAmount: newAmount => dispatch(actions.updateSendAmount(newAmount)),
updateSendErrors: error => dispatch(updateSendErrors(error)),
}
@ -112,7 +112,7 @@ CustomizeGasModal.prototype.save = function (gasPrice, gasLimit, gasTotal) {
setGasPrice,
setGasLimit,
hideModal,
updateGasData,
setGasTotal,
maxModeOn,
selectedToken,
balance,
@ -131,7 +131,7 @@ CustomizeGasModal.prototype.save = function (gasPrice, gasLimit, gasTotal) {
setGasPrice(ethUtil.addHexPrefix(gasPrice))
setGasLimit(ethUtil.addHexPrefix(gasLimit))
updateGasData(ethUtil.addHexPrefix(gasTotal))
setGasTotal(ethUtil.addHexPrefix(gasTotal))
updateSendErrors({ insufficientFunds: false })
hideModal()
}

@ -5,6 +5,7 @@ const CurrencyInput = require('../currency-input')
const { conversionUtil, multiplyCurrencies } = require('../../conversion-util')
const currencyFormatter = require('currency-formatter')
const currencies = require('currency-formatter/currencies')
const ethUtil = require('ethereumjs-util')
module.exports = CurrencyDisplay
@ -35,18 +36,17 @@ CurrencyDisplay.prototype.getAmount = function (value) {
CurrencyDisplay.prototype.getValueToRender = function () {
const { selectedToken, conversionRate, value } = this.props
const { decimals, symbol } = selectedToken || {}
const multiplier = Math.pow(10, Number(decimals || 0))
return selectedToken
? conversionUtil(value, {
? conversionUtil(ethUtil.addHexPrefix(value), {
fromNumericBase: 'hex',
toCurrency: symbol,
conversionRate: multiplier,
invertConversionRate: true,
})
: conversionUtil(value, {
: conversionUtil(ethUtil.addHexPrefix(value), {
fromNumericBase: 'hex',
toNumericBase: 'dec',
fromDenomination: 'WEI',

@ -28,6 +28,7 @@ export default class SendTransactionScreen extends PersistentForm {
history: PropTypes.object,
network: PropTypes.string,
primaryCurrency: PropTypes.string,
recentBlocks: PropTypes.array,
selectedAddress: PropTypes.string,
selectedToken: PropTypes.object,
tokenBalance: PropTypes.string,
@ -43,6 +44,7 @@ export default class SendTransactionScreen extends PersistentForm {
editingTransactionId,
gasLimit,
gasPrice,
recentBlocks,
selectedAddress,
selectedToken = {},
updateAndSetGasTotal,
@ -53,6 +55,7 @@ export default class SendTransactionScreen extends PersistentForm {
editingTransactionId,
gasLimit,
gasPrice,
recentBlocks,
selectedAddress,
selectedToken,
})

@ -28,6 +28,13 @@ const NEGATIVE_ETH_ERROR = 'negativeETH'
const INVALID_RECIPIENT_ADDRESS_ERROR = 'invalidAddressRecipient'
const REQUIRED_ERROR = 'required'
const ONE_GWEI_IN_WEI_HEX = ethUtil.addHexPrefix(conversionUtil('0x1', {
fromDenomination: 'GWEI',
toDenomination: 'WEI',
fromNumericBase: 'hex',
toNumericBase: 'hex',
}))
module.exports = {
INSUFFICIENT_FUNDS_ERROR,
INSUFFICIENT_TOKENS_ERROR,
@ -39,6 +46,7 @@ module.exports = {
MIN_GAS_PRICE_HEX,
MIN_GAS_TOTAL,
NEGATIVE_ETH_ERROR,
ONE_GWEI_IN_WEI_HEX,
REQUIRED_ERROR,
TOKEN_TRANSFER_FUNCTION_SIGNATURE,
}

@ -10,6 +10,7 @@ import {
getGasPrice,
getGasTotal,
getPrimaryCurrency,
getRecentBlocks,
getSelectedAddress,
getSelectedToken,
getSelectedTokenContract,
@ -53,6 +54,7 @@ function mapStateToProps (state) {
gasTotal: getGasTotal(state),
network: getCurrentNetwork(state),
primaryCurrency: getPrimaryCurrency(state),
recentBlocks: getRecentBlocks(state),
selectedAddress: getSelectedAddress(state),
selectedToken: getSelectedToken(state),
tokenBalance: getTokenBalance(state),
@ -68,12 +70,12 @@ function mapDispatchToProps (dispatch) {
editingTransactionId,
gasLimit,
gasPrice,
recentBlocks,
selectedAddress,
selectedToken,
}) => {
console.log(`editingTransactionId`, editingTransactionId)
!editingTransactionId
? dispatch(updateGasData({ selectedAddress, selectedToken, data }))
? dispatch(updateGasData({ recentBlocks, selectedAddress, selectedToken, data }))
: dispatch(setGasTotal(calcGasTotal(gasLimit, gasPrice)))
},
updateSendTokenBalance: ({ selectedToken, tokenContract, address }) => {

@ -3,6 +3,9 @@ const abi = require('human-standard-token-abi')
const {
multiplyCurrencies,
} = require('../../conversion-util')
const {
estimateGasPriceFromRecentBlocks,
} = require('./send.utils')
const selectors = {
accountsWithSendEtherInfoSelector,
@ -18,8 +21,10 @@ const selectors = {
getForceGasMin,
getGasLimit,
getGasPrice,
getGasPriceFromRecentBlocks,
getGasTotal,
getPrimaryCurrency,
getRecentBlocks,
getSelectedAccount,
getSelectedAddress,
getSelectedIdentity,
@ -124,6 +129,10 @@ function getGasPrice (state) {
return state.metamask.send.gasPrice
}
function getGasPriceFromRecentBlocks (state) {
return estimateGasPriceFromRecentBlocks(state.metamask.recentBlocks)
}
function getGasTotal (state) {
return state.metamask.send.gasTotal
}
@ -133,6 +142,10 @@ function getPrimaryCurrency (state) {
return selectedToken && selectedToken.symbol
}
function getRecentBlocks (state) {
return state.metamask.recentBlocks
}
function getSelectedAccount (state) {
const accounts = state.metamask.accounts
const selectedAddress = getSelectedAddress(state)

@ -12,12 +12,15 @@ const {
INSUFFICIENT_FUNDS_ERROR,
INSUFFICIENT_TOKENS_ERROR,
NEGATIVE_ETH_ERROR,
ONE_GWEI_IN_WEI_HEX,
} = require('./send.constants')
const abi = require('ethereumjs-abi')
module.exports = {
calcGasTotal,
doesAmountErrorRequireUpdate,
estimateGas,
estimateGasPriceFromRecentBlocks,
generateTokenTransferData,
getAmountErrorObject,
getParamsForGasEstimate,
@ -179,6 +182,17 @@ function doesAmountErrorRequireUpdate ({
return amountErrorRequiresUpdate
}
function estimateGas (params = {}) {
return new Promise((resolve, reject) => {
global.ethQuery.estimateGas(params, (err, data) => {
if (err) {
return reject(err)
}
return resolve(data)
})
})
}
function generateTokenTransferData (selectedAddress, selectedToken) {
if (!selectedToken) return
console.log(`abi.rawEncode`, abi.rawEncode)
@ -187,3 +201,26 @@ function generateTokenTransferData (selectedAddress, selectedToken) {
x => ('00' + x.toString(16)).slice(-2)
).join('')
}
function hexComparator (a, b) {
return conversionGreaterThan(
{ value: a, fromNumericBase: 'hex' },
{ value: b, fromNumericBase: 'hex' },
) ? 1 : -1
}
function estimateGasPriceFromRecentBlocks (recentBlocks) {
// Return 1 gwei if no blocks have been observed:
if (!recentBlocks || recentBlocks.length === 0) {
return ONE_GWEI_IN_WEI_HEX
}
const lowestPrices = recentBlocks.map((block) => {
if (!block.gasPrices || block.gasPrices.length < 1) {
return ONE_GWEI_IN_WEI_HEX
}
return block.gasPrices
.sort(hexComparator)[0]
})
.sort(hexComparator)
return lowestPrices[Math.floor(lowestPrices.length / 2)]
}

@ -42,6 +42,7 @@ describe.only('Send Component', function () {
history={{ mockProp: 'history-abc'}}
network={'3'}
primaryCurrency={'mockPrimaryCurrency'}
recentBlocks={['mockBlock']}
selectedAddress={'mockSelectedAddress'}
selectedToken={'mockSelectedToken'}
tokenBalance={'mockTokenBalance'}
@ -211,6 +212,7 @@ describe.only('Send Component', function () {
editingTransactionId: 'mockEditingTransactionId',
gasLimit: 'mockGasLimit',
gasPrice: 'mockGasPrice',
recentBlocks: ['mockBlock'],
selectedAddress: 'mockSelectedAddress',
selectedToken: 'mockSelectedToken',
}

@ -32,6 +32,7 @@ proxyquire('../send.container.js', {
getGasPrice: (s) => `mockGasPrice:${s}`,
getGasTotal: (s) => `mockGasTotal:${s}`,
getPrimaryCurrency: (s) => `mockPrimaryCurrency:${s}`,
getRecentBlocks: (s) => `mockRecentBlocks:${s}`,
getSelectedAddress: (s) => `mockSelectedAddress:${s}`,
getSelectedToken: (s) => `mockSelectedToken:${s}`,
getSelectedTokenContract: (s) => `mockTokenContract:${s}`,
@ -66,6 +67,7 @@ describe('send container', () => {
gasTotal: 'mockGasTotal:mockState',
network: 'mockNetwork:mockState',
primaryCurrency: 'mockPrimaryCurrency:mockState',
recentBlocks: 'mockRecentBlocks:mockState',
selectedAddress: 'mockSelectedAddress:mockState',
selectedToken: 'mockSelectedToken:mockState',
tokenBalance: 'mockTokenBalance:mockState',
@ -91,6 +93,7 @@ describe('send container', () => {
editingTransactionId: '0x2',
gasLimit: '0x3',
gasPrice: '0x4',
recentBlocks: ['mockBlock'],
selectedAddress: '0x4',
selectedToken: { address: '0x1' },
}
@ -105,14 +108,14 @@ describe('send container', () => {
})
it('should dispatch an updateGasData action when editingTransactionId is falsy', () => {
const { selectedAddress, selectedToken, data } = mockProps
const { selectedAddress, selectedToken, data, recentBlocks } = mockProps
mapDispatchToPropsObject.updateAndSetGasTotal(
Object.assign(mockProps, {editingTransactionId: false})
Object.assign({}, mockProps, {editingTransactionId: false})
)
assert(dispatchSpy.calledOnce)
assert.deepEqual(
actionSpies.updateGasData.getCall(0).args[0],
{ selectedAddress, selectedToken, data }
{ selectedAddress, selectedToken, data, recentBlocks }
)
})
})

@ -198,6 +198,7 @@ module.exports = {
},
},
'currentLocale': 'en',
recentBlocks: ['mockBlock1', 'mockBlock2', 'mockBlock3'],
},
'appState': {
'menuOpen': false,

@ -17,6 +17,7 @@ const {
getGasPrice,
getGasTotal,
getPrimaryCurrency,
getRecentBlocks,
getSelectedAccount,
getSelectedAddress,
getSelectedIdentity,
@ -239,6 +240,15 @@ describe('send selectors', () => {
})
})
describe('getRecentBlocks()', () => {
it('should return the recent blocks', () => {
assert.deepEqual(
getRecentBlocks(mockState),
['mockBlock1', 'mockBlock2', 'mockBlock3']
)
})
})
describe('getSelectedAccount()', () => {
it('should return the currently selected account', () => {
assert.deepEqual(

@ -1,6 +1,13 @@
import assert from 'assert'
import sinon from 'sinon'
import proxyquire from 'proxyquire'
import {
ONE_GWEI_IN_WEI_HEX,
} from '../send.constants'
const {
addCurrencies,
subtractCurrencies,
} = require('../../../conversion-util')
const {
INSUFFICIENT_FUNDS_ERROR,
@ -31,7 +38,9 @@ const sendUtils = proxyquire('../send.utils.js', {
const {
calcGasTotal,
estimateGas,
doesAmountErrorRequireUpdate,
estimateGasPriceFromRecentBlocks,
generateTokenTransferData,
getAmountErrorObject,
getParamsForGasEstimate,
@ -261,4 +270,101 @@ describe('send utils', () => {
})
})
describe('estimateGas', () => {
let tempEthQuery
beforeEach(() => {
tempEthQuery = global.ethQuery
global.ethQuery = {
estimateGas: sinon.stub().callsFake((data, cb) => {
return cb(
data.isMockErr ? 'mockErr' : null,
Object.assign(data, { estimateGasCalled: true })
)
})
}
})
afterEach(() => {
global.ethQuery = tempEthQuery
})
it('should call ethQuery.estimateGas and resolve that call\'s data', async () => {
const result = await estimateGas({ mockParam: 'someData' })
assert.equal(global.ethQuery.estimateGas.callCount, 1)
assert.deepEqual(
result,
{ mockParam: 'someData', estimateGasCalled: true }
)
})
it('should reject with ethQuery.estimateGas error', async () => {
try {
await estimateGas({ mockParam: 'someData', isMockErr: true })
} catch (err) {
assert.equal(err, 'mockErr')
}
})
})
describe('estimateGasPriceFromRecentBlocks', () => {
const ONE_GWEI_IN_WEI_HEX_PLUS_ONE = addCurrencies(ONE_GWEI_IN_WEI_HEX, '0x1', {
aBase: 16,
bBase: 16,
toNumericBase: 'hex',
})
const ONE_GWEI_IN_WEI_HEX_PLUS_TWO = addCurrencies(ONE_GWEI_IN_WEI_HEX, '0x2', {
aBase: 16,
bBase: 16,
toNumericBase: 'hex',
})
const ONE_GWEI_IN_WEI_HEX_MINUS_ONE = subtractCurrencies(ONE_GWEI_IN_WEI_HEX, '0x1', {
aBase: 16,
bBase: 16,
toNumericBase: 'hex',
})
it(`should return ${ONE_GWEI_IN_WEI_HEX} if recentBlocks is falsy`, () => {
assert.equal(estimateGasPriceFromRecentBlocks(), ONE_GWEI_IN_WEI_HEX)
})
it(`should return ${ONE_GWEI_IN_WEI_HEX} if recentBlocks is empty`, () => {
assert.equal(estimateGasPriceFromRecentBlocks([]), ONE_GWEI_IN_WEI_HEX)
})
it(`should estimate a block's gasPrice as ${ONE_GWEI_IN_WEI_HEX} if it has no gas prices`, () => {
const mockRecentBlocks = [
{ gasPrices: null },
{ gasPrices: [ ONE_GWEI_IN_WEI_HEX_PLUS_ONE ] },
{ gasPrices: [ ONE_GWEI_IN_WEI_HEX_MINUS_ONE ] },
]
assert.equal(estimateGasPriceFromRecentBlocks(mockRecentBlocks), ONE_GWEI_IN_WEI_HEX)
})
it(`should estimate a block's gasPrice as ${ONE_GWEI_IN_WEI_HEX} if it has empty gas prices`, () => {
const mockRecentBlocks = [
{ gasPrices: [] },
{ gasPrices: [ ONE_GWEI_IN_WEI_HEX_PLUS_ONE ] },
{ gasPrices: [ ONE_GWEI_IN_WEI_HEX_MINUS_ONE ] },
]
assert.equal(estimateGasPriceFromRecentBlocks(mockRecentBlocks), ONE_GWEI_IN_WEI_HEX)
})
it(`should return the middle value of all blocks lowest prices`, () => {
const mockRecentBlocks = [
{ gasPrices: [ ONE_GWEI_IN_WEI_HEX_PLUS_TWO ] },
{ gasPrices: [ ONE_GWEI_IN_WEI_HEX_MINUS_ONE ] },
{ gasPrices: [ ONE_GWEI_IN_WEI_HEX_PLUS_ONE ] },
]
assert.equal(estimateGasPriceFromRecentBlocks(mockRecentBlocks), ONE_GWEI_IN_WEI_HEX_PLUS_ONE)
})
it(`should work if a block has multiple gas prices`, () => {
const mockRecentBlocks = [
{ gasPrices: [ '0x1', '0x2', '0x3', '0x4', '0x5' ] },
{ gasPrices: [ '0x101', '0x100', '0x103', '0x104', '0x102' ] },
{ gasPrices: [ '0x150', '0x50', '0x100', '0x200', '0x5' ] },
]
assert.equal(estimateGasPriceFromRecentBlocks(mockRecentBlocks), '0x5')
})
})
})

Loading…
Cancel
Save