diff --git a/src/__swaps__/safe-math/SafeMath.ts b/src/__swaps__/safe-math/SafeMath.ts index d8bb0216637..be161c34fcf 100644 --- a/src/__swaps__/safe-math/SafeMath.ts +++ b/src/__swaps__/safe-math/SafeMath.ts @@ -21,7 +21,7 @@ const isZeroWorklet = (value: string): boolean => { }; // Utility function to scale the number up to 20 decimal places -const scaleUpWorklet = (bigIntNum: bigint, decimalPlaces: number): bigint => { +export const scaleUpWorklet = (bigIntNum: bigint, decimalPlaces: number): bigint => { 'worklet'; const scaleFactor = BigInt(10) ** BigInt(20); return (bigIntNum * scaleFactor) / BigInt(10) ** BigInt(decimalPlaces); @@ -171,27 +171,6 @@ export function modWorklet(num1: string | number, num2: string | number): string return formatResultWorklet(result); } -// Power function -export function powWorklet(base: string | number, exponent: string | number): string { - 'worklet'; - const baseStr = toStringWorklet(base); - const exponentStr = toStringWorklet(exponent); - - if (!isNumberStringWorklet(baseStr) || !isNumberStringWorklet(exponentStr)) { - throw new Error('Arguments must be a numeric string or number'); - } - if (isZeroWorklet(baseStr)) { - return '0'; - } - if (isZeroWorklet(exponentStr)) { - return '1'; - } - const [bigIntBase, decimalPlaces] = removeDecimalWorklet(baseStr); - const scaledBigIntBase = scaleUpWorklet(bigIntBase, decimalPlaces); - const result = scaledBigIntBase ** BigInt(exponentStr) / BigInt(10) ** BigInt(20); - return formatResultWorklet(result); -} - // Logarithm base 10 function export function log10Worklet(num: string | number): string { 'worklet'; @@ -291,6 +270,41 @@ export function lessThanOrEqualToWorklet(num1: string | number, num2: string | n return scaledBigInt1 <= scaledBigInt2; } +// Power function +export function powWorklet(base: string | number, exponent: string | number): string { + 'worklet'; + const baseStr = toStringWorklet(base); + const exponentStr = toStringWorklet(exponent); + + if (!isNumberStringWorklet(baseStr) || !isNumberStringWorklet(exponentStr)) { + throw new Error('Arguments must be a numeric string or number'); + } + if (isZeroWorklet(baseStr)) { + return '0'; + } + if (isZeroWorklet(exponentStr)) { + return '1'; + } + if (exponentStr === '1') { + return baseStr; + } + + if (lessThanWorklet(exponentStr, 0)) { + return divWorklet(1, powWorklet(base, Math.abs(Number(exponent)))); + } + + const [bigIntBase, decimalPlaces] = removeDecimalWorklet(baseStr); + let result; + if (decimalPlaces > 0) { + const scaledBigIntBase = scaleUpWorklet(bigIntBase, decimalPlaces); + result = scaledBigIntBase ** BigInt(exponentStr) / BigInt(10) ** BigInt(20); + return formatResultWorklet(result); + } else { + result = bigIntBase ** BigInt(exponentStr); + return result.toString(); + } +} + // toFixed function export function toFixedWorklet(num: string | number, decimalPlaces: number): string { 'worklet'; @@ -366,3 +380,13 @@ export function roundWorklet(num: string | number): string { return formatResultWorklet(roundBigInt); } + +export function minWorklet(numA: string | number, numB: string | number) { + 'worklet'; + return lessThanOrEqualToWorklet(numA, numB) ? numA : numB; +} + +export function maxWorklet(numA: string | number, numB: string | number) { + 'worklet'; + return greaterThanOrEqualToWorklet(numA, numB) ? numA : numB; +} diff --git a/src/__swaps__/safe-math/__tests__/SafeMath.test.ts b/src/__swaps__/safe-math/__tests__/SafeMath.test.ts index 634d159d41f..60933f3fb98 100644 --- a/src/__swaps__/safe-math/__tests__/SafeMath.test.ts +++ b/src/__swaps__/safe-math/__tests__/SafeMath.test.ts @@ -31,6 +31,7 @@ const RESULTS = { ceil: '1243426', floor: '1243425', toScaledInteger: '57464009350560633', + negativePow: '0.001', }; const VALUE_A = '1243425.345'; @@ -38,9 +39,12 @@ const VALUE_B = '3819.24'; const VALUE_C = '2'; const VALUE_D = '1243425.745'; const VALUE_E = '0.057464009350560633'; +const VALUE_F = '0.001'; const NEGATIVE_VALUE = '-2412.12'; const ZERO = '0'; const ONE = '1'; +const TEN = '10'; +const MINUS_3 = '-3'; const NON_NUMERIC_STRING = 'abc'; describe('SafeMath', () => { @@ -108,6 +112,7 @@ describe('SafeMath', () => { expect(powWorklet(VALUE_A, VALUE_C)).toBe(RESULTS.pow); expect(powWorklet(Number(VALUE_A), VALUE_C)).toBe(RESULTS.pow); expect(powWorklet(VALUE_A, Number(VALUE_C))).toBe(RESULTS.pow); + expect(powWorklet(TEN, Number(MINUS_3))).toBe(RESULTS.negativePow); }); test('log10Worklet', () => { diff --git a/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts b/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts index aa29cf1cc5c..f1aa2851d3d 100644 --- a/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts +++ b/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts @@ -29,6 +29,7 @@ import { } from '@/resources/assets/externalAssetsQuery'; import { ethereumUtils } from '@/utils'; import { queryClient } from '@/react-query'; +import { divWorklet, equalWorklet, greaterThanWorklet, mulWorklet, toFixedWorklet } from '@/__swaps__/safe-math/SafeMath'; function getInitialInputValues(initialSelectedInputAsset: ExtendedAnimatedAssetWithColors | null) { const initialBalance = Number(initialSelectedInputAsset?.balance.amount) ?? 0; @@ -44,8 +45,9 @@ function getInitialInputValues(initialSelectedInputAsset: ExtendedAnimatedAssetW sliderXPosition: SLIDER_WIDTH / 2, stripSeparators: true, }); + const initialInputNativeValue = addCommasToNumber( - (Number(initialInputAmount) * (initialSelectedInputAsset?.price?.value ?? 0)).toFixed(2) + toFixedWorklet(mulWorklet(initialInputAmount, initialSelectedInputAsset?.price?.value ?? 0), 2) ); return { @@ -95,7 +97,7 @@ export function useSwapInputsController({ const niceIncrement = useDerivedValue(() => { if (!internalSelectedInputAsset.value?.balance.amount) return 0.1; - return findNiceIncrement(Number(internalSelectedInputAsset.value?.balance.amount)); + return findNiceIncrement(internalSelectedInputAsset.value?.balance.amount); }); const incrementDecimalPlaces = useDerivedValue(() => countDecimalPlaces(niceIncrement.value)); @@ -128,7 +130,7 @@ export function useSwapInputsController({ }); } - const balance = Number(internalSelectedInputAsset.value?.balance.amount ?? 0); + const balance = internalSelectedInputAsset.value?.balance.amount || 0; return niceIncrementFormatter({ incrementDecimalPlaces: incrementDecimalPlaces.value, @@ -145,7 +147,7 @@ export function useSwapInputsController({ return '$0.00'; } - const nativeValue = `$${inputValues.value.inputNativeValue.toLocaleString('en-US', { + const nativeValue = `$${Number(inputValues.value.inputNativeValue).toLocaleString('en-US', { useGrouping: true, minimumFractionDigits: 2, maximumFractionDigits: 2, @@ -180,12 +182,11 @@ export function useSwapInputsController({ return '$0.00'; } - const nativeValue = `$${inputValues.value.outputNativeValue.toLocaleString('en-US', { + const nativeValue = `$${Number(inputValues.value.outputNativeValue).toLocaleString('en-US', { useGrouping: true, minimumFractionDigits: 2, maximumFractionDigits: 2, })}`; - return nativeValue || '$0.00'; }); @@ -298,7 +299,7 @@ export function useSwapInputsController({ // If the quote has been superseded, isQuoteStale and isFetching should already be correctly set in response // to the newer input, as long as the inputs aren't empty, so we handle the empty inputs case and then return, // discarding the result of the superseded quote. - const areInputsEmpty = Number(inputValues.value.inputAmount) === 0 && Number(inputValues.value.outputAmount) === 0; + const areInputsEmpty = equalWorklet(inputValues.value.inputAmount, 0) && equalWorklet(inputValues.value.outputAmount, 0); if (areInputsEmpty) { isFetching.value = false; @@ -319,7 +320,7 @@ export function useSwapInputsController({ return { ...prev, inputAmount, - inputNativeValue: inputAmount * (inputPrice || inputNativePrice.value), + inputNativeValue: mulWorklet(inputAmount, inputPrice || inputNativePrice.value), }; }); } @@ -329,7 +330,7 @@ export function useSwapInputsController({ return { ...prev, outputAmount, - outputNativeValue: outputAmount * (outputPrice || outputNativePrice.value), + outputNativeValue: mulWorklet(outputAmount, outputPrice || outputNativePrice.value), }; }); } @@ -339,8 +340,10 @@ export function useSwapInputsController({ if (!inputAmount || inputAmount === 0) { sliderXPosition.value = withSpring(0, snappySpringConfig); } else { - const inputBalance = Number(internalSelectedInputAsset.value?.balance.amount || '0'); - const updatedSliderPosition = inputBalance > 0 ? clamp((inputAmount / inputBalance) * SLIDER_WIDTH, 0, SLIDER_WIDTH) : 0; + const inputBalance = internalSelectedInputAsset.value?.balance.amount || '0'; + const updatedSliderPosition = greaterThanWorklet(inputBalance, 0) + ? clamp(Number(divWorklet(inputAmount, inputBalance)) * SLIDER_WIDTH, 0, SLIDER_WIDTH) + : 0; sliderXPosition.value = withSpring(updatedSliderPosition, snappySpringConfig); } } @@ -507,7 +510,8 @@ export function useSwapInputsController({ const fetchQuoteAndAssetPrices = () => { 'worklet'; - const isSomeInputGreaterThanZero = Number(inputValues.value.inputAmount) > 0 || Number(inputValues.value.outputAmount) > 0; + const isSomeInputGreaterThanZero = + greaterThanWorklet(inputValues.value.inputAmount, 0) || greaterThanWorklet(inputValues.value.outputAmount, 0); // If both inputs are 0 or the assets aren't set, return early if (!internalSelectedInputAsset.value || !internalSelectedOutputAsset.value || !isSomeInputGreaterThanZero) { @@ -559,8 +563,8 @@ export function useSwapInputsController({ // If the user enters a new inputAmount, update the slider position ahead of the quote fetch, because // we can derive the slider position directly from the entered amount. if (inputKey === 'inputAmount') { - const inputAssetBalance = Number(internalSelectedInputAsset.value?.balance.amount || '0'); - const updatedSliderPosition = clamp((amount / inputAssetBalance) * SLIDER_WIDTH, 0, SLIDER_WIDTH); + const inputAssetBalance = internalSelectedInputAsset.value?.balance.amount || '0'; + const updatedSliderPosition = clamp(Number(divWorklet(amount, inputAssetBalance)) * SLIDER_WIDTH, 0, SLIDER_WIDTH); sliderXPosition.value = withSpring(updatedSliderPosition, snappySpringConfig); } fetchQuoteAndAssetPrices(); @@ -612,7 +616,7 @@ export function useSwapInputsController({ (current, previous) => { if (previous && current !== previous && typeof inputValues.value[previous.focusedInput] === 'string') { const typedValue = inputValues.value[previous.focusedInput].toString(); - if (Number(typedValue) === 0) { + if (equalWorklet(typedValue, 0)) { inputValues.modify(values => { return { ...values, @@ -693,8 +697,7 @@ export function useSwapInputsController({ sliderXPosition: sliderXPosition.value, stripSeparators: true, }); - - const inputNativeValue = Number(inputAmount) * inputNativePrice.value; + const inputNativeValue = mulWorklet(inputAmount, inputNativePrice.value); inputValues.modify(values => { return { ...values, @@ -704,9 +707,9 @@ export function useSwapInputsController({ }); } } - if (inputMethod.value === 'inputAmount' && Number(current.values.inputAmount) !== Number(previous.values.inputAmount)) { + if (inputMethod.value === 'inputAmount' && !equalWorklet(current.values.inputAmount, previous.values.inputAmount)) { // If the number in the input field changes - if (Number(current.values.inputAmount) === 0) { + if (equalWorklet(current.values.inputAmount, 0)) { // If the input amount was set to 0 quoteFetchingInterval.stop(); isQuoteStale.value = 0; @@ -734,7 +737,7 @@ export function useSwapInputsController({ if (!internalSelectedInputAsset.value) return; if (isQuoteStale.value !== 1) isQuoteStale.value = 1; - const inputNativeValue = Number(current.values.inputAmount) * inputNativePrice.value; + const inputNativeValue = mulWorklet(current.values.inputAmount, inputNativePrice.value); inputValues.modify(values => { return { @@ -743,17 +746,21 @@ export function useSwapInputsController({ }; }); - const inputAssetBalance = Number(internalSelectedInputAsset.value?.balance.amount || '0'); - const updatedSliderPosition = clamp((Number(current.values.inputAmount) / inputAssetBalance) * SLIDER_WIDTH, 0, SLIDER_WIDTH); + const inputAssetBalance = internalSelectedInputAsset.value?.balance.amount || '0'; + const updatedSliderPosition = clamp( + Number(divWorklet(current.values.inputAmount, inputAssetBalance)) * SLIDER_WIDTH, + 0, + SLIDER_WIDTH + ); sliderXPosition.value = withSpring(updatedSliderPosition, snappySpringConfig); runOnJS(onTypedNumber)(Number(current.values.inputAmount), 'inputAmount', true); } } - if (inputMethod.value === 'outputAmount' && Number(current.values.outputAmount) !== Number(previous.values.outputAmount)) { + if (inputMethod.value === 'outputAmount' && !equalWorklet(current.values.outputAmount, previous.values.outputAmount)) { // If the number in the output field changes - if (Number(current.values.outputAmount) === 0) { + if (equalWorklet(current.values.outputAmount, 0)) { // If the output amount was set to 0 quoteFetchingInterval.stop(); isQuoteStale.value = 0; @@ -777,12 +784,11 @@ export function useSwapInputsController({ } else { runOnJS(onTypedNumber)(0, 'outputAmount'); } - } else if (Number(current.values.outputAmount) > 0) { + } else if (greaterThanWorklet(current.values.outputAmount, 0)) { // If the output amount was set to a non-zero value if (isQuoteStale.value !== 1) isQuoteStale.value = 1; - const outputAmount = Number(current.values.outputAmount); - const outputNativeValue = outputAmount * outputNativePrice.value; + const outputNativeValue = mulWorklet(current.values.outputAmount, outputNativePrice.value); inputValues.modify(values => { return { @@ -849,7 +855,7 @@ export function useSwapInputsController({ stripSeparators: true, }); - const inputNativeValue = Number(inputAmount) * inputNativePrice.value; + const inputNativeValue = mulWorklet(inputAmount, inputNativePrice.value); inputValues.modify(values => { return { ...values, @@ -867,9 +873,7 @@ export function useSwapInputsController({ const inputAmount = Number( valueBasedDecimalFormatter({ amount: - inputNativePrice > 0 - ? Number(inputValues.value.inputNativeValue) / inputNativePrice - : Number(inputValues.value.outputAmount), + inputNativePrice > 0 ? divWorklet(inputValues.value.inputNativeValue, inputNativePrice) : inputValues.value.outputAmount, usdTokenPrice: inputNativePrice, roundingMode: 'up', precisionAdjustment: -1, @@ -882,12 +886,10 @@ export function useSwapInputsController({ return { ...values, inputAmount, - inputNativeValue: Number(inputValues.value.inputNativeValue), + inputNativeValue: inputValues.value.inputNativeValue, outputAmount: - outputNativePrice > 0 - ? Number(inputValues.value.outputNativeValue) / outputNativePrice - : Number(inputValues.value.inputAmount), - outputNativeValue: Number(inputValues.value.outputNativeValue), + outputNativePrice > 0 ? divWorklet(inputValues.value.outputNativeValue, outputNativePrice) : inputValues.value.inputAmount, + outputNativeValue: inputValues.value.outputNativeValue, }; }); } diff --git a/src/__swaps__/screens/Swap/hooks/useSwapTextStyles.ts b/src/__swaps__/screens/Swap/hooks/useSwapTextStyles.ts index 77f2ea45d80..2470bd8aae8 100644 --- a/src/__swaps__/screens/Swap/hooks/useSwapTextStyles.ts +++ b/src/__swaps__/screens/Swap/hooks/useSwapTextStyles.ts @@ -22,6 +22,7 @@ import { import { inputKeys, inputMethods, inputValuesType } from '@/__swaps__/types/swap'; import { getColorValueForThemeWorklet, opacity } from '@/__swaps__/utils/swaps'; import { ExtendedAnimatedAssetWithColors } from '@/__swaps__/types/assets'; +import { equalWorklet } from '@/__swaps__/safe-math/SafeMath'; import { SPRING_CONFIGS, TIMING_CONFIGS } from '@/components/animations/animationConfigs'; export function useSwapTextStyles({ @@ -78,12 +79,12 @@ export function useSwapTextStyles({ const isZero = !internalSelectedInputAsset.value || (inputValues.value.inputAmount === 0 && inputMethod.value !== 'slider') || - (inputMethod.value === 'slider' && Number(inputValues.value.inputAmount) === 0); + (inputMethod.value === 'slider' && equalWorklet(inputValues.value.inputAmount, 0)); return isZero; }); const isOutputZero = useDerivedValue(() => { - const isZero = !internalSelectedOutputAsset.value || inputValues.value.outputAmount === 0; + const isZero = !internalSelectedOutputAsset.value || equalWorklet(inputValues.value.outputAmount, 0); return isZero; }); @@ -170,7 +171,7 @@ export function useSwapTextStyles({ inputProgress.value === 0 && outputProgress.value === 0 && (inputMethod.value !== 'slider' || - (inputMethod.value === 'slider' && Number(inputValues.value.inputAmount) === 0) || + (inputMethod.value === 'slider' && equalWorklet(inputValues.value.inputAmount, 0)) || (sliderPressProgress.value === SLIDER_COLLAPSED_HEIGHT / SLIDER_HEIGHT && isQuoteStale.value === 0)); const opacity = shouldShow @@ -188,7 +189,7 @@ export function useSwapTextStyles({ const isZero = (inputMethod.value !== 'slider' && inputValues.value.inputAmount === 0) || - (inputMethod.value === 'slider' && Number(inputValues.value.inputAmount) === 0); + (inputMethod.value === 'slider' && equalWorklet(inputValues.value.inputAmount, 0)); return { display: shouldShow ? 'flex' : 'none', @@ -203,7 +204,7 @@ export function useSwapTextStyles({ inputProgress.value === 0 && outputProgress.value === 0 && (inputMethod.value !== 'slider' || - (inputMethod.value === 'slider' && Number(inputValues.value.inputAmount) === 0) || + (inputMethod.value === 'slider' && equalWorklet(inputValues.value.inputAmount, 0)) || (sliderPressProgress.value === SLIDER_COLLAPSED_HEIGHT / SLIDER_HEIGHT && isQuoteStale.value === 0)); const opacity = shouldShow @@ -221,7 +222,7 @@ export function useSwapTextStyles({ const isZero = (inputMethod.value !== 'slider' && inputValues.value.outputAmount === 0) || - (inputMethod.value === 'slider' && Number(inputValues.value.inputAmount) === 0); + (inputMethod.value === 'slider' && equalWorklet(inputValues.value.inputAmount, 0)); return { display: shouldShow ? 'flex' : 'none', diff --git a/src/__swaps__/screens/Swap/providers/swap-provider.tsx b/src/__swaps__/screens/Swap/providers/swap-provider.tsx index 0f3b7501123..b63615582da 100644 --- a/src/__swaps__/screens/Swap/providers/swap-provider.tsx +++ b/src/__swaps__/screens/Swap/providers/swap-provider.tsx @@ -40,6 +40,7 @@ import { userAssetsQueryKey } from '@/resources/assets/UserAssetsQuery'; import { useAccountSettings } from '@/hooks'; import { getGasSettingsBySpeed, getSelectedGas } from '../hooks/useSelectedGas'; import { LegacyTransactionGasParamAmounts, TransactionGasParamAmounts } from '@/entities'; +import { equalWorklet } from '@/__swaps__/safe-math/SafeMath'; import { useSwapSettings } from '../hooks/useSwapSettings'; import { useSwapOutputQuotesDisabled } from '../hooks/useSwapOutputQuotesDisabled'; import { getNetworkObj } from '@/networks'; @@ -504,8 +505,8 @@ export const SwapProvider = ({ children }: SwapProviderProps) => { return ''; } - const isInputZero = Number(SwapInputController.inputValues.value.inputAmount) === 0; - const isOutputZero = Number(SwapInputController.inputValues.value.outputAmount) === 0; + const isInputZero = equalWorklet(SwapInputController.inputValues.value.inputAmount, 0); + const isOutputZero = equalWorklet(SwapInputController.inputValues.value.outputAmount, 0); if ( (isInputZero && isOutputZero) || @@ -533,8 +534,8 @@ export const SwapProvider = ({ children }: SwapProviderProps) => { return fetchingPrices; } - const isInputZero = Number(SwapInputController.inputValues.value.inputAmount) === 0; - const isOutputZero = Number(SwapInputController.inputValues.value.outputAmount) === 0; + const isInputZero = equalWorklet(SwapInputController.inputValues.value.inputAmount, 0); + const isOutputZero = equalWorklet(SwapInputController.inputValues.value.outputAmount, 0); if (SwapInputController.inputMethod.value !== 'slider' && (isInputZero || isOutputZero) && !isFetching.value) { return enterAmount; @@ -549,8 +550,8 @@ export const SwapProvider = ({ children }: SwapProviderProps) => { }); const confirmButtonIconStyle = useAnimatedStyle(() => { - const isInputZero = Number(SwapInputController.inputValues.value.inputAmount) === 0; - const isOutputZero = Number(SwapInputController.inputValues.value.outputAmount) === 0; + const isInputZero = equalWorklet(SwapInputController.inputValues.value.inputAmount, 0); + const isOutputZero = equalWorklet(SwapInputController.inputValues.value.outputAmount, 0); const sliderCondition = SwapInputController.inputMethod.value === 'slider' && diff --git a/src/__swaps__/utils/numbers.ts b/src/__swaps__/utils/numbers.ts index 04fb8be2836..8cf984850e2 100644 --- a/src/__swaps__/utils/numbers.ts +++ b/src/__swaps__/utils/numbers.ts @@ -138,12 +138,6 @@ export const convertStringToNumber = (value: BigNumberish) => new BigNumber(valu export const lessThan = (numberOne: BigNumberish, numberTwo: BigNumberish): boolean => new BigNumber(numberOne).lt(numberTwo); -export const lessThanWorklet = (numberOne: BigNumberish, numberTwo: BigNumberish): boolean => { - 'worklet'; - - return new BigNumber(numberOne).lt(numberTwo); -}; - export const lessOrEqualThan = (numberOne: BigNumberish, numberTwo: BigNumberish): boolean => new BigNumber(numberOne).lt(numberTwo) || new BigNumber(numberOne).eq(numberTwo); @@ -165,21 +159,6 @@ export const handleSignificantDecimals = (value: BigNumberish, decimals: number, return resultBN.dp() <= 2 ? resultBN.toFormat(skipDecimals ? 0 : 2) : resultBN.toFormat(); }; -export const handleSignificantDecimalsWorklet = (value: BigNumberish, decimals: number, buffer = 3, skipDecimals = false): string => { - 'worklet'; - - let dec; - if (lessThanWorklet(new BigNumber(value).abs(), 1)) { - dec = new BigNumber(value).toFixed()?.slice?.(2).search(/[^0]/g) + buffer; - dec = Math.min(decimals, 8); - } else { - dec = Math.min(decimals, buffer); - } - const result = new BigNumber(new BigNumber(value).toFixed(dec)).toFixed(); - const resultBN = new BigNumber(result); - return resultBN.dp() <= 2 ? resultBN.toFormat(skipDecimals ? 0 : 2) : resultBN.toFormat(); -}; - export const handleSignificantDecimalsAsNumber = (value: BigNumberish, decimals: number): string => { return new BigNumber(new BigNumber(multiply(value, new BigNumber(10).pow(decimals))).toFixed(0)) .dividedBy(new BigNumber(10).pow(decimals)) @@ -191,15 +170,6 @@ export const handleSignificantDecimalsAsNumber = (value: BigNumberish, decimals: */ export const convertAmountToNativeAmount = (amount: BigNumberish, priceUnit: BigNumberish): string => multiply(amount, priceUnit); -/** - * @desc convert from asset BigNumber amount to native price BigNumber amount - */ -export const convertAmountToNativeAmountWorklet = (amount: BigNumberish, priceUnit: BigNumberish): string => { - 'worklet'; - - return multiply(amount, priceUnit); -}; - /** * @desc convert from amount to display formatted string */ @@ -218,26 +188,6 @@ export const convertAmountAndPriceToNativeDisplay = ( }; }; -/** - * @desc convert from amount to display formatted string - */ -export const convertAmountAndPriceToNativeDisplayWorklet = ( - amount: BigNumberish, - priceUnit: BigNumberish, - nativeCurrency: keyof nativeCurrencyType, - buffer?: number, - skipDecimals = false -): { amount: string; display: string } => { - 'worklet'; - - const nativeBalanceRaw = convertAmountToNativeAmountWorklet(amount, priceUnit); - const nativeDisplay = convertAmountToNativeDisplayWorklet(nativeBalanceRaw, nativeCurrency, buffer, skipDecimals); - return { - amount: nativeBalanceRaw, - display: nativeDisplay, - }; -}; - export const convertAmountAndPriceToNativeDisplayWithThreshold = ( amount: BigNumberish, priceUnit: BigNumberish, @@ -266,23 +216,6 @@ export const convertRawAmountToNativeDisplay = ( return ret; }; -/** - * @desc convert from raw amount to display formatted string - */ -export const convertRawAmountToNativeDisplayWorklet = ( - rawAmount: BigNumberish, - assetDecimals: number, - priceUnit: BigNumberish, - nativeCurrency: keyof nativeCurrencyType, - buffer?: number -) => { - 'worklet'; - - const assetBalance = convertRawAmountToDecimalFormatWorklet(rawAmount, assetDecimals); - const ret = convertAmountAndPriceToNativeDisplayWorklet(assetBalance, priceUnit, nativeCurrency, buffer); - return ret; -}; - /** * @desc convert from raw amount to balance object */ @@ -352,26 +285,6 @@ export const convertAmountToNativeDisplay = ( return `${display} ${nativeSelected.symbol}`; }; -/** - * @desc convert from amount value to display formatted string - */ -export const convertAmountToNativeDisplayWorklet = ( - value: BigNumberish, - nativeCurrency: keyof nativeCurrencyType, - buffer?: number, - skipDecimals?: boolean -) => { - 'worklet'; - - const nativeSelected = supportedNativeCurrencies?.[nativeCurrency]; - const { decimals } = nativeSelected; - const display = handleSignificantDecimalsWorklet(value, decimals, buffer, skipDecimals); - if (nativeSelected.alignment === 'left') { - return `${nativeSelected.symbol}${display}`; - } - return `${display} ${nativeSelected.symbol}`; -}; - export const convertAmountToNativeDisplayWithThreshold = (value: BigNumberish, nativeCurrency: keyof nativeCurrencyType) => { const nativeSelected = supportedNativeCurrencies?.[nativeCurrency]; const display = handleSignificantDecimalsWithThreshold(value, nativeSelected.decimals, nativeSelected.decimals < 4 ? '0.01' : '0.0001'); @@ -387,14 +300,6 @@ export const convertAmountToNativeDisplayWithThreshold = (value: BigNumberish, n export const convertRawAmountToDecimalFormat = (value: BigNumberish, decimals = 18): string => new BigNumber(value).dividedBy(new BigNumber(10).pow(decimals)).toFixed(); -/** - * @desc convert from raw amount to decimal format - */ -export const convertRawAmountToDecimalFormatWorklet = (value: BigNumberish, decimals = 18): string => { - 'worklet'; - return new BigNumber(value).dividedBy(new BigNumber(10).pow(decimals)).toFixed(); -}; - /** * @desc convert from decimal format to raw amount */ diff --git a/src/__swaps__/utils/swaps.ts b/src/__swaps__/utils/swaps.ts index 3b8a605a255..dc57412ed99 100644 --- a/src/__swaps__/utils/swaps.ts +++ b/src/__swaps__/utils/swaps.ts @@ -17,6 +17,20 @@ import { TokenColors } from '@/graphql/__generated__/metadata'; import { userAssetsStore } from '@/state/assets/userAssets'; import { colors } from '@/styles'; import { convertAmountToRawAmount } from './numbers'; +import { + ceilWorklet, + divWorklet, + equalWorklet, + floorWorklet, + log10Worklet, + lessThanOrEqualToWorklet, + mulWorklet, + powWorklet, + roundWorklet, + toFixedWorklet, + greaterThanOrEqualToWorklet, + sumWorklet, +} from '../safe-math/SafeMath'; // /---- 🎨 Color functions 🎨 ----/ // // @@ -119,10 +133,10 @@ export const clampJS = (value: number, lowerBound: number, upperBound: number) = return Math.min(Math.max(lowerBound, value), upperBound); }; -export const countDecimalPlaces = (number: number): number => { +export const countDecimalPlaces = (number: number | string): number => { 'worklet'; - const numAsString = number.toString(); + const numAsString = typeof number === 'string' ? number : number.toString(); if (numAsString.includes('.')) { // Return the number of digits after the decimal point, excluding trailing zeros @@ -133,7 +147,7 @@ export const countDecimalPlaces = (number: number): number => { return 0; }; -export const findNiceIncrement = (availableBalance: number): number => { +export const findNiceIncrement = (availableBalance: string | number) => { 'worklet'; // We'll use one of these factors to adjust the base increment @@ -143,25 +157,26 @@ export const findNiceIncrement = (availableBalance: number): number => { const niceFactors = [1, 2, 10]; // Calculate the exact increment for 100 steps - const exactIncrement = availableBalance / 100; + const exactIncrement = divWorklet(availableBalance, 100); // Calculate the order of magnitude of the exact increment - const orderOfMagnitude = Math.floor(Math.log10(exactIncrement)); - const baseIncrement = Math.pow(10, orderOfMagnitude); + const orderOfMagnitude = floorWorklet(log10Worklet(exactIncrement)); + + const baseIncrement = powWorklet(10, orderOfMagnitude); let adjustedIncrement = baseIncrement; // Find the first nice increment that ensures at least 100 steps for (let i = niceFactors.length - 1; i >= 0; i--) { - const potentialIncrement = baseIncrement * niceFactors[i]; - if (potentialIncrement <= exactIncrement) { + const potentialIncrement = mulWorklet(baseIncrement, niceFactors[i]); + if (lessThanOrEqualToWorklet(potentialIncrement, exactIncrement)) { adjustedIncrement = potentialIncrement; break; } } - return adjustedIncrement; }; + // // /---- END JS utils ----/ // @@ -178,7 +193,7 @@ export function addCommasToNumber(number: string | n return numberString; } - if (Number(number) >= 1000) { + if (greaterThanOrEqualToWorklet(number, 1000)) { const parts = numberString.split('.'); parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ','); return parts.join('.'); @@ -211,7 +226,7 @@ export function valueBasedDecimalFormatter({ isStablecoin, stripSeparators = true, }: { - amount: number; + amount: number | string; usdTokenPrice: number; roundingMode?: 'up' | 'down'; precisionAdjustment?: number; @@ -220,8 +235,8 @@ export function valueBasedDecimalFormatter({ }): string { 'worklet'; - function precisionBasedOffMagnitude(amount: number): number { - const magnitude = -Math.floor(Math.log10(amount) + 1); + function precisionBasedOffMagnitude(amount: number | string): number { + const magnitude = -Number(floorWorklet(sumWorklet(log10Worklet(amount), 1))); return (precisionAdjustment ?? 0) + magnitude; } @@ -239,17 +254,17 @@ export function valueBasedDecimalFormatter({ const decimalPlaces = isStablecoin ? 2 : calculateDecimalPlaces(usdTokenPrice); - let roundedAmount: number; + let roundedAmount; const factor = Math.pow(10, decimalPlaces); // Apply rounding based on the specified rounding mode if (roundingMode === 'up') { - roundedAmount = Math.ceil(amount * factor) / factor; + roundedAmount = divWorklet(ceilWorklet(mulWorklet(amount, factor)), factor); } else if (roundingMode === 'down') { - roundedAmount = Math.floor(amount * factor) / factor; + roundedAmount = divWorklet(floorWorklet(mulWorklet(amount, factor)), factor); } else { // Default to normal rounding if no rounding mode is specified - roundedAmount = Math.round(amount * factor) / factor; + roundedAmount = divWorklet(roundWorklet(mulWorklet(amount, factor)), factor); } // Format the number to add separators and trim trailing zeros @@ -259,9 +274,9 @@ export function valueBasedDecimalFormatter({ useGrouping: true, }); - if (stripSeparators) return stripCommas(numberFormatter.format(roundedAmount)); + if (stripSeparators) return stripCommas(numberFormatter.format(Number(roundedAmount))); - return numberFormatter.format(roundedAmount); + return numberFormatter.format(Number(roundedAmount)); } export function niceIncrementFormatter({ @@ -274,9 +289,9 @@ export function niceIncrementFormatter({ stripSeparators, }: { incrementDecimalPlaces: number; - inputAssetBalance: number; + inputAssetBalance: number | string; inputAssetUsdPrice: number; - niceIncrement: number; + niceIncrement: number | string; percentageToSwap: number; sliderXPosition: number; stripSeparators?: boolean; @@ -285,21 +300,21 @@ export function niceIncrementFormatter({ if (percentageToSwap === 0) return '0'; if (percentageToSwap === 0.25) return valueBasedDecimalFormatter({ - amount: inputAssetBalance * 0.25, + amount: mulWorklet(inputAssetBalance, 0.25), usdTokenPrice: inputAssetUsdPrice, roundingMode: 'up', precisionAdjustment: -3, }); if (percentageToSwap === 0.5) return valueBasedDecimalFormatter({ - amount: inputAssetBalance * 0.5, + amount: mulWorklet(inputAssetBalance, 0.5), usdTokenPrice: inputAssetUsdPrice, roundingMode: 'up', precisionAdjustment: -3, }); if (percentageToSwap === 0.75) return valueBasedDecimalFormatter({ - amount: inputAssetBalance * 0.75, + amount: mulWorklet(inputAssetBalance, 0.75), usdTokenPrice: inputAssetUsdPrice, roundingMode: 'up', precisionAdjustment: -3, @@ -311,16 +326,22 @@ export function niceIncrementFormatter({ roundingMode: 'up', }); - const exactIncrement = inputAssetBalance / 100; - const isIncrementExact = niceIncrement === exactIncrement; - const numberOfIncrements = inputAssetBalance / niceIncrement; - const incrementStep = 1 / numberOfIncrements; + const exactIncrement = divWorklet(inputAssetBalance, 100); + const isIncrementExact = equalWorklet(niceIncrement, exactIncrement); + const numberOfIncrements = divWorklet(inputAssetBalance, niceIncrement); + const incrementStep = divWorklet(1, numberOfIncrements); const percentage = isIncrementExact ? percentageToSwap - : Math.round(clamp((sliderXPosition - SCRUBBER_WIDTH / SLIDER_WIDTH) / SLIDER_WIDTH, 0, 1) * (1 / incrementStep)) / (1 / incrementStep); + : divWorklet( + roundWorklet( + mulWorklet(clamp((sliderXPosition - SCRUBBER_WIDTH / SLIDER_WIDTH) / SLIDER_WIDTH, 0, 1), divWorklet(1, incrementStep)) + ), + divWorklet(1, incrementStep) + ); + + const rawAmount = mulWorklet(roundWorklet(divWorklet(mulWorklet(percentage, inputAssetBalance), niceIncrement)), niceIncrement); - const rawAmount = Math.round((percentage * inputAssetBalance) / niceIncrement) * niceIncrement; - const amountToFixedDecimals = rawAmount.toFixed(incrementDecimalPlaces); + const amountToFixedDecimals = toFixedWorklet(rawAmount, incrementDecimalPlaces); const formattedAmount = `${Number(amountToFixedDecimals).toLocaleString('en-US', { useGrouping: true, diff --git a/src/handlers/swap.ts b/src/handlers/swap.ts index abcc81cfe1e..bb35bf34d82 100644 --- a/src/handlers/swap.ts +++ b/src/handlers/swap.ts @@ -14,14 +14,12 @@ import { } from '@rainbow-me/swaps'; import { Contract } from '@ethersproject/contracts'; import { MaxUint256 } from '@ethersproject/constants'; -import { get, mapKeys, mapValues } from 'lodash'; import { IS_TESTING } from 'react-native-dotenv'; import { Token } from '../entities/tokens'; import { estimateGasWithPadding, getProviderForNetwork, toHexNoLeadingZeros } from './web3'; import { getRemoteConfig } from '@/model/remoteConfig'; import { Asset } from '@/entities'; import { add, convertRawAmountToDecimalFormat, divide, lessThan, multiply, subtract } from '@/helpers/utilities'; -import { Network } from '@/helpers/networkTypes'; import { erc20ABI, ethUnits } from '@/references'; import { ethereumUtils, logger } from '@/utils';