Skip to content

Commit

Permalink
Merge pull request #2 from kyscott18/types
Browse files Browse the repository at this point in the history
More sophisticated types and fully bigintish support
  • Loading branch information
kyscott18 authored Jul 16, 2023
2 parents 120a844 + 281847f commit df80f86
Show file tree
Hide file tree
Showing 14 changed files with 512 additions and 163 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,15 @@
}
},
"dependencies": {
"viem": "^1.2.14"
"tiny-invariant": "^1.3.1",
"viem": "^1.2.15"
},
"devDependencies": {
"@uniswap/sdk-core": "^4.0.3",
"@viem/anvil": "^0.0.6",
"@wagmi/cli": "^1.3.0",
"husky": "^8.0.3",
"rome": "^12.1.3",
"tiny-invariant": "^1.3.1",
"typescript": "^5.1.6",
"vitest": "^0.33.0"
}
Expand Down
17 changes: 8 additions & 9 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 25 additions & 2 deletions src/currencyAmountUtils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,20 @@ import {
} from "./currencyAmountUtils.js";
import { makeFraction } from "./fractionUtils.js";
import { mockERC20 } from "./test/constants.js";
import type { CurrencyAmount } from "./types.js";
import { parseEther } from "viem/utils";
import { describe, expect, test } from "vitest";

const one = { currency: mockERC20, amount: parseEther("1") };
const two = { currency: mockERC20, amount: parseEther("2") };
const one = {
type: "currencyAmount",
currency: mockERC20,
amount: parseEther("1"),
} as const satisfies CurrencyAmount<typeof mockERC20>;
const two = {
type: "currencyAmount",
currency: mockERC20,
amount: parseEther("2"),
} as const satisfies CurrencyAmount<typeof mockERC20>;

describe.concurrent("currency amount utils", () => {
test("can make currency amount from string", () => {
Expand Down Expand Up @@ -66,6 +75,7 @@ describe.concurrent("currency amount utils", () => {
}),
).toThrowError();
expect(currencyAmountEqualTo(currencyAmountAdd(one, one), two)).toBe(true);
expect(currencyAmountEqualTo(currencyAmountAdd(one, 1), two)).toBe(true);
});

test("can subtract", () => {
Expand All @@ -78,6 +88,9 @@ describe.concurrent("currency amount utils", () => {
expect(currencyAmountEqualTo(currencyAmountSubtract(two, one), one)).toBe(
true,
);
expect(currencyAmountEqualTo(currencyAmountSubtract(two, 1), one)).toBe(
true,
);
});

test("can multiply", () => {
Expand All @@ -96,6 +109,9 @@ describe.concurrent("currency amount utils", () => {
expect(currencyAmountEqualTo(currencyAmountMultiply(two, one), two)).toBe(
true,
);
expect(currencyAmountEqualTo(currencyAmountMultiply(two, 1), two)).toBe(
true,
);
});

test("can divide", () => {
Expand All @@ -114,6 +130,7 @@ describe.concurrent("currency amount utils", () => {
expect(currencyAmountEqualTo(currencyAmountDivide(two, one), two)).toBe(
true,
);
expect(currencyAmountEqualTo(currencyAmountDivide(two, 1), two)).toBe(true);
});

test("can equal", () => {
Expand All @@ -126,6 +143,8 @@ describe.concurrent("currency amount utils", () => {
expect(currencyAmountEqualTo(one, one)).toBe(true);
expect(currencyAmountEqualTo(two, two)).toBe(true);
expect(currencyAmountEqualTo(two, one)).toBe(false);
expect(currencyAmountEqualTo(two, 2)).toBe(true);
expect(currencyAmountEqualTo(two, 1)).toBe(false);
});

test("can less than", () => {
Expand All @@ -138,6 +157,8 @@ describe.concurrent("currency amount utils", () => {
expect(currencyAmountLessThan(one, two)).toBe(true);
expect(currencyAmountLessThan(two, one)).toBe(false);
expect(currencyAmountLessThan(one, one)).toBe(false);
expect(currencyAmountLessThan(two, 1)).toBe(false);
expect(currencyAmountLessThan(one, 1)).toBe(false);
});

test("can greater than", () => {
Expand All @@ -150,6 +171,8 @@ describe.concurrent("currency amount utils", () => {
expect(currencyAmountGreaterThan(two, one)).toBe(true);
expect(currencyAmountGreaterThan(one, two)).toBe(false);
expect(currencyAmountGreaterThan(one, one)).toBe(false);
expect(currencyAmountGreaterThan(one, 2)).toBe(false);
expect(currencyAmountGreaterThan(one, 1)).toBe(false);
});

test.todo("can print fixed");
Expand Down
143 changes: 102 additions & 41 deletions src/currencyAmountUtils.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
import { currencyEqualTo } from "./currencyUtils.js";
import {
fractionMultiply,
fractionQuotient,
makeFraction,
} from "./fractionUtils.js";
import type { Currency, CurrencyAmount, Fraction } from "./types.js";
import type { BigIntIsh, Currency, CurrencyAmount, Fraction } from "./types.js";
import invariant from "tiny-invariant";
import { parseUnits } from "viem/utils";

export const scaleUp = (currency: Currency, amount: bigint) =>
amount * 10n ** BigInt(currency.decimals);

export const scaleDown = (currency: Currency, amount: bigint) =>
amount / 10n ** BigInt(currency.decimals);

export const isCurrencyAmount = <TCurrency extends Currency>(
x: CurrencyAmount<TCurrency> | BigIntIsh,
): x is CurrencyAmount<TCurrency> =>
typeof x === "object" && "type" in x && x.type === "currencyAmount";

export const makeCurrencyAmountFromString = <TCurrency extends Currency>(
currency: TCurrency,
amount: string,
) => ({
): CurrencyAmount<TCurrency> => ({
type: "currencyAmount",
currency,
amount: parseUnits(amount, currency.decimals),
});
Expand All @@ -20,87 +27,141 @@ export const makeCurrencyAmountFromFraction = <TCurrency extends Currency>(
currency: TCurrency,
amount: Fraction,
): CurrencyAmount<TCurrency> => ({
type: "currencyAmount",
currency,
amount: fractionQuotient(
fractionMultiply(amount, makeFraction(10n ** BigInt(currency.decimals))),
),
amount: scaleUp(currency, amount.numerator) / amount.denominator,
});

export const makeCurrencyAmountFromRaw = <TCurrency extends Currency>(
currency: TCurrency,
amount: bigint,
): CurrencyAmount<TCurrency> => ({
type: "currencyAmount",
currency,
amount,
});

export const currencyAmountAdd = <TCurrency extends Currency>(
a: CurrencyAmount<TCurrency>,
b: CurrencyAmount<TCurrency>,
b: CurrencyAmount<TCurrency> | BigIntIsh,
): CurrencyAmount<TCurrency> => {
invariant(currencyEqualTo(a.currency, b.currency));

return { currency: a.currency, amount: a.amount + b.amount };
if (isCurrencyAmount(b)) {
invariant(currencyEqualTo(a.currency, b.currency));
}

return isCurrencyAmount(b)
? {
type: "currencyAmount",
currency: a.currency,
amount: a.amount + b.amount,
}
: {
type: "currencyAmount",
currency: a.currency,
amount: a.amount + scaleUp(a.currency, BigInt(b)),
};
};

export const currencyAmountSubtract = <TCurrency extends Currency>(
a: CurrencyAmount<TCurrency>,
b: CurrencyAmount<TCurrency>,
b: CurrencyAmount<TCurrency> | BigIntIsh,
): CurrencyAmount<TCurrency> => {
invariant(currencyEqualTo(a.currency, b.currency));

return { currency: a.currency, amount: a.amount - b.amount };
if (isCurrencyAmount(b)) {
invariant(currencyEqualTo(a.currency, b.currency));
}

return isCurrencyAmount(b)
? {
type: "currencyAmount",
currency: a.currency,
amount: a.amount - b.amount,
}
: {
type: "currencyAmount",
currency: a.currency,
amount: a.amount - scaleUp(a.currency, BigInt(b)),
};
};

export const currencyAmountMultiply = <TCurrency extends Currency>(
a: CurrencyAmount<TCurrency>,
b: CurrencyAmount<TCurrency>,
b: CurrencyAmount<TCurrency> | BigIntIsh,
): CurrencyAmount<TCurrency> => {
invariant(currencyEqualTo(a.currency, b.currency));

return {
currency: a.currency,
amount: (a.amount * b.amount) / 10n ** BigInt(a.currency.decimals),
};
if (isCurrencyAmount(b)) {
invariant(currencyEqualTo(a.currency, b.currency));
}

return isCurrencyAmount(b)
? {
type: "currencyAmount",
currency: a.currency,
amount: scaleDown(a.currency, a.amount * b.amount),
}
: {
type: "currencyAmount",
currency: a.currency,
amount: a.amount * BigInt(b),
};
};

export const currencyAmountDivide = <TCurrency extends Currency>(
a: CurrencyAmount<TCurrency>,
b: CurrencyAmount<TCurrency>,
b: CurrencyAmount<TCurrency> | BigIntIsh,
): CurrencyAmount<TCurrency> => {
invariant(currencyEqualTo(a.currency, b.currency));

return {
currency: a.currency,
amount: (a.amount * 10n ** BigInt(a.currency.decimals)) / b.amount,
};
if (isCurrencyAmount(b)) {
invariant(currencyEqualTo(a.currency, b.currency));
}

return isCurrencyAmount(b)
? {
type: "currencyAmount",
currency: a.currency,
amount: scaleUp(a.currency, a.amount) / b.amount,
}
: {
type: "currencyAmount",
currency: a.currency,
amount: a.amount / BigInt(b),
};
};

export const currencyAmountLessThan = <TCurrency extends Currency>(
a: CurrencyAmount<TCurrency>,
b: CurrencyAmount<TCurrency>,
b: CurrencyAmount<TCurrency> | BigIntIsh,
): boolean => {
invariant(currencyEqualTo(a.currency, b.currency));
if (isCurrencyAmount(b)) {
invariant(currencyEqualTo(a.currency, b.currency));
}

return a.amount < b.amount;
return isCurrencyAmount(b)
? a.amount < b.amount
: a.amount < scaleUp(a.currency, BigInt(b));
};

export const currencyAmountEqualTo = <TCurrency extends Currency>(
a: CurrencyAmount<TCurrency>,
b: CurrencyAmount<TCurrency>,
b: CurrencyAmount<TCurrency> | BigIntIsh,
): boolean => {
invariant(currencyEqualTo(a.currency, b.currency));
if (isCurrencyAmount(b)) {
invariant(currencyEqualTo(a.currency, b.currency));
}

return a.amount === b.amount;
return isCurrencyAmount(b)
? a.amount === b.amount
: a.amount === scaleUp(a.currency, BigInt(b));
};

export const currencyAmountGreaterThan = <TCurrency extends Currency>(
a: CurrencyAmount<TCurrency>,
b: CurrencyAmount<TCurrency>,
b: CurrencyAmount<TCurrency> | BigIntIsh,
): boolean => {
invariant(currencyEqualTo(a.currency, b.currency));
if (isCurrencyAmount(b)) {
invariant(currencyEqualTo(a.currency, b.currency));
}

return a.amount > b.amount;
return isCurrencyAmount(b)
? a.amount > b.amount
: a.amount > scaleUp(a.currency, BigInt(b));
};

// toSignificant
Expand Down
5 changes: 4 additions & 1 deletion src/currencyUtils.bench.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import { currencyEqualTo, currencySortsBefore } from "./currencyUtils.js";
import { mockERC20 } from "./test/constants.js";
import type { Token } from "./types.js";
import { Token as UniswapToken } from "@uniswap/sdk-core";
import { zeroAddress } from "viem";
import { bench, describe } from "vitest";

const zeroToken = {
type: "token",
chainID: 1,
address: zeroAddress,
name: "Zero Token",
symbol: "ZERO",
decimals: 18,
};
} as const satisfies Token;

const uniswapMockERC20 = new UniswapToken(
mockERC20.chainID,
mockERC20.address,
Expand Down
Loading

0 comments on commit df80f86

Please sign in to comment.