Skip to content

Commit

Permalink
feat: registry handlers (#31)
Browse files Browse the repository at this point in the history
# 🤖 Linear

Closes GIT-138 GIT-145 GIT-146 GIT-147 GIT-148

## Description
All Registry contract event handlers.

## Checklist before requesting a review

-   [x] I have conducted a self-review of my code.
-   [x] I have conducted a QA.
-   [x] If it is a core feature, I have included comprehensive tests.
  • Loading branch information
0xkenj1 authored Nov 13, 2024
1 parent 658bd7b commit 81254f2
Show file tree
Hide file tree
Showing 51 changed files with 799 additions and 110 deletions.
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}`);
}
}

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
File renamed without changes.
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";
}
}
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">;
/**
* 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
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[]> {
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",
},
},
},
];
}
}
File renamed without changes.
Loading

0 comments on commit 81254f2

Please sign in to comment.