diff --git a/packages/processors/src/exceptions/index.ts b/packages/processors/src/exceptions/index.ts index 0b2e527..9916a5b 100644 --- a/packages/processors/src/exceptions/index.ts +++ b/packages/processors/src/exceptions/index.ts @@ -2,9 +2,5 @@ export * from "./tokenPriceNotFound.exception.js"; export * from "./unsupportedEvent.exception.js"; export * from "./invalidArgument.exception.js"; export * from "./unsupportedStrategy.exception.js"; -export * from "./projectNotFound.exception.js"; -export * from "./roundNotFound.exception.js"; -export * from "./applicationNotFound.exception.js"; -export * from "./unknownToken.exception.js"; export * from "./metadataParsingFailed.exception.js"; export * from "./metadataNotFound.exception.js"; diff --git a/packages/processors/src/exceptions/unknownToken.exception.ts b/packages/processors/src/exceptions/unknownToken.exception.ts deleted file mode 100644 index 3e29931..0000000 --- a/packages/processors/src/exceptions/unknownToken.exception.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { ChainId } from "@grants-stack-indexer/shared"; - -export class UnknownToken extends Error { - constructor(tokenAddress: string, chainId?: ChainId) { - super(`Unknown token: ${tokenAddress} ${chainId ? `on chain ${chainId}` : ""}`); - } -} diff --git a/packages/processors/src/strategy/common/baseFundsDistributed.handler.ts b/packages/processors/src/strategy/common/baseFundsDistributed.handler.ts index c869005..f183fe8 100644 --- a/packages/processors/src/strategy/common/baseFundsDistributed.handler.ts +++ b/packages/processors/src/strategy/common/baseFundsDistributed.handler.ts @@ -1,14 +1,9 @@ -import { Address, getAddress } from "viem"; +import { getAddress } from "viem"; -import { Application, Changeset, Round } from "@grants-stack-indexer/repository"; +import { Changeset } from "@grants-stack-indexer/repository"; import { ChainId, ProcessorEvent } from "@grants-stack-indexer/shared"; -import { - ApplicationNotFound, - IEventHandler, - ProcessorDependencies, - RoundNotFound, -} from "../../internal.js"; +import { IEventHandler, ProcessorDependencies } from "../../internal.js"; type Dependencies = Pick< ProcessorDependencies, @@ -45,12 +40,21 @@ export class BaseFundsDistributedHandler implements IEventHandler<"Strategy", "F * 2. IncrementRoundTotalDistributed: Increments the total distributed amount for a round. */ async handle(): Promise { + const { roundRepository, applicationRepository } = this.dependencies; + const strategyAddress = getAddress(this.event.srcAddress); - const round = await this.getRoundOrThrow(strategyAddress); + const round = await roundRepository.getRoundByStrategyAddressOrThrow( + this.chainId, + strategyAddress, + ); const roundId = round.id; const anchorAddress = getAddress(this.event.params.recipientId); - const application = await this.getApplicationOrThrow(roundId, anchorAddress); + const application = await applicationRepository.getApplicationByAnchorAddressOrThrow( + this.chainId, + roundId, + anchorAddress, + ); return [ { @@ -74,49 +78,4 @@ export class BaseFundsDistributedHandler implements IEventHandler<"Strategy", "F }, ]; } - - /** - * Retrieves a round by its strategy address. - * @param {Address} strategyAddress - The address of the strategy. - * @returns {Promise} The round found. - * @throws {RoundNotFound} if the round does not exist. - */ - private async getRoundOrThrow(strategyAddress: Address): Promise { - const { roundRepository } = this.dependencies; - const round = await roundRepository.getRoundByStrategyAddress( - this.chainId, - strategyAddress, - ); - - if (!round) { - throw new RoundNotFound(this.chainId, strategyAddress); - } - - return round; - } - - /** - * Retrieves an application by its round ID and recipient address. - * @param {string} roundId - The ID of the round. - * @param {Address} recipientId - The address of the recipient. - * @returns {Promise} The application found. - * @throws {ApplicationNotFound} if the application does not exist. - */ - private async getApplicationOrThrow( - roundId: string, - anchorAddress: Address, - ): Promise { - const { applicationRepository } = this.dependencies; - const application = await applicationRepository.getApplicationByAnchorAddress( - this.chainId, - roundId, - anchorAddress, - ); - - if (!application) { - throw new ApplicationNotFound(this.chainId, roundId, anchorAddress); - } - - return application; - } } diff --git a/packages/processors/src/strategy/common/baseRecipientStatusUpdated.handler.ts b/packages/processors/src/strategy/common/baseRecipientStatusUpdated.handler.ts index d29bb2c..90cc0fe 100644 --- a/packages/processors/src/strategy/common/baseRecipientStatusUpdated.handler.ts +++ b/packages/processors/src/strategy/common/baseRecipientStatusUpdated.handler.ts @@ -1,15 +1,10 @@ import StatusesBitmap from "statuses-bitmap"; -import { Address, getAddress } from "viem"; +import { getAddress } from "viem"; -import { Application, Changeset, Round } from "@grants-stack-indexer/repository"; +import { Application, Changeset } from "@grants-stack-indexer/repository"; import { ChainId, ProcessorEvent } from "@grants-stack-indexer/shared"; -import { - ApplicationStatus, - IEventHandler, - ProcessorDependencies, - RoundNotFound, -} from "../../internal.js"; +import { ApplicationStatus, IEventHandler, ProcessorDependencies } from "../../internal.js"; import { createStatusUpdate, isValidApplicationStatus } from "../helpers/index.js"; type Dependencies = Pick< @@ -52,7 +47,13 @@ export class BaseRecipientStatusUpdatedHandler * @returns An array of changesets to update application statuses. */ async handle(): Promise { - const round = await this.getRoundOrThrow(this.event.srcAddress); + const { roundRepository } = this.dependencies; + + const strategyAddress = getAddress(this.event.srcAddress); + const round = await roundRepository.getRoundByStrategyAddressOrThrow( + this.chainId, + strategyAddress, + ); const applicationsToUpdate = await this.getApplicationsToUpdate(round.id); @@ -108,26 +109,4 @@ export class BaseRecipientStatusUpdatedHandler return applications; } - - /** - * Retrieves a round by its strategy address - * @param address - The address of the strategy - * @throws RoundNotFound if the round is not found - */ - private async getRoundOrThrow(address: Address): Promise { - const normalizedAddress = getAddress(address); - const round = await this.dependencies.roundRepository.getRoundByStrategyAddress( - this.chainId, - normalizedAddress, - ); - - if (!round) { - this.dependencies.logger.warn( - `RecipientStatusUpdated: Round not found for strategy address ${normalizedAddress}`, - ); - throw new RoundNotFound(this.chainId, normalizedAddress); - } - - return round; - } } diff --git a/packages/processors/src/strategy/donationVotingMerkleDistributionDirectTransfer/handlers/allocated.handler.ts b/packages/processors/src/strategy/donationVotingMerkleDistributionDirectTransfer/handlers/allocated.handler.ts index 888badf..cdde774 100644 --- a/packages/processors/src/strategy/donationVotingMerkleDistributionDirectTransfer/handlers/allocated.handler.ts +++ b/packages/processors/src/strategy/donationVotingMerkleDistributionDirectTransfer/handlers/allocated.handler.ts @@ -1,17 +1,10 @@ -import { Address, encodePacked, getAddress, keccak256 } from "viem"; +import { encodePacked, getAddress, keccak256 } from "viem"; -import { Application, Changeset, Donation, Round } from "@grants-stack-indexer/repository"; -import { ChainId, getToken, ProcessorEvent, Token } from "@grants-stack-indexer/shared"; +import { Changeset, Donation } from "@grants-stack-indexer/repository"; +import { ChainId, getTokenOrThrow, ProcessorEvent } from "@grants-stack-indexer/shared"; import { getTokenAmountInUsd, getUsdInTokenAmount } from "../../../helpers/index.js"; -import { - ApplicationNotFound, - IEventHandler, - MetadataParsingFailed, - ProcessorDependencies, - RoundNotFound, - UnknownToken, -} from "../../../internal.js"; +import { IEventHandler, MetadataParsingFailed, ProcessorDependencies } from "../../../internal.js"; import { ApplicationMetadata, ApplicationMetadataSchema } from "../../../schemas/index.js"; type Dependencies = Pick< @@ -46,16 +39,24 @@ export class DVMDAllocatedHandler implements IEventHandler<"Strategy", "Allocate * @throws {MetadataParsingFailed} if the metadata is invalid */ async handle(): Promise { + const { roundRepository, applicationRepository } = this.dependencies; const { srcAddress } = this.event; const { recipientId: _recipientId, amount, token: _token } = this.event.params; - const round = await this.getRoundOrThrow(srcAddress); - const application = await this.getApplicationOrThrow(round.id, _recipientId); + const round = await roundRepository.getRoundByStrategyAddressOrThrow( + this.chainId, + getAddress(srcAddress), + ); + const application = await applicationRepository.getApplicationByAnchorAddressOrThrow( + this.chainId, + round.id, + getAddress(_recipientId), + ); const donationId = this.getDonationId(this.event.blockNumber, this.event.logIndex); - const token = this.getTokenOrThrow(_token); - const matchToken = this.getTokenOrThrow(round.matchTokenAddress); + const token = getTokenOrThrow(this.chainId, _token); + const matchToken = getTokenOrThrow(this.chainId, round.matchTokenAddress); const { amountInUsd, timestamp: priceTimestamp } = await getTokenAmountInUsd( this.dependencies.pricingProvider, @@ -103,52 +104,6 @@ export class DVMDAllocatedHandler implements IEventHandler<"Strategy", "Allocate ]; } - /** - * Retrieves a round by its strategy address. - * @param {Address} strategyAddress - The address of the strategy. - * @returns {Promise} The round found. - * @throws {RoundNotFound} if the round does not exist. - */ - private async getRoundOrThrow(strategyAddress: Address): Promise { - const normalizedStrategyAddress = getAddress(strategyAddress); - const round = await this.dependencies.roundRepository.getRoundByStrategyAddress( - this.chainId, - normalizedStrategyAddress, - ); - - if (!round) { - throw new RoundNotFound(this.chainId, normalizedStrategyAddress); - } - - return round; - } - - /** - * Retrieves an application by its round ID and recipient address. - * @param {string} roundId - The ID of the round. - * @param {Address} recipientId - The address of the recipient. - * @returns {Promise} The application found. - * @throws {ApplicationNotFound} if the application does not exist. - */ - private async getApplicationOrThrow( - roundId: string, - recipientId: Address, - ): Promise { - const normalizedRecipientId = getAddress(recipientId); - const application = - await this.dependencies.applicationRepository.getApplicationByAnchorAddress( - this.chainId, - roundId, - normalizedRecipientId, - ); - - if (!application) { - throw new ApplicationNotFound(this.chainId, roundId, normalizedRecipientId); - } - - return application; - } - /** * DONATION_ID = keccak256(abi.encodePacked(blockNumber, "-", logIndex)); */ @@ -156,18 +111,6 @@ export class DVMDAllocatedHandler implements IEventHandler<"Strategy", "Allocate return keccak256(encodePacked(["string"], [`${blockNumber}-${logIndex}`])); } - /** - * Retrieves a token by its address and chain ID. - * @param {Address} tokenAddress - The address of the token. - * @returns {Token} The token found. - * @throws {UnknownToken} if the token does not exist. - */ - private getTokenOrThrow(tokenAddress: Address): Token { - const token = getToken(this.chainId, getAddress(tokenAddress)); - if (!token) throw new UnknownToken(tokenAddress, this.chainId); - return token; - } - /** * Parses the application metadata. * @param {unknown} metadata - The metadata to parse. diff --git a/packages/processors/src/strategy/donationVotingMerkleDistributionDirectTransfer/handlers/registered.handler.ts b/packages/processors/src/strategy/donationVotingMerkleDistributionDirectTransfer/handlers/registered.handler.ts index 99cd2a0..8d8a59c 100644 --- a/packages/processors/src/strategy/donationVotingMerkleDistributionDirectTransfer/handlers/registered.handler.ts +++ b/packages/processors/src/strategy/donationVotingMerkleDistributionDirectTransfer/handlers/registered.handler.ts @@ -3,12 +3,7 @@ import { getAddress } from "viem"; import { Changeset, NewApplication } from "@grants-stack-indexer/repository"; import { ChainId, ProcessorEvent } from "@grants-stack-indexer/shared"; -import { - IEventHandler, - ProcessorDependencies, - ProjectNotFound, - RoundNotFound, -} from "../../../internal.js"; +import { IEventHandler, ProcessorDependencies } from "../../../internal.js"; import { decodeDVMDApplicationData } from "../helpers/index.js"; type Dependencies = Pick< @@ -41,22 +36,17 @@ export class DVMDRegisteredHandler implements IEventHandler<"Strategy", "Registe const { blockNumber, blockTimestamp } = this.event; const anchorAddress = getAddress(recipientId); - const project = await projectRepository.getProjectByAnchor(this.chainId, anchorAddress); - - if (!project) { - throw new ProjectNotFound(this.chainId, anchorAddress); - } + const project = await projectRepository.getProjectByAnchorOrThrow( + this.chainId, + anchorAddress, + ); const strategyAddress = getAddress(this.event.srcAddress); - const round = await roundRepository.getRoundByStrategyAddress( + const round = await roundRepository.getRoundByStrategyAddressOrThrow( this.chainId, strategyAddress, ); - if (!round) { - throw new RoundNotFound(this.chainId, strategyAddress); - } - const values = decodeDVMDApplicationData(encodedData); // ID is defined as recipientsCounter - 1, which is a value emitted by the strategy const id = (Number(values.recipientsCounter) - 1).toString(); diff --git a/packages/processors/src/strategy/donationVotingMerkleDistributionDirectTransfer/handlers/timestampsUpdated.handler.ts b/packages/processors/src/strategy/donationVotingMerkleDistributionDirectTransfer/handlers/timestampsUpdated.handler.ts index 8f13224..b20e38c 100644 --- a/packages/processors/src/strategy/donationVotingMerkleDistributionDirectTransfer/handlers/timestampsUpdated.handler.ts +++ b/packages/processors/src/strategy/donationVotingMerkleDistributionDirectTransfer/handlers/timestampsUpdated.handler.ts @@ -5,7 +5,6 @@ import { ChainId, ProcessorEvent } from "@grants-stack-indexer/shared"; import type { IEventHandler, ProcessorDependencies } from "../../../internal.js"; import { getDateFromTimestamp } from "../../../helpers/index.js"; -import { RoundNotFound } from "../../../internal.js"; type Dependencies = Pick; @@ -36,15 +35,11 @@ export class DVMDTimestampsUpdatedHandler */ async handle(): Promise { const strategyAddress = getAddress(this.event.srcAddress); - const round = await this.dependencies.roundRepository.getRoundByStrategyAddress( + const round = await this.dependencies.roundRepository.getRoundByStrategyAddressOrThrow( this.chainId, strategyAddress, ); - if (!round) { - throw new RoundNotFound(this.chainId, strategyAddress); - } - const { registrationStartTime, registrationEndTime, diff --git a/packages/processors/src/strategy/donationVotingMerkleDistributionDirectTransfer/handlers/updatedRegistration.handler.ts b/packages/processors/src/strategy/donationVotingMerkleDistributionDirectTransfer/handlers/updatedRegistration.handler.ts index c3911dd..97009bd 100644 --- a/packages/processors/src/strategy/donationVotingMerkleDistributionDirectTransfer/handlers/updatedRegistration.handler.ts +++ b/packages/processors/src/strategy/donationVotingMerkleDistributionDirectTransfer/handlers/updatedRegistration.handler.ts @@ -1,17 +1,9 @@ -// import StatusesBitmap from "statuses-bitmap"; -import { Address, getAddress } from "viem"; +import { getAddress } from "viem"; -import { Application, Changeset, Project, Round } from "@grants-stack-indexer/repository"; +import { Application, Changeset } from "@grants-stack-indexer/repository"; import { ChainId, ProcessorEvent } from "@grants-stack-indexer/shared"; -import { - ApplicationNotFound, - ApplicationStatus, - IEventHandler, - ProcessorDependencies, - ProjectNotFound, - RoundNotFound, -} from "../../../internal.js"; +import { ApplicationStatus, IEventHandler, ProcessorDependencies } from "../../../internal.js"; import { createStatusUpdate, isValidApplicationStatus } from "../../helpers/index.js"; import { decodeDVMDApplicationData } from "../helpers/index.js"; @@ -43,7 +35,13 @@ export class DVMDUpdatedRegistrationHandler ) {} async handle(): Promise { - const { metadataProvider, logger } = this.dependencies; + const { + metadataProvider, + logger, + roundRepository, + applicationRepository, + projectRepository, + } = this.dependencies; if (!isValidApplicationStatus(this.event.params.status)) { logger.warn( @@ -53,9 +51,19 @@ export class DVMDUpdatedRegistrationHandler return []; } - const project = await this.getProjectOrThrow(this.event.params.recipientId); - const round = await this.getRoundOrThrow(this.event.srcAddress); - const application = await this.getApplicationOrThrow(round.id, project.anchorAddress!); + const project = await projectRepository.getProjectByAnchorOrThrow( + this.chainId, + getAddress(this.event.params.recipientId), + ); + const round = await roundRepository.getRoundByStrategyAddressOrThrow( + this.chainId, + getAddress(this.event.srcAddress), + ); + const application = await applicationRepository.getApplicationByAnchorAddressOrThrow( + this.chainId, + round.id, + project.anchorAddress!, + ); const encodedData = this.event.params.data; const values = decodeDVMDApplicationData(encodedData); @@ -88,71 +96,4 @@ export class DVMDUpdatedRegistrationHandler }, ]; } - - /** - * Get the round by the strategy address. - * @param strategyAddress - The strategy address. - * @returns The round. - * @throws If the round is not found. - */ - private async getRoundOrThrow(strategyAddress: Address): Promise { - const round = await this.dependencies.roundRepository.getRoundByStrategyAddress( - this.chainId, - strategyAddress, - ); - - if (!round) { - this.dependencies.logger.warn( - `RecipientStatusUpdated: Round not found for strategy address ${strategyAddress}`, - ); - throw new RoundNotFound(this.chainId, strategyAddress); - } - - return round; - } - - /** - * Get the project by the anchor address. - * @param anchorAddress - The anchor address. - * @returns The project. - * @throws If the project is not found. - */ - private async getProjectOrThrow(anchorAddress: Address): Promise { - const _anchorAddress = getAddress(anchorAddress); - const project = await this.dependencies.projectRepository.getProjectByAnchor( - this.chainId, - _anchorAddress, - ); - - if (!project) { - throw new ProjectNotFound(this.chainId, _anchorAddress); - } - - return project; - } - - /** - * Get the application by the anchor address. - * @param roundId - The round ID. - * @param anchorAddress - The anchor address. - * @returns The application. - * @throws If the application is not found. - */ - private async getApplicationOrThrow( - roundId: Round["id"], - anchorAddress: Address, - ): Promise { - const application = - await this.dependencies.applicationRepository.getApplicationByAnchorAddress( - this.chainId, - roundId, - anchorAddress, - ); - - if (!application) { - throw new ApplicationNotFound(this.chainId, roundId, anchorAddress); - } - - return application; - } } diff --git a/packages/processors/test/strategy/common/baseFundsDistributed.handler.spec.ts b/packages/processors/test/strategy/common/baseFundsDistributed.handler.spec.ts index 1f42a05..c36f49e 100644 --- a/packages/processors/test/strategy/common/baseFundsDistributed.handler.spec.ts +++ b/packages/processors/test/strategy/common/baseFundsDistributed.handler.spec.ts @@ -2,9 +2,11 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; import { Application, + ApplicationNotFound, IApplicationRepository, IRoundReadRepository, Round, + RoundNotFound, } from "@grants-stack-indexer/repository"; import { ChainId, @@ -14,7 +16,8 @@ import { ProcessorEvent, } from "@grants-stack-indexer/shared"; -import { ApplicationNotFound, RoundNotFound } from "../../../src/exceptions/index.js"; +import "../../../src/exceptions/index.js"; + import { BaseFundsDistributedHandler } from "../../../src/strategy/common/index.js"; function createMockEvent( @@ -55,10 +58,10 @@ describe("BaseFundsDistributedHandler", () => { beforeEach(() => { mockRoundRepository = { - getRoundByStrategyAddress: vi.fn(), + getRoundByStrategyAddressOrThrow: vi.fn(), } as unknown as IRoundReadRepository; mockApplicationRepository = { - getApplicationByAnchorAddress: vi.fn(), + getApplicationByAnchorAddressOrThrow: vi.fn(), } as unknown as IApplicationRepository; mockLogger = { warn: vi.fn(), @@ -73,10 +76,13 @@ describe("BaseFundsDistributedHandler", () => { const mockRound = { id: "round1" } as unknown as Round; const mockApplication = { id: "app1" } as unknown as Application; - vi.spyOn(mockRoundRepository, "getRoundByStrategyAddress").mockResolvedValue(mockRound); - vi.spyOn(mockApplicationRepository, "getApplicationByAnchorAddress").mockResolvedValue( - mockApplication, + vi.spyOn(mockRoundRepository, "getRoundByStrategyAddressOrThrow").mockResolvedValue( + mockRound, ); + vi.spyOn( + mockApplicationRepository, + "getApplicationByAnchorAddressOrThrow", + ).mockResolvedValue(mockApplication); handler = new BaseFundsDistributedHandler(mockEvent, chainId, { roundRepository: mockRoundRepository, @@ -111,7 +117,9 @@ describe("BaseFundsDistributedHandler", () => { it("throws RoundNotFound if round is not found", async () => { mockEvent = createMockEvent(); - vi.spyOn(mockRoundRepository, "getRoundByStrategyAddress").mockResolvedValue(undefined); + vi.spyOn(mockRoundRepository, "getRoundByStrategyAddressOrThrow").mockRejectedValue( + new RoundNotFound(chainId, mockEvent.strategyId), + ); handler = new BaseFundsDistributedHandler(mockEvent, chainId, { roundRepository: mockRoundRepository, @@ -126,9 +134,14 @@ describe("BaseFundsDistributedHandler", () => { mockEvent = createMockEvent(); const mockRound = { id: "round1" } as unknown as Round; - vi.spyOn(mockRoundRepository, "getRoundByStrategyAddress").mockResolvedValue(mockRound); - vi.spyOn(mockApplicationRepository, "getApplicationByAnchorAddress").mockResolvedValue( - undefined, + vi.spyOn(mockRoundRepository, "getRoundByStrategyAddressOrThrow").mockResolvedValue( + mockRound, + ); + vi.spyOn( + mockApplicationRepository, + "getApplicationByAnchorAddressOrThrow", + ).mockRejectedValue( + new ApplicationNotFound(chainId, mockRound.id, mockEvent.params.recipientId), ); handler = new BaseFundsDistributedHandler(mockEvent, chainId, { diff --git a/packages/processors/test/strategy/common/baseRecipientStatusUpdated.handler.spec.ts b/packages/processors/test/strategy/common/baseRecipientStatusUpdated.handler.spec.ts index ae646ab..a05a639 100644 --- a/packages/processors/test/strategy/common/baseRecipientStatusUpdated.handler.spec.ts +++ b/packages/processors/test/strategy/common/baseRecipientStatusUpdated.handler.spec.ts @@ -6,6 +6,7 @@ import { IRoundReadRepository, PartialApplication, Round, + RoundNotFound, } from "@grants-stack-indexer/repository"; import { ChainId, @@ -15,7 +16,6 @@ import { ProcessorEvent, } from "@grants-stack-indexer/shared"; -import { RoundNotFound } from "../../../src/exceptions/index.js"; import { BaseRecipientStatusUpdatedHandler } from "../../../src/strategy/common/baseRecipientStatusUpdated.handler.js"; function createMockEvent( @@ -55,7 +55,7 @@ describe("BaseRecipientStatusUpdatedHandler", () => { beforeEach(() => { mockRoundRepository = { - getRoundByStrategyAddress: vi.fn(), + getRoundByStrategyAddressOrThrow: vi.fn(), } as unknown as IRoundReadRepository; mockApplicationRepository = { getApplicationById: vi.fn(), @@ -90,7 +90,9 @@ describe("BaseRecipientStatusUpdatedHandler", () => { statusUpdatedAtBlock: 12344n, } as unknown as Application; - vi.spyOn(mockRoundRepository, "getRoundByStrategyAddress").mockResolvedValue(mockRound); + vi.spyOn(mockRoundRepository, "getRoundByStrategyAddressOrThrow").mockResolvedValue( + mockRound, + ); vi.spyOn(mockApplicationRepository, "getApplicationById") .mockResolvedValueOnce(mockApplication1) .mockResolvedValueOnce(mockApplication2) @@ -174,7 +176,9 @@ describe("BaseRecipientStatusUpdatedHandler", () => { it("throws RoundNotFound if round is not found", async () => { mockEvent = createMockEvent(); - vi.spyOn(mockRoundRepository, "getRoundByStrategyAddress").mockResolvedValue(undefined); + vi.spyOn(mockRoundRepository, "getRoundByStrategyAddressOrThrow").mockRejectedValue( + new RoundNotFound(chainId, mockEvent.strategyId), + ); handler = new BaseRecipientStatusUpdatedHandler(mockEvent, chainId, { roundRepository: mockRoundRepository, @@ -183,14 +187,15 @@ describe("BaseRecipientStatusUpdatedHandler", () => { }); await expect(handler.handle()).rejects.toThrow(RoundNotFound); - expect(mockLogger.warn).toHaveBeenCalled(); }); it("skips applications that are not found", async () => { mockEvent = createMockEvent(); const mockRound = { id: "round1" } as Round; - vi.spyOn(mockRoundRepository, "getRoundByStrategyAddress").mockResolvedValue(mockRound); + vi.spyOn(mockRoundRepository, "getRoundByStrategyAddressOrThrow").mockResolvedValue( + mockRound, + ); vi.spyOn(mockApplicationRepository, "getApplicationById").mockResolvedValue(undefined); handler = new BaseRecipientStatusUpdatedHandler(mockEvent, chainId, { @@ -212,7 +217,9 @@ describe("BaseRecipientStatusUpdatedHandler", () => { }); const mockRound = { id: "round1" } as Round; - vi.spyOn(mockRoundRepository, "getRoundByStrategyAddress").mockResolvedValue(mockRound); + vi.spyOn(mockRoundRepository, "getRoundByStrategyAddressOrThrow").mockResolvedValue( + mockRound, + ); handler = new BaseRecipientStatusUpdatedHandler(mockEvent, chainId, { roundRepository: mockRoundRepository, @@ -240,7 +247,9 @@ describe("BaseRecipientStatusUpdatedHandler", () => { statusUpdatedAtBlock: 12344n, } as Application; - vi.spyOn(mockRoundRepository, "getRoundByStrategyAddress").mockResolvedValue(mockRound); + vi.spyOn(mockRoundRepository, "getRoundByStrategyAddressOrThrow").mockResolvedValue( + mockRound, + ); vi.spyOn(mockApplicationRepository, "getApplicationById").mockResolvedValue( mockApplication, ); @@ -278,7 +287,9 @@ describe("BaseRecipientStatusUpdatedHandler", () => { statusUpdatedAtBlock: 12344n, } as unknown as Application; - vi.spyOn(mockRoundRepository, "getRoundByStrategyAddress").mockResolvedValue(mockRound); + vi.spyOn(mockRoundRepository, "getRoundByStrategyAddressOrThrow").mockResolvedValue( + mockRound, + ); vi.spyOn(mockApplicationRepository, "getApplicationById").mockResolvedValue( mockApplication, ); diff --git a/packages/processors/test/strategy/donationVotingMerkleDistributionDirectTransfer/handlers/allocated.handler.spec.ts b/packages/processors/test/strategy/donationVotingMerkleDistributionDirectTransfer/handlers/allocated.handler.spec.ts index 3523787..ba77df8 100644 --- a/packages/processors/test/strategy/donationVotingMerkleDistributionDirectTransfer/handlers/allocated.handler.spec.ts +++ b/packages/processors/test/strategy/donationVotingMerkleDistributionDirectTransfer/handlers/allocated.handler.spec.ts @@ -4,18 +4,23 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; import { IPricingProvider } from "@grants-stack-indexer/pricing"; import { Application, + ApplicationNotFound, IApplicationRepository, IRoundRepository, Round, + RoundNotFound, } from "@grants-stack-indexer/repository"; -import { ChainId, DeepPartial, mergeDeep, ProcessorEvent } from "@grants-stack-indexer/shared"; +import { + ChainId, + DeepPartial, + mergeDeep, + ProcessorEvent, + UnknownToken, +} from "@grants-stack-indexer/shared"; import { - ApplicationNotFound, MetadataParsingFailed, - RoundNotFound, TokenPriceNotFoundError, - UnknownToken, } from "../../../../src/exceptions/index.js"; import { DVMDAllocatedHandler } from "../../../../src/strategy/donationVotingMerkleDistributionDirectTransfer/handlers/allocated.handler.js"; @@ -59,10 +64,10 @@ describe("DVMDAllocatedHandler", () => { beforeEach(() => { mockRoundRepository = { - getRoundByStrategyAddress: vi.fn(), + getRoundByStrategyAddressOrThrow: vi.fn(), } as unknown as IRoundRepository; mockApplicationRepository = { - getApplicationByAnchorAddress: vi.fn(), + getApplicationByAnchorAddressOrThrow: vi.fn(), } as unknown as IApplicationRepository; mockPricingProvider = { getTokenPrice: vi.fn(), @@ -87,10 +92,13 @@ describe("DVMDAllocatedHandler", () => { projectId: "project1", } as unknown as Application; - vi.spyOn(mockRoundRepository, "getRoundByStrategyAddress").mockResolvedValue(mockRound); - vi.spyOn(mockApplicationRepository, "getApplicationByAnchorAddress").mockResolvedValue( - mockApplication, + vi.spyOn(mockRoundRepository, "getRoundByStrategyAddressOrThrow").mockResolvedValue( + mockRound, ); + vi.spyOn( + mockApplicationRepository, + "getApplicationByAnchorAddressOrThrow", + ).mockResolvedValue(mockApplication); vi.spyOn(mockPricingProvider, "getTokenPrice").mockResolvedValue({ timestampMs: 1000000000, priceUsd: 2000, @@ -147,10 +155,13 @@ describe("DVMDAllocatedHandler", () => { projectId: "project1", } as unknown as Application; - vi.spyOn(mockRoundRepository, "getRoundByStrategyAddress").mockResolvedValue(mockRound); - vi.spyOn(mockApplicationRepository, "getApplicationByAnchorAddress").mockResolvedValue( - mockApplication, + vi.spyOn(mockRoundRepository, "getRoundByStrategyAddressOrThrow").mockResolvedValue( + mockRound, ); + vi.spyOn( + mockApplicationRepository, + "getApplicationByAnchorAddressOrThrow", + ).mockResolvedValue(mockApplication); vi.spyOn(mockPricingProvider, "getTokenPrice") .mockResolvedValueOnce({ timestampMs: 1000000000, @@ -188,7 +199,9 @@ describe("DVMDAllocatedHandler", () => { it("throws RoundNotFound if round is not found", async () => { mockEvent = createMockEvent(); - vi.spyOn(mockRoundRepository, "getRoundByStrategyAddress").mockResolvedValue(undefined); + vi.spyOn(mockRoundRepository, "getRoundByStrategyAddressOrThrow").mockRejectedValue( + new RoundNotFound(chainId, mockEvent.strategyId), + ); handler = new DVMDAllocatedHandler(mockEvent, chainId, { roundRepository: mockRoundRepository, @@ -206,9 +219,14 @@ describe("DVMDAllocatedHandler", () => { matchTokenAddress: "0x0987654321098765432109876543210987654321", } as unknown as Round; - vi.spyOn(mockRoundRepository, "getRoundByStrategyAddress").mockResolvedValue(mockRound); - vi.spyOn(mockApplicationRepository, "getApplicationByAnchorAddress").mockResolvedValue( - undefined, + vi.spyOn(mockRoundRepository, "getRoundByStrategyAddressOrThrow").mockResolvedValue( + mockRound, + ); + vi.spyOn( + mockApplicationRepository, + "getApplicationByAnchorAddressOrThrow", + ).mockRejectedValue( + new ApplicationNotFound(chainId, mockRound.id, mockEvent.params.recipientId), ); handler = new DVMDAllocatedHandler(mockEvent, chainId, { @@ -239,10 +257,13 @@ describe("DVMDAllocatedHandler", () => { projectId: "project1", } as unknown as Application; - vi.spyOn(mockRoundRepository, "getRoundByStrategyAddress").mockResolvedValue(mockRound); - vi.spyOn(mockApplicationRepository, "getApplicationByAnchorAddress").mockResolvedValue( - mockApplication, + vi.spyOn(mockRoundRepository, "getRoundByStrategyAddressOrThrow").mockResolvedValue( + mockRound, ); + vi.spyOn( + mockApplicationRepository, + "getApplicationByAnchorAddressOrThrow", + ).mockResolvedValue(mockApplication); handler = new DVMDAllocatedHandler(mockEvent, chainId, { roundRepository: mockRoundRepository, @@ -270,10 +291,13 @@ describe("DVMDAllocatedHandler", () => { projectId: "project1", } as unknown as Application; - vi.spyOn(mockRoundRepository, "getRoundByStrategyAddress").mockResolvedValue(mockRound); - vi.spyOn(mockApplicationRepository, "getApplicationByAnchorAddress").mockResolvedValue( - mockApplication, + vi.spyOn(mockRoundRepository, "getRoundByStrategyAddressOrThrow").mockResolvedValue( + mockRound, ); + vi.spyOn( + mockApplicationRepository, + "getApplicationByAnchorAddressOrThrow", + ).mockResolvedValue(mockApplication); handler = new DVMDAllocatedHandler(mockEvent, chainId, { roundRepository: mockRoundRepository, @@ -301,10 +325,13 @@ describe("DVMDAllocatedHandler", () => { projectId: "project1", } as unknown as Application; - vi.spyOn(mockRoundRepository, "getRoundByStrategyAddress").mockResolvedValue(mockRound); - vi.spyOn(mockApplicationRepository, "getApplicationByAnchorAddress").mockResolvedValue( - mockApplication, + vi.spyOn(mockRoundRepository, "getRoundByStrategyAddressOrThrow").mockResolvedValue( + mockRound, ); + vi.spyOn( + mockApplicationRepository, + "getApplicationByAnchorAddressOrThrow", + ).mockResolvedValue(mockApplication); vi.spyOn(mockPricingProvider, "getTokenPrice").mockResolvedValue(undefined); handler = new DVMDAllocatedHandler(mockEvent, chainId, { @@ -333,10 +360,13 @@ describe("DVMDAllocatedHandler", () => { projectId: "project1", } as unknown as Application; - vi.spyOn(mockRoundRepository, "getRoundByStrategyAddress").mockResolvedValue(mockRound); - vi.spyOn(mockApplicationRepository, "getApplicationByAnchorAddress").mockResolvedValue( - mockApplication, + vi.spyOn(mockRoundRepository, "getRoundByStrategyAddressOrThrow").mockResolvedValue( + mockRound, ); + vi.spyOn( + mockApplicationRepository, + "getApplicationByAnchorAddressOrThrow", + ).mockResolvedValue(mockApplication); vi.spyOn(mockPricingProvider, "getTokenPrice").mockResolvedValue({ timestampMs: 1000000000, priceUsd: 2000, diff --git a/packages/processors/test/strategy/donationVotingMerkleDistributionDirectTransfer/handlers/registered.handler.spec.ts b/packages/processors/test/strategy/donationVotingMerkleDistributionDirectTransfer/handlers/registered.handler.spec.ts index c76b4a1..103a1d5 100644 --- a/packages/processors/test/strategy/donationVotingMerkleDistributionDirectTransfer/handlers/registered.handler.spec.ts +++ b/packages/processors/test/strategy/donationVotingMerkleDistributionDirectTransfer/handlers/registered.handler.spec.ts @@ -6,11 +6,12 @@ import { IRoundReadRepository, NewApplication, Project, + ProjectNotFound, Round, + RoundNotFound, } from "@grants-stack-indexer/repository"; import { ChainId, DeepPartial, mergeDeep, ProcessorEvent } from "@grants-stack-indexer/shared"; -import { ProjectNotFound, RoundNotFound } from "../../../../src/exceptions/index.js"; import { DVMDRegisteredHandler } from "../../../../src/strategy/donationVotingMerkleDistributionDirectTransfer/handlers/index.js"; function createMockEvent( @@ -50,10 +51,10 @@ describe("DVMDRegisteredHandler", () => { beforeEach(() => { mockRoundRepository = { - getRoundByStrategyAddress: vi.fn(), + getRoundByStrategyAddressOrThrow: vi.fn(), } as unknown as IRoundReadRepository; mockProjectRepository = { - getProjectByAnchor: vi.fn(), + getProjectByAnchorOrThrow: vi.fn(), } as unknown as IProjectReadRepository; mockMetadataProvider = { getMetadata: vi.fn(), @@ -66,8 +67,10 @@ describe("DVMDRegisteredHandler", () => { const mockRound = { id: "round1" } as Round; const mockMetadata = { name: "Test Project" }; - vi.spyOn(mockProjectRepository, "getProjectByAnchor").mockResolvedValue(mockProject); - vi.spyOn(mockRoundRepository, "getRoundByStrategyAddress").mockResolvedValue(mockRound); + vi.spyOn(mockProjectRepository, "getProjectByAnchorOrThrow").mockResolvedValue(mockProject); + vi.spyOn(mockRoundRepository, "getRoundByStrategyAddressOrThrow").mockResolvedValue( + mockRound, + ); vi.spyOn(mockMetadataProvider, "getMetadata").mockResolvedValue(mockMetadata); handler = new DVMDRegisteredHandler(mockEvent, chainId, { @@ -112,7 +115,9 @@ describe("DVMDRegisteredHandler", () => { it("throw ProjectNotFound if project is not found", async () => { mockEvent = createMockEvent(); - vi.spyOn(mockProjectRepository, "getProjectByAnchor").mockResolvedValue(undefined); + vi.spyOn(mockProjectRepository, "getProjectByAnchorOrThrow").mockRejectedValue( + new ProjectNotFound(chainId, mockEvent.srcAddress), + ); handler = new DVMDRegisteredHandler(mockEvent, chainId, { projectRepository: mockProjectRepository, @@ -125,8 +130,10 @@ describe("DVMDRegisteredHandler", () => { it("throw RoundNotFound if round is not found", async () => { mockEvent = createMockEvent(); const mockProject = { id: "project1" } as Project; - vi.spyOn(mockProjectRepository, "getProjectByAnchor").mockResolvedValue(mockProject); - vi.spyOn(mockRoundRepository, "getRoundByStrategyAddress").mockResolvedValue(undefined); + vi.spyOn(mockProjectRepository, "getProjectByAnchorOrThrow").mockResolvedValue(mockProject); + vi.spyOn(mockRoundRepository, "getRoundByStrategyAddressOrThrow").mockRejectedValue( + new RoundNotFound(chainId, mockEvent.strategyId), + ); handler = new DVMDRegisteredHandler(mockEvent, chainId, { projectRepository: mockProjectRepository, @@ -141,8 +148,10 @@ describe("DVMDRegisteredHandler", () => { const mockProject = { id: "project1" } as Project; const mockRound = { id: "round1" } as Round; - vi.spyOn(mockProjectRepository, "getProjectByAnchor").mockResolvedValue(mockProject); - vi.spyOn(mockRoundRepository, "getRoundByStrategyAddress").mockResolvedValue(mockRound); + vi.spyOn(mockProjectRepository, "getProjectByAnchorOrThrow").mockResolvedValue(mockProject); + vi.spyOn(mockRoundRepository, "getRoundByStrategyAddressOrThrow").mockResolvedValue( + mockRound, + ); vi.spyOn(mockMetadataProvider, "getMetadata").mockResolvedValue(undefined); handler = new DVMDRegisteredHandler(mockEvent, chainId, { @@ -167,8 +176,10 @@ describe("DVMDRegisteredHandler", () => { const mockProject = { id: "project1" } as Project; const mockRound = { id: "round1" } as Round; - vi.spyOn(mockProjectRepository, "getProjectByAnchor").mockResolvedValue(mockProject); - vi.spyOn(mockRoundRepository, "getRoundByStrategyAddress").mockResolvedValue(mockRound); + vi.spyOn(mockProjectRepository, "getProjectByAnchorOrThrow").mockResolvedValue(mockProject); + vi.spyOn(mockRoundRepository, "getRoundByStrategyAddressOrThrow").mockResolvedValue( + mockRound, + ); handler = new DVMDRegisteredHandler(mockEvent, chainId, { projectRepository: mockProjectRepository, diff --git a/packages/processors/test/strategy/donationVotingMerkleDistributionDirectTransfer/handlers/timestampsUpdated.handler.spec.ts b/packages/processors/test/strategy/donationVotingMerkleDistributionDirectTransfer/handlers/timestampsUpdated.handler.spec.ts index 4d6e5cd..81389de 100644 --- a/packages/processors/test/strategy/donationVotingMerkleDistributionDirectTransfer/handlers/timestampsUpdated.handler.spec.ts +++ b/packages/processors/test/strategy/donationVotingMerkleDistributionDirectTransfer/handlers/timestampsUpdated.handler.spec.ts @@ -1,9 +1,13 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; -import { IRoundReadRepository, PartialRound, Round } from "@grants-stack-indexer/repository"; +import { + IRoundReadRepository, + PartialRound, + Round, + RoundNotFound, +} from "@grants-stack-indexer/repository"; import { ChainId, DeepPartial, mergeDeep, ProcessorEvent } from "@grants-stack-indexer/shared"; -import { RoundNotFound } from "../../../../src/exceptions/index.js"; import { DVMDTimestampsUpdatedHandler } from "../../../../src/strategy/donationVotingMerkleDistributionDirectTransfer/handlers/index.js"; function createMockEvent( @@ -48,7 +52,7 @@ describe("DVMDTimestampsUpdatedHandler", () => { beforeEach(() => { mockRoundRepository = { - getRoundByStrategyAddress: vi.fn(), + getRoundByStrategyAddressOrThrow: vi.fn(), } as unknown as IRoundReadRepository; }); @@ -65,7 +69,9 @@ describe("DVMDTimestampsUpdatedHandler", () => { }); const mockRound = { id: "round1" } as Round; - vi.spyOn(mockRoundRepository, "getRoundByStrategyAddress").mockResolvedValue(mockRound); + vi.spyOn(mockRoundRepository, "getRoundByStrategyAddressOrThrow").mockResolvedValue( + mockRound, + ); handler = new DVMDTimestampsUpdatedHandler(mockEvent, chainId, { roundRepository: mockRoundRepository, @@ -94,7 +100,9 @@ describe("DVMDTimestampsUpdatedHandler", () => { it("throws RoundNotFound if round is not found", async () => { mockEvent = createMockEvent(); - vi.spyOn(mockRoundRepository, "getRoundByStrategyAddress").mockResolvedValue(undefined); + vi.spyOn(mockRoundRepository, "getRoundByStrategyAddressOrThrow").mockRejectedValue( + new RoundNotFound(chainId, mockEvent.strategyId), + ); handler = new DVMDTimestampsUpdatedHandler(mockEvent, chainId, { roundRepository: mockRoundRepository, @@ -103,43 +111,45 @@ describe("DVMDTimestampsUpdatedHandler", () => { await expect(handler.handle()).rejects.toThrow(RoundNotFound); }); - // it("correctly convert timestamps to Date objects", async () => { - // const timestamps = { - // registrationStartTime: 1704067200n, // 2024-01-01 00:00:00 - // registrationEndTime: 1704153600n, // 2024-01-02 00:00:00 - // allocationStartTime: 1704240000n, // 2024-01-03 00:00:00 - // allocationEndTime: 1704326400n, // 2024-01-04 00:00:00 - // }; - - // mockEvent = createMockEvent({ - // params: timestamps, - // }); - // const mockRound = { id: "round1" } as Round; - - // vi.spyOn(mockRoundRepository, "getRoundByStrategyAddress").mockResolvedValue(mockRound); - - // handler = new DVMDTimestampsUpdatedHandler(mockEvent, chainId, { - // roundRepository: mockRoundRepository, - // }); - - // const result = await handler.handle(); - - // expect(result.length).toBe(1); - // const changeset = result[0] as { - // type: "UpdateRound"; - // args: { chainId: ChainId; roundId: string; round: PartialRound }; - // }; - - // expect(changeset.type).toBe("UpdateRound"); - // expect(changeset.args.chainId).toBe(chainId); - // expect(changeset.args.roundId).toBe("round1"); - // expect(changeset.args.round).toBeDefined(); - - // const partialRound = changeset.args.round; - - // expect(partialRound.applicationsStartTime).toEqual(new Date("2024-01-01T00:00:00.000Z")); - // expect(partialRound.applicationsEndTime).toEqual(new Date("2024-01-02T00:00:00.000Z")); - // expect(partialRound.donationsStartTime).toEqual(new Date("2024-01-03T00:00:00.000Z")); - // expect(partialRound.donationsEndTime).toEqual(new Date("2024-01-04T00:00:00.000Z")); - // }); + it("correctly convert timestamps to Date objects", async () => { + const timestamps = { + registrationStartTime: 1704067200n, // 2024-01-01 00:00:00 + registrationEndTime: 1704153600n, // 2024-01-02 00:00:00 + allocationStartTime: 1704240000n, // 2024-01-03 00:00:00 + allocationEndTime: 1704326400n, // 2024-01-04 00:00:00 + }; + + mockEvent = createMockEvent({ + params: timestamps, + }); + const mockRound = { id: "round1" } as Round; + + vi.spyOn(mockRoundRepository, "getRoundByStrategyAddressOrThrow").mockResolvedValue( + mockRound, + ); + + handler = new DVMDTimestampsUpdatedHandler(mockEvent, chainId, { + roundRepository: mockRoundRepository, + }); + + const result = await handler.handle(); + + expect(result.length).toBe(1); + const changeset = result[0] as { + type: "UpdateRound"; + args: { chainId: ChainId; roundId: string; round: PartialRound }; + }; + + expect(changeset.type).toBe("UpdateRound"); + expect(changeset.args.chainId).toBe(chainId); + expect(changeset.args.roundId).toBe("round1"); + expect(changeset.args.round).toBeDefined(); + + const partialRound = changeset.args.round; + + expect(partialRound.applicationsStartTime).toEqual(new Date("2024-01-01T00:00:00.000Z")); + expect(partialRound.applicationsEndTime).toEqual(new Date("2024-01-02T00:00:00.000Z")); + expect(partialRound.donationsStartTime).toEqual(new Date("2024-01-03T00:00:00.000Z")); + expect(partialRound.donationsEndTime).toEqual(new Date("2024-01-04T00:00:00.000Z")); + }); }); diff --git a/packages/processors/test/strategy/donationVotingMerkleDistributionDirectTransfer/handlers/updatedRegistration.handler.spec.ts b/packages/processors/test/strategy/donationVotingMerkleDistributionDirectTransfer/handlers/updatedRegistration.handler.spec.ts index 2ad7a0e..73263ab 100644 --- a/packages/processors/test/strategy/donationVotingMerkleDistributionDirectTransfer/handlers/updatedRegistration.handler.spec.ts +++ b/packages/processors/test/strategy/donationVotingMerkleDistributionDirectTransfer/handlers/updatedRegistration.handler.spec.ts @@ -3,12 +3,15 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; import { IMetadataProvider } from "@grants-stack-indexer/metadata"; import { Application, + ApplicationNotFound, IApplicationRepository, IProjectRepository, IRoundReadRepository, PartialApplication, Project, + ProjectNotFound, Round, + RoundNotFound, } from "@grants-stack-indexer/repository"; import { ChainId, @@ -18,11 +21,6 @@ import { ProcessorEvent, } from "@grants-stack-indexer/shared"; -import { - ApplicationNotFound, - ProjectNotFound, - RoundNotFound, -} from "../../../../src/exceptions/index.js"; import { DVMDUpdatedRegistrationHandler } from "../../../../src/strategy/donationVotingMerkleDistributionDirectTransfer/handlers/index.js"; function createMockEvent( @@ -65,13 +63,13 @@ describe("DVMDUpdatedRegistrationHandler", () => { beforeEach(() => { mockRoundRepository = { - getRoundByStrategyAddress: vi.fn(), + getRoundByStrategyAddressOrThrow: vi.fn(), } as unknown as IRoundReadRepository; mockApplicationRepository = { - getApplicationByAnchorAddress: vi.fn(), + getApplicationByAnchorAddressOrThrow: vi.fn(), } as unknown as IApplicationRepository; mockProjectRepository = { - getProjectByAnchor: vi.fn(), + getProjectByAnchorOrThrow: vi.fn(), } as unknown as IProjectRepository; mockMetadataProvider = { getMetadata: vi.fn(), @@ -99,11 +97,14 @@ describe("DVMDUpdatedRegistrationHandler", () => { } as unknown as Application; const mockMetadata = { name: "Test Project" }; - vi.spyOn(mockProjectRepository, "getProjectByAnchor").mockResolvedValue(mockProject); - vi.spyOn(mockRoundRepository, "getRoundByStrategyAddress").mockResolvedValue(mockRound); - vi.spyOn(mockApplicationRepository, "getApplicationByAnchorAddress").mockResolvedValue( - mockApplication, + vi.spyOn(mockProjectRepository, "getProjectByAnchorOrThrow").mockResolvedValue(mockProject); + vi.spyOn(mockRoundRepository, "getRoundByStrategyAddressOrThrow").mockResolvedValue( + mockRound, ); + vi.spyOn( + mockApplicationRepository, + "getApplicationByAnchorAddressOrThrow", + ).mockResolvedValue(mockApplication); vi.spyOn(mockMetadataProvider, "getMetadata").mockResolvedValue(mockMetadata); handler = new DVMDUpdatedRegistrationHandler(mockEvent, chainId, { @@ -166,7 +167,9 @@ describe("DVMDUpdatedRegistrationHandler", () => { it("throws ProjectNotFound if project is not found", async () => { mockEvent = createMockEvent(); - vi.spyOn(mockProjectRepository, "getProjectByAnchor").mockResolvedValue(undefined); + vi.spyOn(mockProjectRepository, "getProjectByAnchorOrThrow").mockRejectedValue( + new ProjectNotFound(chainId, mockEvent.params.recipientId), + ); handler = new DVMDUpdatedRegistrationHandler(mockEvent, chainId, { roundRepository: mockRoundRepository, @@ -186,8 +189,10 @@ describe("DVMDUpdatedRegistrationHandler", () => { anchorAddress: mockEvent.params.recipientId, } as Project; - vi.spyOn(mockProjectRepository, "getProjectByAnchor").mockResolvedValue(mockProject); - vi.spyOn(mockRoundRepository, "getRoundByStrategyAddress").mockResolvedValue(undefined); + vi.spyOn(mockProjectRepository, "getProjectByAnchorOrThrow").mockResolvedValue(mockProject); + vi.spyOn(mockRoundRepository, "getRoundByStrategyAddressOrThrow").mockRejectedValue( + new RoundNotFound(chainId, mockEvent.strategyId), + ); handler = new DVMDUpdatedRegistrationHandler(mockEvent, chainId, { roundRepository: mockRoundRepository, @@ -198,7 +203,6 @@ describe("DVMDUpdatedRegistrationHandler", () => { }); await expect(handler.handle()).rejects.toThrow(RoundNotFound); - expect(mockLogger.warn).toHaveBeenCalled(); }); it("throws ApplicationNotFound if application is not found", async () => { @@ -209,10 +213,15 @@ describe("DVMDUpdatedRegistrationHandler", () => { } as Project; const mockRound = { id: "round1" } as Round; - vi.spyOn(mockProjectRepository, "getProjectByAnchor").mockResolvedValue(mockProject); - vi.spyOn(mockRoundRepository, "getRoundByStrategyAddress").mockResolvedValue(mockRound); - vi.spyOn(mockApplicationRepository, "getApplicationByAnchorAddress").mockResolvedValue( - undefined, + vi.spyOn(mockProjectRepository, "getProjectByAnchorOrThrow").mockResolvedValue(mockProject); + vi.spyOn(mockRoundRepository, "getRoundByStrategyAddressOrThrow").mockResolvedValue( + mockRound, + ); + vi.spyOn( + mockApplicationRepository, + "getApplicationByAnchorAddressOrThrow", + ).mockRejectedValue( + new ApplicationNotFound(chainId, mockRound.id, mockEvent.params.recipientId), ); handler = new DVMDUpdatedRegistrationHandler(mockEvent, chainId, { @@ -240,11 +249,14 @@ describe("DVMDUpdatedRegistrationHandler", () => { statusUpdatedAtBlock: 12344n, } as unknown as Application; - vi.spyOn(mockProjectRepository, "getProjectByAnchor").mockResolvedValue(mockProject); - vi.spyOn(mockRoundRepository, "getRoundByStrategyAddress").mockResolvedValue(mockRound); - vi.spyOn(mockApplicationRepository, "getApplicationByAnchorAddress").mockResolvedValue( - mockApplication, + vi.spyOn(mockProjectRepository, "getProjectByAnchorOrThrow").mockResolvedValue(mockProject); + vi.spyOn(mockRoundRepository, "getRoundByStrategyAddressOrThrow").mockResolvedValue( + mockRound, ); + vi.spyOn( + mockApplicationRepository, + "getApplicationByAnchorAddressOrThrow", + ).mockResolvedValue(mockApplication); vi.spyOn(mockMetadataProvider, "getMetadata").mockResolvedValue(undefined); handler = new DVMDUpdatedRegistrationHandler(mockEvent, chainId, { @@ -286,11 +298,14 @@ describe("DVMDUpdatedRegistrationHandler", () => { statusUpdatedAtBlock: 12344n, } as Application; - vi.spyOn(mockProjectRepository, "getProjectByAnchor").mockResolvedValue(mockProject); - vi.spyOn(mockRoundRepository, "getRoundByStrategyAddress").mockResolvedValue(mockRound); - vi.spyOn(mockApplicationRepository, "getApplicationByAnchorAddress").mockResolvedValue( - mockApplication, + vi.spyOn(mockProjectRepository, "getProjectByAnchorOrThrow").mockResolvedValue(mockProject); + vi.spyOn(mockRoundRepository, "getRoundByStrategyAddressOrThrow").mockResolvedValue( + mockRound, ); + vi.spyOn( + mockApplicationRepository, + "getApplicationByAnchorAddressOrThrow", + ).mockResolvedValue(mockApplication); vi.spyOn(mockMetadataProvider, "getMetadata").mockResolvedValue(null); handler = new DVMDUpdatedRegistrationHandler(mockEvent, chainId, { diff --git a/packages/processors/src/exceptions/applicationNotFound.exception.ts b/packages/repository/src/exceptions/applicationNotFound.exception.ts similarity index 100% rename from packages/processors/src/exceptions/applicationNotFound.exception.ts rename to packages/repository/src/exceptions/applicationNotFound.exception.ts diff --git a/packages/repository/src/exceptions/index.ts b/packages/repository/src/exceptions/index.ts new file mode 100644 index 0000000..710e180 --- /dev/null +++ b/packages/repository/src/exceptions/index.ts @@ -0,0 +1,3 @@ +export * from "./roundNotFound.exception.js"; +export * from "./applicationNotFound.exception.js"; +export * from "./projectNotFound.exception.js"; diff --git a/packages/processors/src/exceptions/projectNotFound.exception.ts b/packages/repository/src/exceptions/projectNotFound.exception.ts similarity index 100% rename from packages/processors/src/exceptions/projectNotFound.exception.ts rename to packages/repository/src/exceptions/projectNotFound.exception.ts diff --git a/packages/processors/src/exceptions/roundNotFound.exception.ts b/packages/repository/src/exceptions/roundNotFound.exception.ts similarity index 100% rename from packages/processors/src/exceptions/roundNotFound.exception.ts rename to packages/repository/src/exceptions/roundNotFound.exception.ts diff --git a/packages/repository/src/external.ts b/packages/repository/src/external.ts index 3568aaa..a2f7752 100644 --- a/packages/repository/src/external.ts +++ b/packages/repository/src/external.ts @@ -47,6 +47,8 @@ export { KyselyDonationRepository, } from "./repositories/kysely/index.js"; +export { RoundNotFound, ApplicationNotFound, ProjectNotFound } from "./internal.js"; + export { createKyselyPostgresDb as createKyselyDatabase } from "./internal.js"; export { migrateToLatest, resetDatabase } from "./db/index.js"; diff --git a/packages/repository/src/interfaces/applicationRepository.interface.ts b/packages/repository/src/interfaces/applicationRepository.interface.ts index 627fe8c..2eb2f60 100644 --- a/packages/repository/src/interfaces/applicationRepository.interface.ts +++ b/packages/repository/src/interfaces/applicationRepository.interface.ts @@ -42,6 +42,20 @@ export interface IApplicationReadRepository { anchorAddress: Address, ): Promise; + /** + * Retrieves a specific application by its chain ID, round ID, and anchor address. + * @param chainId The chain ID of the application. + * @param roundId The round ID of the application. + * @param anchorAddress The anchor address of the application. + * @returns A promise that resolves to an Application object + * @throws {ApplicationNotFound} if the application does not exist + */ + getApplicationByAnchorAddressOrThrow( + chainId: ChainId, + roundId: string, + anchorAddress: Address, + ): Promise; + /** * Retrieves all applications for a given chain ID and round ID. * @param chainId The chain ID of the applications. diff --git a/packages/repository/src/interfaces/projectRepository.interface.ts b/packages/repository/src/interfaces/projectRepository.interface.ts index 717bfd3..a3dce7d 100644 --- a/packages/repository/src/interfaces/projectRepository.interface.ts +++ b/packages/repository/src/interfaces/projectRepository.interface.ts @@ -47,6 +47,15 @@ export interface IProjectReadRepository { * @returns A promise that resolves to a Project object if found, or undefined if not found. */ getProjectByAnchor(chainId: ChainId, anchorAddress: Address): Promise; + + /** + * Retrieves a project by its anchor address and chain ID. + * @param chainId The chain ID of the project. + * @param anchorAddress The anchor address of the project. + * @returns A promise that resolves to a Project object + * @throws {ProjectNotFound} if the project does not exist + */ + getProjectByAnchorOrThrow(chainId: ChainId, anchorAddress: Address): Promise; } export interface IProjectRepository extends IProjectReadRepository { diff --git a/packages/repository/src/interfaces/roundRepository.interface.ts b/packages/repository/src/interfaces/roundRepository.interface.ts index f937ee7..12ac0ba 100644 --- a/packages/repository/src/interfaces/roundRepository.interface.ts +++ b/packages/repository/src/interfaces/roundRepository.interface.ts @@ -38,6 +38,15 @@ export interface IRoundReadRepository { strategyAddress: Address, ): Promise; + /** + * Retrieves a round by its strategy address and chain ID. + * @param chainId The chain ID of the round. + * @param strategyAddress The strategy address of the round. + * @returns A promise that resolves to a Round object + * @throws {RoundNotFound} if the round does not exist + */ + getRoundByStrategyAddressOrThrow(chainId: ChainId, strategyAddress: Address): Promise; + /** * Retrieves a round by a specific role and role value. * @param chainId The chain ID of the round. diff --git a/packages/repository/src/internal.ts b/packages/repository/src/internal.ts index c27844e..df70aa9 100644 --- a/packages/repository/src/internal.ts +++ b/packages/repository/src/internal.ts @@ -3,3 +3,4 @@ export * from "./interfaces/index.js"; export * from "./db/connection.js"; export * from "./repositories/kysely/index.js"; export * from "./db/helpers.js"; +export * from "./exceptions/index.js"; diff --git a/packages/repository/src/repositories/kysely/application.repository.ts b/packages/repository/src/repositories/kysely/application.repository.ts index 05a3c21..48d6f8f 100644 --- a/packages/repository/src/repositories/kysely/application.repository.ts +++ b/packages/repository/src/repositories/kysely/application.repository.ts @@ -4,6 +4,7 @@ import { Address, ChainId, stringify } from "@grants-stack-indexer/shared"; import { Application, + ApplicationNotFound, Database, IApplicationRepository, NewApplication, @@ -64,6 +65,25 @@ export class KyselyApplicationRepository implements IApplicationRepository { .executeTakeFirst(); } + /* @inheritdoc */ + async getApplicationByAnchorAddressOrThrow( + chainId: ChainId, + roundId: string, + anchorAddress: Address, + ): Promise { + const application = await this.getApplicationByAnchorAddress( + chainId, + roundId, + anchorAddress, + ); + + if (!application) { + throw new ApplicationNotFound(chainId, roundId, anchorAddress); + } + + return application; + } + /* @inheritdoc */ async getApplicationsByRoundId(chainId: ChainId, roundId: string): Promise { return this.db diff --git a/packages/repository/src/repositories/kysely/project.repository.ts b/packages/repository/src/repositories/kysely/project.repository.ts index a058331..70e48d0 100644 --- a/packages/repository/src/repositories/kysely/project.repository.ts +++ b/packages/repository/src/repositories/kysely/project.repository.ts @@ -11,6 +11,7 @@ import { PartialProject, PendingProjectRole, Project, + ProjectNotFound, ProjectRoleNames, } from "../../internal.js"; @@ -57,6 +58,13 @@ export class KyselyProjectRepository implements IProjectRepository { .executeTakeFirst(); } + /* @inheritdoc */ + async getProjectByAnchorOrThrow(chainId: ChainId, anchorAddress: Address): Promise { + const project = await this.getProjectByAnchor(chainId, anchorAddress); + if (!project) throw new ProjectNotFound(chainId, anchorAddress); + return project; + } + /* @inheritdoc */ async insertProject(project: NewProject): Promise { await this.db.withSchema(this.schemaName).insertInto("projects").values(project).execute(); diff --git a/packages/repository/src/repositories/kysely/round.repository.ts b/packages/repository/src/repositories/kysely/round.repository.ts index d3103c2..10fd434 100644 --- a/packages/repository/src/repositories/kysely/round.repository.ts +++ b/packages/repository/src/repositories/kysely/round.repository.ts @@ -11,6 +11,7 @@ import { PartialRound, PendingRoundRole, Round, + RoundNotFound, RoundRole, RoundRoleNames, } from "../../internal.js"; @@ -58,6 +59,18 @@ export class KyselyRoundRepository implements IRoundRepository { .executeTakeFirst(); } + /* @inheritdoc */ + async getRoundByStrategyAddressOrThrow( + chainId: ChainId, + strategyAddress: Address, + ): Promise { + const round = await this.getRoundByStrategyAddress(chainId, strategyAddress); + if (!round) { + throw new RoundNotFound(chainId, strategyAddress); + } + return round; + } + /* @inheritdoc */ async getRoundByRole( chainId: ChainId, diff --git a/packages/shared/src/external.ts b/packages/shared/src/external.ts index 7d7bcd8..01ba50e 100644 --- a/packages/shared/src/external.ts +++ b/packages/shared/src/external.ts @@ -17,7 +17,7 @@ export { BigNumber } from "./internal.js"; export type { BigNumberType } from "./internal.js"; export type { TokenCode, Token } from "./internal.js"; -export { TOKENS, getToken } from "./tokens/tokens.js"; +export { TOKENS, getToken, getTokenOrThrow, UnknownToken } from "./internal.js"; export { isAlloEvent, isRegistryEvent, isStrategyEvent } from "./internal.js"; export { stringify } from "./internal.js"; diff --git a/packages/shared/src/tokens/tokens.ts b/packages/shared/src/tokens/tokens.ts index 3167001..dcb3961 100644 --- a/packages/shared/src/tokens/tokens.ts +++ b/packages/shared/src/tokens/tokens.ts @@ -1,6 +1,6 @@ import { Branded } from "viem"; -import { Address } from "../internal.js"; +import { Address, ChainId } from "../internal.js"; export type TokenCode = Branded; @@ -606,3 +606,15 @@ export const TOKENS: { export const getToken = (chainId: number, tokenAddress: Address): Token | undefined => { return TOKENS[chainId]?.[tokenAddress]; }; + +export class UnknownToken extends Error { + constructor(tokenAddress: string, chainId?: ChainId) { + super(`Unknown token: ${tokenAddress} ${chainId ? `on chain ${chainId}` : ""}`); + } +} + +export const getTokenOrThrow = (chainId: ChainId, tokenAddress: Address): Token => { + const token = getToken(chainId, tokenAddress); + if (!token) throw new UnknownToken(tokenAddress, chainId); + return token; +};