diff --git a/app/core/Engine.test.js b/app/core/Engine.test.js index 8ee16837372..dc49543ba6b 100644 --- a/app/core/Engine.test.js +++ b/app/core/Engine.test.js @@ -1,7 +1,9 @@ import Engine from './Engine'; import { backgroundState } from '../util/test/initial-root-state'; +import { zeroAddress } from 'ethereumjs-util'; jest.unmock('./Engine'); +jest.mock('../store', () => ({ store: { getState: jest.fn(() => ({})) } })); describe('Engine', () => { it('should expose an API', () => { @@ -91,4 +93,154 @@ describe('Engine', () => { `No account found for address: ${invalidAddress}`, ); }); + + describe('getTotalFiatAccountBalance', () => { + let engine; + afterEach(() => engine?.destroyEngineInstance()); + + const selectedAddress = '0x9DeE4BF1dE9E3b930E511Db5cEBEbC8d6F855Db0'; + const chainId = '0x1'; + const ticker = 'ETH'; + const ethConversionRate = 4000; // $4,000 / ETH + + const state = { + AccountsController: { + internalAccounts: { + selectedAccount: selectedAddress, + accounts: { [selectedAddress]: { address: selectedAddress } }, + }, + }, + NetworkController: { + state: { providerConfig: { chainId, ticker } }, + }, + CurrencyRateController: { + currencyRates: { + [ticker]: { conversionRate: ethConversionRate }, + }, + }, + }; + + it('calculates when theres no balances', () => { + engine = Engine.init(state); + const totalFiatBalance = engine.getTotalFiatAccountBalance(); + expect(totalFiatBalance).toStrictEqual({ + ethFiat: 0, + ethFiat1dAgo: 0, + tokenFiat: 0, + tokenFiat1dAgo: 0, + }); + }); + + it('calculates when theres only ETH', () => { + const ethBalance = 1; // 1 ETH + const ethPricePercentChange1d = 5; // up 5% + + engine = Engine.init({ + ...state, + AccountTrackerController: { + accountsByChainId: { + [chainId]: { + [selectedAddress]: { balance: ethBalance * 1e18 }, + }, + }, + }, + TokenRatesController: { + marketData: { + [chainId]: { + [zeroAddress()]: { + pricePercentChange1d: ethPricePercentChange1d, + }, + }, + }, + }, + }); + + const totalFiatBalance = engine.getTotalFiatAccountBalance(); + + const ethFiat = ethBalance * ethConversionRate; + expect(totalFiatBalance).toStrictEqual({ + ethFiat, + ethFiat1dAgo: ethFiat / (1 + ethPricePercentChange1d / 100), + tokenFiat: 0, + tokenFiat1dAgo: 0, + }); + }); + + it('calculates when there are ETH and tokens', () => { + const ethBalance = 1; + const ethPricePercentChange1d = 5; + + const tokens = [ + { + address: '0x001', + balance: 1, + price: '1', + pricePercentChange1d: -1, + }, + { + address: '0x002', + balance: 2, + price: '2', + pricePercentChange1d: 2, + }, + ]; + + engine = Engine.init({ + ...state, + AccountTrackerController: { + accountsByChainId: { + [chainId]: { + [selectedAddress]: { balance: ethBalance * 1e18 }, + }, + }, + }, + TokensController: { + tokens: tokens.map((token) => ({ + address: token.address, + balance: token.balance, + })), + }, + TokenRatesController: { + marketData: { + [chainId]: { + [zeroAddress()]: { + pricePercentChange1d: ethPricePercentChange1d, + }, + ...tokens.reduce( + (acc, token) => ({ + ...acc, + [token.address]: { + price: token.price, + pricePercentChange1d: token.pricePercentChange1d, + }, + }), + {}, + ), + }, + }, + }, + }); + + const totalFiatBalance = engine.getTotalFiatAccountBalance(); + + const ethFiat = ethBalance * ethConversionRate; + const [tokenFiat, tokenFiat1dAgo] = tokens.reduce( + ([fiat, fiat1d], token) => { + const value = token.balance * token.price * ethConversionRate; + return [ + fiat + value, + fiat1d + value / (1 + token.pricePercentChange1d / 100), + ]; + }, + [0, 0], + ); + + expect(totalFiatBalance).toStrictEqual({ + ethFiat, + ethFiat1dAgo: ethFiat / (1 + ethPricePercentChange1d / 100), + tokenFiat, + tokenFiat1dAgo, + }); + }); + }); }); diff --git a/app/core/Engine.ts b/app/core/Engine.ts index 1269832365d..9bb9bc9e28c 100644 --- a/app/core/Engine.ts +++ b/app/core/Engine.ts @@ -1625,9 +1625,7 @@ class Engine { toChecksumHexAddress(selectedInternalAccount.address); const { currentCurrency } = CurrencyRateController.state; const { chainId, ticker } = NetworkController.state.providerConfig; - const { - settings: { showFiatOnTestnets }, - } = store.getState(); + const { settings: { showFiatOnTestnets } = {} } = store.getState(); if (isTestNet(chainId) && !showFiatOnTestnets) { return { ethFiat: 0, tokenFiat: 0, ethFiat1dAgo: 0, tokenFiat1dAgo: 0 }; @@ -1639,7 +1637,8 @@ class Engine { const { accountsByChainId } = AccountTrackerController.state; const { tokens } = TokensController.state; - const { marketData: tokenExchangeRates } = TokenRatesController.state; + const { marketData } = TokenRatesController.state; + const tokenExchangeRates = marketData?.[toHexadecimal(chainId)]; let ethFiat = 0; let ethFiat1dAgo = 0; @@ -1660,24 +1659,19 @@ class Engine { ); } + const ethPricePercentChange1d = + tokenExchangeRates?.[zeroAddress() as Hex]?.pricePercentChange1d; + ethFiat1dAgo = - ethFiat + - (ethFiat * - tokenExchangeRates?.[toHexadecimal(chainId)]?.[ - zeroAddress() as `0x${string}` - ]?.pricePercentChange1d) / - 100 || ethFiat; + ethPricePercentChange1d !== undefined + ? ethFiat / (1 + ethPricePercentChange1d / 100) + : ethFiat; if (tokens.length > 0) { const { contractBalances: tokenBalances } = TokenBalancesController.state; - const { marketData } = TokenRatesController.state; - const tokenExchangeRates = marketData[chainId]; tokens.forEach( (item: { address: string; balance?: string; decimals: number }) => { - const exchangeRate = - tokenExchangeRates && item.address in tokenExchangeRates - ? tokenExchangeRates[item.address as Hex]?.price - : undefined; + const exchangeRate = tokenExchangeRates?.[item.address as Hex]?.price; const tokenBalance = item.balance || @@ -1696,12 +1690,13 @@ class Engine { decimalsToShow, ); + const tokenPricePercentChange1d = + tokenExchangeRates?.[item.address as Hex]?.pricePercentChange1d; + const tokenBalance1dAgo = - tokenBalanceFiat + - (tokenBalanceFiat * - tokenExchangeRates?.[item.address as `0x${string}`] - ?.pricePercentChange1d) / - 100 || tokenBalanceFiat; + tokenPricePercentChange1d !== undefined + ? tokenBalanceFiat / (1 + tokenPricePercentChange1d / 100) + : tokenBalanceFiat; tokenFiat += tokenBalanceFiat; tokenFiat1dAgo += tokenBalance1dAgo;