Skip to content
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: all remaining event handlers for DVMD strategy #30

Merged
merged 5 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions apps/indexer/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,14 @@ contracts:
- event: RecipientStatusUpdated(uint256 indexed rowIndex, uint256 fullRow, address sender)
name: RecipientStatusUpdatedWithFullRow

# UpdatedRegistration
- event: UpdatedRegistration(address indexed recipientId, bytes data, address sender, uint8 status)
name: UpdatedRegistrationWithStatus
- event: UpdatedRegistration(address indexed recipientId, bytes data, address sender)
name: UpdatedRegistration
- event: UpdatedRegistration(address indexed recipientId, uint256 applicationId, bytes data, address sender, uint8 status)
name: UpdatedRegistrationWithApplicationId

# ########################## ALLO v2.1 ##########################

# # AllocationExtension
Expand Down
5 changes: 5 additions & 0 deletions apps/indexer/src/handlers/Strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,8 @@ Strategy.DirectAllocated.handler(async ({}) => {});
Strategy.RecipientStatusUpdatedWithApplicationId.handler(async ({}) => {});
Strategy.RecipientStatusUpdatedWithRecipientStatus.handler(async ({}) => {});
Strategy.RecipientStatusUpdatedWithFullRow.handler(async ({}) => {});

// UpdatedRegistration Handlers
Strategy.UpdatedRegistrationWithStatus.handler(async ({}) => {});
Strategy.UpdatedRegistration.handler(async ({}) => {});
Strategy.UpdatedRegistrationWithApplicationId.handler(async ({}) => {});
2 changes: 1 addition & 1 deletion packages/data-flow/test/unit/eventsProcessor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ describe("EventsProcessor", () => {
sender: "0x0",
recipientAddress: "0x0",
recipientId: "0x0",
amount: 1n,
amount: "1",
},
transactionFields: {
hash: "0x0",
Expand Down
9 changes: 9 additions & 0 deletions packages/data-flow/test/unit/orchestrator.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,15 @@ describe("Orchestrator", { sequential: true }, () => {
AllocatedWithData: "",
AllocatedWithVotes: "",
AllocatedWithStatus: "",
TimestampsUpdatedWithRegistrationAndAllocation: "",
DistributionUpdated: "",
FundsDistributed: "",
RecipientStatusUpdatedWithApplicationId: "",
RecipientStatusUpdatedWithRecipientStatus: "",
RecipientStatusUpdatedWithFullRow: "",
UpdatedRegistrationWithStatus: "",
UpdatedRegistration: "",
UpdatedRegistrationWithApplicationId: "",
};

for (const event of Object.keys(strategyEvents) as StrategyEvent[]) {
Expand Down
1 change: 1 addition & 0 deletions packages/processors/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"@grants-stack-indexer/pricing": "workspace:*",
"@grants-stack-indexer/repository": "workspace:*",
"@grants-stack-indexer/shared": "workspace:*",
"statuses-bitmap": "github:gitcoinco/statuses-bitmap#f123d7778e42e16adb98fff2b2ba18c0fee57227",
"viem": "2.21.19",
"zod": "3.23.8"
}
Expand Down
6 changes: 6 additions & 0 deletions packages/processors/src/constants/enums.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export enum ApplicationStatus {
NONE = 0,
PENDING,
APPROVED,
REJECTED,
}
1 change: 1 addition & 0 deletions packages/processors/src/constants/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./enums.js";
5 changes: 1 addition & 4 deletions packages/processors/src/exceptions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +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";
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export class MetadataNotFound extends Error {
constructor(message: string) {
super(message);
this.name = "MetadataNotFoundError";
}
}
7 changes: 0 additions & 7 deletions packages/processors/src/exceptions/unknownToken.exception.ts

This file was deleted.

1 change: 1 addition & 0 deletions packages/processors/src/internal.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Types and interfaces
export * from "./types/index.js";
export * from "./interfaces/index.js";
export * from "./constants/index.js";

// Exceptions
export * from "./exceptions/index.js";
Expand Down
1 change: 1 addition & 0 deletions packages/processors/src/schemas/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./projectMetadata.js";
export * from "./roundMetadata.js";
export * from "./applicationMetadata.js";
export * from "./matchingDistribution.js";
24 changes: 24 additions & 0 deletions packages/processors/src/schemas/matchingDistribution.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { z } from "zod";

export type MatchingDistribution = z.infer<typeof MatchingDistributionSchema>;

const BigIntSchema = z.string().or(
z.object({ type: z.literal("BigNumber"), hex: z.string() }).transform((val) => {
return BigInt(val.hex).toString();
}),
);

export const MatchingDistributionSchema = z.object({
matchingDistribution: z.array(
z.object({
applicationId: z.string(),
projectPayoutAddress: z.string(),
projectId: z.string(),
projectName: z.string(),
matchPoolPercentage: z.number().or(z.string().min(1)).pipe(z.coerce.number()),
contributionsCount: z.number().or(z.string().min(1)).pipe(z.coerce.number()),
originalMatchAmountInToken: BigIntSchema.default("0"),
matchAmountInToken: BigIntSchema.default("0"),
}),
),
});
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export class BaseDistributedHandler
args: {
chainId: this.chainId,
roundId: round.id,
amount: this.event.params.amount,
amount: BigInt(this.event.params.amount),
},
},
];
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { getAddress } from "viem";

import { Changeset } from "@grants-stack-indexer/repository";
import { ChainId, ProcessorEvent } from "@grants-stack-indexer/shared";

import {
IEventHandler,
MetadataNotFound,
MetadataParsingFailed,
ProcessorDependencies,
} from "../../internal.js";
import { MatchingDistribution, MatchingDistributionSchema } from "../../schemas/index.js";

type Dependencies = Pick<ProcessorDependencies, "metadataProvider" | "logger">;

/**
* BaseDistributionUpdatedHandler: Processes 'DistributionUpdated' events
*
* - Decodes the updated distribution metadata
* - Creates a changeset to update the round with the new distribution
* - Serves as a base class as all strategies share the same logic for this event.
*
* @dev:
* - Strategy handlers that want to handle the DistributionUpdated event should create an instance of this class corresponding to the event.
*
*/

export class BaseDistributionUpdatedHandler
implements IEventHandler<"Strategy", "DistributionUpdated">
{
constructor(
readonly event: ProcessorEvent<"Strategy", "DistributionUpdated">,
private readonly chainId: ChainId,
private readonly dependencies: Dependencies,
) {}

/* @inheritdoc */
async handle(): Promise<Changeset[]> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

inheritdoc?

const { logger, metadataProvider } = this.dependencies;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [_, pointer] = this.event.params.metadata;

const strategyAddress = getAddress(this.event.srcAddress);
const rawDistribution = await metadataProvider.getMetadata<
MatchingDistribution | undefined
>(pointer);

if (!rawDistribution) {
logger.warn(`No matching distribution found for pointer: ${pointer}`);

throw new MetadataNotFound(`No matching distribution found for pointer: ${pointer}`);
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should MetadataProvider be responsible for throwing a "not found" error instead of having the handler doing it?


const distribution = MatchingDistributionSchema.safeParse(rawDistribution);

if (!distribution.success) {
logger.warn(`Failed to parse matching distribution: ${distribution.error.message}`);

throw new MetadataParsingFailed(
`Failed to parse matching distribution: ${distribution.error.message}`,
);
}

return [
{
type: "UpdateRoundByStrategyAddress",
args: {
chainId: this.chainId,
strategyAddress,
round: {
readyForPayoutTransaction: this.event.transactionFields.hash,
matchingDistribution: distribution.data.matchingDistribution,
},
},
},
];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { getAddress } from "viem";

import { Changeset } from "@grants-stack-indexer/repository";
import { ChainId, ProcessorEvent } from "@grants-stack-indexer/shared";

import { IEventHandler, ProcessorDependencies } from "../../internal.js";

type Dependencies = Pick<
ProcessorDependencies,
"roundRepository" | "applicationRepository" | "logger"
>;

/**
* BaseFundsDistributedHandler: Processes 'FundsDistributed' events
*
* - Handles funds distributed events across all strategies.
* - Creates two changesets:
* 1. UpdateApplication: Updates the application with the transaction hash.
* 2. IncrementRoundTotalDistributed: Increments the total distributed amount for a round.
* - Serves as a base class as all strategies share the same logic for this event.
*
* @dev:
* - Strategy handlers that want to handle the FundsDistributed event should create an instance of this class corresponding to the event.
*
*/

export class BaseFundsDistributedHandler implements IEventHandler<"Strategy", "FundsDistributed"> {
constructor(
readonly event: ProcessorEvent<"Strategy", "FundsDistributed">,
private readonly chainId: ChainId,
private readonly dependencies: Dependencies,
) {}

/**
* Handles the FundsDistributed event.
* @throws {RoundNotFound} if the round is not found.
* @throws {ApplicationNotFound} if the application is not found.
* @returns An array of changesets with the following:
* 1. UpdateApplication: Updates the application with the transaction hash.
* 2. IncrementRoundTotalDistributed: Increments the total distributed amount for a round.
*/
async handle(): Promise<Changeset[]> {
const { roundRepository, applicationRepository } = this.dependencies;

const strategyAddress = getAddress(this.event.srcAddress);
const round = await roundRepository.getRoundByStrategyAddressOrThrow(
this.chainId,
strategyAddress,
);

const roundId = round.id;
const anchorAddress = getAddress(this.event.params.recipientId);
const application = await applicationRepository.getApplicationByAnchorAddressOrThrow(
this.chainId,
roundId,
anchorAddress,
);

return [
{
type: "UpdateApplication",
args: {
chainId: this.chainId,
roundId,
applicationId: application.id,
application: {
distributionTransaction: this.event.transactionFields.hash,
},
},
},
{
type: "IncrementRoundTotalDistributed",
args: {
chainId: this.chainId,
roundId: round.id,
amount: BigInt(this.event.params.amount),
},
},
];
}
}
Loading