From 610805aaddd7cb6395a0f202b2ac82411777cf11 Mon Sep 17 00:00:00 2001 From: William O'Beirne Date: Mon, 11 Dec 2017 15:36:22 -0500 Subject: [PATCH] Equivalent Values Fixes (#500) * Initialize with no requested currencies, so that the initial request always fires. * Adjust tokens with different decimals for equivalent values. * Reuse libs units function. * Create lib function and tests for base conversion behavior. --- .../BalanceSidebar/EquivalentValues.tsx | 47 ++++++++----------- common/libs/units.ts | 14 ++++-- spec/libs/units.spec.ts | 39 +++++++++++++-- 3 files changed, 66 insertions(+), 34 deletions(-) diff --git a/common/components/BalanceSidebar/EquivalentValues.tsx b/common/components/BalanceSidebar/EquivalentValues.tsx index c822259a..6e7e8c1b 100644 --- a/common/components/BalanceSidebar/EquivalentValues.tsx +++ b/common/components/BalanceSidebar/EquivalentValues.tsx @@ -5,6 +5,7 @@ import { State } from 'reducers/rates'; import { rateSymbols, TFetchCCRates } from 'actions/rates'; import { TokenBalance } from 'selectors/wallet'; import { Balance } from 'libs/wallet'; +import { ETH_DECIMAL, convertTokenBase } from 'libs/units'; import Spinner from 'components/ui/Spinner'; import UnitDisplay from 'components/ui/UnitDisplay'; import './EquivalentValues.scss'; @@ -28,7 +29,8 @@ export default class EquivalentValues extends React.Component { currency: ALL_OPTION }; private balanceLookup: { [key: string]: Balance['wei'] | undefined } = {}; - private requestedCurrencies: string[] = []; + private decimalLookup: { [key: string]: number } = {}; + private requestedCurrencies: string[] | null = null; public constructor(props) { super(props); @@ -41,10 +43,7 @@ export default class EquivalentValues extends React.Component { public componentWillReceiveProps(nextProps) { const { balance, tokenBalances } = this.props; - if ( - nextProps.balance !== balance || - nextProps.tokenBalances !== tokenBalances - ) { + if (nextProps.balance !== balance || nextProps.tokenBalances !== tokenBalances) { this.makeBalanceLookup(nextProps); this.fetchRates(nextProps); } @@ -57,10 +56,7 @@ export default class EquivalentValues extends React.Component { // There are a bunch of reasons why the incorrect balances might be rendered // while we have incomplete data that's being fetched. const isFetching = - !balance || - balance.isPending || - !tokenBalances || - Object.keys(rates).length === 0; + !balance || balance.isPending || !tokenBalances || Object.keys(rates).length === 0; let valuesEl; if (!isFetching && (rates[currency] || currency === ALL_OPTION)) { @@ -72,15 +68,9 @@ export default class EquivalentValues extends React.Component { return (
  • - - {key}: - {' '} + {key}:{' '} - +
  • ); @@ -137,10 +127,10 @@ export default class EquivalentValues extends React.Component { const tokenBalances = props.tokenBalances || []; this.balanceLookup = tokenBalances.reduce( (prev, tk) => { - return { - ...prev, - [tk.symbol]: tk.balance - }; + // Piggy-back off of this reduce to add to decimal lookup + this.decimalLookup[tk.symbol] = tk.decimal; + prev[tk.symbol] = tk.balance; + return prev; }, { ETH: props.balance && props.balance.wei } ); @@ -159,7 +149,7 @@ export default class EquivalentValues extends React.Component { .sort(); // If it's the same currencies as we have, skip it - if (currencies.join() === this.requestedCurrencies.join()) { + if (this.requestedCurrencies && currencies.join() === this.requestedCurrencies.join()) { return; } @@ -175,12 +165,10 @@ export default class EquivalentValues extends React.Component { } { // Recursively call on all currencies if (currency === ALL_OPTION) { - return ['ETH'].concat(this.requestedCurrencies).reduce( + return ['ETH'].concat(this.requestedCurrencies || []).reduce( (prev, curr) => { const currValues = this.getEquivalentValues(curr); - rateSymbols.forEach( - sym => (prev[sym] = prev[sym].add(currValues[sym] || new BN(0))) - ); + rateSymbols.forEach(sym => (prev[sym] = prev[sym].add(currValues[sym] || new BN(0)))); return prev; }, rateSymbols.reduce((prev, sym) => { @@ -197,8 +185,13 @@ export default class EquivalentValues extends React.Component { return {}; } + // Tokens with non-ether like decimals need to be adjusted to match + const decimal = + this.decimalLookup[currency] === undefined ? ETH_DECIMAL : this.decimalLookup[currency]; + const adjustedBalance = convertTokenBase(balance, decimal, ETH_DECIMAL); + return rateSymbols.reduce((prev, sym) => { - prev[sym] = balance ? balance.muln(rates[currency][sym]) : null; + prev[sym] = adjustedBalance.muln(rates[currency][sym]); return prev; }, {}); } diff --git a/common/libs/units.ts b/common/libs/units.ts index 5df2c93d..fc7f795e 100644 --- a/common/libs/units.ts +++ b/common/libs/units.ts @@ -5,6 +5,8 @@ type UnitKey = keyof typeof Units; type Wei = BN; type TokenValue = BN; +export const ETH_DECIMAL = 18; + const Units = { wei: '1', kwei: '1000', @@ -33,9 +35,7 @@ const Units = { }; const handleValues = (input: string | BN) => { if (typeof input === 'string') { - return input.startsWith('0x') - ? new BN(stripHexPrefix(input), 16) - : new BN(input); + return input.startsWith('0x') ? new BN(stripHexPrefix(input), 16) : new BN(input); } if (typeof input === 'number') { return new BN(input); @@ -92,12 +92,20 @@ const fromTokenBase = (value: TokenValue, decimal: number) => const toTokenBase = (value: string, decimal: number) => TokenValue(convertedToBaseUnit(value, decimal)); +const convertTokenBase = (value: TokenValue, oldDecimal: number, newDecimal: number) => { + if (oldDecimal === newDecimal) { + return value; + } + return toTokenBase(fromTokenBase(value, oldDecimal), newDecimal); +}; + export { TokenValue, fromWei, toWei, toTokenBase, fromTokenBase, + convertTokenBase, Wei, getDecimal, UnitKey diff --git a/spec/libs/units.spec.ts b/spec/libs/units.spec.ts index b0829a55..5951a029 100644 --- a/spec/libs/units.spec.ts +++ b/spec/libs/units.spec.ts @@ -4,6 +4,7 @@ import { toWei, toTokenBase, fromTokenBase, + convertTokenBase, getDecimal, TokenValue } from 'libs/units'; @@ -77,10 +78,10 @@ describe('Units', () => { const tokens = '732156.34592016'; const decimal = 18; const tokenBase = toTokenBase(tokens, decimal); - it('should equal 732156345920160000000000', () => { + it('toTokenBase should equal 732156345920160000000000', () => { expect(tokenBase.toString()).toEqual('732156345920160000000000'); }); - it('should equal 732156.34592016', () => { + it('fromTokenBase should equal 732156.34592016', () => { expect(fromTokenBase(tokenBase, decimal)).toEqual(tokens); }); }); @@ -88,12 +89,42 @@ describe('Units', () => { const tokens = '8000'; const decimal = 8; const converted = fromTokenBase(TokenValue(tokens), decimal); - it('should equal 0.00008', () => { + it('fromTokenBase should equal 0.00008', () => { expect(converted).toEqual('0.00008'); }); - it('should equal 8000', () => { + it('toTokenBase should equal 8000', () => { expect(toTokenBase(converted, decimal)); }); }); + describe('convertTokenBase', () => { + const conversions = [ + { + oldDecimal: 0, + newDecimal: 18, + startValue: '42', + endValue: '42000000000000000000' + }, + { + oldDecimal: 6, + newDecimal: 12, + startValue: '547834782', + endValue: '547834782000000' + }, + { + oldDecimal: 18, + newDecimal: 18, + startValue: '311095801958902158012580', + endValue: '311095801958902158012580' + } + ]; + + conversions.forEach(c => { + it(`should convert decimal ${c.oldDecimal} to decimal ${c.newDecimal}`, () => { + const tokenValue = TokenValue(c.startValue); + const converted = convertTokenBase(tokenValue, c.oldDecimal, c.newDecimal); + expect(converted.toString()).toEqual(c.endValue); + }); + }); + }); }); });