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 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
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
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,9 @@ export class ProjectNotFound extends Error {
super(`Project not found for chainId: ${chainId} and anchorAddress: ${anchorAddress}`);
}
}

Copy link
Collaborator

Choose a reason for hiding this comment

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

I guess the above exception should be called ProjectByAnchorNotFound since there are multiple of these but just a suggestion

export class ProjectByRoleNotFound extends Error {
constructor(chainId: ChainId, role: string) {
super(`Project not found for chainId: ${chainId} and role: ${role}`);
}
}
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,15 +3,22 @@ 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,
"projectRepository" | "evmProvider" | "metadataProvider" | "logger"
>;
/**
* Handles the ProfileCreated event for the Registry contract from Allo protocol.
*
* This handler performs the following steps:
* - Fetches the metadata for the profile from the metadata provider
* - Parses the metadata to extract the project type
* - Returns the changeset to insert the project with the metadata
*
* If the metadata is not valid, it sets the metadata to null and the project type to canonical.
*/
export class ProfileCreatedHandler implements IEventHandler<"Registry", "ProfileCreated"> {
constructor(
Expand All @@ -35,14 +42,6 @@ export class ProfileCreatedHandler implements IEventHandler<"Registry", "Profile
projectType = this.getProjectTypeFromMetadata(parsedMetadata.data);
isProgram = parsedMetadata.data.type === "program";
metadataValue = parsedMetadata.data;
} else {
//TODO: Replace with logger
// this.logger.warn({
// msg: `ProfileCreated: Failed to parse metadata for profile ${profileId}`,
// event: this.event,
// metadataCid,
// metadata,
// });
}

const createdBy =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
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.
*
* This handler performs the following steps:
* - Fetches the metadata for the profile from the metadata provider
* - Parses the metadata to extract the project type
* - Returns the changeset to update the project with the metadata
*/
export class ProfileMetadataUpdatedHandler
implements IEventHandler<"Registry", "ProfileMetadataUpdated">
{
constructor(
readonly event: ProcessorEvent<"Registry", "ProfileMetadataUpdated">,
readonly chainId: ChainId,
private dependencies: Dependencies,
) {}
/* @inheritdoc */
async handle(): Promise<Changeset[]> {
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) {
return [
{
type: "UpdateProject",
args: {
chainId: this.chainId,
projectId: this.event.params.profileId,
project: {
metadataCid: metadataCid,
metadata: null,
projectType: "canonical",
},
},
},
];
}

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

return [
{
type: "UpdateProject",
args: {
chainId: this.chainId,
projectId: this.event.params.profileId,
project: {
metadataCid: metadataCid,
metadata: metadata,
projectType,
},
},
},
];
}
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,37 @@
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.
*
* This handler performs the following steps:
* - Returns the changeset to update the project with the new name
*/
export class ProfileNameUpdatedHandler implements IEventHandler<"Registry", "ProfileNameUpdated"> {
constructor(
readonly event: ProcessorEvent<"Registry", "ProfileNameUpdated">,
readonly chainId: ChainId,
private dependencies: Dependencies,
) {}
/* @inheritdoc */
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),
},
},
},
];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
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.
*
* This handler performs the following steps:
*
* - Returns the changeset to delete all project roles with the role "owner"
* for the profile and insert a new project role with the new owner address.
*/
export class ProfileOwnerUpdatedHandler
implements IEventHandler<"Registry", "ProfileOwnerUpdated">
{
constructor(
readonly event: ProcessorEvent<"Registry", "ProfileOwnerUpdated">,
readonly chainId: ChainId,
private dependencies: Dependencies,
) {}
/* @inheritdoc */
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,53 @@
import { getAddress } from "viem";

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

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

type Dependencies = Pick<ProcessorDependencies, "projectRepository" | "logger">;
/**
* Handles the RoleRevoked event for the Registry contract from Allo protocol.
*
* This handler performs the following steps:
*
* - Returns the changeset to delete all project roles with the role "member"
* for the profile and address.
*
* If the project with the role id doesn't exist, it throws an error.
*/
export class RoleRevokedHandler implements IEventHandler<"Registry", "RoleRevoked"> {
constructor(
readonly event: ProcessorEvent<"Registry", "RoleRevoked">,
readonly chainId: ChainId,
private 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 { 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) {
throw new ProjectByRoleNotFound(this.chainId, role);
}

return [
{
type: "DeleteAllProjectRolesByRoleAndAddress",
args: {
projectRole: {
chainId: this.chainId,
projectId: project.id,
address: account,
role: "member",
},
},
},
];
}
}
Loading
Loading