Skip to content

Commit

Permalink
feat: add bignumberjs for money ops
Browse files Browse the repository at this point in the history
  • Loading branch information
0xnigir1 committed Oct 17, 2024
1 parent 7fc6f04 commit 11c9bbe
Show file tree
Hide file tree
Showing 15 changed files with 86 additions and 53 deletions.
7 changes: 1 addition & 6 deletions packages/processors/src/allo/allo.processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,7 @@ export class AlloProcessor implements IProcessor<"Allo", AlloEvent> {
async process(event: ProtocolEvent<"Allo", AlloEvent>): Promise<Changeset[]> {
switch (event.eventName) {
case "PoolCreated":
return new PoolCreatedHandler(event, this.chainId, {
evmProvider: this.dependencies.evmProvider,
pricingProvider: this.dependencies.pricingProvider,
metadataProvider: this.dependencies.metadataProvider,
roundRepository: this.dependencies.roundRepository,
}).handle();
return new PoolCreatedHandler(event, this.chainId, this.dependencies).handle();
default:
throw new Error(`Unknown event name: ${event.eventName}`);
}
Expand Down
8 changes: 4 additions & 4 deletions packages/processors/src/allo/handlers/poolCreated.handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ export class PoolCreatedHandler implements IEventHandler<"Allo", "PoolCreated">
};

let matchAmount = 0n;
let matchAmountInUsd = 0;
let matchAmountInUsd = "0";

if (strategy) {
strategyTimings = await getStrategyTimings(evmProvider, strategy, strategyAddress);
Expand All @@ -102,7 +102,7 @@ export class PoolCreatedHandler implements IEventHandler<"Allo", "PoolCreated">
}
}

let fundedAmountInUsd = 0;
let fundedAmountInUsd = "0";

if (token !== null && fundedAmount > 0n) {
fundedAmountInUsd = await this.getTokenAmountInUsd(
Expand All @@ -122,7 +122,7 @@ export class PoolCreatedHandler implements IEventHandler<"Allo", "PoolCreated">
id: poolId.toString(),
tags: ["allo-v2", ...(parsedRoundMetadata.success ? ["grants-stack"] : [])],
totalDonationsCount: 0,
totalAmountDonatedInUsd: 0,
totalAmountDonatedInUsd: "0",
uniqueDonorsCount: 0,
matchTokenAddress,
matchAmount,
Expand Down Expand Up @@ -209,7 +209,7 @@ export class PoolCreatedHandler implements IEventHandler<"Allo", "PoolCreated">
token: { address: Address; decimals: number },
amount: bigint,
timestamp: number,
): Promise<number> {
): Promise<string> {
const { pricingProvider } = this.dependencies;
const tokenPrice = await pricingProvider.getTokenPrice(
this.chainId,
Expand Down
23 changes: 11 additions & 12 deletions packages/processors/src/helpers/tokenMath.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { formatUnits, parseUnits } from "viem";
import { BigNumber } from "@grants-stack-indexer/shared";

/**
* Calculates the amount in USD
Expand All @@ -10,23 +10,22 @@ import { formatUnits, parseUnits } from "viem";
*/
export const calculateAmountInUsd = (
amount: bigint,
tokenPriceInUsd: number,
tokenPriceInUsd: string | number,
tokenDecimals: number,
truncateDecimals?: number,
): number => {
const amountInUsd = Number(
formatUnits(
amount * parseUnits(tokenPriceInUsd.toString(), tokenDecimals),
tokenDecimals * 2,
),
);
): string => {
const amountBN = new BigNumber(amount.toString());
const tokenPriceBN = new BigNumber(tokenPriceInUsd.toString());
const scaleFactor = new BigNumber(10).pow(tokenDecimals);

if (truncateDecimals) {
let amountInUsd = amountBN.multipliedBy(tokenPriceBN).dividedBy(scaleFactor);

if (truncateDecimals !== undefined) {
if (truncateDecimals < 0 || truncateDecimals > 18) {
throw new Error("Truncate decimals must be between 0 and 18");
}
return Number(amountInUsd.toFixed(truncateDecimals));
amountInUsd = amountInUsd.decimalPlaces(truncateDecimals);
}

return amountInUsd;
return amountInUsd.toString();
};
11 changes: 5 additions & 6 deletions packages/processors/test/allo/allo.processor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,11 @@ describe("AlloProcessor", () => {

await processor.process(mockEvent);

expect(PoolCreatedHandler).toHaveBeenCalledWith(mockEvent, mockChainId, {
evmProvider: mockEvmProvider,
pricingProvider: mockPricingProvider,
metadataProvider: mockMetadataProvider,
roundRepository: mockRoundRepository,
});
expect(PoolCreatedHandler).toHaveBeenCalledWith(
mockEvent,
mockChainId,
processor["dependencies"],
);
expect(PoolCreatedHandler.prototype.handle).toHaveBeenCalled();
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ describe("PoolCreatedHandler", () => {
expect(changeset.type).toBe("InsertRound");
expect(changeset.args.round).toMatchObject({
fundedAmount: fundedAmount,
fundedAmountInUsd: 1000,
fundedAmountInUsd: "1000",
});
expect(mockPricingProvider.getTokenPrice).toHaveBeenCalled();
expect(mockMetadataProvider.getMetadata).toHaveBeenCalled();
Expand Down Expand Up @@ -188,13 +188,13 @@ describe("PoolCreatedHandler", () => {
id: "10",
tags: ["allo-v2", "grants-stack"],
totalDonationsCount: 0,
totalAmountDonatedInUsd: 0,
totalAmountDonatedInUsd: "0",
uniqueDonorsCount: 0,
matchTokenAddress: mockEvent.params.token,
matchAmount: parseUnits("1", 18),
matchAmountInUsd: 100,
matchAmountInUsd: "100",
fundedAmount: 0n,
fundedAmountInUsd: 0,
fundedAmountInUsd: "0",
applicationMetadataCid: "bafkreihrjyu5tney6wia2hmkertc74nzfpsgxw2epvnxm72bxj6ifnd4ku",
applicationMetadata: {
version: "1.0.0",
Expand Down Expand Up @@ -291,9 +291,9 @@ describe("PoolCreatedHandler", () => {
id: "10",
tags: ["allo-v2"],
matchAmount: 0n,
matchAmountInUsd: 0,
matchAmountInUsd: "0",
fundedAmount: 0n,
fundedAmountInUsd: 0,
fundedAmountInUsd: "0",
applicationMetadataCid: "bafkreihrjyu5tney6wia2hmkertc74nzfpsgxw2epvnxm72bxj6ifnd4ku",
applicationMetadata: {},
roundMetadataCid: "bafkreihrjyu5tney6wia2hmkertc74nzfpsgxw2epvnxm72bxj6ifnd4ku",
Expand Down
44 changes: 32 additions & 12 deletions packages/processors/test/helpers/tokenMath.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ describe("calculateAmountInUsd", () => {
const tokenDecimals = 18;

const result = calculateAmountInUsd(amount, tokenPriceInUsd, tokenDecimals);
expect(result).toBe(100);
expect(result).toBe("100");
});

it("calculate USD amount for 18 decimal token with float price", () => {
Expand All @@ -19,7 +19,7 @@ describe("calculateAmountInUsd", () => {
const tokenDecimals = 18;

const result = calculateAmountInUsd(amount, tokenPriceInUsd, tokenDecimals);
expect(result).toBeCloseTo(41.025, 5);
expect(result).toBe("41.025");
});

it("calculate USD amount for 8 decimal token with integer price", () => {
Expand All @@ -28,7 +28,7 @@ describe("calculateAmountInUsd", () => {
const tokenDecimals = 8;

const result = calculateAmountInUsd(amount, tokenPriceInUsd, tokenDecimals);
expect(result).toBe(50);
expect(result).toBe("50");
});

// Test case for 8 decimal token with float price
Expand All @@ -38,7 +38,7 @@ describe("calculateAmountInUsd", () => {
const tokenDecimals = 8;

const result = calculateAmountInUsd(amount, tokenPriceInUsd, tokenDecimals);
expect(result).toBeCloseTo(19.125, 5);
expect(result).toBe("19.125");
});

it("correctly calculate USD amount for 1gwei token amount", () => {
Expand All @@ -47,7 +47,7 @@ describe("calculateAmountInUsd", () => {
const tokenDecimals = 18;

const result = calculateAmountInUsd(amount, tokenPriceInUsd, tokenDecimals);
expect(result).toBe(0.000001);
expect(result).toBe("0.000001");
});

it("correctly truncate decimals when specified", () => {
Expand All @@ -56,7 +56,27 @@ describe("calculateAmountInUsd", () => {
const tokenDecimals = 18;

const result = calculateAmountInUsd(amount, tokenPriceInUsd, tokenDecimals, 4);
expect(result).toBe(1.5185);
expect(result).toBe("1.5185");
});

it("handle token price with 19 decimal digits", () => {
const amount = 1000000000000000000n; // 1 token
const tokenPriceInUsd = 1e-19; // 19 decimal places
const tokenDecimals = 18;

const result = calculateAmountInUsd(amount, tokenPriceInUsd, tokenDecimals);

expect(result).toBe("0.0000000000000000001");
});

it("handle scientific notation token price with interspersed non-zero digits in result", () => {
const amount = 123456789012345678n; // 0.123456789012345678 tokens
const tokenPriceInUsd = 1.23e-15; // 0.00000000000000123
const tokenDecimals = 18;

const result = calculateAmountInUsd(amount, tokenPriceInUsd, tokenDecimals);

expect(result).toBe("0.00000000000000015185");
});

it("return zero for zero token amount", () => {
Expand All @@ -65,7 +85,7 @@ describe("calculateAmountInUsd", () => {
const tokenDecimals = 18;

const result = calculateAmountInUsd(amount, tokenPriceInUsd, tokenDecimals);
expect(result).toBe(0);
expect(result).toBe("0");
});

it("should return zero for zero token price", () => {
Expand All @@ -74,7 +94,7 @@ describe("calculateAmountInUsd", () => {
const tokenDecimals = 18;

const result = calculateAmountInUsd(amount, tokenPriceInUsd, tokenDecimals);
expect(result).toBe(0);
expect(result).toBe("0");
});

it("throw an error for invalid truncate decimals", () => {
Expand All @@ -87,12 +107,12 @@ describe("calculateAmountInUsd", () => {
});

test("migrated cases", () => {
expect(calculateAmountInUsd(3400000000000000000n, 1, 18, 8)).toBe(3.4);
expect(calculateAmountInUsd(3400000000000000000n, 1, 18, 8)).toBe("3.4");

expect(calculateAmountInUsd(50000000000n, 1, 18, 8)).toBe(0.00000005);
expect(calculateAmountInUsd(50000000000n, 1, 18, 8)).toBe("0.00000005");

expect(calculateAmountInUsd(3400000000000000000n, 0.5, 18, 8)).toBe(1.7);
expect(calculateAmountInUsd(3400000000000000000n, 0.5, 18, 8)).toBe("1.7");

expect(calculateAmountInUsd(3400000000000000000n, 2, 18, 8)).toBe(6.8);
expect(calculateAmountInUsd(3400000000000000000n, 2, 18, 8)).toBe("6.8");
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ export interface IRoundRepository extends IRoundReadRepository {
roundId: string;
},
amount: bigint,
amountInUsd: number,
amountInUsd: string,
): Promise<void>;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export class KyselyRoundRepository implements IRoundRepository {
roundId: string;
},
amount: bigint,
amountInUsd: number,
amountInUsd: string,
): Promise<void> {
await this.db
.withSchema(this.schemaName)
Expand Down
4 changes: 2 additions & 2 deletions packages/repository/src/types/changeset.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,15 +88,15 @@ export type Changeset =
chainId: ChainId;
roundId: string;
fundedAmount: bigint;
fundedAmountInUsd: number;
fundedAmountInUsd: string;
};
}
| {
type: "IncrementRoundDonationStats";
args: {
chainId: ChainId;
roundId: Address;
amountInUsd: number;
amountInUsd: string;
};
}
| {
Expand Down
6 changes: 3 additions & 3 deletions packages/repository/src/types/round.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ export type Round = {
chainId: ChainId;
matchAmount: bigint;
matchTokenAddress: Address;
matchAmountInUsd: number;
matchAmountInUsd: string;
fundedAmount: bigint;
fundedAmountInUsd: number;
fundedAmountInUsd: string;
applicationMetadataCid: string;
applicationMetadata: unknown | null;
roundMetadataCid: string | null;
Expand All @@ -30,7 +30,7 @@ export type Round = {
createdByAddress: Address;
createdAtBlock: bigint;
updatedAtBlock: bigint;
totalAmountDonatedInUsd: number;
totalAmountDonatedInUsd: string;
totalDonationsCount: number;
totalDistributed: bigint;
uniqueDonorsCount: number;
Expand Down
1 change: 1 addition & 0 deletions packages/shared/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"test:cov": "vitest run --config vitest.config.ts --coverage"
},
"dependencies": {
"bignumber.js": "9.1.2",
"viem": "2.21.19",
"winston": "3.15.0"
}
Expand Down
3 changes: 3 additions & 0 deletions packages/shared/src/external.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,6 @@ export {
export type { DeepPartial } from "./utils/testing.js";
export { mergeDeep } from "./utils/testing.js";
export type { ILogger, Logger } from "./internal.js";

export { BigNumber } from "./internal.js";
export type { BigNumberType } from "./internal.js";
1 change: 1 addition & 0 deletions packages/shared/src/internal.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export type { Address } from "viem";
export * from "./math/bignumber.js";
export * from "./types/index.js";
export * from "./constants/index.js";
export * from "./utils/testing.js";
Expand Down
4 changes: 4 additions & 0 deletions packages/shared/src/math/bignumber.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import * as b from "bignumber.js";

export const BigNumber = b.BigNumber.clone({ EXPONENTIAL_AT: 32 });
export type BigNumberType = typeof BigNumber;
11 changes: 11 additions & 0 deletions pnpm-lock.yaml

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

0 comments on commit 11c9bbe

Please sign in to comment.