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.
This commit is contained in:
William O'Beirne 2017-12-11 15:36:22 -05:00 committed by Daniel Ternyak
parent e6a958d6c1
commit 610805aadd
3 changed files with 66 additions and 34 deletions

View File

@ -5,6 +5,7 @@ import { State } from 'reducers/rates';
import { rateSymbols, TFetchCCRates } from 'actions/rates'; import { rateSymbols, TFetchCCRates } from 'actions/rates';
import { TokenBalance } from 'selectors/wallet'; import { TokenBalance } from 'selectors/wallet';
import { Balance } from 'libs/wallet'; import { Balance } from 'libs/wallet';
import { ETH_DECIMAL, convertTokenBase } from 'libs/units';
import Spinner from 'components/ui/Spinner'; import Spinner from 'components/ui/Spinner';
import UnitDisplay from 'components/ui/UnitDisplay'; import UnitDisplay from 'components/ui/UnitDisplay';
import './EquivalentValues.scss'; import './EquivalentValues.scss';
@ -28,7 +29,8 @@ export default class EquivalentValues extends React.Component<Props, CmpState> {
currency: ALL_OPTION currency: ALL_OPTION
}; };
private balanceLookup: { [key: string]: Balance['wei'] | undefined } = {}; private balanceLookup: { [key: string]: Balance['wei'] | undefined } = {};
private requestedCurrencies: string[] = []; private decimalLookup: { [key: string]: number } = {};
private requestedCurrencies: string[] | null = null;
public constructor(props) { public constructor(props) {
super(props); super(props);
@ -41,10 +43,7 @@ export default class EquivalentValues extends React.Component<Props, CmpState> {
public componentWillReceiveProps(nextProps) { public componentWillReceiveProps(nextProps) {
const { balance, tokenBalances } = this.props; const { balance, tokenBalances } = this.props;
if ( if (nextProps.balance !== balance || nextProps.tokenBalances !== tokenBalances) {
nextProps.balance !== balance ||
nextProps.tokenBalances !== tokenBalances
) {
this.makeBalanceLookup(nextProps); this.makeBalanceLookup(nextProps);
this.fetchRates(nextProps); this.fetchRates(nextProps);
} }
@ -57,10 +56,7 @@ export default class EquivalentValues extends React.Component<Props, CmpState> {
// There are a bunch of reasons why the incorrect balances might be rendered // There are a bunch of reasons why the incorrect balances might be rendered
// while we have incomplete data that's being fetched. // while we have incomplete data that's being fetched.
const isFetching = const isFetching =
!balance || !balance || balance.isPending || !tokenBalances || Object.keys(rates).length === 0;
balance.isPending ||
!tokenBalances ||
Object.keys(rates).length === 0;
let valuesEl; let valuesEl;
if (!isFetching && (rates[currency] || currency === ALL_OPTION)) { if (!isFetching && (rates[currency] || currency === ALL_OPTION)) {
@ -72,15 +68,9 @@ export default class EquivalentValues extends React.Component<Props, CmpState> {
return ( return (
<li className="EquivalentValues-values-currency" key={key}> <li className="EquivalentValues-values-currency" key={key}>
<span className="EquivalentValues-values-currency-label"> <span className="EquivalentValues-values-currency-label">{key}:</span>{' '}
{key}:
</span>{' '}
<span className="EquivalentValues-values-currency-value"> <span className="EquivalentValues-values-currency-value">
<UnitDisplay <UnitDisplay unit={'ether'} value={values[key]} displayShortBalance={3} />
unit={'ether'}
value={values[key]}
displayShortBalance={3}
/>
</span> </span>
</li> </li>
); );
@ -137,10 +127,10 @@ export default class EquivalentValues extends React.Component<Props, CmpState> {
const tokenBalances = props.tokenBalances || []; const tokenBalances = props.tokenBalances || [];
this.balanceLookup = tokenBalances.reduce( this.balanceLookup = tokenBalances.reduce(
(prev, tk) => { (prev, tk) => {
return { // Piggy-back off of this reduce to add to decimal lookup
...prev, this.decimalLookup[tk.symbol] = tk.decimal;
[tk.symbol]: tk.balance prev[tk.symbol] = tk.balance;
}; return prev;
}, },
{ ETH: props.balance && props.balance.wei } { ETH: props.balance && props.balance.wei }
); );
@ -159,7 +149,7 @@ export default class EquivalentValues extends React.Component<Props, CmpState> {
.sort(); .sort();
// If it's the same currencies as we have, skip it // 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; return;
} }
@ -175,12 +165,10 @@ export default class EquivalentValues extends React.Component<Props, CmpState> {
} { } {
// Recursively call on all currencies // Recursively call on all currencies
if (currency === ALL_OPTION) { if (currency === ALL_OPTION) {
return ['ETH'].concat(this.requestedCurrencies).reduce( return ['ETH'].concat(this.requestedCurrencies || []).reduce(
(prev, curr) => { (prev, curr) => {
const currValues = this.getEquivalentValues(curr); const currValues = this.getEquivalentValues(curr);
rateSymbols.forEach( rateSymbols.forEach(sym => (prev[sym] = prev[sym].add(currValues[sym] || new BN(0))));
sym => (prev[sym] = prev[sym].add(currValues[sym] || new BN(0)))
);
return prev; return prev;
}, },
rateSymbols.reduce((prev, sym) => { rateSymbols.reduce((prev, sym) => {
@ -197,8 +185,13 @@ export default class EquivalentValues extends React.Component<Props, CmpState> {
return {}; 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) => { return rateSymbols.reduce((prev, sym) => {
prev[sym] = balance ? balance.muln(rates[currency][sym]) : null; prev[sym] = adjustedBalance.muln(rates[currency][sym]);
return prev; return prev;
}, {}); }, {});
} }

View File

@ -5,6 +5,8 @@ type UnitKey = keyof typeof Units;
type Wei = BN; type Wei = BN;
type TokenValue = BN; type TokenValue = BN;
export const ETH_DECIMAL = 18;
const Units = { const Units = {
wei: '1', wei: '1',
kwei: '1000', kwei: '1000',
@ -33,9 +35,7 @@ const Units = {
}; };
const handleValues = (input: string | BN) => { const handleValues = (input: string | BN) => {
if (typeof input === 'string') { if (typeof input === 'string') {
return input.startsWith('0x') return input.startsWith('0x') ? new BN(stripHexPrefix(input), 16) : new BN(input);
? new BN(stripHexPrefix(input), 16)
: new BN(input);
} }
if (typeof input === 'number') { if (typeof input === 'number') {
return new BN(input); return new BN(input);
@ -92,12 +92,20 @@ const fromTokenBase = (value: TokenValue, decimal: number) =>
const toTokenBase = (value: string, decimal: number) => const toTokenBase = (value: string, decimal: number) =>
TokenValue(convertedToBaseUnit(value, decimal)); TokenValue(convertedToBaseUnit(value, decimal));
const convertTokenBase = (value: TokenValue, oldDecimal: number, newDecimal: number) => {
if (oldDecimal === newDecimal) {
return value;
}
return toTokenBase(fromTokenBase(value, oldDecimal), newDecimal);
};
export { export {
TokenValue, TokenValue,
fromWei, fromWei,
toWei, toWei,
toTokenBase, toTokenBase,
fromTokenBase, fromTokenBase,
convertTokenBase,
Wei, Wei,
getDecimal, getDecimal,
UnitKey UnitKey

View File

@ -4,6 +4,7 @@ import {
toWei, toWei,
toTokenBase, toTokenBase,
fromTokenBase, fromTokenBase,
convertTokenBase,
getDecimal, getDecimal,
TokenValue TokenValue
} from 'libs/units'; } from 'libs/units';
@ -77,10 +78,10 @@ describe('Units', () => {
const tokens = '732156.34592016'; const tokens = '732156.34592016';
const decimal = 18; const decimal = 18;
const tokenBase = toTokenBase(tokens, decimal); const tokenBase = toTokenBase(tokens, decimal);
it('should equal 732156345920160000000000', () => { it('toTokenBase should equal 732156345920160000000000', () => {
expect(tokenBase.toString()).toEqual('732156345920160000000000'); expect(tokenBase.toString()).toEqual('732156345920160000000000');
}); });
it('should equal 732156.34592016', () => { it('fromTokenBase should equal 732156.34592016', () => {
expect(fromTokenBase(tokenBase, decimal)).toEqual(tokens); expect(fromTokenBase(tokenBase, decimal)).toEqual(tokens);
}); });
}); });
@ -88,12 +89,42 @@ describe('Units', () => {
const tokens = '8000'; const tokens = '8000';
const decimal = 8; const decimal = 8;
const converted = fromTokenBase(TokenValue(tokens), decimal); const converted = fromTokenBase(TokenValue(tokens), decimal);
it('should equal 0.00008', () => { it('fromTokenBase should equal 0.00008', () => {
expect(converted).toEqual('0.00008'); expect(converted).toEqual('0.00008');
}); });
it('should equal 8000', () => { it('toTokenBase should equal 8000', () => {
expect(toTokenBase(converted, decimal)); 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);
});
});
});
}); });
}); });