1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 09:52:26 +01:00

Fix rounding issue when sending max tokens ()

* Fix rounding issue when sending max tokens

* Ensure amount row shows exact amount of max tokens on send screen ()

* Fix tests

* Change stored redux value from BigNumber to hex string. Fix TokenInput default value
This commit is contained in:
Alexander Tseung 2018-11-19 16:06:34 -08:00 committed by GitHub
parent 7fe37276a1
commit 4c87c05a02
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 138 additions and 104 deletions

View File

@ -840,7 +840,7 @@ describe('MetaMask', function () {
})
it('renders the balance for the new token', async () => {
const balance = await findElement(driver, By.css('.transaction-view-balance .transaction-view-balance__token-balance'))
const balance = await findElement(driver, By.css('.transaction-view-balance .transaction-view-balance__primary-balance'))
await driver.wait(until.elementTextMatches(balance, /^100\s*TST\s*$/))
const tokenAmount = await balance.getText()
assert.ok(/^100\s*TST\s*$/.test(tokenAmount))
@ -1019,8 +1019,8 @@ describe('MetaMask', function () {
// test cancelled on firefox until https://github.com/mozilla/geckodriver/issues/906 is resolved,
// or possibly until we use latest version of firefox in the tests
if (process.env.SELENIUM_BROWSER !== 'firefox') {
const tokenBalanceAmount = await findElement(driver, By.css('.transaction-view-balance__token-balance'))
assert.equal(await tokenBalanceAmount.getText(), '43 TST')
const tokenBalanceAmount = await findElements(driver, By.css('.transaction-view-balance__primary-balance'))
await driver.wait(until.elementTextMatches(tokenBalanceAmount[0], /43\s*TST/))
}
})
})
@ -1180,7 +1180,7 @@ describe('MetaMask', function () {
})
it('renders the balance for the chosen token', async () => {
const balance = await findElement(driver, By.css('.transaction-view-balance__token-balance'))
const balance = await findElement(driver, By.css('.transaction-view-balance__primary-balance'))
await driver.wait(until.elementTextMatches(balance, /0\s*BAT/))
await delay(regularDelayMs)
})

View File

@ -148,7 +148,7 @@ describe('Selectors', function () {
it('#getSelectedTokenToFiatRate', () => {
const selectedTokenToFiatRate = selectors.getSelectedTokenToFiatRate(mockState)
assert.equal(selectedTokenToFiatRate, '0.21880988420033493')
assert.equal(selectedTokenToFiatRate, '0.21880988420033492152')
})
describe('#getSelectedTokenContract', () => {

View File

@ -995,7 +995,7 @@ function updateSendTokenBalance ({
.then(usersToken => {
if (usersToken) {
const newTokenBalance = calcTokenBalance({ selectedToken, usersToken })
dispatch(setSendTokenBalance(newTokenBalance.toString(10)))
dispatch(setSendTokenBalance(newTokenBalance))
}
})
.catch(err => {

View File

@ -20,15 +20,24 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
currency,
denomination,
hideLabel,
displayValue: propsDisplayValue,
suffix: propsSuffix,
...restOwnProps
} = ownProps
const toCurrency = currency || currentCurrency
const convertedValue = getValueFromWeiHex({
value, fromCurrency: nativeCurrency, toCurrency, conversionRate, numberOfDecimals, toDenomination: denomination,
})
const displayValue = formatCurrency(convertedValue, toCurrency)
const suffix = hideLabel ? undefined : toCurrency.toUpperCase()
const displayValue = propsDisplayValue || formatCurrency(
getValueFromWeiHex({
value,
fromCurrency: nativeCurrency,
toCurrency, conversionRate,
numberOfDecimals,
toDenomination: denomination,
}),
toCurrency
)
const suffix = propsSuffix || (hideLabel ? undefined : toCurrency.toUpperCase())
return {
...restStateProps,

View File

@ -131,7 +131,7 @@ describe('CurrencyDisplay container', () => {
},
result: {
nativeCurrency: 'ETH',
displayValue: '1e-9',
displayValue: '0.000000001',
suffix: undefined,
},
},

View File

@ -40,6 +40,8 @@
@import './tabs/index';
@import './token-balance/index';
@import './transaction-activity-log/index';
@import './transaction-breakdown/index';

View File

@ -11,11 +11,11 @@ export default class AmountMaxButton extends Component {
setAmountToMax: PropTypes.func,
setMaxModeTo: PropTypes.func,
tokenBalance: PropTypes.string,
};
}
static contextTypes = {
t: PropTypes.func,
};
}
setMaxAmount () {
const {

View File

@ -5,16 +5,23 @@ const {
const ethUtil = require('ethereumjs-util')
function calcMaxAmount ({ balance, gasTotal, selectedToken, tokenBalance }) {
const { decimals } = selectedToken || {}
const multiplier = Math.pow(10, Number(decimals || 0))
const { decimals } = selectedToken || {}
const multiplier = Math.pow(10, Number(decimals || 0))
return selectedToken
? multiplyCurrencies(tokenBalance, multiplier, {toNumericBase: 'hex'})
: subtractCurrencies(
ethUtil.addHexPrefix(balance),
ethUtil.addHexPrefix(gasTotal),
{ toNumericBase: 'hex' }
)
return selectedToken
? multiplyCurrencies(
tokenBalance,
multiplier,
{
toNumericBase: 'hex',
multiplicandBase: 16,
}
)
: subtractCurrencies(
ethUtil.addHexPrefix(balance),
ethUtil.addHexPrefix(gasTotal),
{ toNumericBase: 'hex' }
)
}
module.exports = {

View File

@ -19,7 +19,7 @@ describe('amount-max-button utils', () => {
selectedToken: {
decimals: 10,
},
tokenBalance: 100,
tokenBalance: '64',
}), 'e8d4a51000')
})
})

View File

@ -26,11 +26,11 @@ export default class SendAmountRow extends Component {
updateSendAmount: PropTypes.func,
updateSendAmountError: PropTypes.func,
updateGas: PropTypes.func,
};
}
static contextTypes = {
t: PropTypes.func,
};
}
validateAmount (amount) {
const {
@ -58,7 +58,6 @@ export default class SendAmountRow extends Component {
if (selectedToken) {
updateGasFeeError({
amount,
amountConversionRate,
balance,
conversionRate,

View File

@ -45,10 +45,10 @@ function mapDispatchToProps (dispatch) {
setMaxModeTo: bool => dispatch(setMaxModeTo(bool)),
updateSendAmount: newAmount => dispatch(updateSendAmount(newAmount)),
updateGasFeeError: (amountDataObject) => {
dispatch(updateSendErrors(getGasFeeErrorObject(amountDataObject)))
dispatch(updateSendErrors(getGasFeeErrorObject(amountDataObject)))
},
updateSendAmountError: (amountDataObject) => {
dispatch(updateSendErrors(getAmountErrorObject(amountDataObject)))
dispatch(updateSendErrors(getAmountErrorObject(amountDataObject)))
},
}
}

View File

@ -82,7 +82,6 @@ describe('SendAmountRow Component', function () {
assert.deepEqual(
propsMethodSpies.updateGasFeeError.getCall(0).args,
[{
amount: 'someAmount',
amountConversionRate: 'mockAmountConversionRate',
balance: 'mockBalance',
conversionRate: 7,

View File

@ -12,11 +12,11 @@ export default class FromDropdown extends Component {
onSelect: PropTypes.func,
openDropdown: PropTypes.func,
selectedAccount: PropTypes.object,
};
}
static contextTypes = {
t: PropTypes.func,
};
}
render () {
const {

View File

@ -15,11 +15,11 @@ export default class SendFromRow extends Component {
tokenContract: PropTypes.object,
updateSendFrom: PropTypes.func,
setSendTokenBalance: PropTypes.func,
};
}
static contextTypes = {
t: PropTypes.func,
};
}
async handleFromChange (newFrom) {
const {
@ -32,6 +32,7 @@ export default class SendFromRow extends Component {
const usersToken = await tokenContract.balanceOf(newFrom.address)
setSendTokenBalance(usersToken)
}
updateSendFrom(newFrom)
}

View File

@ -10,8 +10,8 @@ import {
} from './send-from-row.selectors.js'
import { calcTokenBalance } from '../../send.utils.js'
import {
updateSendFrom,
setSendTokenBalance,
updateSendFrom,
setSendTokenBalance,
} from '../../../../actions'
import {
closeFromDropdown,
@ -37,10 +37,10 @@ function mapDispatchToProps (dispatch) {
openFromDropdown: () => dispatch(openFromDropdown()),
updateSendFrom: newFrom => dispatch(updateSendFrom(newFrom)),
setSendTokenBalance: (usersToken, selectedToken) => {
if (!usersToken) return
if (!usersToken) return
const tokenBalance = calcTokenBalance({ usersToken, selectedToken })
dispatch(setSendTokenBalance(tokenBalance))
const tokenBalance = calcTokenBalance({ usersToken, selectedToken })
dispatch(setSendTokenBalance(tokenBalance))
},
}
}

View File

@ -12,11 +12,11 @@ export default class SendGasRow extends Component {
gasLoadingError: PropTypes.bool,
gasTotal: PropTypes.string,
showCustomizeGasModal: PropTypes.func,
};
}
static contextTypes = {
t: PropTypes.func,
};
}
render () {
const {

View File

@ -1,8 +1,8 @@
import { connect } from 'react-redux'
import {
getConversionRate,
getCurrentCurrency,
getGasTotal,
getConversionRate,
getCurrentCurrency,
getGasTotal,
} from '../../send.selectors.js'
import { getGasLoadingError, gasFeeIsInError } from './send-gas-row.selectors.js'
import { showModal } from '../../../../actions'

View File

@ -19,11 +19,11 @@ export default class SendToRow extends Component {
updateSendTo: PropTypes.func,
updateSendToError: PropTypes.func,
scanQrCode: PropTypes.func,
};
}
static contextTypes = {
t: PropTypes.func,
};
}
handleToChange (to, nickname = '', toError) {
const { hasHexData, updateSendTo, updateSendToError, updateGas } = this.props

View File

@ -26,11 +26,11 @@ export default class SendFooter extends Component {
tokenBalance: PropTypes.string,
unapprovedTxs: PropTypes.object,
update: PropTypes.func,
};
}
static contextTypes = {
t: PropTypes.func,
};
}
onCancel () {
this.props.clearSend()

View File

@ -41,11 +41,11 @@ export default class SendTransactionScreen extends PersistentForm {
scanQrCode: PropTypes.func,
qrCodeDetected: PropTypes.func,
qrCodeData: PropTypes.object,
};
}
static contextTypes = {
t: PropTypes.func,
};
}
componentWillReceiveProps (nextProps) {
if (nextProps.qrCodeData) {
@ -138,14 +138,12 @@ export default class SendTransactionScreen extends PersistentForm {
})
const gasFeeErrorObject = selectedToken
? getGasFeeErrorObject({
amount,
amountConversionRate,
balance,
conversionRate,
gasTotal,
primaryCurrency,
selectedToken,
tokenBalance,
})
: { gasFee: null }
updateSendErrors(Object.assign(amountErrorObject, gasFeeErrorObject))

View File

@ -89,11 +89,10 @@ function isTokenBalanceSufficient ({
const tokenBalanceIsSufficient = conversionGTE(
{
value: tokenBalance,
fromNumericBase: 'dec',
fromNumericBase: 'hex',
},
{
value: calcTokenAmount(amountInDec, decimals),
fromNumericBase: 'dec',
},
)
@ -151,7 +150,6 @@ function getAmountErrorObject ({
}
function getGasFeeErrorObject ({
amount,
amountConversionRate,
balance,
conversionRate,
@ -180,7 +178,7 @@ function getGasFeeErrorObject ({
function calcTokenBalance ({ selectedToken, usersToken }) {
const { decimals } = selectedToken || {}
return calcTokenAmount(usersToken.balance.toString(), decimals) + ''
return calcTokenAmount(usersToken.balance.toString(), decimals).toString(16)
}
function doesAmountErrorRequireUpdate ({

View File

@ -158,14 +158,12 @@ describe('Send Component', function () {
assert.deepEqual(
utilsMethodStubs.getGasFeeErrorObject.getCall(0).args[0],
{
amount: 'mockAmount',
amountConversionRate: 'mockAmountConversionRate',
balance: 'mockBalance',
conversionRate: 10,
gasTotal: 'mockGasTotal',
primaryCurrency: 'mockPrimaryCurrency',
selectedToken: 'mockSelectedToken',
tokenBalance: 'mockTokenBalance',
}
)
})

View File

@ -285,11 +285,10 @@ describe('send utils', () => {
[
{
value: 123,
fromNumericBase: 'dec',
fromNumericBase: 'hex',
},
{
value: 'calc:1610',
fromNumericBase: 'dec',
},
]
)

View File

@ -0,0 +1,14 @@
.token-balance-component {
display: flex;
align-items: center;
&__text {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
&__suffix {
padding-left: 4px;
}
}

View File

@ -1,6 +1,6 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
import CurrencyDisplay from '../currency-display'
export default class TokenBalance extends PureComponent {
static propTypes = {
@ -12,12 +12,14 @@ export default class TokenBalance extends PureComponent {
}
render () {
const { className, string, withSymbol, symbol } = this.props
const { className, string, symbol } = this.props
return (
<div className={classnames('hide-text-overflow', className)}>
{ string + (withSymbol ? ` ${symbol}` : '') }
</div>
<CurrencyDisplay
className={className}
displayValue={string}
suffix={symbol}
/>
)
}
}

View File

@ -1,6 +1,6 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import CurrencyDisplay from '../currency-display/currency-display.component'
import CurrencyDisplay from '../currency-display'
import { getTokenData } from '../../helpers/transactions.util'
import { getTokenValue, calcTokenAmount } from '../../token-util'
@ -12,6 +12,7 @@ export default class TokenCurrencyDisplay extends PureComponent {
state = {
displayValue: '',
suffix: '',
}
componentDidMount () {
@ -29,25 +30,27 @@ export default class TokenCurrencyDisplay extends PureComponent {
setDisplayValue () {
const { transactionData: data, token } = this.props
const { decimals = '', symbol = '' } = token
const { decimals = '', symbol: suffix = '' } = token
const tokenData = getTokenData(data)
let displayValue
if (tokenData.params && tokenData.params.length) {
const tokenValue = getTokenValue(tokenData.params)
const tokenAmount = calcTokenAmount(tokenValue, decimals)
displayValue = `${tokenAmount} ${symbol}`
displayValue = calcTokenAmount(tokenValue, decimals).toString()
}
this.setState({ displayValue })
this.setState({ displayValue, suffix })
}
render () {
const { displayValue, suffix } = this.state
return (
<CurrencyDisplay
{...this.props}
displayValue={this.state.displayValue}
displayValue={displayValue}
suffix={suffix}
/>
)
}

View File

@ -32,7 +32,7 @@ export default class TokenInput extends PureComponent {
super(props)
const { value: hexValue } = props
const decimalValue = hexValue ? this.getDecimalValue(props) : 0
const decimalValue = hexValue ? this.getValue(props) : 0
this.state = {
decimalValue,
@ -46,12 +46,12 @@ export default class TokenInput extends PureComponent {
const { hexValue: stateHexValue } = this.state
if (prevPropsHexValue !== propsHexValue && propsHexValue !== stateHexValue) {
const decimalValue = this.getDecimalValue(this.props)
const decimalValue = this.getValue(this.props)
this.setState({ hexValue: propsHexValue, decimalValue })
}
}
getDecimalValue (props) {
getValue (props) {
const { value: hexValue, selectedToken: { decimals, symbol } = {} } = props
const multiplier = Math.pow(10, Number(decimals || 0))
@ -63,7 +63,7 @@ export default class TokenInput extends PureComponent {
invertConversionRate: true,
})
return Number(decimalValueString) || 0
return Number(decimalValueString) ? decimalValueString : ''
}
handleChange = decimalValue => {

View File

@ -80,6 +80,8 @@
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
min-width: 0;
max-width: 100%;
&--primary {
text-align: end;

View File

@ -6,6 +6,12 @@
height: 54px;
min-width: 0;
@media screen and (max-width: $break-small) {
flex-direction: column;
height: initial;
width: 100%;
}
&__balance {
margin: 0 12px;
display: flex;
@ -15,17 +21,8 @@
@media screen and (max-width: $break-small) {
align-items: center;
margin: 16px 0;
}
}
&__token-balance {
margin-left: 12px;
font-size: 1.5rem;
@media screen and (max-width: $break-small) {
margin: 12px 0;
margin-left: 0;
font-size: 1.75rem;
padding: 0 16px;
max-width: 100%;
}
}
@ -34,6 +31,7 @@
@media screen and (max-width: $break-small) {
font-size: 1.75rem;
width: 100%;
}
}
@ -51,6 +49,7 @@
@media screen and (max-width: $break-small) {
flex-direction: column;
width: 100%;
}
}
@ -71,9 +70,4 @@
margin-right: 12px;
}
}
@media screen and (max-width: $break-small) {
flex-direction: column;
height: initial
}
}

View File

@ -26,11 +26,13 @@ export default class TransactionViewBalance extends PureComponent {
return selectedToken
? (
<TokenBalance
token={selectedToken}
withSymbol
className="transaction-view-balance__token-balance"
/>
<div className="transaction-view-balance__balance">
<TokenBalance
token={selectedToken}
withSymbol
className="transaction-view-balance__primary-balance"
/>
</div>
) : (
<div className="transaction-view-balance__balance">
<UserPreferencedCurrencyDisplay

View File

@ -38,6 +38,10 @@
align-items: center;
}
&__suffix {
margin-left: 3px;
}
&--error {
border-color: $red;
}

View File

@ -66,7 +66,7 @@ export default class UnitInput extends PureComponent {
const valueString = String(value)
const valueLength = valueString.length || 1
const decimalPointDeficit = valueString.match(/\./) ? -0.5 : 0
return (valueLength + decimalPointDeficit + 0.75) + 'ch'
return (valueLength + decimalPointDeficit + 0.5) + 'ch'
}
render () {

View File

@ -62,7 +62,7 @@ const toSpecifiedDenomination = {
}
const baseChange = {
hex: n => n.toString(16),
dec: n => Number(n).toString(10),
dec: n => (new BigNumber(n)).toString(10),
BN: n => new BN(n.toString(16)),
}

View File

@ -552,6 +552,7 @@
&__form-field {
flex: 1 1 auto;
min-width: 0;
.currency-display {
color: $tundora;
@ -580,6 +581,7 @@
line-height: 22px;
width: 88px;
font-weight: 400;
flex: 0 0 auto;
}
&__from-dropdown {

View File

@ -33,7 +33,7 @@ function reduceMetamask (state, action) {
gasLimit: null,
gasPrice: null,
gasTotal: null,
tokenBalance: null,
tokenBalance: '0x0',
from: '',
to: '',
amount: '0x0',

View File

@ -137,11 +137,12 @@ export const tokenAmountAndToAddressSelector = createSelector(
const valueParam = params.find(param => param.name === TOKEN_PARAM_VALUE)
toAddress = toParam ? toParam.value : params[0].value
const value = valueParam ? Number(valueParam.value) : Number(params[1].value)
tokenAmount = roundExponential(value)
if (tokenDecimals) {
tokenAmount = calcTokenAmount(value, tokenDecimals)
tokenAmount = calcTokenAmount(value, tokenDecimals).toNumber()
}
tokenAmount = roundExponential(tokenAmount)
}
return {
@ -163,7 +164,7 @@ export const approveTokenAmountAndToAddressSelector = createSelector(
const value = Number(params.find(param => param.name === TOKEN_PARAM_VALUE).value)
if (tokenDecimals) {
tokenAmount = calcTokenAmount(value, tokenDecimals)
tokenAmount = calcTokenAmount(value, tokenDecimals).toNumber()
}
tokenAmount = roundExponential(tokenAmount)
@ -188,7 +189,7 @@ export const sendTokenTokenAmountAndToAddressSelector = createSelector(
let value = Number(params.find(param => param.name === TOKEN_PARAM_VALUE).value)
if (tokenDecimals) {
value = calcTokenAmount(value, tokenDecimals)
value = calcTokenAmount(value, tokenDecimals).toNumber()
}
tokenAmount = roundExponential(value)

View File

@ -109,7 +109,7 @@ export function tokenInfoGetter () {
export function calcTokenAmount (value, decimals) {
const multiplier = Math.pow(10, Number(decimals || 0))
return new BigNumber(String(value)).div(multiplier).toNumber()
return new BigNumber(String(value)).div(multiplier)
}
export function getTokenValue (tokenParams = []) {