mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Merge pull request #4350 from MetaMask/i3914-fix-newui-send-gas-estimation
NewUI gas estimation produces same results as old-ui (exception: contract addresses)
This commit is contained in:
commit
139f930185
@ -384,6 +384,8 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
updateAndApproveTransaction: nodeify(txController.updateAndApproveTransaction, txController),
|
updateAndApproveTransaction: nodeify(txController.updateAndApproveTransaction, txController),
|
||||||
retryTransaction: nodeify(this.retryTransaction, this),
|
retryTransaction: nodeify(this.retryTransaction, this),
|
||||||
getFilteredTxList: nodeify(txController.getFilteredTxList, txController),
|
getFilteredTxList: nodeify(txController.getFilteredTxList, txController),
|
||||||
|
isNonceTaken: nodeify(txController.isNonceTaken, txController),
|
||||||
|
estimateGas: nodeify(this.estimateGas, this),
|
||||||
|
|
||||||
// messageManager
|
// messageManager
|
||||||
signMessage: nodeify(this.signMessage, this),
|
signMessage: nodeify(this.signMessage, this),
|
||||||
@ -922,6 +924,18 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
|
estimateGas (estimateGasParams) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
return this.txController.txGasUtil.query.estimateGas(estimateGasParams, (err, res) => {
|
||||||
|
if (err) {
|
||||||
|
return reject(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolve(res)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
// PASSWORD MANAGEMENT
|
// PASSWORD MANAGEMENT
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
|
@ -117,7 +117,7 @@ async function runSendFlowTest(assert, done) {
|
|||||||
const sendGasField = await queryAsync($, '.send-v2__gas-fee-display')
|
const sendGasField = await queryAsync($, '.send-v2__gas-fee-display')
|
||||||
assert.equal(
|
assert.equal(
|
||||||
sendGasField.find('.currency-display__input-wrapper > input').val(),
|
sendGasField.find('.currency-display__input-wrapper > input').val(),
|
||||||
'0.000198',
|
'0.000198264',
|
||||||
'send gas field should show estimated gas total'
|
'send gas field should show estimated gas total'
|
||||||
)
|
)
|
||||||
assert.equal(
|
assert.equal(
|
||||||
|
@ -4,8 +4,9 @@ const getBuyEthUrl = require('../../app/scripts/lib/buy-eth-url')
|
|||||||
const { getTokenAddressFromTokenObject } = require('./util')
|
const { getTokenAddressFromTokenObject } = require('./util')
|
||||||
const {
|
const {
|
||||||
calcGasTotal,
|
calcGasTotal,
|
||||||
getParamsForGasEstimate,
|
|
||||||
calcTokenBalance,
|
calcTokenBalance,
|
||||||
|
estimateGas,
|
||||||
|
estimateGasPriceFromRecentBlocks,
|
||||||
} = require('./components/send_/send.utils')
|
} = require('./components/send_/send.utils')
|
||||||
const ethUtil = require('ethereumjs-util')
|
const ethUtil = require('ethereumjs-util')
|
||||||
const { fetchLocale } = require('../i18n-helper')
|
const { fetchLocale } = require('../i18n-helper')
|
||||||
@ -160,8 +161,6 @@ var actions = {
|
|||||||
updateTransactionParams,
|
updateTransactionParams,
|
||||||
UPDATE_TRANSACTION_PARAMS: 'UPDATE_TRANSACTION_PARAMS',
|
UPDATE_TRANSACTION_PARAMS: 'UPDATE_TRANSACTION_PARAMS',
|
||||||
// send screen
|
// send screen
|
||||||
estimateGas,
|
|
||||||
getGasPrice,
|
|
||||||
UPDATE_GAS_LIMIT: 'UPDATE_GAS_LIMIT',
|
UPDATE_GAS_LIMIT: 'UPDATE_GAS_LIMIT',
|
||||||
UPDATE_GAS_PRICE: 'UPDATE_GAS_PRICE',
|
UPDATE_GAS_PRICE: 'UPDATE_GAS_PRICE',
|
||||||
UPDATE_GAS_TOTAL: 'UPDATE_GAS_TOTAL',
|
UPDATE_GAS_TOTAL: 'UPDATE_GAS_TOTAL',
|
||||||
@ -176,9 +175,9 @@ var actions = {
|
|||||||
CLEAR_SEND: 'CLEAR_SEND',
|
CLEAR_SEND: 'CLEAR_SEND',
|
||||||
OPEN_FROM_DROPDOWN: 'OPEN_FROM_DROPDOWN',
|
OPEN_FROM_DROPDOWN: 'OPEN_FROM_DROPDOWN',
|
||||||
CLOSE_FROM_DROPDOWN: 'CLOSE_FROM_DROPDOWN',
|
CLOSE_FROM_DROPDOWN: 'CLOSE_FROM_DROPDOWN',
|
||||||
updateGasLimit,
|
setGasLimit,
|
||||||
updateGasPrice,
|
setGasPrice,
|
||||||
updateGasTotal,
|
updateGasData,
|
||||||
setGasTotal,
|
setGasTotal,
|
||||||
setSendTokenBalance,
|
setSendTokenBalance,
|
||||||
updateSendTokenBalance,
|
updateSendTokenBalance,
|
||||||
@ -710,46 +709,14 @@ function signTx (txData) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function estimateGas (params = {}) {
|
function setGasLimit (gasLimit) {
|
||||||
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.updateGasLimit(data))
|
|
||||||
return resolve(data)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateGasLimit (gasLimit) {
|
|
||||||
return {
|
return {
|
||||||
type: actions.UPDATE_GAS_LIMIT,
|
type: actions.UPDATE_GAS_LIMIT,
|
||||||
value: gasLimit,
|
value: gasLimit,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getGasPrice () {
|
function setGasPrice (gasPrice) {
|
||||||
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.updateGasPrice(data))
|
|
||||||
return resolve(data)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateGasPrice (gasPrice) {
|
|
||||||
return {
|
return {
|
||||||
type: actions.UPDATE_GAS_PRICE,
|
type: actions.UPDATE_GAS_PRICE,
|
||||||
value: gasPrice,
|
value: gasPrice,
|
||||||
@ -763,17 +730,35 @@ function setGasTotal (gasTotal) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateGasTotal ({ selectedAddress, selectedToken, data }) {
|
function updateGasData ({
|
||||||
|
blockGasLimit,
|
||||||
|
recentBlocks,
|
||||||
|
selectedAddress,
|
||||||
|
selectedToken,
|
||||||
|
to,
|
||||||
|
value,
|
||||||
|
}) {
|
||||||
|
const estimatedGasPrice = estimateGasPriceFromRecentBlocks(recentBlocks)
|
||||||
return (dispatch) => {
|
return (dispatch) => {
|
||||||
const { symbol } = selectedToken || {}
|
|
||||||
const estimateGasParams = getParamsForGasEstimate(selectedAddress, symbol, data)
|
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
dispatch(actions.getGasPrice()),
|
Promise.resolve(estimatedGasPrice),
|
||||||
dispatch(actions.estimateGas(estimateGasParams)),
|
estimateGas({
|
||||||
|
estimateGasMethod: background.estimateGas,
|
||||||
|
blockGasLimit,
|
||||||
|
selectedAddress,
|
||||||
|
selectedToken,
|
||||||
|
to,
|
||||||
|
value,
|
||||||
|
gasPrice: estimatedGasPrice,
|
||||||
|
}),
|
||||||
])
|
])
|
||||||
.then(([gasPrice, gas]) => {
|
.then(([gasPrice, gas]) => {
|
||||||
const newGasTotal = calcGasTotal(gas, gasPrice)
|
dispatch(actions.setGasPrice(gasPrice))
|
||||||
dispatch(actions.setGasTotal(newGasTotal))
|
dispatch(actions.setGasLimit(gas))
|
||||||
|
return calcGasTotal(gas, gasPrice)
|
||||||
|
})
|
||||||
|
.then((gasEstimate) => {
|
||||||
|
dispatch(actions.setGasTotal(gasEstimate))
|
||||||
dispatch(updateSendErrors({ gasLoadingError: null }))
|
dispatch(updateSendErrors({ gasLoadingError: null }))
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
|
@ -65,9 +65,9 @@ function mapStateToProps (state) {
|
|||||||
function mapDispatchToProps (dispatch) {
|
function mapDispatchToProps (dispatch) {
|
||||||
return {
|
return {
|
||||||
hideModal: () => dispatch(actions.hideModal()),
|
hideModal: () => dispatch(actions.hideModal()),
|
||||||
updateGasPrice: newGasPrice => dispatch(actions.updateGasPrice(newGasPrice)),
|
setGasPrice: newGasPrice => dispatch(actions.setGasPrice(newGasPrice)),
|
||||||
updateGasLimit: newGasLimit => dispatch(actions.updateGasLimit(newGasLimit)),
|
setGasLimit: newGasLimit => dispatch(actions.setGasLimit(newGasLimit)),
|
||||||
updateGasTotal: newGasTotal => dispatch(actions.setGasTotal(newGasTotal)),
|
setGasTotal: newGasTotal => dispatch(actions.setGasTotal(newGasTotal)),
|
||||||
updateSendAmount: newAmount => dispatch(actions.updateSendAmount(newAmount)),
|
updateSendAmount: newAmount => dispatch(actions.updateSendAmount(newAmount)),
|
||||||
updateSendErrors: error => dispatch(updateSendErrors(error)),
|
updateSendErrors: error => dispatch(updateSendErrors(error)),
|
||||||
}
|
}
|
||||||
@ -109,10 +109,10 @@ module.exports = connect(mapStateToProps, mapDispatchToProps)(CustomizeGasModal)
|
|||||||
|
|
||||||
CustomizeGasModal.prototype.save = function (gasPrice, gasLimit, gasTotal) {
|
CustomizeGasModal.prototype.save = function (gasPrice, gasLimit, gasTotal) {
|
||||||
const {
|
const {
|
||||||
updateGasPrice,
|
setGasPrice,
|
||||||
updateGasLimit,
|
setGasLimit,
|
||||||
hideModal,
|
hideModal,
|
||||||
updateGasTotal,
|
setGasTotal,
|
||||||
maxModeOn,
|
maxModeOn,
|
||||||
selectedToken,
|
selectedToken,
|
||||||
balance,
|
balance,
|
||||||
@ -129,9 +129,9 @@ CustomizeGasModal.prototype.save = function (gasPrice, gasLimit, gasTotal) {
|
|||||||
updateSendAmount(maxAmount)
|
updateSendAmount(maxAmount)
|
||||||
}
|
}
|
||||||
|
|
||||||
updateGasPrice(ethUtil.addHexPrefix(gasPrice))
|
setGasPrice(ethUtil.addHexPrefix(gasPrice))
|
||||||
updateGasLimit(ethUtil.addHexPrefix(gasLimit))
|
setGasLimit(ethUtil.addHexPrefix(gasLimit))
|
||||||
updateGasTotal(ethUtil.addHexPrefix(gasTotal))
|
setGasTotal(ethUtil.addHexPrefix(gasTotal))
|
||||||
updateSendErrors({ insufficientFunds: false })
|
updateSendErrors({ insufficientFunds: false })
|
||||||
hideModal()
|
hideModal()
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ const inherits = require('util').inherits
|
|||||||
const { conversionUtil, multiplyCurrencies } = require('../../conversion-util')
|
const { conversionUtil, multiplyCurrencies } = require('../../conversion-util')
|
||||||
const currencyFormatter = require('currency-formatter')
|
const currencyFormatter = require('currency-formatter')
|
||||||
const currencies = require('currency-formatter/currencies')
|
const currencies = require('currency-formatter/currencies')
|
||||||
|
const ethUtil = require('ethereumjs-util')
|
||||||
|
|
||||||
module.exports = CurrencyDisplay
|
module.exports = CurrencyDisplay
|
||||||
|
|
||||||
@ -48,23 +49,23 @@ CurrencyDisplay.prototype.getAmount = function (value) {
|
|||||||
: toHexWei(value)
|
: toHexWei(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
CurrencyDisplay.prototype.getValueToRender = function ({ selectedToken, conversionRate, value }) {
|
CurrencyDisplay.prototype.getValueToRender = function ({ selectedToken, conversionRate, value, readOnly }) {
|
||||||
if (value === '0x0') return '0'
|
if (value === '0x0') return readOnly ? '0' : ''
|
||||||
const { decimals, symbol } = selectedToken || {}
|
const { decimals, symbol } = selectedToken || {}
|
||||||
const multiplier = Math.pow(10, Number(decimals || 0))
|
const multiplier = Math.pow(10, Number(decimals || 0))
|
||||||
|
|
||||||
return selectedToken
|
return selectedToken
|
||||||
? conversionUtil(value, {
|
? conversionUtil(ethUtil.addHexPrefix(value), {
|
||||||
fromNumericBase: 'hex',
|
fromNumericBase: 'hex',
|
||||||
toCurrency: symbol,
|
toCurrency: symbol,
|
||||||
conversionRate: multiplier,
|
conversionRate: multiplier,
|
||||||
invertConversionRate: true,
|
invertConversionRate: true,
|
||||||
})
|
})
|
||||||
: conversionUtil(value, {
|
: conversionUtil(ethUtil.addHexPrefix(value), {
|
||||||
fromNumericBase: 'hex',
|
fromNumericBase: 'hex',
|
||||||
toNumericBase: 'dec',
|
toNumericBase: 'dec',
|
||||||
fromDenomination: 'WEI',
|
fromDenomination: 'WEI',
|
||||||
numberOfDecimals: 6,
|
numberOfDecimals: 9,
|
||||||
conversionRate,
|
conversionRate,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
import PageContainerContent from '../../page-container/page-container-content.component'
|
import PageContainerContent from '../../page-container/page-container-content.component'
|
||||||
import SendAmountRow from './send-amount-row/'
|
import SendAmountRow from './send-amount-row/'
|
||||||
import SendFromRow from './send-from-row/'
|
import SendFromRow from './send-from-row/'
|
||||||
@ -7,12 +8,16 @@ import SendToRow from './send-to-row/'
|
|||||||
|
|
||||||
export default class SendContent extends Component {
|
export default class SendContent extends Component {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
updateGas: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
return (
|
return (
|
||||||
<PageContainerContent>
|
<PageContainerContent>
|
||||||
<div className="send-v2__form">
|
<div className="send-v2__form">
|
||||||
<SendFromRow />
|
<SendFromRow />
|
||||||
<SendToRow />
|
<SendToRow updateGas={(updateData) => this.props.updateGas(updateData)} />
|
||||||
<SendAmountRow />
|
<SendAmountRow />
|
||||||
<SendGasRow />
|
<SendGasRow />
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,6 +2,7 @@ import React, { Component } from 'react'
|
|||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import SendRowWrapper from '../send-row-wrapper/'
|
import SendRowWrapper from '../send-row-wrapper/'
|
||||||
import EnsInput from '../../../ens-input'
|
import EnsInput from '../../../ens-input'
|
||||||
|
import { getToErrorObject } from './send-to-row.utils.js'
|
||||||
|
|
||||||
export default class SendToRow extends Component {
|
export default class SendToRow extends Component {
|
||||||
|
|
||||||
@ -13,14 +14,19 @@ export default class SendToRow extends Component {
|
|||||||
to: PropTypes.string,
|
to: PropTypes.string,
|
||||||
toAccounts: PropTypes.array,
|
toAccounts: PropTypes.array,
|
||||||
toDropdownOpen: PropTypes.bool,
|
toDropdownOpen: PropTypes.bool,
|
||||||
|
updateGas: PropTypes.func,
|
||||||
updateSendTo: PropTypes.func,
|
updateSendTo: PropTypes.func,
|
||||||
updateSendToError: PropTypes.func,
|
updateSendToError: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
handleToChange (to, nickname = '') {
|
handleToChange (to, nickname = '') {
|
||||||
const { updateSendTo, updateSendToError } = this.props
|
const { updateSendTo, updateSendToError, updateGas } = this.props
|
||||||
|
const toErrorObject = getToErrorObject(to)
|
||||||
updateSendTo(to, nickname)
|
updateSendTo(to, nickname)
|
||||||
updateSendToError(to)
|
updateSendToError(toErrorObject)
|
||||||
|
if (toErrorObject.to === null) {
|
||||||
|
updateGas({ to })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
|
@ -8,7 +8,6 @@ import {
|
|||||||
getToDropdownOpen,
|
getToDropdownOpen,
|
||||||
sendToIsInError,
|
sendToIsInError,
|
||||||
} from './send-to-row.selectors.js'
|
} from './send-to-row.selectors.js'
|
||||||
import { getToErrorObject } from './send-to-row.utils.js'
|
|
||||||
import {
|
import {
|
||||||
updateSendTo,
|
updateSendTo,
|
||||||
} from '../../../../actions'
|
} from '../../../../actions'
|
||||||
@ -36,8 +35,8 @@ function mapDispatchToProps (dispatch) {
|
|||||||
closeToDropdown: () => dispatch(closeToDropdown()),
|
closeToDropdown: () => dispatch(closeToDropdown()),
|
||||||
openToDropdown: () => dispatch(openToDropdown()),
|
openToDropdown: () => dispatch(openToDropdown()),
|
||||||
updateSendTo: (to, nickname) => dispatch(updateSendTo(to, nickname)),
|
updateSendTo: (to, nickname) => dispatch(updateSendTo(to, nickname)),
|
||||||
updateSendToError: (to) => {
|
updateSendToError: (toErrorObject) => {
|
||||||
dispatch(updateSendErrors(getToErrorObject(to)))
|
dispatch(updateSendErrors(toErrorObject))
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,9 +8,9 @@ function getToErrorObject (to) {
|
|||||||
let toError = null
|
let toError = null
|
||||||
|
|
||||||
if (!to) {
|
if (!to) {
|
||||||
toError = REQUIRED_ERROR
|
toError = REQUIRED_ERROR
|
||||||
} else if (!isValidAddress(to)) {
|
} else if (!isValidAddress(to)) {
|
||||||
toError = INVALID_RECIPIENT_ADDRESS_ERROR
|
toError = INVALID_RECIPIENT_ADDRESS_ERROR
|
||||||
}
|
}
|
||||||
|
|
||||||
return { to: toError }
|
return { to: toError }
|
||||||
|
@ -2,7 +2,15 @@ import React from 'react'
|
|||||||
import assert from 'assert'
|
import assert from 'assert'
|
||||||
import { shallow } from 'enzyme'
|
import { shallow } from 'enzyme'
|
||||||
import sinon from 'sinon'
|
import sinon from 'sinon'
|
||||||
import SendToRow from '../send-to-row.component.js'
|
import proxyquire from 'proxyquire'
|
||||||
|
|
||||||
|
const SendToRow = proxyquire('../send-to-row.component.js', {
|
||||||
|
'./send-to-row.utils.js': {
|
||||||
|
getToErrorObject: (to) => ({
|
||||||
|
to: to === false ? null : `mockToErrorObject:${to}`,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}).default
|
||||||
|
|
||||||
import SendRowWrapper from '../../send-row-wrapper/send-row-wrapper.component'
|
import SendRowWrapper from '../../send-row-wrapper/send-row-wrapper.component'
|
||||||
import EnsInput from '../../../../ens-input'
|
import EnsInput from '../../../../ens-input'
|
||||||
@ -10,6 +18,7 @@ import EnsInput from '../../../../ens-input'
|
|||||||
const propsMethodSpies = {
|
const propsMethodSpies = {
|
||||||
closeToDropdown: sinon.spy(),
|
closeToDropdown: sinon.spy(),
|
||||||
openToDropdown: sinon.spy(),
|
openToDropdown: sinon.spy(),
|
||||||
|
updateGas: sinon.spy(),
|
||||||
updateSendTo: sinon.spy(),
|
updateSendTo: sinon.spy(),
|
||||||
updateSendToError: sinon.spy(),
|
updateSendToError: sinon.spy(),
|
||||||
}
|
}
|
||||||
@ -29,6 +38,7 @@ describe('SendToRow Component', function () {
|
|||||||
to={'mockTo'}
|
to={'mockTo'}
|
||||||
toAccounts={['mockAccount']}
|
toAccounts={['mockAccount']}
|
||||||
toDropdownOpen={false}
|
toDropdownOpen={false}
|
||||||
|
updateGas={propsMethodSpies.updateGas}
|
||||||
updateSendTo={propsMethodSpies.updateSendTo}
|
updateSendTo={propsMethodSpies.updateSendTo}
|
||||||
updateSendToError={propsMethodSpies.updateSendToError}
|
updateSendToError={propsMethodSpies.updateSendToError}
|
||||||
/>, { context: { t: str => str + '_t' } })
|
/>, { context: { t: str => str + '_t' } })
|
||||||
@ -61,10 +71,21 @@ describe('SendToRow Component', function () {
|
|||||||
assert.equal(propsMethodSpies.updateSendToError.callCount, 1)
|
assert.equal(propsMethodSpies.updateSendToError.callCount, 1)
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
propsMethodSpies.updateSendToError.getCall(0).args,
|
propsMethodSpies.updateSendToError.getCall(0).args,
|
||||||
['mockTo2']
|
[{ to: 'mockToErrorObject:mockTo2' }]
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should not call updateGas if there is a to error', () => {
|
||||||
|
assert.equal(propsMethodSpies.updateGas.callCount, 0)
|
||||||
|
instance.handleToChange('mockTo2')
|
||||||
|
assert.equal(propsMethodSpies.updateGas.callCount, 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should call updateGas if there is no to error', () => {
|
||||||
|
assert.equal(propsMethodSpies.updateGas.callCount, 0)
|
||||||
|
instance.handleToChange(false)
|
||||||
|
assert.equal(propsMethodSpies.updateGas.callCount, 1)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('render', () => {
|
describe('render', () => {
|
||||||
|
@ -31,7 +31,6 @@ proxyquire('../send-to-row.container.js', {
|
|||||||
getToDropdownOpen: (s) => `mockToDropdownOpen:${s}`,
|
getToDropdownOpen: (s) => `mockToDropdownOpen:${s}`,
|
||||||
sendToIsInError: (s) => `mockInError:${s}`,
|
sendToIsInError: (s) => `mockInError:${s}`,
|
||||||
},
|
},
|
||||||
'./send-to-row.utils.js': { getToErrorObject: (t) => `mockError:${t}` },
|
|
||||||
'../../../../actions': actionSpies,
|
'../../../../actions': actionSpies,
|
||||||
'../../../../ducks/send.duck': duckActionSpies,
|
'../../../../ducks/send.duck': duckActionSpies,
|
||||||
})
|
})
|
||||||
@ -99,12 +98,12 @@ describe('send-to-row container', () => {
|
|||||||
|
|
||||||
describe('updateSendToError()', () => {
|
describe('updateSendToError()', () => {
|
||||||
it('should dispatch an action', () => {
|
it('should dispatch an action', () => {
|
||||||
mapDispatchToPropsObject.updateSendToError('mockTo')
|
mapDispatchToPropsObject.updateSendToError('mockToErrorObject')
|
||||||
assert(dispatchSpy.calledOnce)
|
assert(dispatchSpy.calledOnce)
|
||||||
assert(duckActionSpies.updateSendErrors.calledOnce)
|
assert(duckActionSpies.updateSendErrors.calledOnce)
|
||||||
assert.equal(
|
assert.equal(
|
||||||
duckActionSpies.updateSendErrors.getCall(0).args[0],
|
duckActionSpies.updateSendErrors.getCall(0).args[0],
|
||||||
'mockError:mockTo'
|
'mockToErrorObject'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -42,7 +42,6 @@ function constructUpdatedTx ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (selectedToken) {
|
if (selectedToken) {
|
||||||
console.log(`ethAbi.rawEncode`, ethAbi.rawEncode)
|
|
||||||
const data = TOKEN_TRANSFER_FUNCTION_SIGNATURE + Array.prototype.map.call(
|
const data = TOKEN_TRANSFER_FUNCTION_SIGNATURE + Array.prototype.map.call(
|
||||||
ethAbi.rawEncode(['address', 'uint256'], [to, ethUtil.addHexPrefix(amount)]),
|
ethAbi.rawEncode(['address', 'uint256'], [to, ethUtil.addHexPrefix(amount)]),
|
||||||
x => ('00' + x.toString(16)).slice(-2)
|
x => ('00' + x.toString(16)).slice(-2)
|
||||||
|
@ -18,8 +18,8 @@ export default class SendTransactionScreen extends PersistentForm {
|
|||||||
PropTypes.string,
|
PropTypes.string,
|
||||||
PropTypes.number,
|
PropTypes.number,
|
||||||
]),
|
]),
|
||||||
|
blockGasLimit: PropTypes.string,
|
||||||
conversionRate: PropTypes.number,
|
conversionRate: PropTypes.number,
|
||||||
data: PropTypes.string,
|
|
||||||
editingTransactionId: PropTypes.string,
|
editingTransactionId: PropTypes.string,
|
||||||
from: PropTypes.object,
|
from: PropTypes.object,
|
||||||
gasLimit: PropTypes.string,
|
gasLimit: PropTypes.string,
|
||||||
@ -28,6 +28,7 @@ export default class SendTransactionScreen extends PersistentForm {
|
|||||||
history: PropTypes.object,
|
history: PropTypes.object,
|
||||||
network: PropTypes.string,
|
network: PropTypes.string,
|
||||||
primaryCurrency: PropTypes.string,
|
primaryCurrency: PropTypes.string,
|
||||||
|
recentBlocks: PropTypes.array,
|
||||||
selectedAddress: PropTypes.string,
|
selectedAddress: PropTypes.string,
|
||||||
selectedToken: PropTypes.object,
|
selectedToken: PropTypes.object,
|
||||||
tokenBalance: PropTypes.string,
|
tokenBalance: PropTypes.string,
|
||||||
@ -37,24 +38,29 @@ export default class SendTransactionScreen extends PersistentForm {
|
|||||||
updateSendTokenBalance: PropTypes.func,
|
updateSendTokenBalance: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
updateGas () {
|
updateGas ({ to } = {}) {
|
||||||
const {
|
const {
|
||||||
data,
|
amount,
|
||||||
|
blockGasLimit,
|
||||||
editingTransactionId,
|
editingTransactionId,
|
||||||
gasLimit,
|
gasLimit,
|
||||||
gasPrice,
|
gasPrice,
|
||||||
|
recentBlocks,
|
||||||
selectedAddress,
|
selectedAddress,
|
||||||
selectedToken = {},
|
selectedToken = {},
|
||||||
updateAndSetGasTotal,
|
updateAndSetGasTotal,
|
||||||
} = this.props
|
} = this.props
|
||||||
|
|
||||||
updateAndSetGasTotal({
|
updateAndSetGasTotal({
|
||||||
data,
|
blockGasLimit,
|
||||||
editingTransactionId,
|
editingTransactionId,
|
||||||
gasLimit,
|
gasLimit,
|
||||||
gasPrice,
|
gasPrice,
|
||||||
|
recentBlocks,
|
||||||
selectedAddress,
|
selectedAddress,
|
||||||
selectedToken,
|
selectedToken,
|
||||||
|
to: to && to.toLowerCase(),
|
||||||
|
value: amount,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,7 +147,7 @@ export default class SendTransactionScreen extends PersistentForm {
|
|||||||
return (
|
return (
|
||||||
<div className="page-container">
|
<div className="page-container">
|
||||||
<SendHeader history={history}/>
|
<SendHeader history={history}/>
|
||||||
<SendContent/>
|
<SendContent updateGas={(updateData) => this.updateGas(updateData)}/>
|
||||||
<SendFooter history={history}/>
|
<SendFooter history={history}/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -28,6 +28,15 @@ const NEGATIVE_ETH_ERROR = 'negativeETH'
|
|||||||
const INVALID_RECIPIENT_ADDRESS_ERROR = 'invalidAddressRecipient'
|
const INVALID_RECIPIENT_ADDRESS_ERROR = 'invalidAddressRecipient'
|
||||||
const REQUIRED_ERROR = 'required'
|
const REQUIRED_ERROR = 'required'
|
||||||
|
|
||||||
|
const ONE_GWEI_IN_WEI_HEX = ethUtil.addHexPrefix(conversionUtil('0x1', {
|
||||||
|
fromDenomination: 'GWEI',
|
||||||
|
toDenomination: 'WEI',
|
||||||
|
fromNumericBase: 'hex',
|
||||||
|
toNumericBase: 'hex',
|
||||||
|
}))
|
||||||
|
|
||||||
|
const SIMPLE_GAS_COST = '0x5208' // Hex for 21000, cost of a simple send.
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
INSUFFICIENT_FUNDS_ERROR,
|
INSUFFICIENT_FUNDS_ERROR,
|
||||||
INSUFFICIENT_TOKENS_ERROR,
|
INSUFFICIENT_TOKENS_ERROR,
|
||||||
@ -39,6 +48,8 @@ module.exports = {
|
|||||||
MIN_GAS_PRICE_HEX,
|
MIN_GAS_PRICE_HEX,
|
||||||
MIN_GAS_TOTAL,
|
MIN_GAS_TOTAL,
|
||||||
NEGATIVE_ETH_ERROR,
|
NEGATIVE_ETH_ERROR,
|
||||||
|
ONE_GWEI_IN_WEI_HEX,
|
||||||
REQUIRED_ERROR,
|
REQUIRED_ERROR,
|
||||||
|
SIMPLE_GAS_COST,
|
||||||
TOKEN_TRANSFER_FUNCTION_SIGNATURE,
|
TOKEN_TRANSFER_FUNCTION_SIGNATURE,
|
||||||
}
|
}
|
||||||
|
@ -4,12 +4,14 @@ import { withRouter } from 'react-router-dom'
|
|||||||
import { compose } from 'recompose'
|
import { compose } from 'recompose'
|
||||||
import {
|
import {
|
||||||
getAmountConversionRate,
|
getAmountConversionRate,
|
||||||
|
getBlockGasLimit,
|
||||||
getConversionRate,
|
getConversionRate,
|
||||||
getCurrentNetwork,
|
getCurrentNetwork,
|
||||||
getGasLimit,
|
getGasLimit,
|
||||||
getGasPrice,
|
getGasPrice,
|
||||||
getGasTotal,
|
getGasTotal,
|
||||||
getPrimaryCurrency,
|
getPrimaryCurrency,
|
||||||
|
getRecentBlocks,
|
||||||
getSelectedAddress,
|
getSelectedAddress,
|
||||||
getSelectedToken,
|
getSelectedToken,
|
||||||
getSelectedTokenContract,
|
getSelectedTokenContract,
|
||||||
@ -21,7 +23,7 @@ import {
|
|||||||
} from './send.selectors'
|
} from './send.selectors'
|
||||||
import {
|
import {
|
||||||
updateSendTokenBalance,
|
updateSendTokenBalance,
|
||||||
updateGasTotal,
|
updateGasData,
|
||||||
setGasTotal,
|
setGasTotal,
|
||||||
} from '../../actions'
|
} from '../../actions'
|
||||||
import {
|
import {
|
||||||
@ -29,7 +31,6 @@ import {
|
|||||||
} from '../../ducks/send.duck'
|
} from '../../ducks/send.duck'
|
||||||
import {
|
import {
|
||||||
calcGasTotal,
|
calcGasTotal,
|
||||||
generateTokenTransferData,
|
|
||||||
} from './send.utils.js'
|
} from './send.utils.js'
|
||||||
|
|
||||||
module.exports = compose(
|
module.exports = compose(
|
||||||
@ -38,14 +39,11 @@ module.exports = compose(
|
|||||||
)(SendEther)
|
)(SendEther)
|
||||||
|
|
||||||
function mapStateToProps (state) {
|
function mapStateToProps (state) {
|
||||||
const selectedAddress = getSelectedAddress(state)
|
|
||||||
const selectedToken = getSelectedToken(state)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
amount: getSendAmount(state),
|
amount: getSendAmount(state),
|
||||||
amountConversionRate: getAmountConversionRate(state),
|
amountConversionRate: getAmountConversionRate(state),
|
||||||
|
blockGasLimit: getBlockGasLimit(state),
|
||||||
conversionRate: getConversionRate(state),
|
conversionRate: getConversionRate(state),
|
||||||
data: generateTokenTransferData(selectedAddress, selectedToken),
|
|
||||||
editingTransactionId: getSendEditingTransactionId(state),
|
editingTransactionId: getSendEditingTransactionId(state),
|
||||||
from: getSendFromObject(state),
|
from: getSendFromObject(state),
|
||||||
gasLimit: getGasLimit(state),
|
gasLimit: getGasLimit(state),
|
||||||
@ -53,6 +51,7 @@ function mapStateToProps (state) {
|
|||||||
gasTotal: getGasTotal(state),
|
gasTotal: getGasTotal(state),
|
||||||
network: getCurrentNetwork(state),
|
network: getCurrentNetwork(state),
|
||||||
primaryCurrency: getPrimaryCurrency(state),
|
primaryCurrency: getPrimaryCurrency(state),
|
||||||
|
recentBlocks: getRecentBlocks(state),
|
||||||
selectedAddress: getSelectedAddress(state),
|
selectedAddress: getSelectedAddress(state),
|
||||||
selectedToken: getSelectedToken(state),
|
selectedToken: getSelectedToken(state),
|
||||||
tokenBalance: getTokenBalance(state),
|
tokenBalance: getTokenBalance(state),
|
||||||
@ -64,16 +63,18 @@ function mapStateToProps (state) {
|
|||||||
function mapDispatchToProps (dispatch) {
|
function mapDispatchToProps (dispatch) {
|
||||||
return {
|
return {
|
||||||
updateAndSetGasTotal: ({
|
updateAndSetGasTotal: ({
|
||||||
data,
|
blockGasLimit,
|
||||||
editingTransactionId,
|
editingTransactionId,
|
||||||
gasLimit,
|
gasLimit,
|
||||||
gasPrice,
|
gasPrice,
|
||||||
|
recentBlocks,
|
||||||
selectedAddress,
|
selectedAddress,
|
||||||
selectedToken,
|
selectedToken,
|
||||||
|
to,
|
||||||
|
value,
|
||||||
}) => {
|
}) => {
|
||||||
console.log(`editingTransactionId`, editingTransactionId)
|
|
||||||
!editingTransactionId
|
!editingTransactionId
|
||||||
? dispatch(updateGasTotal({ selectedAddress, selectedToken, data }))
|
? dispatch(updateGasData({ recentBlocks, selectedAddress, selectedToken, blockGasLimit, to, value }))
|
||||||
: dispatch(setGasTotal(calcGasTotal(gasLimit, gasPrice)))
|
: dispatch(setGasTotal(calcGasTotal(gasLimit, gasPrice)))
|
||||||
},
|
},
|
||||||
updateSendTokenBalance: ({ selectedToken, tokenContract, address }) => {
|
updateSendTokenBalance: ({ selectedToken, tokenContract, address }) => {
|
||||||
|
@ -3,12 +3,16 @@ const abi = require('human-standard-token-abi')
|
|||||||
const {
|
const {
|
||||||
multiplyCurrencies,
|
multiplyCurrencies,
|
||||||
} = require('../../conversion-util')
|
} = require('../../conversion-util')
|
||||||
|
const {
|
||||||
|
estimateGasPriceFromRecentBlocks,
|
||||||
|
} = require('./send.utils')
|
||||||
|
|
||||||
const selectors = {
|
const selectors = {
|
||||||
accountsWithSendEtherInfoSelector,
|
accountsWithSendEtherInfoSelector,
|
||||||
// autoAddToBetaUI,
|
// autoAddToBetaUI,
|
||||||
getAddressBook,
|
getAddressBook,
|
||||||
getAmountConversionRate,
|
getAmountConversionRate,
|
||||||
|
getBlockGasLimit,
|
||||||
getConversionRate,
|
getConversionRate,
|
||||||
getConvertedCurrency,
|
getConvertedCurrency,
|
||||||
getCurrentAccountWithSendEtherInfo,
|
getCurrentAccountWithSendEtherInfo,
|
||||||
@ -18,8 +22,10 @@ const selectors = {
|
|||||||
getForceGasMin,
|
getForceGasMin,
|
||||||
getGasLimit,
|
getGasLimit,
|
||||||
getGasPrice,
|
getGasPrice,
|
||||||
|
getGasPriceFromRecentBlocks,
|
||||||
getGasTotal,
|
getGasTotal,
|
||||||
getPrimaryCurrency,
|
getPrimaryCurrency,
|
||||||
|
getRecentBlocks,
|
||||||
getSelectedAccount,
|
getSelectedAccount,
|
||||||
getSelectedAddress,
|
getSelectedAddress,
|
||||||
getSelectedIdentity,
|
getSelectedIdentity,
|
||||||
@ -84,6 +90,10 @@ function getAmountConversionRate (state) {
|
|||||||
: getConversionRate(state)
|
: getConversionRate(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getBlockGasLimit (state) {
|
||||||
|
return state.metamask.currentBlockGasLimit
|
||||||
|
}
|
||||||
|
|
||||||
function getConversionRate (state) {
|
function getConversionRate (state) {
|
||||||
return state.metamask.conversionRate
|
return state.metamask.conversionRate
|
||||||
}
|
}
|
||||||
@ -124,6 +134,10 @@ function getGasPrice (state) {
|
|||||||
return state.metamask.send.gasPrice
|
return state.metamask.send.gasPrice
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getGasPriceFromRecentBlocks (state) {
|
||||||
|
return estimateGasPriceFromRecentBlocks(state.metamask.recentBlocks)
|
||||||
|
}
|
||||||
|
|
||||||
function getGasTotal (state) {
|
function getGasTotal (state) {
|
||||||
return state.metamask.send.gasTotal
|
return state.metamask.send.gasTotal
|
||||||
}
|
}
|
||||||
@ -133,6 +147,10 @@ function getPrimaryCurrency (state) {
|
|||||||
return selectedToken && selectedToken.symbol
|
return selectedToken && selectedToken.symbol
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getRecentBlocks (state) {
|
||||||
|
return state.metamask.recentBlocks
|
||||||
|
}
|
||||||
|
|
||||||
function getSelectedAccount (state) {
|
function getSelectedAccount (state) {
|
||||||
const accounts = state.metamask.accounts
|
const accounts = state.metamask.accounts
|
||||||
const selectedAddress = getSelectedAddress(state)
|
const selectedAddress = getSelectedAddress(state)
|
||||||
|
@ -12,16 +12,21 @@ const {
|
|||||||
INSUFFICIENT_FUNDS_ERROR,
|
INSUFFICIENT_FUNDS_ERROR,
|
||||||
INSUFFICIENT_TOKENS_ERROR,
|
INSUFFICIENT_TOKENS_ERROR,
|
||||||
NEGATIVE_ETH_ERROR,
|
NEGATIVE_ETH_ERROR,
|
||||||
|
ONE_GWEI_IN_WEI_HEX,
|
||||||
|
SIMPLE_GAS_COST,
|
||||||
|
TOKEN_TRANSFER_FUNCTION_SIGNATURE,
|
||||||
} = require('./send.constants')
|
} = require('./send.constants')
|
||||||
const abi = require('ethereumjs-abi')
|
const abi = require('ethereumjs-abi')
|
||||||
|
const ethUtil = require('ethereumjs-util')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
calcGasTotal,
|
calcGasTotal,
|
||||||
|
calcTokenBalance,
|
||||||
doesAmountErrorRequireUpdate,
|
doesAmountErrorRequireUpdate,
|
||||||
|
estimateGas,
|
||||||
|
estimateGasPriceFromRecentBlocks,
|
||||||
generateTokenTransferData,
|
generateTokenTransferData,
|
||||||
getAmountErrorObject,
|
getAmountErrorObject,
|
||||||
getParamsForGasEstimate,
|
|
||||||
calcTokenBalance,
|
|
||||||
isBalanceSufficient,
|
isBalanceSufficient,
|
||||||
isTokenBalanceSufficient,
|
isTokenBalanceSufficient,
|
||||||
}
|
}
|
||||||
@ -139,23 +144,6 @@ function getAmountErrorObject ({
|
|||||||
return { amount: amountError }
|
return { amount: amountError }
|
||||||
}
|
}
|
||||||
|
|
||||||
function getParamsForGasEstimate (selectedAddress, symbol, data) {
|
|
||||||
const estimatedGasParams = {
|
|
||||||
from: selectedAddress,
|
|
||||||
gas: '746a528800',
|
|
||||||
}
|
|
||||||
|
|
||||||
if (symbol) {
|
|
||||||
Object.assign(estimatedGasParams, { value: '0x0' })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data) {
|
|
||||||
Object.assign(estimatedGasParams, { data })
|
|
||||||
}
|
|
||||||
|
|
||||||
return estimatedGasParams
|
|
||||||
}
|
|
||||||
|
|
||||||
function calcTokenBalance ({ selectedToken, usersToken }) {
|
function calcTokenBalance ({ selectedToken, usersToken }) {
|
||||||
const { decimals } = selectedToken || {}
|
const { decimals } = selectedToken || {}
|
||||||
return calcTokenAmount(usersToken.balance.toString(), decimals) + ''
|
return calcTokenAmount(usersToken.balance.toString(), decimals) + ''
|
||||||
@ -178,11 +166,74 @@ function doesAmountErrorRequireUpdate ({
|
|||||||
return amountErrorRequiresUpdate
|
return amountErrorRequiresUpdate
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateTokenTransferData (selectedAddress, selectedToken) {
|
async function estimateGas ({ selectedAddress, selectedToken, blockGasLimit, to, value, gasPrice, estimateGasMethod }) {
|
||||||
|
const paramsForGasEstimate = { from: selectedAddress, value, gasPrice }
|
||||||
|
|
||||||
|
if (selectedToken) {
|
||||||
|
paramsForGasEstimate.value = '0x0'
|
||||||
|
paramsForGasEstimate.data = generateTokenTransferData({ toAddress: to, amount: value, selectedToken })
|
||||||
|
}
|
||||||
|
|
||||||
|
// if recipient has no code, gas is 21k max:
|
||||||
|
const hasRecipient = Boolean(to)
|
||||||
|
if (hasRecipient && !selectedToken) {
|
||||||
|
const code = await global.eth.getCode(to)
|
||||||
|
if (!code || code === '0x') {
|
||||||
|
return SIMPLE_GAS_COST
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
paramsForGasEstimate.to = selectedToken ? selectedToken.address : to
|
||||||
|
|
||||||
|
// if not, fall back to block gasLimit
|
||||||
|
paramsForGasEstimate.gas = ethUtil.addHexPrefix(multiplyCurrencies(blockGasLimit, 0.95, {
|
||||||
|
multiplicandBase: 16,
|
||||||
|
multiplierBase: 10,
|
||||||
|
roundDown: '0',
|
||||||
|
toNumericBase: 'hex',
|
||||||
|
}))
|
||||||
|
// run tx
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
return estimateGasMethod(paramsForGasEstimate, (err, estimatedGas) => {
|
||||||
|
if (err) {
|
||||||
|
const simulationFailed = (
|
||||||
|
err.message.includes('Transaction execution error.') ||
|
||||||
|
err.message.includes('gas required exceeds allowance or always failing transaction')
|
||||||
|
)
|
||||||
|
if (simulationFailed) {
|
||||||
|
return resolve(paramsForGasEstimate.gas)
|
||||||
|
} else {
|
||||||
|
return reject(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resolve(estimatedGas.toString(16))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateTokenTransferData ({ toAddress = '0x0', amount = '0x0', selectedToken }) {
|
||||||
if (!selectedToken) return
|
if (!selectedToken) return
|
||||||
console.log(`abi.rawEncode`, abi.rawEncode)
|
return TOKEN_TRANSFER_FUNCTION_SIGNATURE + Array.prototype.map.call(
|
||||||
return Array.prototype.map.call(
|
abi.rawEncode(['address', 'uint256'], [toAddress, ethUtil.addHexPrefix(amount)]),
|
||||||
abi.rawEncode(['address', 'uint256'], [selectedAddress, '0x0']),
|
|
||||||
x => ('00' + x.toString(16)).slice(-2)
|
x => ('00' + x.toString(16)).slice(-2)
|
||||||
).join('')
|
).join('')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.reduce((currentLowest, next) => {
|
||||||
|
return parseInt(next, 16) < parseInt(currentLowest, 16) ? next : currentLowest
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.sort((a, b) => parseInt(a, 16) > parseInt(b, 16) ? 1 : -1)
|
||||||
|
|
||||||
|
return lowestPrices[Math.floor(lowestPrices.length / 2)]
|
||||||
|
}
|
||||||
|
@ -32,8 +32,8 @@ describe('Send Component', function () {
|
|||||||
wrapper = shallow(<SendTransactionScreen
|
wrapper = shallow(<SendTransactionScreen
|
||||||
amount={'mockAmount'}
|
amount={'mockAmount'}
|
||||||
amountConversionRate={'mockAmountConversionRate'}
|
amountConversionRate={'mockAmountConversionRate'}
|
||||||
|
blockGasLimit={'mockBlockGasLimit'}
|
||||||
conversionRate={10}
|
conversionRate={10}
|
||||||
data={'mockData'}
|
|
||||||
editingTransactionId={'mockEditingTransactionId'}
|
editingTransactionId={'mockEditingTransactionId'}
|
||||||
from={ { address: 'mockAddress', balance: 'mockBalance' } }
|
from={ { address: 'mockAddress', balance: 'mockBalance' } }
|
||||||
gasLimit={'mockGasLimit'}
|
gasLimit={'mockGasLimit'}
|
||||||
@ -42,6 +42,7 @@ describe('Send Component', function () {
|
|||||||
history={{ mockProp: 'history-abc'}}
|
history={{ mockProp: 'history-abc'}}
|
||||||
network={'3'}
|
network={'3'}
|
||||||
primaryCurrency={'mockPrimaryCurrency'}
|
primaryCurrency={'mockPrimaryCurrency'}
|
||||||
|
recentBlocks={['mockBlock']}
|
||||||
selectedAddress={'mockSelectedAddress'}
|
selectedAddress={'mockSelectedAddress'}
|
||||||
selectedToken={'mockSelectedToken'}
|
selectedToken={'mockSelectedToken'}
|
||||||
tokenBalance={'mockTokenBalance'}
|
tokenBalance={'mockTokenBalance'}
|
||||||
@ -207,15 +208,24 @@ describe('Send Component', function () {
|
|||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
propsMethodSpies.updateAndSetGasTotal.getCall(0).args[0],
|
propsMethodSpies.updateAndSetGasTotal.getCall(0).args[0],
|
||||||
{
|
{
|
||||||
data: 'mockData',
|
blockGasLimit: 'mockBlockGasLimit',
|
||||||
editingTransactionId: 'mockEditingTransactionId',
|
editingTransactionId: 'mockEditingTransactionId',
|
||||||
gasLimit: 'mockGasLimit',
|
gasLimit: 'mockGasLimit',
|
||||||
gasPrice: 'mockGasPrice',
|
gasPrice: 'mockGasPrice',
|
||||||
|
recentBlocks: ['mockBlock'],
|
||||||
selectedAddress: 'mockSelectedAddress',
|
selectedAddress: 'mockSelectedAddress',
|
||||||
selectedToken: 'mockSelectedToken',
|
selectedToken: 'mockSelectedToken',
|
||||||
|
to: undefined,
|
||||||
|
value: 'mockAmount',
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should call updateAndSetGasTotal with to set to lowercase if passed', () => {
|
||||||
|
propsMethodSpies.updateAndSetGasTotal.resetHistory()
|
||||||
|
wrapper.instance().updateGas({ to: '0xABC' })
|
||||||
|
assert.equal(propsMethodSpies.updateAndSetGasTotal.getCall(0).args[0].to, '0xabc')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('render', () => {
|
describe('render', () => {
|
||||||
|
@ -7,7 +7,7 @@ let mapDispatchToProps
|
|||||||
|
|
||||||
const actionSpies = {
|
const actionSpies = {
|
||||||
updateSendTokenBalance: sinon.spy(),
|
updateSendTokenBalance: sinon.spy(),
|
||||||
updateGasTotal: sinon.spy(),
|
updateGasData: sinon.spy(),
|
||||||
setGasTotal: sinon.spy(),
|
setGasTotal: sinon.spy(),
|
||||||
}
|
}
|
||||||
const duckActionSpies = {
|
const duckActionSpies = {
|
||||||
@ -26,12 +26,14 @@ proxyquire('../send.container.js', {
|
|||||||
'recompose': { compose: (arg1, arg2) => () => arg2() },
|
'recompose': { compose: (arg1, arg2) => () => arg2() },
|
||||||
'./send.selectors': {
|
'./send.selectors': {
|
||||||
getAmountConversionRate: (s) => `mockAmountConversionRate:${s}`,
|
getAmountConversionRate: (s) => `mockAmountConversionRate:${s}`,
|
||||||
|
getBlockGasLimit: (s) => `mockBlockGasLimit:${s}`,
|
||||||
getConversionRate: (s) => `mockConversionRate:${s}`,
|
getConversionRate: (s) => `mockConversionRate:${s}`,
|
||||||
getCurrentNetwork: (s) => `mockNetwork:${s}`,
|
getCurrentNetwork: (s) => `mockNetwork:${s}`,
|
||||||
getGasLimit: (s) => `mockGasLimit:${s}`,
|
getGasLimit: (s) => `mockGasLimit:${s}`,
|
||||||
getGasPrice: (s) => `mockGasPrice:${s}`,
|
getGasPrice: (s) => `mockGasPrice:${s}`,
|
||||||
getGasTotal: (s) => `mockGasTotal:${s}`,
|
getGasTotal: (s) => `mockGasTotal:${s}`,
|
||||||
getPrimaryCurrency: (s) => `mockPrimaryCurrency:${s}`,
|
getPrimaryCurrency: (s) => `mockPrimaryCurrency:${s}`,
|
||||||
|
getRecentBlocks: (s) => `mockRecentBlocks:${s}`,
|
||||||
getSelectedAddress: (s) => `mockSelectedAddress:${s}`,
|
getSelectedAddress: (s) => `mockSelectedAddress:${s}`,
|
||||||
getSelectedToken: (s) => `mockSelectedToken:${s}`,
|
getSelectedToken: (s) => `mockSelectedToken:${s}`,
|
||||||
getSelectedTokenContract: (s) => `mockTokenContract:${s}`,
|
getSelectedTokenContract: (s) => `mockTokenContract:${s}`,
|
||||||
@ -45,7 +47,6 @@ proxyquire('../send.container.js', {
|
|||||||
'../../ducks/send.duck': duckActionSpies,
|
'../../ducks/send.duck': duckActionSpies,
|
||||||
'./send.utils.js': {
|
'./send.utils.js': {
|
||||||
calcGasTotal: (gasLimit, gasPrice) => gasLimit + gasPrice,
|
calcGasTotal: (gasLimit, gasPrice) => gasLimit + gasPrice,
|
||||||
generateTokenTransferData: (a, b) => `mockData:${a + b}`,
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -57,8 +58,8 @@ describe('send container', () => {
|
|||||||
assert.deepEqual(mapStateToProps('mockState'), {
|
assert.deepEqual(mapStateToProps('mockState'), {
|
||||||
amount: 'mockAmount:mockState',
|
amount: 'mockAmount:mockState',
|
||||||
amountConversionRate: 'mockAmountConversionRate:mockState',
|
amountConversionRate: 'mockAmountConversionRate:mockState',
|
||||||
|
blockGasLimit: 'mockBlockGasLimit:mockState',
|
||||||
conversionRate: 'mockConversionRate:mockState',
|
conversionRate: 'mockConversionRate:mockState',
|
||||||
data: 'mockData:mockSelectedAddress:mockStatemockSelectedToken:mockState',
|
|
||||||
editingTransactionId: 'mockEditingTransactionId:mockState',
|
editingTransactionId: 'mockEditingTransactionId:mockState',
|
||||||
from: 'mockFrom:mockState',
|
from: 'mockFrom:mockState',
|
||||||
gasLimit: 'mockGasLimit:mockState',
|
gasLimit: 'mockGasLimit:mockState',
|
||||||
@ -66,6 +67,7 @@ describe('send container', () => {
|
|||||||
gasTotal: 'mockGasTotal:mockState',
|
gasTotal: 'mockGasTotal:mockState',
|
||||||
network: 'mockNetwork:mockState',
|
network: 'mockNetwork:mockState',
|
||||||
primaryCurrency: 'mockPrimaryCurrency:mockState',
|
primaryCurrency: 'mockPrimaryCurrency:mockState',
|
||||||
|
recentBlocks: 'mockRecentBlocks:mockState',
|
||||||
selectedAddress: 'mockSelectedAddress:mockState',
|
selectedAddress: 'mockSelectedAddress:mockState',
|
||||||
selectedToken: 'mockSelectedToken:mockState',
|
selectedToken: 'mockSelectedToken:mockState',
|
||||||
tokenBalance: 'mockTokenBalance:mockState',
|
tokenBalance: 'mockTokenBalance:mockState',
|
||||||
@ -87,12 +89,15 @@ describe('send container', () => {
|
|||||||
|
|
||||||
describe('updateAndSetGasTotal()', () => {
|
describe('updateAndSetGasTotal()', () => {
|
||||||
const mockProps = {
|
const mockProps = {
|
||||||
data: '0x1',
|
blockGasLimit: 'mockBlockGasLimit',
|
||||||
editingTransactionId: '0x2',
|
editingTransactionId: '0x2',
|
||||||
gasLimit: '0x3',
|
gasLimit: '0x3',
|
||||||
gasPrice: '0x4',
|
gasPrice: '0x4',
|
||||||
|
recentBlocks: ['mockBlock'],
|
||||||
selectedAddress: '0x4',
|
selectedAddress: '0x4',
|
||||||
selectedToken: { address: '0x1' },
|
selectedToken: { address: '0x1' },
|
||||||
|
to: 'mockTo',
|
||||||
|
value: 'mockValue',
|
||||||
}
|
}
|
||||||
|
|
||||||
it('should dispatch a setGasTotal action when editingTransactionId is truthy', () => {
|
it('should dispatch a setGasTotal action when editingTransactionId is truthy', () => {
|
||||||
@ -104,15 +109,15 @@ describe('send container', () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should dispatch an updateGasTotal action when editingTransactionId is falsy', () => {
|
it('should dispatch an updateGasData action when editingTransactionId is falsy', () => {
|
||||||
const { selectedAddress, selectedToken, data } = mockProps
|
const { selectedAddress, selectedToken, recentBlocks, blockGasLimit, to, value } = mockProps
|
||||||
mapDispatchToPropsObject.updateAndSetGasTotal(
|
mapDispatchToPropsObject.updateAndSetGasTotal(
|
||||||
Object.assign(mockProps, {editingTransactionId: false})
|
Object.assign({}, mockProps, {editingTransactionId: false})
|
||||||
)
|
)
|
||||||
assert(dispatchSpy.calledOnce)
|
assert(dispatchSpy.calledOnce)
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
actionSpies.updateGasTotal.getCall(0).args[0],
|
actionSpies.updateGasData.getCall(0).args[0],
|
||||||
{ selectedAddress, selectedToken, data }
|
{ selectedAddress, selectedToken, recentBlocks, blockGasLimit, to, value }
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -22,6 +22,7 @@ module.exports = {
|
|||||||
'name': 'Send Account 4',
|
'name': 'Send Account 4',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
'currentBlockGasLimit': '0x4c1878',
|
||||||
'currentCurrency': 'USD',
|
'currentCurrency': 'USD',
|
||||||
'conversionRate': 1200.88200327,
|
'conversionRate': 1200.88200327,
|
||||||
'conversionDate': 1489013762,
|
'conversionDate': 1489013762,
|
||||||
@ -198,6 +199,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
'currentLocale': 'en',
|
'currentLocale': 'en',
|
||||||
|
recentBlocks: ['mockBlock1', 'mockBlock2', 'mockBlock3'],
|
||||||
},
|
},
|
||||||
'appState': {
|
'appState': {
|
||||||
'menuOpen': false,
|
'menuOpen': false,
|
||||||
|
@ -5,6 +5,7 @@ const {
|
|||||||
accountsWithSendEtherInfoSelector,
|
accountsWithSendEtherInfoSelector,
|
||||||
// autoAddToBetaUI,
|
// autoAddToBetaUI,
|
||||||
getAddressBook,
|
getAddressBook,
|
||||||
|
getBlockGasLimit,
|
||||||
getAmountConversionRate,
|
getAmountConversionRate,
|
||||||
getConversionRate,
|
getConversionRate,
|
||||||
getConvertedCurrency,
|
getConvertedCurrency,
|
||||||
@ -17,6 +18,7 @@ const {
|
|||||||
getGasPrice,
|
getGasPrice,
|
||||||
getGasTotal,
|
getGasTotal,
|
||||||
getPrimaryCurrency,
|
getPrimaryCurrency,
|
||||||
|
getRecentBlocks,
|
||||||
getSelectedAccount,
|
getSelectedAccount,
|
||||||
getSelectedAddress,
|
getSelectedAddress,
|
||||||
getSelectedIdentity,
|
getSelectedIdentity,
|
||||||
@ -134,6 +136,15 @@ describe('send selectors', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('getBlockGasLimit', () => {
|
||||||
|
it('should return the current block gas limit', () => {
|
||||||
|
assert.deepEqual(
|
||||||
|
getBlockGasLimit(mockState),
|
||||||
|
'0x4c1878'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('getConversionRate()', () => {
|
describe('getConversionRate()', () => {
|
||||||
it('should return the eth conversion rate', () => {
|
it('should return the eth conversion rate', () => {
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
@ -239,6 +250,15 @@ describe('send selectors', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('getRecentBlocks()', () => {
|
||||||
|
it('should return the recent blocks', () => {
|
||||||
|
assert.deepEqual(
|
||||||
|
getRecentBlocks(mockState),
|
||||||
|
['mockBlock1', 'mockBlock2', 'mockBlock3']
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('getSelectedAccount()', () => {
|
describe('getSelectedAccount()', () => {
|
||||||
it('should return the currently selected account', () => {
|
it('should return the currently selected account', () => {
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
|
@ -1,6 +1,14 @@
|
|||||||
import assert from 'assert'
|
import assert from 'assert'
|
||||||
import sinon from 'sinon'
|
import sinon from 'sinon'
|
||||||
import proxyquire from 'proxyquire'
|
import proxyquire from 'proxyquire'
|
||||||
|
import {
|
||||||
|
ONE_GWEI_IN_WEI_HEX,
|
||||||
|
SIMPLE_GAS_COST,
|
||||||
|
} from '../send.constants'
|
||||||
|
const {
|
||||||
|
addCurrencies,
|
||||||
|
subtractCurrencies,
|
||||||
|
} = require('../../../conversion-util')
|
||||||
|
|
||||||
const {
|
const {
|
||||||
INSUFFICIENT_FUNDS_ERROR,
|
INSUFFICIENT_FUNDS_ERROR,
|
||||||
@ -11,7 +19,7 @@ const stubs = {
|
|||||||
addCurrencies: sinon.stub().callsFake((a, b, obj) => a + b),
|
addCurrencies: sinon.stub().callsFake((a, b, obj) => a + b),
|
||||||
conversionUtil: sinon.stub().callsFake((val, obj) => parseInt(val, 16)),
|
conversionUtil: sinon.stub().callsFake((val, obj) => parseInt(val, 16)),
|
||||||
conversionGTE: sinon.stub().callsFake((obj1, obj2) => obj1.value > obj2.value),
|
conversionGTE: sinon.stub().callsFake((obj1, obj2) => obj1.value > obj2.value),
|
||||||
multiplyCurrencies: sinon.stub().callsFake((a, b) => a * b),
|
multiplyCurrencies: sinon.stub().callsFake((a, b) => `${a}x${b}`),
|
||||||
calcTokenAmount: sinon.stub().callsFake((a, d) => 'calc:' + a + d),
|
calcTokenAmount: sinon.stub().callsFake((a, d) => 'calc:' + a + d),
|
||||||
rawEncode: sinon.stub().returns([16, 1100]),
|
rawEncode: sinon.stub().returns([16, 1100]),
|
||||||
}
|
}
|
||||||
@ -31,10 +39,11 @@ const sendUtils = proxyquire('../send.utils.js', {
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
calcGasTotal,
|
calcGasTotal,
|
||||||
|
estimateGas,
|
||||||
doesAmountErrorRequireUpdate,
|
doesAmountErrorRequireUpdate,
|
||||||
|
estimateGasPriceFromRecentBlocks,
|
||||||
generateTokenTransferData,
|
generateTokenTransferData,
|
||||||
getAmountErrorObject,
|
getAmountErrorObject,
|
||||||
getParamsForGasEstimate,
|
|
||||||
calcTokenBalance,
|
calcTokenBalance,
|
||||||
isBalanceSufficient,
|
isBalanceSufficient,
|
||||||
isTokenBalanceSufficient,
|
isTokenBalanceSufficient,
|
||||||
@ -45,7 +54,7 @@ describe('send utils', () => {
|
|||||||
describe('calcGasTotal()', () => {
|
describe('calcGasTotal()', () => {
|
||||||
it('should call multiplyCurrencies with the correct params and return the multiplyCurrencies return', () => {
|
it('should call multiplyCurrencies with the correct params and return the multiplyCurrencies return', () => {
|
||||||
const result = calcGasTotal(12, 15)
|
const result = calcGasTotal(12, 15)
|
||||||
assert.equal(result, 180)
|
assert.equal(result, '12x15')
|
||||||
const call_ = stubs.multiplyCurrencies.getCall(0).args
|
const call_ = stubs.multiplyCurrencies.getCall(0).args
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
call_,
|
call_,
|
||||||
@ -97,11 +106,23 @@ describe('send utils', () => {
|
|||||||
|
|
||||||
describe('generateTokenTransferData()', () => {
|
describe('generateTokenTransferData()', () => {
|
||||||
it('should return undefined if not passed a selected token', () => {
|
it('should return undefined if not passed a selected token', () => {
|
||||||
assert.equal(generateTokenTransferData('mockAddress', false), undefined)
|
assert.equal(generateTokenTransferData({ toAddress: 'mockAddress', amount: '0xa', selectedToken: false}), undefined)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should call abi.rawEncode with the correct params', () => {
|
||||||
|
stubs.rawEncode.resetHistory()
|
||||||
|
generateTokenTransferData({ toAddress: 'mockAddress', amount: 'ab', selectedToken: true})
|
||||||
|
assert.deepEqual(
|
||||||
|
stubs.rawEncode.getCall(0).args,
|
||||||
|
[['address', 'uint256'], ['mockAddress', '0xab']]
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should return encoded token transfer data', () => {
|
it('should return encoded token transfer data', () => {
|
||||||
assert.equal(generateTokenTransferData('mockAddress', true), '104c')
|
assert.equal(
|
||||||
|
generateTokenTransferData({ toAddress: 'mockAddress', amount: '0xa', selectedToken: true}),
|
||||||
|
'0xa9059cbb104c'
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -136,41 +157,6 @@ describe('send utils', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('getParamsForGasEstimate()', () => {
|
|
||||||
it('should return from and gas properties if no symbol or data', () => {
|
|
||||||
assert.deepEqual(
|
|
||||||
getParamsForGasEstimate('mockAddress'),
|
|
||||||
{
|
|
||||||
from: 'mockAddress',
|
|
||||||
gas: '746a528800',
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should return value property if symbol provided', () => {
|
|
||||||
assert.deepEqual(
|
|
||||||
getParamsForGasEstimate('mockAddress', 'ABC'),
|
|
||||||
{
|
|
||||||
from: 'mockAddress',
|
|
||||||
gas: '746a528800',
|
|
||||||
value: '0x0',
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should return data property if data provided', () => {
|
|
||||||
assert.deepEqual(
|
|
||||||
getParamsForGasEstimate('mockAddress', 'ABC', 'somedata'),
|
|
||||||
{
|
|
||||||
from: 'mockAddress',
|
|
||||||
gas: '746a528800',
|
|
||||||
value: '0x0',
|
|
||||||
data: 'somedata',
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('calcTokenBalance()', () => {
|
describe('calcTokenBalance()', () => {
|
||||||
it('should return the calculated token blance', () => {
|
it('should return the calculated token blance', () => {
|
||||||
assert.equal(calcTokenBalance({
|
assert.equal(calcTokenBalance({
|
||||||
@ -261,4 +247,158 @@ describe('send utils', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('estimateGas', () => {
|
||||||
|
const baseMockParams = {
|
||||||
|
blockGasLimit: '0x64',
|
||||||
|
selectedAddress: 'mockAddress',
|
||||||
|
to: '0xisContract',
|
||||||
|
estimateGasMethod: sinon.stub().callsFake(
|
||||||
|
(data, cb) => cb(
|
||||||
|
data.to.match(/willFailBecauseOf:/) ? { message: data.to.match(/:(.+)$/)[1] } : null,
|
||||||
|
{ toString: (n) => `mockToString:${n}` }
|
||||||
|
)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
const baseExpectedCall = {
|
||||||
|
from: 'mockAddress',
|
||||||
|
gas: '0x64x0.95',
|
||||||
|
to: '0xisContract',
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
global.eth = {
|
||||||
|
getCode: sinon.stub().callsFake(
|
||||||
|
(address) => Promise.resolve(address.match(/isContract/) ? 'not-0x' : '0x')
|
||||||
|
),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
baseMockParams.estimateGasMethod.resetHistory()
|
||||||
|
global.eth.getCode.resetHistory()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should call ethQuery.estimateGas with the expected params', async () => {
|
||||||
|
const result = await estimateGas(baseMockParams)
|
||||||
|
assert.equal(baseMockParams.estimateGasMethod.callCount, 1)
|
||||||
|
assert.deepEqual(
|
||||||
|
baseMockParams.estimateGasMethod.getCall(0).args[0],
|
||||||
|
Object.assign({ gasPrice: undefined, value: undefined }, baseExpectedCall)
|
||||||
|
)
|
||||||
|
assert.equal(result, 'mockToString:16')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should call ethQuery.estimateGas with a value of 0x0 and the expected data and to if passed a selectedToken', async () => {
|
||||||
|
const result = await estimateGas(Object.assign({ data: 'mockData', selectedToken: { address: 'mockAddress' } }, baseMockParams))
|
||||||
|
assert.equal(baseMockParams.estimateGasMethod.callCount, 1)
|
||||||
|
assert.deepEqual(
|
||||||
|
baseMockParams.estimateGasMethod.getCall(0).args[0],
|
||||||
|
Object.assign({}, baseExpectedCall, {
|
||||||
|
gasPrice: undefined,
|
||||||
|
value: '0x0',
|
||||||
|
data: '0xa9059cbb104c',
|
||||||
|
to: 'mockAddress',
|
||||||
|
})
|
||||||
|
)
|
||||||
|
assert.equal(result, 'mockToString:16')
|
||||||
|
})
|
||||||
|
|
||||||
|
it(`should return ${SIMPLE_GAS_COST} if ethQuery.getCode does not return '0x'`, async () => {
|
||||||
|
assert.equal(baseMockParams.estimateGasMethod.callCount, 0)
|
||||||
|
const result = await estimateGas(Object.assign({}, baseMockParams, { to: '0x123' }))
|
||||||
|
assert.equal(result, SIMPLE_GAS_COST)
|
||||||
|
})
|
||||||
|
|
||||||
|
it(`should not return ${SIMPLE_GAS_COST} if passed a selectedToken`, async () => {
|
||||||
|
assert.equal(baseMockParams.estimateGasMethod.callCount, 0)
|
||||||
|
const result = await estimateGas(Object.assign({}, baseMockParams, { to: '0x123', selectedToken: { address: '' } }))
|
||||||
|
assert.notEqual(result, SIMPLE_GAS_COST)
|
||||||
|
})
|
||||||
|
|
||||||
|
it(`should return the adjusted blockGasLimit if it fails with a 'Transaction execution error.'`, async () => {
|
||||||
|
const result = await estimateGas(Object.assign({}, baseMockParams, {
|
||||||
|
to: 'isContract willFailBecauseOf:Transaction execution error.',
|
||||||
|
}))
|
||||||
|
assert.equal(result, '0x64x0.95')
|
||||||
|
})
|
||||||
|
|
||||||
|
it(`should return the adjusted blockGasLimit if it fails with a 'gas required exceeds allowance or always failing transaction.'`, async () => {
|
||||||
|
const result = await estimateGas(Object.assign({}, baseMockParams, {
|
||||||
|
to: 'isContract willFailBecauseOf:gas required exceeds allowance or always failing transaction.',
|
||||||
|
}))
|
||||||
|
assert.equal(result, '0x64x0.95')
|
||||||
|
})
|
||||||
|
|
||||||
|
it(`should reject other errors`, async () => {
|
||||||
|
try {
|
||||||
|
await estimateGas(Object.assign({}, baseMockParams, {
|
||||||
|
to: 'isContract willFailBecauseOf:some other error',
|
||||||
|
}))
|
||||||
|
} catch (err) {
|
||||||
|
assert.deepEqual(err, { message: 'some other error' })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
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')
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -11,7 +11,8 @@
|
|||||||
* @param {string} [options.fromNumericBase = 'hex' | 'dec' | 'BN'] The numeric basic of the passed value.
|
* @param {string} [options.fromNumericBase = 'hex' | 'dec' | 'BN'] The numeric basic of the passed value.
|
||||||
* @param {string} [options.toNumericBase = 'hex' | 'dec' | 'BN'] The desired numeric basic of the result.
|
* @param {string} [options.toNumericBase = 'hex' | 'dec' | 'BN'] The desired numeric basic of the result.
|
||||||
* @param {string} [options.fromDenomination = 'WEI'] The denomination of the passed value
|
* @param {string} [options.fromDenomination = 'WEI'] The denomination of the passed value
|
||||||
* @param {number} [options.numberOfDecimals] The desired number of in the result
|
* @param {string} [options.numberOfDecimals] The desired number of decimals in the result
|
||||||
|
* @param {string} [options.roundDown] The desired number of decimals to round down to
|
||||||
* @param {number} [options.conversionRate] The rate to use to make the fromCurrency -> toCurrency conversion
|
* @param {number} [options.conversionRate] The rate to use to make the fromCurrency -> toCurrency conversion
|
||||||
* @returns {(number | string | BN)}
|
* @returns {(number | string | BN)}
|
||||||
*
|
*
|
||||||
@ -38,6 +39,7 @@ const BIG_NUMBER_GWEI_MULTIPLIER = new BigNumber('1000000000')
|
|||||||
// Individual Setters
|
// Individual Setters
|
||||||
const convert = R.invoker(1, 'times')
|
const convert = R.invoker(1, 'times')
|
||||||
const round = R.invoker(2, 'round')(R.__, BigNumber.ROUND_HALF_DOWN)
|
const round = R.invoker(2, 'round')(R.__, BigNumber.ROUND_HALF_DOWN)
|
||||||
|
const roundDown = R.invoker(2, 'round')(R.__, BigNumber.ROUND_DOWN)
|
||||||
const invertConversionRate = conversionRate => () => new BigNumber(1.0).div(conversionRate)
|
const invertConversionRate = conversionRate => () => new BigNumber(1.0).div(conversionRate)
|
||||||
const decToBigNumberViaString = n => R.pipe(String, toBigNumber['dec'])
|
const decToBigNumberViaString = n => R.pipe(String, toBigNumber['dec'])
|
||||||
|
|
||||||
@ -104,6 +106,7 @@ const converter = R.pipe(
|
|||||||
whenPredSetWithPropAndSetter(fromAndToCurrencyPropsNotEqual, 'conversionRate', convert),
|
whenPredSetWithPropAndSetter(fromAndToCurrencyPropsNotEqual, 'conversionRate', convert),
|
||||||
whenPropApplySetterMap('toDenomination', toSpecifiedDenomination),
|
whenPropApplySetterMap('toDenomination', toSpecifiedDenomination),
|
||||||
whenPredSetWithPropAndSetter(R.prop('numberOfDecimals'), 'numberOfDecimals', round),
|
whenPredSetWithPropAndSetter(R.prop('numberOfDecimals'), 'numberOfDecimals', round),
|
||||||
|
whenPredSetWithPropAndSetter(R.prop('roundDown'), 'roundDown', roundDown),
|
||||||
whenPropApplySetterMap('toNumericBase', baseChange),
|
whenPropApplySetterMap('toNumericBase', baseChange),
|
||||||
R.view(R.lensProp('value'))
|
R.view(R.lensProp('value'))
|
||||||
)
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user