-
Notifications
You must be signed in to change notification settings - Fork 212
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix finance USD conversion mechanism (#1177)
* Fix finance USD conversion mechanism * Add Jest * Simplify conversion function * Add some tests for the conversion utility Co-authored-by: Pierre Bertet <[email protected]>
- Loading branch information
Showing
6 changed files
with
146 additions
and
79 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import { useEffect, useState, useRef } from 'react' | ||
|
||
const CONVERT_API_RETRY_DELAY = 2 * 1000 | ||
const CONVERT_API_RETRY_DELAY_MAX = 60 * 1000 | ||
|
||
function convertRatesUrl(symbolsQuery) { | ||
return `https://min-api.cryptocompare.com/data/price?fsym=USD&tsyms=${symbolsQuery}` | ||
} | ||
|
||
export function useConvertRates(symbols) { | ||
const [rates, setRates] = useState({}) | ||
const retryDelay = useRef(CONVERT_API_RETRY_DELAY) | ||
|
||
const symbolsQuery = symbols.join(',') | ||
|
||
useEffect(() => { | ||
let cancelled = false | ||
let retryTimer = null | ||
|
||
const update = async () => { | ||
if (!symbolsQuery) { | ||
setRates({}) | ||
return | ||
} | ||
|
||
try { | ||
const response = await fetch(convertRatesUrl(symbolsQuery)) | ||
const rates = await response.json() | ||
if (!cancelled) { | ||
setRates(rates) | ||
retryDelay.current = CONVERT_API_RETRY_DELAY | ||
} | ||
} catch (err) { | ||
// The !cancelled check is needed in case: | ||
// 1. The fetch() request is ongoing. | ||
// 2. The component gets unmounted. | ||
// 3. An error gets thrown. | ||
// | ||
// Assuming the fetch() request keeps throwing, it would create new | ||
// requests even though the useEffect() got cancelled. | ||
if (!cancelled) { | ||
// Add more delay after every failed attempt | ||
retryDelay.current = Math.min( | ||
CONVERT_API_RETRY_DELAY_MAX, | ||
retryDelay.current * 1.2 | ||
) | ||
retryTimer = setTimeout(update, retryDelay.current) | ||
} | ||
} | ||
} | ||
update() | ||
|
||
return () => { | ||
cancelled = true | ||
clearTimeout(retryTimer) | ||
retryDelay.current = CONVERT_API_RETRY_DELAY | ||
} | ||
}, [symbolsQuery]) | ||
|
||
return rates | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import BN from 'bn.js' | ||
|
||
export function getConvertedAmount(amount, convertRate) { | ||
const [whole = '', dec = ''] = convertRate.toString().split('.') | ||
// Remove any trailing zeros from the decimal part | ||
const parsedDec = dec.replace(/0*$/, '') | ||
// Construct the final rate, and remove any leading zeros | ||
const rate = `${whole}${parsedDec}`.replace(/^0*/, '') | ||
|
||
// Number of decimals to shift the amount of the token passed in, | ||
// resulting from converting the rate to a number without any decimal | ||
// places | ||
const carryAmount = new BN(parsedDec.length.toString()) | ||
|
||
return amount.mul(new BN('10').pow(carryAmount)).div(new BN(rate)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import BN from 'bn.js' | ||
import { getConvertedAmount } from './conversion-utils' | ||
|
||
const ONE_ETH = new BN('10').pow(new BN('18')) | ||
|
||
describe('getConvertedAmount tests', () => { | ||
test('Converts amounts correctly', () => { | ||
expect(getConvertedAmount(new BN('1'), 1).toString()).toEqual('1') | ||
expect(getConvertedAmount(new BN(ONE_ETH), 1).toString()).toEqual( | ||
ONE_ETH.toString() | ||
) | ||
expect(getConvertedAmount(new BN('1'), 0.5).toString()).toEqual('2') | ||
expect(getConvertedAmount(new BN('1'), 0.25).toString()).toEqual('4') | ||
expect(getConvertedAmount(new BN('1'), 0.125).toString()).toEqual('8') | ||
|
||
expect(getConvertedAmount(new BN('100'), 50).toString()).toEqual('2') | ||
// This is the exact case that broke the previous implementation, | ||
// which is AAVE's amount of WBTC + the exchange rate at a certain | ||
// hour on 2020-06-24 | ||
expect( | ||
getConvertedAmount(new BN('1145054'), 0.00009248).toString() | ||
).toEqual('12381639273') | ||
}) | ||
|
||
test('Throws on invalid inputs', () => { | ||
expect(() => getConvertedAmount(new BN('1'), 0)).toThrow() | ||
expect(() => getConvertedAmount('1000', 0)).toThrow() | ||
}) | ||
}) |