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 all 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
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { getAddress } from "viem";

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

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

type Dependencies = Pick<ProcessorDependencies, "projectRepository" | "logger">;
/**
Expand Down
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[]> {
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}`);
}

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),
},
},
];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import StatusesBitmap from "statuses-bitmap";
import { getAddress } from "viem";

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

import { ApplicationStatus, IEventHandler, ProcessorDependencies } from "../../../internal.js";
import { createStatusUpdate, isValidApplicationStatus } from "../helpers/index.js";

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

type ApplicationUpdate = {
application: Application;
status: number;
};

/**
* BaseRecipientStatusUpdatedHandler: Processes 'RecipientStatusUpdated' events
*
* - Decodes a bitmap containing status updates for multiple applications
* - Validates each status is valid (between 1-3)
* - Creates changesets to update application statuses in bulk
* - Serves as a base class as all strategies share the same logic for this event
*
* @dev:
* - Strategy handlers that want to handle the RecipientStatusUpdated event should create an instance of this class corresponding to the event.
*
*/
export class BaseRecipientStatusUpdatedHandler
implements IEventHandler<"Strategy", "RecipientStatusUpdatedWithFullRow">
{
private readonly bitmap: StatusesBitmap;

constructor(
readonly event: ProcessorEvent<"Strategy", "RecipientStatusUpdatedWithFullRow">,
private readonly chainId: ChainId,
private readonly dependencies: Dependencies,
) {
this.bitmap = new StatusesBitmap(256n, 4n);
}

/**
* Handles the RecipientStatusUpdated event by processing status updates for multiple applications.
* @returns An array of changesets to update application statuses.
*/
async handle(): Promise<Changeset[]> {
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);

return applicationsToUpdate.map(({ application, status }) => {
const statusString = ApplicationStatus[status] as Application["status"];
return {
type: "UpdateApplication",
args: {
chainId: this.chainId,
roundId: round.id,
applicationId: application.id,
application: createStatusUpdate({
application,
newStatus: statusString,
blockNumber: this.event.blockNumber,
blockTimestamp: this.event.blockTimestamp,
}),
},
};
});
}

/**
* Gets the list of applications that need to be updated based on the bitmap row
* @param roundId - The ID of the round.
* @returns An array of application updates.
*/
private async getApplicationsToUpdate(roundId: string): Promise<ApplicationUpdate[]> {
const { rowIndex, fullRow } = this.event.params;
this.bitmap.setRow(BigInt(rowIndex), BigInt(fullRow));

const startIndex = BigInt(rowIndex) * BigInt(this.bitmap.itemsPerRow);
const applications: { application: Application; status: number }[] = [];

for (let i = startIndex; i < startIndex + this.bitmap.itemsPerRow; i++) {
const status = this.bitmap.getStatus(i);
if (isValidApplicationStatus(status)) {
const application =
await this.dependencies.applicationRepository.getApplicationById(
i.toString(),
this.chainId,
roundId,
);

if (application) {
applications.push({
application,
status,
});
}
}
}

return applications;
}
}
3 changes: 3 additions & 0 deletions packages/processors/src/processors/strategy/common/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
export * from "./baseDistributed.handler.js";
export * from "./base.strategy.js";
export * from "./baseDistributionUpdated.handler.js";
export * from "./baseFundsDistributed.handler.js";
export * from "./baseRecipientStatusUpdated.handler.js";
Loading