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

Swaps: fix safe math negative number support, add comparison functions #5776

Merged
merged 2 commits into from
May 27, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
89 changes: 84 additions & 5 deletions src/__swaps__/safe-math/SafeMath.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
// Utility function to remove the decimal point and keep track of the number of decimal places
const removeDecimal = (num: string): [bigint, number] => {
'worklet';
const parts = num.split('.');
const decimalPlaces = parts.length === 2 ? parts[1].length : 0;
const bigIntNum = BigInt(parts.join(''));
return [bigIntNum, decimalPlaces];
};

const isNumberString = (value: string): boolean => {
return /^\d+(\.\d+)?$/.test(value);
'worklet';
return /^-?\d+(\.\d+)?$/.test(value);
};

const isZero = (value: string): boolean => {
'worklet';
if (parseFloat(value) === 0) {
return true;
}
Expand All @@ -19,21 +22,27 @@ const isZero = (value: string): boolean => {

// Utility function to scale the number up to 20 decimal places
const scaleUp = (bigIntNum: bigint, decimalPlaces: number): bigint => {
const scaleFactor = BigInt(10) ** (BigInt(20) - BigInt(decimalPlaces));
return bigIntNum * scaleFactor;
'worklet';
const scaleFactor = BigInt(10) ** BigInt(20);
return (bigIntNum * scaleFactor) / BigInt(10) ** BigInt(decimalPlaces);
};

// Utility function to format the result with 20 decimal places and remove trailing zeros
const formatResult = (result: bigint): string => {
const resultStr = result.toString().padStart(21, '0'); // 20 decimal places + at least 1 integer place
'worklet';
const isNegative = result < 0;
const absResult = isNegative ? -result : result;
const resultStr = absResult.toString().padStart(21, '0'); // 20 decimal places + at least 1 integer place
const integerPart = resultStr.slice(0, -20) || '0';
let fractionalPart = resultStr.slice(-20);
fractionalPart = fractionalPart.replace(/0+$/, ''); // Remove trailing zeros
return fractionalPart.length > 0 ? `${integerPart}.${fractionalPart}` : integerPart;
const formattedResult = fractionalPart.length > 0 ? `${integerPart}.${fractionalPart}` : integerPart;
return isNegative ? `-${formattedResult}` : formattedResult;
};

// Sum function
export function sum(num1: string, num2: string): string {
'worklet';
if (!isNumberString(num1) || !isNumberString(num2)) {
throw new Error('Arguments must be a numeric string');
}
Expand All @@ -54,6 +63,7 @@ export function sum(num1: string, num2: string): string {

// Subtract function
export function sub(num1: string, num2: string): string {
'worklet';
if (!isNumberString(num1) || !isNumberString(num2)) {
throw new Error('Arguments must be a numeric string');
}
Expand All @@ -72,6 +82,7 @@ export function sub(num1: string, num2: string): string {

// Multiply function
export function mul(num1: string, num2: string): string {
'worklet';
if (!isNumberString(num1) || !isNumberString(num2)) {
throw new Error('Arguments must be a numeric string');
}
Expand All @@ -88,6 +99,7 @@ export function mul(num1: string, num2: string): string {

// Divide function
export function div(num1: string, num2: string): string {
'worklet';
if (!isNumberString(num1) || !isNumberString(num2)) {
throw new Error('Arguments must be a numeric string');
}
Expand All @@ -107,6 +119,7 @@ export function div(num1: string, num2: string): string {

// Modulus function
export function mod(num1: string, num2: string): string {
'worklet';
if (!isNumberString(num1) || !isNumberString(num2)) {
throw new Error('Arguments must be a numeric string');
}
Expand All @@ -126,6 +139,7 @@ export function mod(num1: string, num2: string): string {

// Power function
export function pow(base: string, exponent: string): string {
'worklet';
if (!isNumberString(base) || !isNumberString(exponent)) {
throw new Error('Arguments must be a numeric string');
}
Expand All @@ -140,3 +154,68 @@ export function pow(base: string, exponent: string): string {
const result = scaledBigIntBase ** BigInt(exponent) / BigInt(10) ** BigInt(20);
return formatResult(result);
}

// Equality function
export function equal(num1: string, num2: string): boolean {
'worklet';
if (!isNumberString(num1) || !isNumberString(num2)) {
throw new Error('Arguments must be a numeric string');
}
const [bigInt1, decimalPlaces1] = removeDecimal(num1);
const [bigInt2, decimalPlaces2] = removeDecimal(num2);
const scaledBigInt1 = scaleUp(bigInt1, decimalPlaces1);
const scaledBigInt2 = scaleUp(bigInt2, decimalPlaces2);
return scaledBigInt1 === scaledBigInt2;
}

// Greater than function
export function greaterThan(num1: string, num2: string): boolean {
'worklet';
if (!isNumberString(num1) || !isNumberString(num2)) {
throw new Error('Arguments must be a numeric string');
}
const [bigInt1, decimalPlaces1] = removeDecimal(num1);
const [bigInt2, decimalPlaces2] = removeDecimal(num2);
const scaledBigInt1 = scaleUp(bigInt1, decimalPlaces1);
const scaledBigInt2 = scaleUp(bigInt2, decimalPlaces2);
return scaledBigInt1 > scaledBigInt2;
}

// Greater than or equal to function
export function greaterThanOrEqualTo(num1: string, num2: string): boolean {
'worklet';
if (!isNumberString(num1) || !isNumberString(num2)) {
throw new Error('Arguments must be a numeric string');
}
const [bigInt1, decimalPlaces1] = removeDecimal(num1);
const [bigInt2, decimalPlaces2] = removeDecimal(num2);
const scaledBigInt1 = scaleUp(bigInt1, decimalPlaces1);
const scaledBigInt2 = scaleUp(bigInt2, decimalPlaces2);
return scaledBigInt1 >= scaledBigInt2;
}

// Less than function
export function lessThan(num1: string, num2: string): boolean {
'worklet';
if (!isNumberString(num1) || !isNumberString(num2)) {
throw new Error('Arguments must be a numeric string');
}
const [bigInt1, decimalPlaces1] = removeDecimal(num1);
const [bigInt2, decimalPlaces2] = removeDecimal(num2);
const scaledBigInt1 = scaleUp(bigInt1, decimalPlaces1);
const scaledBigInt2 = scaleUp(bigInt2, decimalPlaces2);
return scaledBigInt1 < scaledBigInt2;
}

// Less than or equal to function
export function lessThanOrEqualTo(num1: string, num2: string): boolean {
'worklet';
if (!isNumberString(num1) || !isNumberString(num2)) {
throw new Error('Arguments must be a numeric string');
}
const [bigInt1, decimalPlaces1] = removeDecimal(num1);
const [bigInt2, decimalPlaces2] = removeDecimal(num2);
const scaledBigInt1 = scaleUp(bigInt1, decimalPlaces1);
const scaledBigInt2 = scaleUp(bigInt2, decimalPlaces2);
return scaledBigInt1 <= scaledBigInt2;
}
78 changes: 77 additions & 1 deletion src/__swaps__/safe-math/__tests__/SafeMath.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { sum, sub, mul, div, mod, pow } from '../SafeMath';
import { sum, sub, mul, div, mod, pow, equal, greaterThan, greaterThanOrEqualTo, lessThan, lessThanOrEqualTo } from '../SafeMath';
import BigNumber from 'bignumber.js';

const RESULTS = {
Expand All @@ -13,6 +13,7 @@ const RESULTS = {
const VALUE_A = '1243425.345';
const VALUE_B = '3819.24';
const VALUE_C = '2';
const NEGATIVE_VALUE = '-2412.12';
const ZERO = '0';
const ONE = '1';
const NON_NUMERIC_STRING = 'abc';
Expand All @@ -33,6 +34,7 @@ describe('SafeMath', () => {
expect(sub(ZERO, ZERO)).toBe(ZERO);
expect(sub(VALUE_A, ZERO)).toBe(VALUE_A);
expect(sub(ZERO, VALUE_B)).toBe(`-${VALUE_B}`);
expect(sub(NEGATIVE_VALUE, ZERO)).toBe(NEGATIVE_VALUE);
expect(sub(VALUE_A, VALUE_B)).toBe(RESULTS.sub);
});

Expand Down Expand Up @@ -62,6 +64,7 @@ describe('SafeMath', () => {
expect(mod(ZERO, VALUE_B)).toBe(ZERO);
expect(mod(VALUE_A, VALUE_B)).toBe(RESULTS.mod);
});

test('pow', () => {
expect(() => pow(NON_NUMERIC_STRING, VALUE_B)).toThrow('Arguments must be a numeric string');
expect(() => pow(VALUE_A, NON_NUMERIC_STRING)).toThrow('Arguments must be a numeric string');
Expand All @@ -70,6 +73,51 @@ describe('SafeMath', () => {
expect(pow(ZERO, VALUE_B)).toBe(ZERO);
expect(pow(VALUE_A, VALUE_C)).toBe(RESULTS.pow);
});

test('equal', () => {
expect(() => equal(NON_NUMERIC_STRING, VALUE_B)).toThrow('Arguments must be a numeric string');
expect(() => equal(VALUE_A, NON_NUMERIC_STRING)).toThrow('Arguments must be a numeric string');
expect(equal(ZERO, ZERO)).toBe(true);
expect(equal(VALUE_A, VALUE_A)).toBe(true);
expect(equal(VALUE_A, VALUE_B)).toBe(false);
expect(equal(NEGATIVE_VALUE, NEGATIVE_VALUE)).toBe(true);
});

test('greaterThan', () => {
expect(() => greaterThan(NON_NUMERIC_STRING, VALUE_B)).toThrow('Arguments must be a numeric string');
expect(() => greaterThan(VALUE_A, NON_NUMERIC_STRING)).toThrow('Arguments must be a numeric string');
expect(greaterThan(VALUE_A, VALUE_B)).toBe(true);
expect(greaterThan(VALUE_B, VALUE_A)).toBe(false);
expect(greaterThan(VALUE_A, VALUE_A)).toBe(false);
expect(greaterThan(NEGATIVE_VALUE, VALUE_A)).toBe(false);
});

test('greaterThanOrEqualTo', () => {
expect(() => greaterThanOrEqualTo(NON_NUMERIC_STRING, VALUE_B)).toThrow('Arguments must be a numeric string');
expect(() => greaterThanOrEqualTo(VALUE_A, NON_NUMERIC_STRING)).toThrow('Arguments must be a numeric string');
expect(greaterThanOrEqualTo(VALUE_A, VALUE_B)).toBe(true);
expect(greaterThanOrEqualTo(VALUE_B, VALUE_A)).toBe(false);
expect(greaterThanOrEqualTo(VALUE_A, VALUE_A)).toBe(true);
expect(greaterThanOrEqualTo(NEGATIVE_VALUE, VALUE_A)).toBe(false);
});

test('lessThan', () => {
expect(() => lessThan(NON_NUMERIC_STRING, VALUE_B)).toThrow('Arguments must be a numeric string');
expect(() => lessThan(VALUE_A, NON_NUMERIC_STRING)).toThrow('Arguments must be a numeric string');
expect(lessThan(VALUE_A, VALUE_B)).toBe(false);
expect(lessThan(VALUE_B, VALUE_A)).toBe(true);
expect(lessThan(VALUE_A, VALUE_A)).toBe(false);
expect(lessThan(NEGATIVE_VALUE, VALUE_A)).toBe(true);
});

test('lessThanOrEqualTo', () => {
expect(() => lessThanOrEqualTo(NON_NUMERIC_STRING, VALUE_B)).toThrow('Arguments must be a numeric string');
expect(() => lessThanOrEqualTo(VALUE_A, NON_NUMERIC_STRING)).toThrow('Arguments must be a numeric string');
expect(lessThanOrEqualTo(VALUE_A, VALUE_B)).toBe(false);
expect(lessThanOrEqualTo(VALUE_B, VALUE_A)).toBe(true);
expect(lessThanOrEqualTo(VALUE_A, VALUE_A)).toBe(true);
expect(lessThanOrEqualTo(NEGATIVE_VALUE, VALUE_A)).toBe(true);
});
});

describe('BigNumber', () => {
Expand All @@ -92,7 +140,35 @@ describe('BigNumber', () => {
test('mod', () => {
expect(new BigNumber(VALUE_A).mod(VALUE_B).toString()).toBe(RESULTS.mod);
});

test('pow', () => {
expect(new BigNumber(VALUE_A).pow(VALUE_C).toString()).toBe(RESULTS.pow);
});

test('equal', () => {
expect(new BigNumber(VALUE_A).eq(VALUE_B)).toBe(false);
expect(new BigNumber(VALUE_A).eq(VALUE_A)).toBe(true);
});

test('greaterThan', () => {
expect(new BigNumber(VALUE_A).gt(VALUE_B)).toBe(true);
expect(new BigNumber(VALUE_B).gt(VALUE_A)).toBe(false);
});

test('greaterThanOrEqualTo', () => {
expect(new BigNumber(VALUE_A).gte(VALUE_B)).toBe(true);
expect(new BigNumber(VALUE_B).gte(VALUE_A)).toBe(false);
expect(new BigNumber(VALUE_A).gte(VALUE_A)).toBe(true);
});

test('lessThan', () => {
expect(new BigNumber(VALUE_A).lt(VALUE_B)).toBe(false);
expect(new BigNumber(VALUE_B).lt(VALUE_A)).toBe(true);
});

test('lessThanOrEqualTo', () => {
expect(new BigNumber(VALUE_A).lte(VALUE_B)).toBe(false);
expect(new BigNumber(VALUE_B).lte(VALUE_A)).toBe(true);
expect(new BigNumber(VALUE_A).lte(VALUE_A)).toBe(true);
});
});
Loading