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: registry handlers #31

Merged
merged 7 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
2 changes: 1 addition & 1 deletion packages/data-flow/test/unit/eventsRegistry.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ describe("InMemoryEventsRegistry", () => {
expect(retrievedEvent).toEqual(mockEvent);
});

it("should update the last processed event when saving multiple times", async () => {
it("updates the last processed event when saving multiple times", async () => {
const registry = new InMemoryEventsRegistry(logger);

const firstEvent: ProcessorEvent<"Allo", "PoolCreated"> = {
Expand Down
6 changes: 3 additions & 3 deletions packages/processors/src/internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ export * from "./interfaces/index.js";
export * from "./exceptions/index.js";

// Allo
export * from "./allo/index.js";
export * from "./processors/allo/index.js";

// Strategy
export * from "./strategy/index.js";
export * from "./registry/index.js";
export * from "./processors/strategy/index.js";
export * from "./processors/registry/index.js";
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Changeset } from "@grants-stack-indexer/repository";
import { AlloEvent, ChainId, ProcessorEvent } from "@grants-stack-indexer/shared";

import type { IProcessor, ProcessorDependencies } from "../internal.js";
import { UnsupportedEventException } from "../internal.js";
import type { IProcessor, ProcessorDependencies } from "../../internal.js";
import { UnsupportedEventException } from "../../internal.js";
import { PoolCreatedHandler } from "./handlers/index.js";

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import type { Changeset, NewRound, PendingRoundRole } from "@grants-stack-indexe
import type { ChainId, ProcessorEvent, Token } from "@grants-stack-indexer/shared";
import { getToken, isAlloNativeToken } from "@grants-stack-indexer/shared";

import type { IEventHandler, ProcessorDependencies, StrategyTimings } from "../../internal.js";
import { calculateAmountInUsd, getRoundRoles } from "../../helpers/index.js";
import { StrategyHandlerFactory, TokenPriceNotFoundError } from "../../internal.js";
import { RoundMetadataSchema } from "../../schemas/index.js";
import type { IEventHandler, ProcessorDependencies, StrategyTimings } from "../../../internal.js";
import { calculateAmountInUsd, getRoundRoles } from "../../../helpers/index.js";
import { StrategyHandlerFactory, TokenPriceNotFoundError } from "../../../internal.js";
import { RoundMetadataSchema } from "../../../schemas/index.js";

type Dependencies = Pick<
ProcessorDependencies,
Expand Down
6 changes: 6 additions & 0 deletions packages/processors/src/processors/registry/handlers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export * from "./profileCreated.handler.js";
export * from "./profileMetadataUpdated.handler.js";
export * from "./profileNameUpdated.handler.js";
export * from "./profileOwnerUpdated.handler.js";
export * from "./roleGranted.handler.js";
export * from "./roleRevoked.handler.js";
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { getAddress } from "viem";
import { Changeset, ProjectType } from "@grants-stack-indexer/repository";
import { ChainId, ProcessorEvent } from "@grants-stack-indexer/shared";

import { IEventHandler, ProcessorDependencies } from "../../internal.js";
import { ProjectMetadata, ProjectMetadataSchema } from "../../schemas/projectMetadata.js";
import { IEventHandler, ProcessorDependencies } from "../../../internal.js";
import { ProjectMetadata, ProjectMetadataSchema } from "../../../schemas/projectMetadata.js";

type Dependencies = Pick<
ProcessorDependencies,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { Changeset, ProjectType } from "@grants-stack-indexer/repository";
import { ChainId, ProcessorEvent } from "@grants-stack-indexer/shared";

import { IEventHandler, ProcessorDependencies } from "../../../internal.js";
import { ProjectMetadata, ProjectMetadataSchema } from "../../../schemas/index.js";

type Dependencies = Pick<
ProcessorDependencies,
"projectRepository" | "evmProvider" | "metadataProvider" | "logger"
>;
/**
* Handles the ProfileMetadataUpdated event for the Registry contract from Allo protocol.
*/
Copy link
Collaborator

Choose a reason for hiding this comment

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

i like adding here a general overview of the main actions performed by the handler

for example for PoolCreatedHandler

/**
 * Handles the PoolCreated event for the Allo protocol.
 *
 * This handler performs the following core actions when a new pool is created:
 * - Retrieves the metadata associated with the pool
 * - Determines the correct token address, handling native tokens appropriately.
 * - Extracts the correct strategy information from the provided strategy ID.
 * - Calculates the funded amount in USD based on the token's pricing.
 * - Creates a new round object
 */

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

this is great, i saw it on your prs 💯

export class ProfileMetadataUpdatedHandler
implements IEventHandler<"Registry", "ProfileMetadataUpdated">
{
constructor(
readonly event: ProcessorEvent<"Registry", "ProfileMetadataUpdated">,
readonly chainId: ChainId,
private dependencies: Dependencies,
) {}
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.

Suggested change
) {}
async handle(): Promise<Changeset[]> {
) {}
/* @inheritdoc */
async handle(): Promise<Changeset[]> {

or a more specific description

const { metadataProvider } = this.dependencies;

const metadataCid = this.event.params.metadata[1];
const metadata = await metadataProvider.getMetadata(metadataCid);
const parsedMetadata = ProjectMetadataSchema.safeParse(metadata);

if (!parsedMetadata.success) {
// logger.warn({
// msg: `ProfileMetadataUpdated: Failed to parse metadata`,
// event,
// metadataCid,
// metadata,
// });
return [];
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

should we throw or return empty?


const projectType = this.getProjectTypeFromMetadata(parsedMetadata.data);

return [
{
type: "UpdateProject",
args: {
chainId: this.chainId,
projectId: this.event.params.profileId,
project: {
metadataCid: metadataCid,
metadata: metadata,
projectType,
},
},
},
];
return [];
}
private getProjectTypeFromMetadata(metadata: ProjectMetadata): ProjectType {
// if the metadata contains a canonical reference, it's a linked project
if ("canonical" in metadata) {
return "linked";
}

return "canonical";
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

ditto for description and inheritdoc

Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
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, "logger">;
0xkenj1 marked this conversation as resolved.
Show resolved Hide resolved
/**
* Handles the ProfileNameUpdated event for the Registry contract from Allo protocol.
*/
export class ProfileNameUpdatedHandler implements IEventHandler<"Registry", "ProfileNameUpdated"> {
constructor(
readonly event: ProcessorEvent<"Registry", "ProfileNameUpdated">,
readonly chainId: ChainId,
private dependencies: Dependencies,
) {}
async handle(): Promise<Changeset[]> {
return [
{
type: "UpdateProject",
args: {
chainId: this.chainId,
projectId: this.event.params.profileId,
project: {
name: this.event.params.name,
anchorAddress: getAddress(this.event.params.anchor),
},
},
},
];
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

ditto for description and inheritdoc

Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
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, "logger">;
/**
* Handles the ProfileOwnerUpdated event for the Registry contract from Allo protocol.
*/
export class ProfileOwnerUpdatedHandler
implements IEventHandler<"Registry", "ProfileOwnerUpdated">
{
constructor(
readonly event: ProcessorEvent<"Registry", "ProfileOwnerUpdated">,
readonly chainId: ChainId,
private dependencies: Dependencies,
) {}
async handle(): Promise<Changeset[]> {
return [
{
type: "DeleteAllProjectRolesByRole",
args: {
projectRole: {
chainId: this.chainId,
projectId: this.event.params.profileId,
role: "owner",
},
},
},
{
type: "InsertProjectRole",
args: {
projectRole: {
chainId: this.chainId,
projectId: this.event.params.profileId,
address: getAddress(this.event.params.owner),
role: "owner",
createdAtBlock: BigInt(this.event.blockNumber),
},
},
},
];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { getAddress } from "viem";
import { Changeset } from "@grants-stack-indexer/repository";
import { ALLO_OWNER_ROLE, ChainId, ProcessorEvent } from "@grants-stack-indexer/shared";

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

/**
* Handles the RoleGranted event for the Registry contract from Allo protocol.
Expand Down
Copy link
Collaborator

Choose a reason for hiding this comment

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

ditto for description and inheritdoc

Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
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, "projectRepository" | "logger">;
/**
* Handles the RoleRevoked event for the Registry contract from Allo protocol.
*/
export class RoleRevokedHandler implements IEventHandler<"Registry", "RoleRevoked"> {
constructor(
readonly event: ProcessorEvent<"Registry", "RoleRevoked">,
readonly chainId: ChainId,
private dependencies: Dependencies,
) {}
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 { projectRepository } = this.dependencies;
const account = getAddress(this.event.params.account);
const role = this.event.params.role.toLowerCase();
const project = await projectRepository.getProjectById(this.chainId, role);

// The role value for a member is the profileId in Allo V1
// which is the project id in this database.
// If we don't find a project with that id we can't remove the role.
if (!project) {
return [];
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

should we throw here?


return [
{
type: "DeleteAllProjectRolesByRoleAndAddress",
args: {
projectRole: {
chainId: this.chainId,
projectId: project.id,
address: account,
role: "member",
},
},
},
];
}
}
66 changes: 66 additions & 0 deletions packages/processors/src/processors/registry/registry.processor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { Changeset } from "@grants-stack-indexer/repository";
import { ChainId, ProcessorEvent, RegistryEvent } from "@grants-stack-indexer/shared";

import type { IProcessor } from "../../internal.js";
import { UnsupportedEventException } from "../../internal.js";
import { ProcessorDependencies } from "../../types/processor.types.js";
import {
ProfileCreatedHandler,
ProfileMetadataUpdatedHandler,
ProfileNameUpdatedHandler,
ProfileOwnerUpdatedHandler,
RoleGrantedHandler,
RoleRevokedHandler,
} from "./handlers/index.js";

export class RegistryProcessor implements IProcessor<"Registry", RegistryEvent> {
constructor(
private readonly chainId: ChainId,
private readonly dependencies: ProcessorDependencies,
) {}

async process(event: ProcessorEvent<"Registry", RegistryEvent>): Promise<Changeset[]> {
//TODO: Implement robust error handling and retry logic
switch (event.eventName) {
case "ProfileCreated":
return new ProfileCreatedHandler(
event as ProcessorEvent<"Registry", "ProfileCreated">,
this.chainId,
this.dependencies,
).handle();
case "ProfileMetadataUpdated":
return new ProfileMetadataUpdatedHandler(
event as ProcessorEvent<"Registry", "ProfileMetadataUpdated">,
this.chainId,
this.dependencies,
).handle();
case "ProfileNameUpdated":
return new ProfileNameUpdatedHandler(
event as ProcessorEvent<"Registry", "ProfileNameUpdated">,
this.chainId,
this.dependencies,
).handle();
case "ProfileOwnerUpdated":
return new ProfileOwnerUpdatedHandler(
event as ProcessorEvent<"Registry", "ProfileOwnerUpdated">,
this.chainId,
this.dependencies,
).handle();
case "RoleGranted":
return new RoleGrantedHandler(
event as ProcessorEvent<"Registry", "RoleGranted">,
this.chainId,
this.dependencies,
).handle();
case "RoleRevoked":
return new RoleRevokedHandler(
event as ProcessorEvent<"Registry", "RoleRevoked">,
this.chainId,
this.dependencies,
).handle();

default:
throw new UnsupportedEventException("Registry", event.eventName);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Changeset } from "@grants-stack-indexer/repository";
import { Address, ProcessorEvent, StrategyEvent, Token } from "@grants-stack-indexer/shared";

import { IStrategyHandler, StrategyTimings } from "../../internal.js";
import { IStrategyHandler, StrategyTimings } from "../../../internal.js";

/**
* @abstract
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ 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";
import { IEventHandler, ProcessorDependencies } from "../../../internal.js";

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ import {
Token,
} from "@grants-stack-indexer/shared";

import type { ProcessorDependencies, StrategyTimings } from "../../internal.js";
import DonationVotingMerkleDistributionDirectTransferStrategy from "../../abis/allo-v2/v1/DonationVotingMerkleDistributionDirectTransferStrategy.js";
import { calculateAmountInUsd, getDateFromTimestamp } from "../../helpers/index.js";
import { TokenPriceNotFoundError, UnsupportedEventException } from "../../internal.js";
import type { ProcessorDependencies, StrategyTimings } from "../../../internal.js";
import DonationVotingMerkleDistributionDirectTransferStrategy from "../../../abis/allo-v2/v1/DonationVotingMerkleDistributionDirectTransferStrategy.js";
import { calculateAmountInUsd, getDateFromTimestamp } from "../../../helpers/index.js";
import { TokenPriceNotFoundError, UnsupportedEventException } from "../../../internal.js";
import { BaseDistributedHandler, BaseStrategyHandler } from "../common/index.js";
import { DVMDAllocatedHandler, DVMDRegisteredHandler } from "./handlers/index.js";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@ import { Address, 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 { getTokenAmountInUsd, getUsdInTokenAmount } from "../../../helpers/index.js";
import { getTokenAmountInUsd, getUsdInTokenAmount } from "../../../../helpers/index.js";
import {
ApplicationNotFound,
IEventHandler,
MetadataParsingFailed,
ProcessorDependencies,
RoundNotFound,
UnknownToken,
} from "../../../internal.js";
import { ApplicationMetadata, ApplicationMetadataSchema } from "../../../schemas/index.js";
} from "../../../../internal.js";
import { ApplicationMetadata, ApplicationMetadataSchema } from "../../../../schemas/index.js";

type Dependencies = Pick<
ProcessorDependencies,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
ProcessorDependencies,
ProjectNotFound,
RoundNotFound,
} from "../../../internal.js";
} from "../../../../internal.js";
import { decodeDVMDApplicationData } from "../helpers/index.js";

type Dependencies = Pick<
Expand Down
Loading