Skip to content

Commit 11c9bbe

Browse files
committed
feat: add bignumberjs for money ops
1 parent 7fc6f04 commit 11c9bbe

File tree

15 files changed

+86
-53
lines changed

15 files changed

+86
-53
lines changed

packages/processors/src/allo/allo.processor.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,7 @@ export class AlloProcessor implements IProcessor<"Allo", AlloEvent> {
1313
async process(event: ProtocolEvent<"Allo", AlloEvent>): Promise<Changeset[]> {
1414
switch (event.eventName) {
1515
case "PoolCreated":
16-
return new PoolCreatedHandler(event, this.chainId, {
17-
evmProvider: this.dependencies.evmProvider,
18-
pricingProvider: this.dependencies.pricingProvider,
19-
metadataProvider: this.dependencies.metadataProvider,
20-
roundRepository: this.dependencies.roundRepository,
21-
}).handle();
16+
return new PoolCreatedHandler(event, this.chainId, this.dependencies).handle();
2217
default:
2318
throw new Error(`Unknown event name: ${event.eventName}`);
2419
}

packages/processors/src/allo/handlers/poolCreated.handler.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ export class PoolCreatedHandler implements IEventHandler<"Allo", "PoolCreated">
7878
};
7979

8080
let matchAmount = 0n;
81-
let matchAmountInUsd = 0;
81+
let matchAmountInUsd = "0";
8282

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

105-
let fundedAmountInUsd = 0;
105+
let fundedAmountInUsd = "0";
106106

107107
if (token !== null && fundedAmount > 0n) {
108108
fundedAmountInUsd = await this.getTokenAmountInUsd(
@@ -122,7 +122,7 @@ export class PoolCreatedHandler implements IEventHandler<"Allo", "PoolCreated">
122122
id: poolId.toString(),
123123
tags: ["allo-v2", ...(parsedRoundMetadata.success ? ["grants-stack"] : [])],
124124
totalDonationsCount: 0,
125-
totalAmountDonatedInUsd: 0,
125+
totalAmountDonatedInUsd: "0",
126126
uniqueDonorsCount: 0,
127127
matchTokenAddress,
128128
matchAmount,
@@ -209,7 +209,7 @@ export class PoolCreatedHandler implements IEventHandler<"Allo", "PoolCreated">
209209
token: { address: Address; decimals: number },
210210
amount: bigint,
211211
timestamp: number,
212-
): Promise<number> {
212+
): Promise<string> {
213213
const { pricingProvider } = this.dependencies;
214214
const tokenPrice = await pricingProvider.getTokenPrice(
215215
this.chainId,
Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { formatUnits, parseUnits } from "viem";
1+
import { BigNumber } from "@grants-stack-indexer/shared";
22

33
/**
44
* Calculates the amount in USD
@@ -10,23 +10,22 @@ import { formatUnits, parseUnits } from "viem";
1010
*/
1111
export const calculateAmountInUsd = (
1212
amount: bigint,
13-
tokenPriceInUsd: number,
13+
tokenPriceInUsd: string | number,
1414
tokenDecimals: number,
1515
truncateDecimals?: number,
16-
): number => {
17-
const amountInUsd = Number(
18-
formatUnits(
19-
amount * parseUnits(tokenPriceInUsd.toString(), tokenDecimals),
20-
tokenDecimals * 2,
21-
),
22-
);
16+
): string => {
17+
const amountBN = new BigNumber(amount.toString());
18+
const tokenPriceBN = new BigNumber(tokenPriceInUsd.toString());
19+
const scaleFactor = new BigNumber(10).pow(tokenDecimals);
2320

24-
if (truncateDecimals) {
21+
let amountInUsd = amountBN.multipliedBy(tokenPriceBN).dividedBy(scaleFactor);
22+
23+
if (truncateDecimals !== undefined) {
2524
if (truncateDecimals < 0 || truncateDecimals > 18) {
2625
throw new Error("Truncate decimals must be between 0 and 18");
2726
}
28-
return Number(amountInUsd.toFixed(truncateDecimals));
27+
amountInUsd = amountInUsd.decimalPlaces(truncateDecimals);
2928
}
3029

31-
return amountInUsd;
30+
return amountInUsd.toString();
3231
};

packages/processors/test/allo/allo.processor.spec.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,11 @@ describe("AlloProcessor", () => {
5858

5959
await processor.process(mockEvent);
6060

61-
expect(PoolCreatedHandler).toHaveBeenCalledWith(mockEvent, mockChainId, {
62-
evmProvider: mockEvmProvider,
63-
pricingProvider: mockPricingProvider,
64-
metadataProvider: mockMetadataProvider,
65-
roundRepository: mockRoundRepository,
66-
});
61+
expect(PoolCreatedHandler).toHaveBeenCalledWith(
62+
mockEvent,
63+
mockChainId,
64+
processor["dependencies"],
65+
);
6766
expect(PoolCreatedHandler.prototype.handle).toHaveBeenCalled();
6867
});
6968

packages/processors/test/allo/handlers/poolCreated.handler.spec.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ describe("PoolCreatedHandler", () => {
9494
expect(changeset.type).toBe("InsertRound");
9595
expect(changeset.args.round).toMatchObject({
9696
fundedAmount: fundedAmount,
97-
fundedAmountInUsd: 1000,
97+
fundedAmountInUsd: "1000",
9898
});
9999
expect(mockPricingProvider.getTokenPrice).toHaveBeenCalled();
100100
expect(mockMetadataProvider.getMetadata).toHaveBeenCalled();
@@ -188,13 +188,13 @@ describe("PoolCreatedHandler", () => {
188188
id: "10",
189189
tags: ["allo-v2", "grants-stack"],
190190
totalDonationsCount: 0,
191-
totalAmountDonatedInUsd: 0,
191+
totalAmountDonatedInUsd: "0",
192192
uniqueDonorsCount: 0,
193193
matchTokenAddress: mockEvent.params.token,
194194
matchAmount: parseUnits("1", 18),
195-
matchAmountInUsd: 100,
195+
matchAmountInUsd: "100",
196196
fundedAmount: 0n,
197-
fundedAmountInUsd: 0,
197+
fundedAmountInUsd: "0",
198198
applicationMetadataCid: "bafkreihrjyu5tney6wia2hmkertc74nzfpsgxw2epvnxm72bxj6ifnd4ku",
199199
applicationMetadata: {
200200
version: "1.0.0",
@@ -291,9 +291,9 @@ describe("PoolCreatedHandler", () => {
291291
id: "10",
292292
tags: ["allo-v2"],
293293
matchAmount: 0n,
294-
matchAmountInUsd: 0,
294+
matchAmountInUsd: "0",
295295
fundedAmount: 0n,
296-
fundedAmountInUsd: 0,
296+
fundedAmountInUsd: "0",
297297
applicationMetadataCid: "bafkreihrjyu5tney6wia2hmkertc74nzfpsgxw2epvnxm72bxj6ifnd4ku",
298298
applicationMetadata: {},
299299
roundMetadataCid: "bafkreihrjyu5tney6wia2hmkertc74nzfpsgxw2epvnxm72bxj6ifnd4ku",

packages/processors/test/helpers/tokenMath.spec.ts

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ describe("calculateAmountInUsd", () => {
1010
const tokenDecimals = 18;
1111

1212
const result = calculateAmountInUsd(amount, tokenPriceInUsd, tokenDecimals);
13-
expect(result).toBe(100);
13+
expect(result).toBe("100");
1414
});
1515

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

2121
const result = calculateAmountInUsd(amount, tokenPriceInUsd, tokenDecimals);
22-
expect(result).toBeCloseTo(41.025, 5);
22+
expect(result).toBe("41.025");
2323
});
2424

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

3030
const result = calculateAmountInUsd(amount, tokenPriceInUsd, tokenDecimals);
31-
expect(result).toBe(50);
31+
expect(result).toBe("50");
3232
});
3333

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

4040
const result = calculateAmountInUsd(amount, tokenPriceInUsd, tokenDecimals);
41-
expect(result).toBeCloseTo(19.125, 5);
41+
expect(result).toBe("19.125");
4242
});
4343

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

4949
const result = calculateAmountInUsd(amount, tokenPriceInUsd, tokenDecimals);
50-
expect(result).toBe(0.000001);
50+
expect(result).toBe("0.000001");
5151
});
5252

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

5858
const result = calculateAmountInUsd(amount, tokenPriceInUsd, tokenDecimals, 4);
59-
expect(result).toBe(1.5185);
59+
expect(result).toBe("1.5185");
60+
});
61+
62+
it("handle token price with 19 decimal digits", () => {
63+
const amount = 1000000000000000000n; // 1 token
64+
const tokenPriceInUsd = 1e-19; // 19 decimal places
65+
const tokenDecimals = 18;
66+
67+
const result = calculateAmountInUsd(amount, tokenPriceInUsd, tokenDecimals);
68+
69+
expect(result).toBe("0.0000000000000000001");
70+
});
71+
72+
it("handle scientific notation token price with interspersed non-zero digits in result", () => {
73+
const amount = 123456789012345678n; // 0.123456789012345678 tokens
74+
const tokenPriceInUsd = 1.23e-15; // 0.00000000000000123
75+
const tokenDecimals = 18;
76+
77+
const result = calculateAmountInUsd(amount, tokenPriceInUsd, tokenDecimals);
78+
79+
expect(result).toBe("0.00000000000000015185");
6080
});
6181

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

6787
const result = calculateAmountInUsd(amount, tokenPriceInUsd, tokenDecimals);
68-
expect(result).toBe(0);
88+
expect(result).toBe("0");
6989
});
7090

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

7696
const result = calculateAmountInUsd(amount, tokenPriceInUsd, tokenDecimals);
77-
expect(result).toBe(0);
97+
expect(result).toBe("0");
7898
});
7999

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

89109
test("migrated cases", () => {
90-
expect(calculateAmountInUsd(3400000000000000000n, 1, 18, 8)).toBe(3.4);
110+
expect(calculateAmountInUsd(3400000000000000000n, 1, 18, 8)).toBe("3.4");
91111

92-
expect(calculateAmountInUsd(50000000000n, 1, 18, 8)).toBe(0.00000005);
112+
expect(calculateAmountInUsd(50000000000n, 1, 18, 8)).toBe("0.00000005");
93113

94-
expect(calculateAmountInUsd(3400000000000000000n, 0.5, 18, 8)).toBe(1.7);
114+
expect(calculateAmountInUsd(3400000000000000000n, 0.5, 18, 8)).toBe("1.7");
95115

96-
expect(calculateAmountInUsd(3400000000000000000n, 2, 18, 8)).toBe(6.8);
116+
expect(calculateAmountInUsd(3400000000000000000n, 2, 18, 8)).toBe("6.8");
97117
});
98118
});

packages/repository/src/interfaces/roundRepository.interface.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ export interface IRoundRepository extends IRoundReadRepository {
106106
roundId: string;
107107
},
108108
amount: bigint,
109-
amountInUsd: number,
109+
amountInUsd: string,
110110
): Promise<void>;
111111

112112
/**

packages/repository/src/repositories/kysely/round.repository.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ export class KyselyRoundRepository implements IRoundRepository {
111111
roundId: string;
112112
},
113113
amount: bigint,
114-
amountInUsd: number,
114+
amountInUsd: string,
115115
): Promise<void> {
116116
await this.db
117117
.withSchema(this.schemaName)

packages/repository/src/types/changeset.types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,15 +88,15 @@ export type Changeset =
8888
chainId: ChainId;
8989
roundId: string;
9090
fundedAmount: bigint;
91-
fundedAmountInUsd: number;
91+
fundedAmountInUsd: string;
9292
};
9393
}
9494
| {
9595
type: "IncrementRoundDonationStats";
9696
args: {
9797
chainId: ChainId;
9898
roundId: Address;
99-
amountInUsd: number;
99+
amountInUsd: string;
100100
};
101101
}
102102
| {

packages/repository/src/types/round.types.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ export type Round = {
1616
chainId: ChainId;
1717
matchAmount: bigint;
1818
matchTokenAddress: Address;
19-
matchAmountInUsd: number;
19+
matchAmountInUsd: string;
2020
fundedAmount: bigint;
21-
fundedAmountInUsd: number;
21+
fundedAmountInUsd: string;
2222
applicationMetadataCid: string;
2323
applicationMetadata: unknown | null;
2424
roundMetadataCid: string | null;
@@ -30,7 +30,7 @@ export type Round = {
3030
createdByAddress: Address;
3131
createdAtBlock: bigint;
3232
updatedAtBlock: bigint;
33-
totalAmountDonatedInUsd: number;
33+
totalAmountDonatedInUsd: string;
3434
totalDonationsCount: number;
3535
totalDistributed: bigint;
3636
uniqueDonorsCount: number;

packages/shared/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"test:cov": "vitest run --config vitest.config.ts --coverage"
2929
},
3030
"dependencies": {
31+
"bignumber.js": "9.1.2",
3132
"viem": "2.21.19",
3233
"winston": "3.15.0"
3334
}

packages/shared/src/external.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,6 @@ export {
1010
export type { DeepPartial } from "./utils/testing.js";
1111
export { mergeDeep } from "./utils/testing.js";
1212
export type { ILogger, Logger } from "./internal.js";
13+
14+
export { BigNumber } from "./internal.js";
15+
export type { BigNumberType } from "./internal.js";

packages/shared/src/internal.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export type { Address } from "viem";
2+
export * from "./math/bignumber.js";
23
export * from "./types/index.js";
34
export * from "./constants/index.js";
45
export * from "./utils/testing.js";

packages/shared/src/math/bignumber.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import * as b from "bignumber.js";
2+
3+
export const BigNumber = b.BigNumber.clone({ EXPONENTIAL_AT: 32 });
4+
export type BigNumberType = typeof BigNumber;

pnpm-lock.yaml

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)