Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use SafeMath in worklets #5781

Merged
merged 36 commits into from
Jun 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
d599d3c
initial work
brunobar79 May 27, 2024
d34b999
fix merge conflicts
brunobar79 May 28, 2024
f64b70e
more fixes
brunobar79 May 28, 2024
95f41f0
updates
brunobar79 May 28, 2024
59f6fb5
remove diff files
brunobar79 May 28, 2024
ac891a2
added SafeMath for increments
brunobar79 May 28, 2024
02b155e
fix
brunobar79 May 28, 2024
8669879
fix merge conflicts
brunobar79 May 29, 2024
15f9e93
useSwapInputsController
brunobar79 May 29, 2024
8bfb3c5
useSwapTextStyles
brunobar79 May 29, 2024
1a1f73a
swapProvider
brunobar79 May 29, 2024
773ccd0
missing worklet declaration
brunobar79 May 29, 2024
2f42d79
fix merge conflicts
brunobar79 May 29, 2024
4a29b00
clean up numbers
brunobar79 May 29, 2024
b6ea8ea
add logs
brunobar79 May 30, 2024
7eb89ea
Merge branch 'develop' of github.com:rainbow-me/rainbow into @bruno/s…
brunobar79 May 30, 2024
08ab49f
logs
brunobar79 May 30, 2024
bd7d5a1
fix wrong calculations for increments
brunobar79 May 30, 2024
ec4e022
parse to number percentages
brunobar79 May 30, 2024
6156f55
revert clamp conversion
brunobar79 May 30, 2024
61a3744
clean up
brunobar79 May 30, 2024
f868e3e
final fixes for formatting functions
brunobar79 May 31, 2024
b84caa3
revert casting
brunobar79 May 31, 2024
2fd2663
more clean up
brunobar79 May 31, 2024
fced72a
add logs back
brunobar79 May 31, 2024
83e3bc1
fix merge conflicts
brunobar79 Jun 1, 2024
8dc565f
fix merge conflicts again
brunobar79 Jun 1, 2024
832f927
fix types
brunobar79 Jun 1, 2024
7ee09e6
support negative exponents
brunobar79 Jun 4, 2024
d5cea1a
fix import
brunobar79 Jun 4, 2024
48e7ae1
reorder
brunobar79 Jun 4, 2024
083a97f
remove old imports
brunobar79 Jun 4, 2024
fc73996
fix merge conflicts
brunobar79 Jun 4, 2024
76abf6d
fix types
brunobar79 Jun 4, 2024
0764bbd
fix merge conflicts
brunobar79 Jun 4, 2024
99a3d96
remove import
brunobar79 Jun 4, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 46 additions & 22 deletions src/__swaps__/safe-math/SafeMath.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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';
Expand Down Expand Up @@ -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';
Expand Down Expand Up @@ -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;
}
5 changes: 5 additions & 0 deletions src/__swaps__/safe-math/__tests__/SafeMath.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,20 @@ const RESULTS = {
ceil: '1243426',
floor: '1243425',
toScaledInteger: '57464009350560633',
negativePow: '0.001',
};

const VALUE_A = '1243425.345';
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', () => {
Expand Down Expand Up @@ -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', () => {
Expand Down
74 changes: 38 additions & 36 deletions src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 {
Expand Down Expand Up @@ -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));

Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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';
});

Expand Down Expand Up @@ -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;
Expand All @@ -319,7 +320,7 @@ export function useSwapInputsController({
return {
...prev,
inputAmount,
inputNativeValue: inputAmount * (inputPrice || inputNativePrice.value),
inputNativeValue: mulWorklet(inputAmount, inputPrice || inputNativePrice.value),
};
});
}
Expand All @@ -329,7 +330,7 @@ export function useSwapInputsController({
return {
...prev,
outputAmount,
outputNativeValue: outputAmount * (outputPrice || outputNativePrice.value),
outputNativeValue: mulWorklet(outputAmount, outputPrice || outputNativePrice.value),
};
});
}
Expand All @@ -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);
}
}
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -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;
Expand Down Expand Up @@ -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 {
Expand All @@ -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;
Expand All @@ -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 {
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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,
};
});
}
Expand Down
Loading
Loading