generated from defi-wonderland/ts-turborepo-boilerplate
-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: direct grants simple event handlers #35
Merged
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
52 changes: 52 additions & 0 deletions
52
packages/processors/src/processors/strategy/directGrantsSimple/directGrantsSimple.handler.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import { Changeset } from "@grants-stack-indexer/repository"; | ||
import { ChainId, ProcessorEvent, StrategyEvent } from "@grants-stack-indexer/shared"; | ||
|
||
import { ProcessorDependencies, UnsupportedEventException } from "../../../internal.js"; | ||
import { BaseDistributedHandler, BaseStrategyHandler } from "../index.js"; | ||
import { DGSimpleRegisteredHandler, DGSimpleTimestampsUpdatedHandler } from "./handlers/index.js"; | ||
|
||
const STRATEGY_NAME = "allov2.DirectGrantsSimpleStrategy"; | ||
|
||
/** | ||
* This handler is responsible for processing events related to the | ||
* Direct Grants Simple strategy. | ||
* | ||
* The following events are currently handled by this strategy: | ||
* - TimestampsUpdated | ||
* - RegisteredWithSender | ||
* - DistributedWithRecipientAddress | ||
*/ | ||
export class DGSimpleStrategyHandler extends BaseStrategyHandler { | ||
constructor( | ||
private readonly chainId: ChainId, | ||
private readonly dependencies: ProcessorDependencies, | ||
) { | ||
super(STRATEGY_NAME); | ||
} | ||
|
||
/** @inheritdoc */ | ||
async handle(event: ProcessorEvent<"Strategy", StrategyEvent>): Promise<Changeset[]> { | ||
switch (event.eventName) { | ||
case "TimestampsUpdated": | ||
return new DGSimpleTimestampsUpdatedHandler( | ||
event as ProcessorEvent<"Strategy", "TimestampsUpdated">, | ||
this.chainId, | ||
this.dependencies, | ||
).handle(); | ||
case "RegisteredWithSender": | ||
return new DGSimpleRegisteredHandler( | ||
event as ProcessorEvent<"Strategy", "RegisteredWithSender">, | ||
this.chainId, | ||
this.dependencies, | ||
).handle(); | ||
case "DistributedWithRecipientAddress": | ||
return new BaseDistributedHandler( | ||
event as ProcessorEvent<"Strategy", "DistributedWithRecipientAddress">, | ||
this.chainId, | ||
this.dependencies, | ||
).handle(); | ||
default: | ||
throw new UnsupportedEventException("Strategy", event.eventName, this.name); | ||
} | ||
} | ||
} |
2 changes: 2 additions & 0 deletions
2
packages/processors/src/processors/strategy/directGrantsSimple/handlers/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export * from "./timestampsUpdated.handler.js"; | ||
export * from "./registered.handler.js"; |
95 changes: 95 additions & 0 deletions
95
...ages/processors/src/processors/strategy/directGrantsSimple/handlers/registered.handler.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
import { getAddress } from "viem"; | ||
|
||
import { Changeset, NewApplication } from "@grants-stack-indexer/repository"; | ||
import { ChainId, ProcessorEvent } from "@grants-stack-indexer/shared"; | ||
|
||
import { IEventHandler, ProcessorDependencies } from "../../../../internal.js"; | ||
import { decodeDGApplicationData } from "../../helpers/index.js"; | ||
|
||
type Dependencies = Pick< | ||
ProcessorDependencies, | ||
"roundRepository" | "projectRepository" | "metadataProvider" | ||
>; | ||
|
||
/** | ||
* Handles the Registered event for the Donation Voting Merkle Distribution Direct Transfer strategy. | ||
* | ||
* This handler performs the following core actions when a project registers for a round: | ||
* - Validates that both the project and round exist | ||
* - Decodes the application data from the event | ||
* - Retrieves the application metadata | ||
* - Creates a new application record with PENDING status | ||
* - Links the application to both the project and round | ||
*/ | ||
|
||
export class DGSimpleRegisteredHandler | ||
implements IEventHandler<"Strategy", "RegisteredWithSender"> | ||
{ | ||
constructor( | ||
readonly event: ProcessorEvent<"Strategy", "RegisteredWithSender">, | ||
private readonly chainId: ChainId, | ||
private readonly dependencies: Dependencies, | ||
) {} | ||
|
||
/** | ||
* Handles the RegisteredWithSender event for the Direct Grants Simple strategy. | ||
* @returns The changeset with an InsertApplication operation. | ||
* @throws ProjectNotFound if the project is not found. | ||
* @throws RoundNotFound if the round is not found. | ||
*/ | ||
async handle(): Promise<Changeset[]> { | ||
const { projectRepository, roundRepository, metadataProvider } = this.dependencies; | ||
const { data: encodedData, recipientId, sender } = this.event.params; | ||
const { blockNumber, blockTimestamp } = this.event; | ||
|
||
const anchorAddress = getAddress(recipientId); | ||
const project = await projectRepository.getProjectByAnchorOrThrow( | ||
this.chainId, | ||
anchorAddress, | ||
); | ||
|
||
const strategyAddress = getAddress(this.event.srcAddress); | ||
const round = await roundRepository.getRoundByStrategyAddressOrThrow( | ||
this.chainId, | ||
strategyAddress, | ||
); | ||
|
||
const values = decodeDGApplicationData(encodedData); | ||
const id = recipientId; | ||
|
||
const metadata = await metadataProvider.getMetadata(values.metadata.pointer); | ||
|
||
const application: NewApplication = { | ||
chainId: this.chainId, | ||
id: id, | ||
projectId: project.id, | ||
anchorAddress, | ||
roundId: round.id, | ||
status: "PENDING", | ||
metadataCid: values.metadata.pointer, | ||
metadata: metadata ?? null, | ||
createdAtBlock: BigInt(blockNumber), | ||
createdByAddress: getAddress(sender), | ||
statusUpdatedAtBlock: BigInt(blockNumber), | ||
statusSnapshots: [ | ||
{ | ||
status: "PENDING", | ||
updatedAtBlock: blockNumber.toString(), | ||
updatedAt: new Date(blockTimestamp * 1000), // timestamp is in seconds, convert to ms | ||
}, | ||
], | ||
distributionTransaction: null, | ||
totalAmountDonatedInUsd: 0, | ||
totalDonationsCount: 0, | ||
uniqueDonorsCount: 0, | ||
tags: ["allo-v2"], | ||
}; | ||
|
||
return [ | ||
{ | ||
type: "InsertApplication", | ||
args: application, | ||
}, | ||
]; | ||
} | ||
} |
59 changes: 59 additions & 0 deletions
59
...ocessors/src/processors/strategy/directGrantsSimple/handlers/timestampsUpdated.handler.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import { getAddress } from "viem"; | ||
|
||
import { Changeset } from "@grants-stack-indexer/repository"; | ||
import { ChainId, ProcessorEvent } from "@grants-stack-indexer/shared"; | ||
|
||
import { getDateFromTimestamp } from "../../../../helpers/index.js"; | ||
import { IEventHandler, ProcessorDependencies } from "../../../../internal.js"; | ||
|
||
type Dependencies = Pick<ProcessorDependencies, "roundRepository">; | ||
|
||
/** | ||
* Handles the TimestampsUpdated event for the Direct Grants Simple strategy. | ||
* | ||
* This handler processes updates to the round timestamps: | ||
* - Validates the round exists for the strategy address | ||
* - Converts the updated registration timestamps to dates | ||
* - Returns a changeset to update the round's application timestamps | ||
*/ | ||
export class DGSimpleTimestampsUpdatedHandler | ||
implements IEventHandler<"Strategy", "TimestampsUpdated"> | ||
{ | ||
constructor( | ||
readonly event: ProcessorEvent<"Strategy", "TimestampsUpdated">, | ||
private readonly chainId: ChainId, | ||
private readonly dependencies: Dependencies, | ||
) {} | ||
|
||
/** | ||
* Handles the TimestampsUpdated event for the Direct Grants Simple strategy. | ||
* @returns The changeset with an UpdateRound operation. | ||
* @throws RoundNotFound if the round is not found. | ||
*/ | ||
async handle(): Promise<Changeset[]> { | ||
const strategyAddress = getAddress(this.event.srcAddress); | ||
const round = await this.dependencies.roundRepository.getRoundByStrategyAddressOrThrow( | ||
this.chainId, | ||
strategyAddress, | ||
); | ||
|
||
const { startTime: strStartTime, endTime: strEndTime } = this.event.params; | ||
|
||
const applicationsStartTime = getDateFromTimestamp(BigInt(strStartTime)); | ||
const applicationsEndTime = getDateFromTimestamp(BigInt(strEndTime)); | ||
|
||
return [ | ||
{ | ||
type: "UpdateRound", | ||
args: { | ||
chainId: this.chainId, | ||
roundId: round.id, | ||
round: { | ||
applicationsStartTime, | ||
applicationsEndTime, | ||
}, | ||
}, | ||
}, | ||
]; | ||
} | ||
} |
2 changes: 2 additions & 0 deletions
2
packages/processors/src/processors/strategy/directGrantsSimple/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export * from "./handlers/index.js"; | ||
export * from "./directGrantsSimple.handler.js"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
161 changes: 161 additions & 0 deletions
161
packages/processors/test/strategy/directGrantsSimple/directGrantsSimple.handler.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; | ||
|
||
import { EvmProvider } from "@grants-stack-indexer/chain-providers"; | ||
import { IMetadataProvider } from "@grants-stack-indexer/metadata"; | ||
import { IPricingProvider } from "@grants-stack-indexer/pricing"; | ||
import { | ||
IApplicationReadRepository, | ||
IProjectReadRepository, | ||
IRoundReadRepository, | ||
} from "@grants-stack-indexer/repository"; | ||
import { ChainId, ILogger, ProcessorEvent, StrategyEvent } from "@grants-stack-indexer/shared"; | ||
|
||
import { UnsupportedEventException } from "../../../src/exceptions/index.js"; | ||
import { BaseDistributedHandler } from "../../../src/processors/strategy/common/index.js"; | ||
import { DGSimpleStrategyHandler } from "../../../src/processors/strategy/directGrantsSimple/directGrantsSimple.handler.js"; | ||
import { | ||
DGSimpleRegisteredHandler, | ||
DGSimpleTimestampsUpdatedHandler, | ||
} from "../../../src/processors/strategy/directGrantsSimple/handlers/index.js"; | ||
|
||
vi.mock("../../../src/processors/strategy/directGrantsSimple/handlers/index.js", () => { | ||
const DGSimpleRegisteredHandler = vi.fn(); | ||
const DGSimpleTimestampsUpdatedHandler = vi.fn(); | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access | ||
DGSimpleRegisteredHandler.prototype.handle = vi.fn(); | ||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access | ||
DGSimpleTimestampsUpdatedHandler.prototype.handle = vi.fn(); | ||
|
||
return { | ||
DGSimpleRegisteredHandler, | ||
DGSimpleTimestampsUpdatedHandler, | ||
}; | ||
}); | ||
|
||
vi.mock("../../../src/processors/strategy/common/baseDistributed.handler.js", () => { | ||
const BaseDistributedHandler = vi.fn(); | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access | ||
BaseDistributedHandler.prototype.handle = vi.fn(); | ||
|
||
return { | ||
BaseDistributedHandler, | ||
}; | ||
}); | ||
|
||
describe("DirectGrantsSimpleStrategyHandler", () => { | ||
let handler: DGSimpleStrategyHandler; | ||
let mockMetadataProvider: IMetadataProvider; | ||
let mockRoundRepository: IRoundReadRepository; | ||
let mockProjectRepository: IProjectReadRepository; | ||
let mockEVMProvider: EvmProvider; | ||
let mockPricingProvider: IPricingProvider; | ||
let mockApplicationRepository: IApplicationReadRepository; | ||
const chainId = 10 as ChainId; | ||
|
||
const logger: ILogger = { | ||
debug: vi.fn(), | ||
error: vi.fn(), | ||
info: vi.fn(), | ||
warn: vi.fn(), | ||
}; | ||
|
||
beforeEach(() => { | ||
mockMetadataProvider = {} as IMetadataProvider; | ||
mockRoundRepository = {} as IRoundReadRepository; | ||
mockProjectRepository = {} as IProjectReadRepository; | ||
mockEVMProvider = {} as EvmProvider; | ||
mockPricingProvider = {} as IPricingProvider; | ||
mockApplicationRepository = {} as IApplicationReadRepository; | ||
|
||
handler = new DGSimpleStrategyHandler(chainId, { | ||
metadataProvider: mockMetadataProvider, | ||
roundRepository: mockRoundRepository, | ||
projectRepository: mockProjectRepository, | ||
evmProvider: mockEVMProvider, | ||
pricingProvider: mockPricingProvider, | ||
applicationRepository: mockApplicationRepository, | ||
logger, | ||
}); | ||
}); | ||
|
||
afterEach(() => { | ||
vi.clearAllMocks(); | ||
}); | ||
|
||
it("returns correct name", () => { | ||
expect(handler.name).toBe("allov2.DirectGrantsSimpleStrategy"); | ||
}); | ||
|
||
it("calls DGSimpleTimestampsUpdatedHandler for TimestampsUpdated event", async () => { | ||
const mockEvent = { | ||
eventName: "TimestampsUpdated", | ||
} as ProcessorEvent<"Strategy", "TimestampsUpdated">; | ||
|
||
vi.spyOn(DGSimpleTimestampsUpdatedHandler.prototype, "handle").mockResolvedValue([]); | ||
|
||
await handler.handle(mockEvent); | ||
|
||
expect(DGSimpleTimestampsUpdatedHandler).toHaveBeenCalledWith(mockEvent, chainId, { | ||
metadataProvider: mockMetadataProvider, | ||
roundRepository: mockRoundRepository, | ||
projectRepository: mockProjectRepository, | ||
evmProvider: mockEVMProvider, | ||
pricingProvider: mockPricingProvider, | ||
applicationRepository: mockApplicationRepository, | ||
logger, | ||
}); | ||
expect(DGSimpleTimestampsUpdatedHandler.prototype.handle).toHaveBeenCalled(); | ||
}); | ||
|
||
it("calls DGSimpleRegisteredHandler for RegisteredWithSender event", async () => { | ||
const mockEvent = { | ||
eventName: "RegisteredWithSender", | ||
} as ProcessorEvent<"Strategy", "RegisteredWithSender">; | ||
|
||
vi.spyOn(DGSimpleRegisteredHandler.prototype, "handle").mockResolvedValue([]); | ||
|
||
await handler.handle(mockEvent); | ||
|
||
expect(DGSimpleRegisteredHandler).toHaveBeenCalledWith(mockEvent, chainId, { | ||
metadataProvider: mockMetadataProvider, | ||
roundRepository: mockRoundRepository, | ||
projectRepository: mockProjectRepository, | ||
evmProvider: mockEVMProvider, | ||
pricingProvider: mockPricingProvider, | ||
applicationRepository: mockApplicationRepository, | ||
logger, | ||
}); | ||
expect(DGSimpleRegisteredHandler.prototype.handle).toHaveBeenCalled(); | ||
}); | ||
|
||
it("calls BaseDistributedHandler for DistributedWithRecipientAddress event", async () => { | ||
const mockEvent = { | ||
eventName: "DistributedWithRecipientAddress", | ||
} as ProcessorEvent<"Strategy", "DistributedWithRecipientAddress">; | ||
|
||
vi.spyOn(BaseDistributedHandler.prototype, "handle").mockResolvedValue([]); | ||
|
||
await handler.handle(mockEvent); | ||
|
||
expect(BaseDistributedHandler).toHaveBeenCalledWith(mockEvent, chainId, { | ||
metadataProvider: mockMetadataProvider, | ||
roundRepository: mockRoundRepository, | ||
projectRepository: mockProjectRepository, | ||
evmProvider: mockEVMProvider, | ||
pricingProvider: mockPricingProvider, | ||
applicationRepository: mockApplicationRepository, | ||
logger, | ||
}); | ||
expect(BaseDistributedHandler.prototype.handle).toHaveBeenCalled(); | ||
}); | ||
|
||
it("throws UnsupportedEventException for unknown events", async () => { | ||
const mockEvent = { | ||
eventName: "UnknownEvent", | ||
} as unknown as ProcessorEvent<"Strategy", StrategyEvent>; | ||
|
||
await expect(handler.handle(mockEvent)).rejects.toThrow(UnsupportedEventException); | ||
}); | ||
}); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
may we use complete names for strategies ? or at least use the same for strategy and event handlers :)
Look here packages/processors/src/processors/strategy/directGrantsSimple/directGrantsSimple.handler.ts
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
9d6a824
i renamed to DGSimple to follow the convention on the other strategies