diff options
Diffstat (limited to 'ui/app/components/send_')
27 files changed, 280 insertions, 57 deletions
diff --git a/ui/app/components/send_/account-list-item/account-list-item.container.js b/ui/app/components/send_/account-list-item/account-list-item.container.js index 3151b1f1d..4b4519288 100644 --- a/ui/app/components/send_/account-list-item/account-list-item.container.js +++ b/ui/app/components/send_/account-list-item/account-list-item.container.js @@ -1,7 +1,7 @@ import { connect } from 'react-redux' import { getConversionRate, - getConvertedCurrency, + getCurrentCurrency, } from '../send.selectors.js' import AccountListItem from './account-list-item.component' @@ -10,6 +10,6 @@ export default connect(mapStateToProps)(AccountListItem) function mapStateToProps (state) { return { conversionRate: getConversionRate(state), - currentCurrency: getConvertedCurrency(state), + currentCurrency: getCurrentCurrency(state), } } diff --git a/ui/app/components/send_/account-list-item/tests/account-list-item-container.test.js b/ui/app/components/send_/account-list-item/tests/account-list-item-container.test.js index 49da920e6..af0859117 100644 --- a/ui/app/components/send_/account-list-item/tests/account-list-item-container.test.js +++ b/ui/app/components/send_/account-list-item/tests/account-list-item-container.test.js @@ -12,7 +12,7 @@ proxyquire('../account-list-item.container.js', { }, '../send.selectors.js': { getConversionRate: (s) => `mockConversionRate:${s}`, - getConvertedCurrency: (s) => `mockCurrentCurrency:${s}`, + getCurrentCurrency: (s) => `mockCurrentCurrency:${s}`, }, }) diff --git a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js index 8aefeed4a..196538c11 100644 --- a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js +++ b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js @@ -23,6 +23,7 @@ export default class SendAmountRow extends Component { tokenBalance: PropTypes.string, updateSendAmount: PropTypes.func, updateSendAmountError: PropTypes.func, + updateGas: PropTypes.func, } validateAmount (amount) { @@ -56,6 +57,14 @@ export default class SendAmountRow extends Component { updateSendAmount(amount) } + updateGas (amount) { + const { selectedToken, updateGas } = this.props + + if (selectedToken) { + updateGas({ amount }) + } + } + render () { const { amount, @@ -77,12 +86,16 @@ export default class SendAmountRow extends Component { <CurrencyDisplay conversionRate={amountConversionRate} convertedCurrency={convertedCurrency} - onBlur={newAmount => this.updateAmount(newAmount)} + onBlur={newAmount => { + this.updateGas(newAmount) + this.updateAmount(newAmount) + }} onChange={newAmount => this.validateAmount(newAmount)} inError={inError} primaryCurrency={primaryCurrency || 'ETH'} selectedToken={selectedToken} - value={amount || '0x0'} + value={amount} + step="any" /> </SendRowWrapper> ) diff --git a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.container.js b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.container.js index bbbf56971..b816d948f 100644 --- a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.container.js +++ b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.container.js @@ -2,7 +2,7 @@ import { connect } from 'react-redux' import { getAmountConversionRate, getConversionRate, - getConvertedCurrency, + getCurrentCurrency, getGasTotal, getPrimaryCurrency, getSelectedToken, @@ -31,7 +31,7 @@ function mapStateToProps (state) { amountConversionRate: getAmountConversionRate(state), balance: getSendFromBalance(state), conversionRate: getConversionRate(state), - convertedCurrency: getConvertedCurrency(state), + convertedCurrency: getCurrentCurrency(state), gasTotal: getGasTotal(state), inError: sendAmountIsInError(state), primaryCurrency: getPrimaryCurrency(state), diff --git a/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-component.test.js b/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-component.test.js index 2205579ca..579e18585 100644 --- a/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-component.test.js +++ b/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-component.test.js @@ -12,10 +12,12 @@ const propsMethodSpies = { setMaxModeTo: sinon.spy(), updateSendAmount: sinon.spy(), updateSendAmountError: sinon.spy(), + updateGas: sinon.spy(), } sinon.spy(SendAmountRow.prototype, 'updateAmount') sinon.spy(SendAmountRow.prototype, 'validateAmount') +sinon.spy(SendAmountRow.prototype, 'updateGas') describe('SendAmountRow Component', function () { let wrapper @@ -36,6 +38,7 @@ describe('SendAmountRow Component', function () { tokenBalance={'mockTokenBalance'} updateSendAmount={propsMethodSpies.updateSendAmount} updateSendAmountError={propsMethodSpies.updateSendAmountError} + updateGas={propsMethodSpies.updateGas} />, { context: { t: str => str + '_t' } }) instance = wrapper.instance() }) @@ -139,8 +142,14 @@ describe('SendAmountRow Component', function () { assert.equal(primaryCurrency, 'mockPrimaryCurrency') assert.deepEqual(selectedToken, { address: 'mockTokenAddress' }) assert.equal(value, 'mockAmount') + assert.equal(SendAmountRow.prototype.updateGas.callCount, 0) assert.equal(SendAmountRow.prototype.updateAmount.callCount, 0) onBlur('mockNewAmount') + assert.equal(SendAmountRow.prototype.updateGas.callCount, 1) + assert.deepEqual( + SendAmountRow.prototype.updateGas.getCall(0).args, + ['mockNewAmount'] + ) assert.equal(SendAmountRow.prototype.updateAmount.callCount, 1) assert.deepEqual( SendAmountRow.prototype.updateAmount.getCall(0).args, diff --git a/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-container.test.js b/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-container.test.js index e4c913c69..94d9918a7 100644 --- a/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-container.test.js +++ b/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-container.test.js @@ -24,7 +24,7 @@ proxyquire('../send-amount-row.container.js', { '../../send.selectors': { getAmountConversionRate: (s) => `mockAmountConversionRate:${s}`, getConversionRate: (s) => `mockConversionRate:${s}`, - getConvertedCurrency: (s) => `mockConvertedCurrency:${s}`, + getCurrentCurrency: (s) => `mockConvertedCurrency:${s}`, getGasTotal: (s) => `mockGasTotal:${s}`, getPrimaryCurrency: (s) => `mockPrimaryCurrency:${s}`, getSelectedToken: (s) => `mockSelectedToken:${s}`, diff --git a/ui/app/components/send_/send-content/send-content.component.js b/ui/app/components/send_/send-content/send-content.component.js index 3a14054eb..adc114c0e 100644 --- a/ui/app/components/send_/send-content/send-content.component.js +++ b/ui/app/components/send_/send-content/send-content.component.js @@ -18,7 +18,7 @@ export default class SendContent extends Component { <div className="send-v2__form"> <SendFromRow /> <SendToRow updateGas={(updateData) => this.props.updateGas(updateData)} /> - <SendAmountRow /> + <SendAmountRow updateGas={(updateData) => this.props.updateGas(updateData)} /> <SendGasRow /> </div> </PageContainerContent> diff --git a/ui/app/components/send_/send-content/send-gas-row/gas-fee-display/gas-fee-display.component.js b/ui/app/components/send_/send-content/send-gas-row/gas-fee-display/gas-fee-display.component.js new file mode 100644 index 000000000..b1fd67412 --- /dev/null +++ b/ui/app/components/send_/send-content/send-gas-row/gas-fee-display/gas-fee-display.component.js @@ -0,0 +1,61 @@ +import React, {Component} from 'react' +import PropTypes from 'prop-types' +import CurrencyDisplay from '../../../../send/currency-display' + + +export default class GasFeeDisplay extends Component { + + static propTypes = { + conversionRate: PropTypes.number, + primaryCurrency: PropTypes.string, + convertedCurrency: PropTypes.string, + gasLoadingError: PropTypes.bool, + gasTotal: PropTypes.string, + onClick: PropTypes.func, + }; + + render() { + const { + conversionRate, + gasTotal, + onClick, + primaryCurrency = 'ETH', + convertedCurrency, + gasLoadingError, + } = this.props + + return ( + <div className="send-v2__gas-fee-display"> + {gasTotal + ? <CurrencyDisplay + primaryCurrency={primaryCurrency} + convertedCurrency={convertedCurrency} + value={gasTotal} + conversionRate={conversionRate} + gasLoadingError={gasLoadingError} + convertedPrefix={'$'} + readOnly + /> + : gasLoadingError + ? <div className="currency-display.currency-display--message"> + {this.context.t('setGasPrice')} + </div> + : <div className="currency-display"> + {this.context.t('loading')} + </div> + } + <button + className="sliders-icon-container" + onClick={onClick} + disabled={!gasTotal && !gasLoadingError} + > + <i className="fa fa-sliders sliders-icon" /> + </button> + </div> + ) + } +} + +GasFeeDisplay.contextTypes = { + t: PropTypes.func, +} diff --git a/ui/app/components/send_/send-content/send-gas-row/gas-fee-display/index.js b/ui/app/components/send_/send-content/send-gas-row/gas-fee-display/index.js new file mode 100644 index 000000000..dba0edb7b --- /dev/null +++ b/ui/app/components/send_/send-content/send-gas-row/gas-fee-display/index.js @@ -0,0 +1 @@ +export { default } from './gas-fee-display.component' diff --git a/ui/app/components/send_/send-content/send-gas-row/gas-fee-display/test/gas-fee-display.component.test.js b/ui/app/components/send_/send-content/send-gas-row/gas-fee-display/test/gas-fee-display.component.test.js new file mode 100644 index 000000000..66f3a94df --- /dev/null +++ b/ui/app/components/send_/send-content/send-gas-row/gas-fee-display/test/gas-fee-display.component.test.js @@ -0,0 +1,55 @@ +import React from 'react' +import assert from 'assert' +import {shallow} from 'enzyme' +import GasFeeDisplay from '../gas-fee-display.component' +import CurrencyDisplay from '../../../../../send/currency-display' +import sinon from 'sinon' + + +const propsMethodSpies = { + showCustomizeGasModal: sinon.spy(), +} + +describe('SendGasRow Component', function() { + let wrapper + + beforeEach(() => { + wrapper = shallow(<GasFeeDisplay + conversionRate={20} + gasTotal={'mockGasTotal'} + onClick={propsMethodSpies.showCustomizeGasModal} + primaryCurrency={'mockPrimaryCurrency'} + convertedCurrency={'mockConvertedCurrency'} + />, {context: {t: str => str + '_t'}}) + }) + + afterEach(() => { + propsMethodSpies.showCustomizeGasModal.resetHistory() + }) + + describe('render', () => { + it('should render a CurrencyDisplay component', () => { + assert.equal(wrapper.find(CurrencyDisplay).length, 1) + }) + + it('should render the CurrencyDisplay with the correct props', () => { + const { + conversionRate, + convertedCurrency, + value, + } = wrapper.find(CurrencyDisplay).props() + assert.equal(conversionRate, 20) + assert.equal(convertedCurrency, 'mockConvertedCurrency') + assert.equal(value, 'mockGasTotal') + }) + + it('should render the Button with the correct props', () => { + const { + onClick, + } = wrapper.find('button').props() + assert.equal(propsMethodSpies.showCustomizeGasModal.callCount, 0) + onClick() + assert.equal(propsMethodSpies.showCustomizeGasModal.callCount, 1) + }) + }) +}) diff --git a/ui/app/components/send_/send-content/send-gas-row/send-gas-row.component.js b/ui/app/components/send_/send-content/send-gas-row/send-gas-row.component.js index c80d8c0bb..17cea3d4e 100644 --- a/ui/app/components/send_/send-content/send-gas-row/send-gas-row.component.js +++ b/ui/app/components/send_/send-content/send-gas-row/send-gas-row.component.js @@ -1,7 +1,7 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' import SendRowWrapper from '../send-row-wrapper/' -import GasFeeDisplay from '../../../send/gas-fee-display-v2' +import GasFeeDisplay from './gas-fee-display/gas-fee-display.component' export default class SendGasRow extends Component { diff --git a/ui/app/components/send_/send-content/send-gas-row/send-gas-row.container.js b/ui/app/components/send_/send-content/send-gas-row/send-gas-row.container.js index 20d3daa59..6e6fbc8a8 100644 --- a/ui/app/components/send_/send-content/send-gas-row/send-gas-row.container.js +++ b/ui/app/components/send_/send-content/send-gas-row/send-gas-row.container.js @@ -1,7 +1,7 @@ import { connect } from 'react-redux' import { getConversionRate, - getConvertedCurrency, + getCurrentCurrency, getGasTotal, } from '../../send.selectors.js' import { sendGasIsInError } from './send-gas-row.selectors.js' @@ -13,7 +13,7 @@ export default connect(mapStateToProps, mapDispatchToProps)(SendGasRow) function mapStateToProps (state) { return { conversionRate: getConversionRate(state), - convertedCurrency: getConvertedCurrency(state), + convertedCurrency: getCurrentCurrency(state), gasTotal: getGasTotal(state), gasLoadingError: sendGasIsInError(state), } diff --git a/ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-component.test.js b/ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-component.test.js index e4f05d708..db37f18be 100644 --- a/ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-component.test.js +++ b/ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-component.test.js @@ -5,7 +5,7 @@ import sinon from 'sinon' import SendGasRow from '../send-gas-row.component.js' import SendRowWrapper from '../../send-row-wrapper/send-row-wrapper.component' -import GasFeeDisplay from '../../../../send/gas-fee-display-v2' +import GasFeeDisplay from '../gas-fee-display/gas-fee-display.component' const propsMethodSpies = { showCustomizeGasModal: sinon.spy(), diff --git a/ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-container.test.js b/ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-container.test.js index 9135524d1..e928c8aba 100644 --- a/ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-container.test.js +++ b/ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-container.test.js @@ -19,7 +19,7 @@ proxyquire('../send-gas-row.container.js', { }, '../../send.selectors.js': { getConversionRate: (s) => `mockConversionRate:${s}`, - getConvertedCurrency: (s) => `mockConvertedCurrency:${s}`, + getCurrentCurrency: (s) => `mockConvertedCurrency:${s}`, getGasTotal: (s) => `mockGasTotal:${s}`, }, './send-gas-row.selectors.js': { sendGasIsInError: (s) => `mockGasLoadingError:${s}` }, diff --git a/ui/app/components/send_/send-content/send-to-row/send-to-row.component.js b/ui/app/components/send_/send-content/send-to-row/send-to-row.component.js index 0a83186a5..1c2ecdf9c 100644 --- a/ui/app/components/send_/send-content/send-to-row/send-to-row.component.js +++ b/ui/app/components/send_/send-content/send-to-row/send-to-row.component.js @@ -19,9 +19,9 @@ export default class SendToRow extends Component { updateSendToError: PropTypes.func, }; - handleToChange (to, nickname = '') { + handleToChange (to, nickname = '', toError) { const { updateSendTo, updateSendToError, updateGas } = this.props - const toErrorObject = getToErrorObject(to) + const toErrorObject = getToErrorObject(to, toError) updateSendTo(to, nickname) updateSendToError(toErrorObject) if (toErrorObject.to === null) { @@ -53,7 +53,7 @@ export default class SendToRow extends Component { inError={inError} name={'address'} network={network} - onChange={(newTo, newNickname) => this.handleToChange(newTo, newNickname)} + onChange={({ toAddress, nickname, toError }) => this.handleToChange(toAddress, nickname, toError)} openDropdown={() => openToDropdown()} placeholder={this.context.t('recipientAddress')} to={to} diff --git a/ui/app/components/send_/send-content/send-to-row/send-to-row.utils.js b/ui/app/components/send_/send-content/send-to-row/send-to-row.utils.js index cea51ee20..6b90a9f09 100644 --- a/ui/app/components/send_/send-content/send-to-row/send-to-row.utils.js +++ b/ui/app/components/send_/send-content/send-to-row/send-to-row.utils.js @@ -4,12 +4,10 @@ const { } = require('../../send.constants') const { isValidAddress } = require('../../../../util') -function getToErrorObject (to) { - let toError = null - +function getToErrorObject (to, toError = null) { if (!to) { toError = REQUIRED_ERROR - } else if (!isValidAddress(to)) { + } else if (!isValidAddress(to) && !toError) { toError = INVALID_RECIPIENT_ADDRESS_ERROR } diff --git a/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-component.test.js b/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-component.test.js index 58fe51dcf..781371004 100644 --- a/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-component.test.js +++ b/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-component.test.js @@ -6,8 +6,8 @@ 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}`, + getToErrorObject: (to, toError) => ({ + to: to === false ? null : `mockToErrorObject:${to}${toError}`, }), }, }).default @@ -67,11 +67,11 @@ describe('SendToRow Component', function () { it('should call updateSendToError', () => { assert.equal(propsMethodSpies.updateSendToError.callCount, 0) - instance.handleToChange('mockTo2') + instance.handleToChange('mockTo2', '', 'mockToError') assert.equal(propsMethodSpies.updateSendToError.callCount, 1) assert.deepEqual( propsMethodSpies.updateSendToError.getCall(0).args, - [{ to: 'mockToErrorObject:mockTo2' }] + [{ to: 'mockToErrorObject:mockTo2mockToError' }] ) }) @@ -138,11 +138,11 @@ describe('SendToRow Component', function () { openDropdown() assert.equal(propsMethodSpies.openToDropdown.callCount, 1) assert.equal(SendToRow.prototype.handleToChange.callCount, 0) - onChange('mockNewTo', 'mockNewNickname') + onChange({ toAddress: 'mockNewTo', nickname: 'mockNewNickname', toError: 'mockToError' }) assert.equal(SendToRow.prototype.handleToChange.callCount, 1) assert.deepEqual( SendToRow.prototype.handleToChange.getCall(0).args, - ['mockNewTo', 'mockNewNickname'] + ['mockNewTo', 'mockNewNickname', 'mockToError'] ) }) }) diff --git a/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-utils.test.js b/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-utils.test.js index 615c9581b..4d2447c32 100644 --- a/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-utils.test.js +++ b/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-utils.test.js @@ -40,6 +40,12 @@ describe('send-to-row utils', () => { to: null, }) }) + + it('should return the passed error if to is truthy but invalid if to is truthy and valid', () => { + assert.deepEqual(getToErrorObject('invalid #$ 345878', 'someExplicitError'), { + to: 'someExplicitError', + }) + }) }) }) diff --git a/ui/app/components/send_/send.component.js b/ui/app/components/send_/send.component.js index 516251e22..219b362f2 100644 --- a/ui/app/components/send_/send.component.js +++ b/ui/app/components/send_/send.component.js @@ -3,6 +3,7 @@ import PropTypes from 'prop-types' import PersistentForm from '../../../lib/persistent-form' import { getAmountErrorObject, + getToAddressForGasUpdate, doesAmountErrorRequireUpdate, } from './send.utils' @@ -38,7 +39,7 @@ export default class SendTransactionScreen extends PersistentForm { updateSendTokenBalance: PropTypes.func, }; - updateGas ({ to } = {}) { + updateGas ({ to: updatedToAddress, amount: value } = {}) { const { amount, blockGasLimit, @@ -48,6 +49,7 @@ export default class SendTransactionScreen extends PersistentForm { recentBlocks, selectedAddress, selectedToken = {}, + to: currentToAddress, updateAndSetGasTotal, } = this.props @@ -59,8 +61,8 @@ export default class SendTransactionScreen extends PersistentForm { recentBlocks, selectedAddress, selectedToken, - to: to && to.toLowerCase(), - value: amount, + to: getToAddressForGasUpdate(updatedToAddress, currentToAddress), + value: value || amount, }) } diff --git a/ui/app/components/send_/send.constants.js b/ui/app/components/send_/send.constants.js index df5dee371..8acdf0641 100644 --- a/ui/app/components/send_/send.constants.js +++ b/ui/app/components/send_/send.constants.js @@ -36,6 +36,7 @@ const ONE_GWEI_IN_WEI_HEX = ethUtil.addHexPrefix(conversionUtil('0x1', { })) const SIMPLE_GAS_COST = '0x5208' // Hex for 21000, cost of a simple send. +const BASE_TOKEN_GAS_COST = '0x186a0' // Hex for 100000, a base estimate for token transfers. module.exports = { INSUFFICIENT_FUNDS_ERROR, @@ -52,4 +53,5 @@ module.exports = { REQUIRED_ERROR, SIMPLE_GAS_COST, TOKEN_TRANSFER_FUNCTION_SIGNATURE, + BASE_TOKEN_GAS_COST, } diff --git a/ui/app/components/send_/send.container.js b/ui/app/components/send_/send.container.js index 1fd96d61f..185653c5f 100644 --- a/ui/app/components/send_/send.container.js +++ b/ui/app/components/send_/send.container.js @@ -19,6 +19,7 @@ import { getSendAmount, getSendEditingTransactionId, getSendFromObject, + getSendTo, getTokenBalance, } from './send.selectors' import { @@ -54,6 +55,7 @@ function mapStateToProps (state) { recentBlocks: getRecentBlocks(state), selectedAddress: getSelectedAddress(state), selectedToken: getSelectedToken(state), + to: getSendTo(state), tokenBalance: getTokenBalance(state), tokenContract: getSelectedTokenContract(state), tokenToFiatRate: getSelectedTokenToFiatRate(state), diff --git a/ui/app/components/send_/send.selectors.js b/ui/app/components/send_/send.selectors.js index 7e7cfe2e9..f910f7caf 100644 --- a/ui/app/components/send_/send.selectors.js +++ b/ui/app/components/send_/send.selectors.js @@ -14,7 +14,6 @@ const selectors = { getAmountConversionRate, getBlockGasLimit, getConversionRate, - getConvertedCurrency, getCurrentAccountWithSendEtherInfo, getCurrentCurrency, getCurrentNetwork, @@ -98,10 +97,6 @@ function getConversionRate (state) { return state.metamask.conversionRate } -function getConvertedCurrency (state) { - return state.metamask.currentCurrency -} - function getCurrentAccountWithSendEtherInfo (state) { const currentAddress = getSelectedAddress(state) const accounts = accountsWithSendEtherInfoSelector(state) diff --git a/ui/app/components/send_/send.utils.js b/ui/app/components/send_/send.utils.js index 67699be77..872df1d2f 100644 --- a/ui/app/components/send_/send.utils.js +++ b/ui/app/components/send_/send.utils.js @@ -4,11 +4,13 @@ const { conversionGTE, multiplyCurrencies, conversionGreaterThan, + conversionLessThan, } = require('../../conversion-util') const { calcTokenAmount, } = require('../../token-util') const { + BASE_TOKEN_GAS_COST, INSUFFICIENT_FUNDS_ERROR, INSUFFICIENT_TOKENS_ERROR, NEGATIVE_ETH_ERROR, @@ -20,6 +22,7 @@ const abi = require('ethereumjs-abi') const ethUtil = require('ethereumjs-util') module.exports = { + addGasBuffer, calcGasTotal, calcTokenBalance, doesAmountErrorRequireUpdate, @@ -27,6 +30,7 @@ module.exports = { estimateGasPriceFromRecentBlocks, generateTokenTransferData, getAmountErrorObject, + getToAddressForGasUpdate, isBalanceSufficient, isTokenBalanceSufficient, } @@ -175,12 +179,13 @@ async function estimateGas ({ selectedAddress, selectedToken, blockGasLimit, to, } // if recipient has no code, gas is 21k max: - const hasRecipient = Boolean(to) - if (hasRecipient && !selectedToken) { - const code = await global.eth.getCode(to) + if (!selectedToken) { + const code = Boolean(to) && await global.eth.getCode(to) if (!code || code === '0x') { return SIMPLE_GAS_COST } + } else if (selectedToken && !to) { + return BASE_TOKEN_GAS_COST } paramsForGasEstimate.to = selectedToken ? selectedToken.address : to @@ -201,16 +206,46 @@ async function estimateGas ({ selectedAddress, selectedToken, blockGasLimit, to, err.message.includes('gas required exceeds allowance or always failing transaction') ) if (simulationFailed) { - return resolve(paramsForGasEstimate.gas) + const estimateWithBuffer = addGasBuffer(paramsForGasEstimate.gas, blockGasLimit, 1.5) + return resolve(ethUtil.addHexPrefix(estimateWithBuffer)) } else { return reject(err) } } - return resolve(estimatedGas.toString(16)) + const estimateWithBuffer = addGasBuffer(estimatedGas.toString(16), blockGasLimit, 1.5) + return resolve(ethUtil.addHexPrefix(estimateWithBuffer)) }) }) } +function addGasBuffer (initialGasLimitHex, blockGasLimitHex, bufferMultiplier = 1.5) { + const upperGasLimit = multiplyCurrencies(blockGasLimitHex, 0.9, { + toNumericBase: 'hex', + multiplicandBase: 16, + multiplierBase: 10, + numberOfDecimals: '0', + }) + const bufferedGasLimit = multiplyCurrencies(initialGasLimitHex, bufferMultiplier, { + toNumericBase: 'hex', + multiplicandBase: 16, + multiplierBase: 10, + numberOfDecimals: '0', + }) + + // if initialGasLimit is above blockGasLimit, dont modify it + if (conversionGreaterThan( + { value: initialGasLimitHex, fromNumericBase: 'hex' }, + { value: upperGasLimit, fromNumericBase: 'hex' }, + )) return initialGasLimitHex + // if bufferedGasLimit is below blockGasLimit, use bufferedGasLimit + if (conversionLessThan( + { value: bufferedGasLimit, fromNumericBase: 'hex' }, + { value: upperGasLimit, fromNumericBase: 'hex' }, + )) return bufferedGasLimit + // otherwise use blockGasLimit + return upperGasLimit +} + function generateTokenTransferData ({ toAddress = '0x0', amount = '0x0', selectedToken }) { if (!selectedToken) return return TOKEN_TRANSFER_FUNCTION_SIGNATURE + Array.prototype.map.call( @@ -237,3 +272,7 @@ function estimateGasPriceFromRecentBlocks (recentBlocks) { return lowestPrices[Math.floor(lowestPrices.length / 2)] } + +function getToAddressForGasUpdate (...addresses) { + return [...addresses, ''].find(str => str !== undefined && str !== null).toLowerCase() +} diff --git a/ui/app/components/send_/tests/send-component.test.js b/ui/app/components/send_/tests/send-component.test.js index 4e33d8f63..4ba9b226d 100644 --- a/ui/app/components/send_/tests/send-component.test.js +++ b/ui/app/components/send_/tests/send-component.test.js @@ -201,7 +201,7 @@ describe('Send Component', function () { }) describe('updateGas', () => { - it('should call updateAndSetGasTotal with the correct params', () => { + it('should call updateAndSetGasTotal with the correct params if no to prop is passed', () => { propsMethodSpies.updateAndSetGasTotal.resetHistory() wrapper.instance().updateGas() assert.equal(propsMethodSpies.updateAndSetGasTotal.callCount, 1) @@ -215,12 +215,22 @@ describe('Send Component', function () { recentBlocks: ['mockBlock'], selectedAddress: 'mockSelectedAddress', selectedToken: 'mockSelectedToken', - to: undefined, + to: '', value: 'mockAmount', } ) }) + it('should call updateAndSetGasTotal with the correct params if a to prop is passed', () => { + propsMethodSpies.updateAndSetGasTotal.resetHistory() + wrapper.setProps({ to: 'someAddress' }) + wrapper.instance().updateGas() + assert.equal( + propsMethodSpies.updateAndSetGasTotal.getCall(0).args[0].to, + 'someaddress', + ) + }) + it('should call updateAndSetGasTotal with to set to lowercase if passed', () => { propsMethodSpies.updateAndSetGasTotal.resetHistory() wrapper.instance().updateGas({ to: '0xABC' }) diff --git a/ui/app/components/send_/tests/send-container.test.js b/ui/app/components/send_/tests/send-container.test.js index 056aad148..91484f4d8 100644 --- a/ui/app/components/send_/tests/send-container.test.js +++ b/ui/app/components/send_/tests/send-container.test.js @@ -39,6 +39,7 @@ proxyquire('../send.container.js', { getSelectedTokenContract: (s) => `mockTokenContract:${s}`, getSelectedTokenToFiatRate: (s) => `mockTokenToFiatRate:${s}`, getSendAmount: (s) => `mockAmount:${s}`, + getSendTo: (s) => `mockTo:${s}`, getSendEditingTransactionId: (s) => `mockEditingTransactionId:${s}`, getSendFromObject: (s) => `mockFrom:${s}`, getTokenBalance: (s) => `mockTokenBalance:${s}`, @@ -70,6 +71,7 @@ describe('send container', () => { recentBlocks: 'mockRecentBlocks:mockState', selectedAddress: 'mockSelectedAddress:mockState', selectedToken: 'mockSelectedToken:mockState', + to: 'mockTo:mockState', tokenBalance: 'mockTokenBalance:mockState', tokenContract: 'mockTokenContract:mockState', tokenToFiatRate: 'mockTokenToFiatRate:mockState', diff --git a/ui/app/components/send_/tests/send-selectors.test.js b/ui/app/components/send_/tests/send-selectors.test.js index 152af8059..218da656b 100644 --- a/ui/app/components/send_/tests/send-selectors.test.js +++ b/ui/app/components/send_/tests/send-selectors.test.js @@ -8,7 +8,6 @@ const { getBlockGasLimit, getAmountConversionRate, getConversionRate, - getConvertedCurrency, getCurrentAccountWithSendEtherInfo, getCurrentCurrency, getCurrentNetwork, @@ -154,15 +153,6 @@ describe('send selectors', () => { }) }) - describe('getConvertedCurrency()', () => { - it('should return the currently selected currency', () => { - assert.equal( - getConvertedCurrency(mockState), - 'USD' - ) - }) - }) - describe('getCurrentAccountWithSendEtherInfo()', () => { it('should return the currently selected account with identity info', () => { assert.deepEqual( diff --git a/ui/app/components/send_/tests/send-utils.test.js b/ui/app/components/send_/tests/send-utils.test.js index b3f6372ef..a518a64e9 100644 --- a/ui/app/components/send_/tests/send-utils.test.js +++ b/ui/app/components/send_/tests/send-utils.test.js @@ -2,6 +2,7 @@ import assert from 'assert' import sinon from 'sinon' import proxyquire from 'proxyquire' import { + BASE_TOKEN_GAS_COST, ONE_GWEI_IN_WEI_HEX, SIMPLE_GAS_COST, } from '../send.constants' @@ -18,10 +19,12 @@ const { const stubs = { addCurrencies: sinon.stub().callsFake((a, b, obj) => a + b), 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}x${b}`), calcTokenAmount: sinon.stub().callsFake((a, d) => 'calc:' + a + d), rawEncode: sinon.stub().returns([16, 1100]), + conversionGreaterThan: sinon.stub().callsFake((obj1, obj2) => obj1.value > obj2.value), + conversionLessThan: sinon.stub().callsFake((obj1, obj2) => obj1.value < obj2.value), } const sendUtils = proxyquire('../send.utils.js', { @@ -30,6 +33,8 @@ const sendUtils = proxyquire('../send.utils.js', { conversionUtil: stubs.conversionUtil, conversionGTE: stubs.conversionGTE, multiplyCurrencies: stubs.multiplyCurrencies, + conversionGreaterThan: stubs.conversionGreaterThan, + conversionLessThan: stubs.conversionLessThan, }, '../../token-util': { calcTokenAmount: stubs.calcTokenAmount }, 'ethereumjs-abi': { @@ -44,6 +49,7 @@ const { estimateGasPriceFromRecentBlocks, generateTokenTransferData, getAmountErrorObject, + getToAddressForGasUpdate, calcTokenBalance, isBalanceSufficient, isTokenBalanceSufficient, @@ -255,7 +261,7 @@ describe('send utils', () => { estimateGasMethod: sinon.stub().callsFake( (data, cb) => cb( data.to.match(/willFailBecauseOf:/) ? { message: data.to.match(/:(.+)$/)[1] } : null, - { toString: (n) => `mockToString:${n}` } + { toString: (n) => `0xabc${n}` } ) ), } @@ -279,13 +285,23 @@ describe('send utils', () => { }) it('should call ethQuery.estimateGas with the expected params', async () => { - const result = await estimateGas(baseMockParams) + const result = await sendUtils.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') + assert.equal(result, '0xabc16') + }) + + it('should call ethQuery.estimateGas with the expected params when initialGasLimitHex is lower than the upperGasLimit', async () => { + const result = await estimateGas(Object.assign({}, baseMockParams, { blockGasLimit: '0xbcd' })) + assert.equal(baseMockParams.estimateGasMethod.callCount, 1) + assert.deepEqual( + baseMockParams.estimateGasMethod.getCall(0).args[0], + Object.assign({ gasPrice: undefined, value: undefined }, baseExpectedCall, { gas: '0xbcdx0.95' }) + ) + assert.equal(result, '0xabc16x1.5') }) it('should call ethQuery.estimateGas with a value of 0x0 and the expected data and to if passed a selectedToken', async () => { @@ -300,7 +316,7 @@ describe('send utils', () => { to: 'mockAddress', }) ) - assert.equal(result, 'mockToString:16') + assert.equal(result, '0xabc16') }) it(`should return ${SIMPLE_GAS_COST} if ethQuery.getCode does not return '0x'`, async () => { @@ -309,12 +325,23 @@ describe('send utils', () => { assert.equal(result, SIMPLE_GAS_COST) }) + it(`should return ${SIMPLE_GAS_COST} if not passed a selectedToken or truthy to address`, async () => { + assert.equal(baseMockParams.estimateGasMethod.callCount, 0) + const result = await estimateGas(Object.assign({}, baseMockParams, { to: null })) + 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 ${BASE_TOKEN_GAS_COST} if passed a selectedToken but no to address`, async () => { + const result = await estimateGas(Object.assign({}, baseMockParams, { to: null, selectedToken: { address: '' } })) + assert.equal(result, BASE_TOKEN_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.', @@ -401,4 +428,15 @@ describe('send utils', () => { assert.equal(estimateGasPriceFromRecentBlocks(mockRecentBlocks), '0x5') }) }) + + describe('getToAddressForGasUpdate()', () => { + it('should return empty string if all params are undefined or null', () => { + assert.equal(getToAddressForGasUpdate(undefined, null), '') + }) + + it('should return the first string that is not defined or null in lower case', () => { + assert.equal(getToAddressForGasUpdate('A', null), 'a') + assert.equal(getToAddressForGasUpdate(undefined, 'B'), 'b') + }) + }) }) |