From 121683e77fa98ea4f268ab02fe9e7f97486d25ef Mon Sep 17 00:00:00 2001 From: Hussein Martinez Date: Mon, 15 Jul 2024 16:55:05 +0700 Subject: [PATCH 01/11] feat: extended alias imports in package.json --- package.json | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 7a101172..9c8b3879 100644 --- a/package.json +++ b/package.json @@ -18,8 +18,20 @@ "deploy:development": "docker buildx build . --platform=linux/amd64 -t registry.fly.io/indexer-development:latest && docker push registry.fly.io/indexer-development:latest && flyctl -c fly.development.toml --app indexer-development deploy -i registry.fly.io/indexer-development:latest" }, "imports": { - "#abis/*": { - "default": "./src/indexer/abis/*" + "#src/*": { + "default": "./src/*" + }, + "#database/*": { + "default": "./src/database/*" + }, + "#indexer/*": { + "default": "./src/indexer/*" + }, + "#prices/*": { + "default": "./src/prices/*" + }, + "#test/*": { + "default": "./src/test/*" } }, "author": "", From 85aa2724d9a5e3fdc81eaabafa87fa783f295a31 Mon Sep 17 00:00:00 2001 From: Hussein Martinez Date: Mon, 15 Jul 2024 17:21:30 +0700 Subject: [PATCH 02/11] chore: moved fullProjectId function to utils folder --- src/indexer/allo/v1/handleEvent.ts | 12 +----------- src/indexer/utils/fullProjectId.ts | 12 ++++++++++++ src/indexer/utils/index.ts | 1 + 3 files changed, 14 insertions(+), 11 deletions(-) create mode 100644 src/indexer/utils/fullProjectId.ts create mode 100644 src/indexer/utils/index.ts diff --git a/src/indexer/allo/v1/handleEvent.ts b/src/indexer/allo/v1/handleEvent.ts index 95ddf7db..08f54386 100644 --- a/src/indexer/allo/v1/handleEvent.ts +++ b/src/indexer/allo/v1/handleEvent.ts @@ -34,6 +34,7 @@ import { import { ProjectMetadataSchema } from "../../projectMetadata.js"; import { updateApplicationStatus } from "../application.js"; import { getDateFromTimestamp } from "../../../utils/index.js"; +import { fullProjectId } from "../../utils/index.js"; enum ApplicationStatus { PENDING = 0, @@ -43,17 +44,6 @@ enum ApplicationStatus { IN_REVIEW, } -function fullProjectId( - projectChainId: number, - projectId: number, - projectRegistryAddress: string -) { - return ethers.utils.solidityKeccak256( - ["uint256", "address", "uint256"], - [projectChainId, projectRegistryAddress, projectId] - ); -} - export async function handleEvent( args: EventHandlerArgs ): Promise { diff --git a/src/indexer/utils/fullProjectId.ts b/src/indexer/utils/fullProjectId.ts new file mode 100644 index 00000000..0e3f4a32 --- /dev/null +++ b/src/indexer/utils/fullProjectId.ts @@ -0,0 +1,12 @@ +import { ethers } from "ethers"; + +export function fullProjectId( + projectChainId: number, + projectId: number, + projectRegistryAddress: string +) { + return ethers.utils.solidityKeccak256( + ["uint256", "address", "uint256"], + [projectChainId, projectRegistryAddress, projectId] + ); +} diff --git a/src/indexer/utils/index.ts b/src/indexer/utils/index.ts new file mode 100644 index 00000000..55390d99 --- /dev/null +++ b/src/indexer/utils/index.ts @@ -0,0 +1 @@ +export { fullProjectId } from "./fullProjectId.js"; From 97d466311a61e0e25e3eecb3738f03034dd3b87b Mon Sep 17 00:00:00 2001 From: Hussein Martinez Date: Mon, 15 Jul 2024 17:22:59 +0700 Subject: [PATCH 03/11] chore: moved projectRegistry abis to new contracts folder structure --- src/indexer/abis/index.ts | 4 ++-- .../alloV1/projectRegistry/v1/abi}/ProjectRegistry.ts | 0 .../alloV1/projectRegistry/v2/abi}/ProjectRegistry.ts | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename src/indexer/{abis/allo-v1/v1 => contracts/alloV1/projectRegistry/v1/abi}/ProjectRegistry.ts (100%) rename src/indexer/{abis/allo-v1/v2 => contracts/alloV1/projectRegistry/v2/abi}/ProjectRegistry.ts (100%) diff --git a/src/indexer/abis/index.ts b/src/indexer/abis/index.ts index 1082fb03..abd9f94e 100644 --- a/src/indexer/abis/index.ts +++ b/src/indexer/abis/index.ts @@ -1,5 +1,5 @@ // V1.1 -import ProjectRegistryV1 from "./allo-v1/v1/ProjectRegistry.js"; +import ProjectRegistryV1 from "../contracts/alloV1/projectRegistry/v1/abi/ProjectRegistry.js"; import RoundFactoryV1 from "./allo-v1/v1/RoundFactory.js"; import RoundImplementationV1 from "./allo-v1/v1/RoundImplementation.js"; import QuadraticFundingVotingStrategyFactoryV1 from "./allo-v1/v1/QuadraticFundingVotingStrategyFactory.js"; @@ -8,7 +8,7 @@ import ProgramFactoryV1 from "./allo-v1/v1/ProgramFactory.js"; import ProgramImplementationV1 from "./allo-v1/v1/ProgramImplementation.js"; // V1.2 -import ProjectRegistryV2 from "./allo-v1/v2/ProjectRegistry.js"; +import ProjectRegistryV2 from "../contracts/alloV1/projectRegistry/v2/abi/ProjectRegistry.js"; import RoundFactoryV2 from "./allo-v1/v2/RoundFactory.js"; import RoundImplementationV2 from "./allo-v1/v2/RoundImplementation.js"; import QuadraticFundingVotingStrategyFactoryV2 from "./allo-v1/v2/QuadraticFundingVotingStrategyFactory.js"; diff --git a/src/indexer/abis/allo-v1/v1/ProjectRegistry.ts b/src/indexer/contracts/alloV1/projectRegistry/v1/abi/ProjectRegistry.ts similarity index 100% rename from src/indexer/abis/allo-v1/v1/ProjectRegistry.ts rename to src/indexer/contracts/alloV1/projectRegistry/v1/abi/ProjectRegistry.ts diff --git a/src/indexer/abis/allo-v1/v2/ProjectRegistry.ts b/src/indexer/contracts/alloV1/projectRegistry/v2/abi/ProjectRegistry.ts similarity index 100% rename from src/indexer/abis/allo-v1/v2/ProjectRegistry.ts rename to src/indexer/contracts/alloV1/projectRegistry/v2/abi/ProjectRegistry.ts From 351ed10069fdaf02ccc3bb061397dbe1a2b65bc6 Mon Sep 17 00:00:00 2001 From: Hussein Martinez Date: Mon, 15 Jul 2024 17:25:09 +0700 Subject: [PATCH 04/11] chore: implemented types for new contracts folder structure --- src/indexer/contracts/types.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 src/indexer/contracts/types.ts diff --git a/src/indexer/contracts/types.ts b/src/indexer/contracts/types.ts new file mode 100644 index 00000000..cfff5daa --- /dev/null +++ b/src/indexer/contracts/types.ts @@ -0,0 +1,24 @@ +import { EventHandlerArgs } from "chainsauce"; +import { Changeset } from "#database/index.js"; +import { Indexer } from "#indexer/indexer.js"; + +export interface EventHandler { + (args: EventHandlerArgs): Promise; +} + +export interface Contract { + abi?: any; + handlers?: { [eventName: string]: EventHandler }; +} + +export interface NameContracts { + [version: string]: Contract; +} + +export interface ProtocolContracts { + [name: string]: NameContracts; +} + +export interface Contracts { + [protocol: string]: ProtocolContracts; +} From 50e2ac8a8f95ae6c0c112954c18f1afff1130e17 Mon Sep 17 00:00:00 2001 From: Hussein Martinez Date: Mon, 15 Jul 2024 17:30:11 +0700 Subject: [PATCH 05/11] chore: created new contracts folder structure --- src/indexer/contracts/alloV1/index.ts | 6 ++++++ src/indexer/contracts/alloV1/projectRegistry/index.ts | 8 ++++++++ src/indexer/contracts/alloV1/projectRegistry/v1/index.ts | 7 +++++++ src/indexer/contracts/alloV1/projectRegistry/v2/index.ts | 7 +++++++ src/indexer/contracts/alloV2/index.ts | 1 + src/indexer/contracts/index.ts | 8 ++++++++ 6 files changed, 37 insertions(+) create mode 100644 src/indexer/contracts/alloV1/index.ts create mode 100644 src/indexer/contracts/alloV1/projectRegistry/index.ts create mode 100644 src/indexer/contracts/alloV1/projectRegistry/v1/index.ts create mode 100644 src/indexer/contracts/alloV1/projectRegistry/v2/index.ts create mode 100644 src/indexer/contracts/alloV2/index.ts create mode 100644 src/indexer/contracts/index.ts diff --git a/src/indexer/contracts/alloV1/index.ts b/src/indexer/contracts/alloV1/index.ts new file mode 100644 index 00000000..3d017147 --- /dev/null +++ b/src/indexer/contracts/alloV1/index.ts @@ -0,0 +1,6 @@ +import { ProtocolContracts } from "#indexer/contracts/types.js"; +import { projectRegistry } from "./projectRegistry/index.js"; + +export const AlloV1: ProtocolContracts = { + ProjectRegistry: projectRegistry, +}; diff --git a/src/indexer/contracts/alloV1/projectRegistry/index.ts b/src/indexer/contracts/alloV1/projectRegistry/index.ts new file mode 100644 index 00000000..bc9d0ece --- /dev/null +++ b/src/indexer/contracts/alloV1/projectRegistry/index.ts @@ -0,0 +1,8 @@ +import { NameContracts } from "#indexer/contracts/types.js"; +import { projectRegistryV1 } from "./v1/index.js"; +import { projectRegistryV2 } from "./v2/index.js"; + +export const projectRegistry: NameContracts = { + V1: projectRegistryV1, + V2: projectRegistryV2, +}; diff --git a/src/indexer/contracts/alloV1/projectRegistry/v1/index.ts b/src/indexer/contracts/alloV1/projectRegistry/v1/index.ts new file mode 100644 index 00000000..a4191558 --- /dev/null +++ b/src/indexer/contracts/alloV1/projectRegistry/v1/index.ts @@ -0,0 +1,7 @@ +import abi from "./abi/ProjectRegistry.js"; +import { Contract } from "#indexer/contracts/types.js"; + +export const projectRegistryV1: Contract = { + abi, + handlers: {}, +}; diff --git a/src/indexer/contracts/alloV1/projectRegistry/v2/index.ts b/src/indexer/contracts/alloV1/projectRegistry/v2/index.ts new file mode 100644 index 00000000..12c6e24e --- /dev/null +++ b/src/indexer/contracts/alloV1/projectRegistry/v2/index.ts @@ -0,0 +1,7 @@ +import abi from "./abi/ProjectRegistry.js"; +import { Contract } from "#indexer/contracts/types.js"; + +export const projectRegistryV2: Contract = { + abi, + handlers: {}, +}; diff --git a/src/indexer/contracts/alloV2/index.ts b/src/indexer/contracts/alloV2/index.ts new file mode 100644 index 00000000..c2c8fcac --- /dev/null +++ b/src/indexer/contracts/alloV2/index.ts @@ -0,0 +1 @@ +export const AlloV2 = {}; diff --git a/src/indexer/contracts/index.ts b/src/indexer/contracts/index.ts new file mode 100644 index 00000000..5eea4fba --- /dev/null +++ b/src/indexer/contracts/index.ts @@ -0,0 +1,8 @@ +import { AlloV1 } from "./alloV1/index.js"; +import { AlloV2 } from "./alloV2/index.js"; +import { Contracts } from "./types.js"; + +export const contracts: Contracts = { + AlloV1, + AlloV2, +}; From ae93fb0b0b50357cc7591aaa347003f80dac0403 Mon Sep 17 00:00:00 2001 From: Hussein Martinez Date: Mon, 15 Jul 2024 17:31:53 +0700 Subject: [PATCH 06/11] chore: implemented getEventHandler --- src/indexer/utils/getEventHandler.ts | 45 ++++++++++++++++++++++++++++ src/indexer/utils/index.ts | 1 + 2 files changed, 46 insertions(+) create mode 100644 src/indexer/utils/getEventHandler.ts diff --git a/src/indexer/utils/getEventHandler.ts b/src/indexer/utils/getEventHandler.ts new file mode 100644 index 00000000..8b95faca --- /dev/null +++ b/src/indexer/utils/getEventHandler.ts @@ -0,0 +1,45 @@ +import { contracts } from "#indexer/contracts/index.js"; +import { EventHandler } from "#indexer/contracts/types.js"; + +export const getEventHandler = ( + contractName: string, + eventName: string +): EventHandler | undefined => { + const contractNameParts = contractName.split("/"); + + if (contractNameParts.length !== 3) { + // Invalid format, return undefined to allow fallback to legacy event handler + // Expected format: protocol/name/version, example: AlloV1/ProjectRegistry/V2 + return undefined; + } + + const [protocol, name, version] = contractNameParts; + + const protocolContracts = contracts[protocol as keyof typeof contracts]; + if (!protocolContracts) { + // Protocol not found, return undefined to allow fallback to legacy event handler + return undefined; + } + + const nameContracts = + protocolContracts[name as keyof typeof protocolContracts]; + if (!nameContracts) { + // Name not found, return undefined to allow fallback to legacy event handler + return undefined; + } + + const contract = nameContracts[version as keyof typeof nameContracts]; + if (!contract) { + // Version not found, return undefined to allow fallback to legacy event handler + return undefined; + } + + // Check for event handler within the contract version + const eventHandler = contract.handlers?.[eventName]; + if (!eventHandler) { + // Event handler not found, return undefined to allow fallback to legacy event handler + return undefined; + } + + return eventHandler; +}; diff --git a/src/indexer/utils/index.ts b/src/indexer/utils/index.ts index 55390d99..aea38178 100644 --- a/src/indexer/utils/index.ts +++ b/src/indexer/utils/index.ts @@ -1 +1,2 @@ export { fullProjectId } from "./fullProjectId.js"; +export { getEventHandler } from "./getEventHandler.js"; From c98508be5fee03618d6cf7d719d29e0bc6bc882c Mon Sep 17 00:00:00 2001 From: Hussein Martinez Date: Mon, 15 Jul 2024 17:33:02 +0700 Subject: [PATCH 07/11] chore: implemented projectCreated event handler --- .../alloV1/projectRegistry/v1/index.ts | 5 +- .../projectRegistry/v2/eventHandlers/index.ts | 5 ++ .../v2/eventHandlers/projectCreated.ts | 63 +++++++++++++++++++ .../alloV1/projectRegistry/v2/index.ts | 3 +- 4 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 src/indexer/contracts/alloV1/projectRegistry/v2/eventHandlers/index.ts create mode 100644 src/indexer/contracts/alloV1/projectRegistry/v2/eventHandlers/projectCreated.ts diff --git a/src/indexer/contracts/alloV1/projectRegistry/v1/index.ts b/src/indexer/contracts/alloV1/projectRegistry/v1/index.ts index a4191558..510ae9b9 100644 --- a/src/indexer/contracts/alloV1/projectRegistry/v1/index.ts +++ b/src/indexer/contracts/alloV1/projectRegistry/v1/index.ts @@ -1,7 +1,10 @@ import abi from "./abi/ProjectRegistry.js"; import { Contract } from "#indexer/contracts/types.js"; +import { projectCreatedHandler } from "../v2/eventHandlers/projectCreated.js"; export const projectRegistryV1: Contract = { abi, - handlers: {}, + handlers: { + ProjectCreated: projectCreatedHandler, + }, }; diff --git a/src/indexer/contracts/alloV1/projectRegistry/v2/eventHandlers/index.ts b/src/indexer/contracts/alloV1/projectRegistry/v2/eventHandlers/index.ts new file mode 100644 index 00000000..6aebdd73 --- /dev/null +++ b/src/indexer/contracts/alloV1/projectRegistry/v2/eventHandlers/index.ts @@ -0,0 +1,5 @@ +import { projectCreatedHandler } from "./projectCreated.js"; + +export const projectRegistryV2Handlers = { + ProjectCreated: projectCreatedHandler, +}; diff --git a/src/indexer/contracts/alloV1/projectRegistry/v2/eventHandlers/projectCreated.ts b/src/indexer/contracts/alloV1/projectRegistry/v2/eventHandlers/projectCreated.ts new file mode 100644 index 00000000..1cbd92c2 --- /dev/null +++ b/src/indexer/contracts/alloV1/projectRegistry/v2/eventHandlers/projectCreated.ts @@ -0,0 +1,63 @@ +import { EventHandlerArgs } from "chainsauce"; +import type { Indexer } from "#indexer/indexer.js"; +import { fullProjectId } from "#indexer/utils/fullProjectId.js"; +import { EventHandler } from "#indexer/contracts/types.js"; +import { parseAddress } from "#src/address.js"; + +export const projectCreatedHandler: EventHandler = async ( + args: EventHandlerArgs +) => { + const { + chainId, + event, + context: { rpcClient }, + } = args; + + // We check here the event name only to avoid type errors + // of event.params.projectID + //! TODO Fix it to remove the event.name check + if (event.name === "ProjectCreated") { + const projectId = fullProjectId( + chainId, + Number(event.params.projectID), + event.address + ); + + const tx = await rpcClient.getTransaction({ + hash: event.transactionHash, + }); + + const createdBy = tx.from; + + return [ + { + type: "InsertProject", + project: { + tags: ["allo-v1"], + chainId, + registryAddress: parseAddress(event.address), + id: projectId, + name: "", + projectNumber: Number(event.params.projectID), + metadataCid: null, + metadata: null, + createdByAddress: parseAddress(createdBy), + createdAtBlock: event.blockNumber, + updatedAtBlock: event.blockNumber, + projectType: "canonical", + }, + }, + { + type: "InsertProjectRole", + projectRole: { + chainId, + projectId, + address: parseAddress(event.params.owner), + role: "owner", + createdAtBlock: event.blockNumber, + }, + }, + ]; + } + return []; +}; diff --git a/src/indexer/contracts/alloV1/projectRegistry/v2/index.ts b/src/indexer/contracts/alloV1/projectRegistry/v2/index.ts index 12c6e24e..7b435b7e 100644 --- a/src/indexer/contracts/alloV1/projectRegistry/v2/index.ts +++ b/src/indexer/contracts/alloV1/projectRegistry/v2/index.ts @@ -1,7 +1,8 @@ import abi from "./abi/ProjectRegistry.js"; import { Contract } from "#indexer/contracts/types.js"; +import { projectRegistryV2Handlers } from "./eventHandlers/index.js"; export const projectRegistryV2: Contract = { abi, - handlers: {}, + handlers: projectRegistryV2Handlers, }; From eb615061406cf5423642a9b570d2c96e07b0b8df Mon Sep 17 00:00:00 2001 From: Hussein Martinez Date: Mon, 15 Jul 2024 17:33:56 +0700 Subject: [PATCH 08/11] test: implemented tests for the new event handler approach --- .../contracts/__tests__/eventHandlers.test.ts | 282 ++++++++++++++++++ 1 file changed, 282 insertions(+) create mode 100644 src/indexer/contracts/__tests__/eventHandlers.test.ts diff --git a/src/indexer/contracts/__tests__/eventHandlers.test.ts b/src/indexer/contracts/__tests__/eventHandlers.test.ts new file mode 100644 index 00000000..01049b40 --- /dev/null +++ b/src/indexer/contracts/__tests__/eventHandlers.test.ts @@ -0,0 +1,282 @@ +import { vi, describe, test, expect, beforeEach } from "vitest"; +import { Address as ChecksumAddress, Hex, PublicClient } from "viem"; +import { EventHandlerArgs } from "chainsauce"; +import { Logger } from "pino"; +import { Database } from "#database/index.js"; +import { PriceProvider } from "#prices/provider.js"; +import { Indexer } from "#indexer/indexer.js"; +import { getEventHandler } from "#indexer/utils/getEventHandler.js"; +import { TestPriceProvider } from "#test/utils.js"; + +const addressOne = + "0x0000000000000000000000000000000000000001" as ChecksumAddress; +const addressTwo = + "0x0000000000000000000000000000000000000002" as ChecksumAddress; + +const MOCK_PRICE_PROVIDER = new TestPriceProvider() as unknown as PriceProvider; + +// eslint-disable-next-line @typescript-eslint/require-await +async function MOCK_IPFS_GET(cid: string) { + switch (cid) { + case "project-cid": + return { + title: "my project", + description: "my project description", + } as TReturn; + + case "program-cid": + return { + name: "my program", + } as TReturn; + + case "round-cid": + return { + name: "my round", + } as TReturn; + + default: + throw new Error(`unexpected cid: ${cid}`); + } +} + +function MOCK_RPC_CLIENT() { + return { + getTransaction: vi + .fn() + .mockResolvedValue({ blockNumber: 1n, from: addressTwo }), + } as unknown as PublicClient; +} + +function MOCK_BLOCK_TIMESTAMP_IN_MS() { + return vi.fn().mockResolvedValue(0); +} + +const MOCK_LOGGER = { + debug: () => {}, + info: () => {}, + warn: () => {}, + error: () => {}, +} as unknown as Logger; + +const MOCK_DB = { + query: vi.fn(), +} as unknown as Database; + +const MOCK_READ_CONTRACT: EventHandlerArgs["readContract"] = vi.fn(); + +const MOCK_SUBSCRIBE_TO_CONTRACT: EventHandlerArgs["subscribeToContract"] = + vi.fn(); + +const MOCK_UNSUBSCRIBE_FROM_CONTRACT: EventHandlerArgs["unsubscribeFromContract"] = + vi.fn(); + +const DEFAULT_ARGS = { + chainId: 1, + event: { + name: null, + blockNumber: 1n, + logIndex: 0, + transactionHash: "0x" as Hex, + address: addressOne, + topic: "0x" as Hex, + params: {}, + }, + subscribeToContract: MOCK_SUBSCRIBE_TO_CONTRACT, + unsubscribeFromContract: MOCK_UNSUBSCRIBE_FROM_CONTRACT, + readContract: MOCK_READ_CONTRACT, + getBlock: vi.fn().mockResolvedValue({ timestamp: 0 }), + context: { + priceProvider: MOCK_PRICE_PROVIDER, + ipfsGet: MOCK_IPFS_GET, + chainId: 1, + logger: MOCK_LOGGER, + db: MOCK_DB, + rpcClient: MOCK_RPC_CLIENT(), + blockTimestampInMs: MOCK_BLOCK_TIMESTAMP_IN_MS(), + }, +}; + +describe("handleEvent", () => { + beforeEach(() => { + vi.resetAllMocks(); + }); + + describe("AlloV1/ProjectRegistry/V1", () => { + test("ProjectCreated event should insert project", async () => { + const contractName = "AlloV1/ProjectRegistry/V1"; + const eventName = "ProjectCreated"; + + const args: EventHandlerArgs = { + ...DEFAULT_ARGS, + event: { + ...DEFAULT_ARGS.event, + contractName, + name: eventName, + params: { + projectID: 1n, + owner: addressTwo, + }, + }, + context: { + ...DEFAULT_ARGS.context, + rpcClient: MOCK_RPC_CLIENT(), + }, + }; + + const handler = getEventHandler(contractName, eventName); + + expect(handler).toBeDefined(); + if (!handler) return; + + const changesets = await handler(args); + + expect(changesets).toHaveLength(2); + + expect(changesets[0]).toEqual({ + type: "InsertProject", + project: { + chainId: 1, + createdByAddress: addressTwo, + createdAtBlock: 1n, + updatedAtBlock: 1n, + id: "0xe31382b762a33e568e1e9ef38d64f4a2b4dbb51ec0f79ec41779fc5be79ead32", + name: "", + metadata: null, + metadataCid: null, + projectNumber: 1, + registryAddress: addressOne, + tags: ["allo-v1"], + projectType: "canonical", + }, + }); + + expect(changesets[1]).toEqual({ + type: "InsertProjectRole", + projectRole: { + chainId: 1, + projectId: + "0xe31382b762a33e568e1e9ef38d64f4a2b4dbb51ec0f79ec41779fc5be79ead32", + address: addressTwo, + role: "owner", + createdAtBlock: 1n, + }, + }); + }); + }); + + describe("AlloV1/ProjectCreated/V2", () => { + test("ProjectCreated event should insert project", async () => { + const contractName = "AlloV1/ProjectRegistry/V2"; + const eventName = "ProjectCreated"; + + const args: EventHandlerArgs = { + ...DEFAULT_ARGS, + event: { + ...DEFAULT_ARGS.event, + contractName, + name: eventName, + params: { + projectID: 1n, + owner: addressTwo, + }, + }, + context: { + ...DEFAULT_ARGS.context, + rpcClient: MOCK_RPC_CLIENT(), + }, + }; + + const handler = getEventHandler(contractName, eventName); + + expect(handler).toBeDefined(); + if (!handler) return; + + const changesets = await handler(args); + + expect(changesets).toHaveLength(2); + + expect(changesets[0]).toEqual({ + type: "InsertProject", + project: { + chainId: 1, + createdByAddress: addressTwo, + createdAtBlock: 1n, + updatedAtBlock: 1n, + id: "0xe31382b762a33e568e1e9ef38d64f4a2b4dbb51ec0f79ec41779fc5be79ead32", + name: "", + metadata: null, + metadataCid: null, + projectNumber: 1, + registryAddress: addressOne, + tags: ["allo-v1"], + projectType: "canonical", + }, + }); + + expect(changesets[1]).toEqual({ + type: "InsertProjectRole", + projectRole: { + chainId: 1, + projectId: + "0xe31382b762a33e568e1e9ef38d64f4a2b4dbb51ec0f79ec41779fc5be79ead32", + address: addressTwo, + role: "owner", + createdAtBlock: 1n, + }, + }); + }); + + test("MetadataUpdated event, NOT MIGRATED -> getEventHandler should return undefined", async () => { + const contractName = "AlloV1/ProjectRegistry/V2"; + const eventName = "MetadataUpdated"; + + const args = { + ...DEFAULT_ARGS, + event: { + ...DEFAULT_ARGS.event, + contractName, + name: eventName, + params: { + projectID: 1n, + metaPtr: { + pointer: "project-cid", + protocol: 0n, + }, + }, + }, + }; + + const handler = getEventHandler(contractName, eventName); + + expect(handler).toBeUndefined(); + }); + }); + + describe("Unknown contract name", () => { + test("getEventHandler should return undefined", async () => { + const contractName = "Unknown"; + const eventName = "ProjectCreated"; + + const args = { + ...DEFAULT_ARGS, + event: { + ...DEFAULT_ARGS.event, + contractName, + name: eventName, + params: { + projectID: 1n, + owner: addressTwo, + }, + }, + context: { + ...DEFAULT_ARGS.context, + rpcClient: MOCK_RPC_CLIENT(), + }, + }; + + const handler = getEventHandler(contractName, eventName); + + expect(handler).toBeUndefined(); + }); + }); +}); From ace55edef3ad5320b2991f5875a38a31ced2ac46 Mon Sep 17 00:00:00 2001 From: Hussein Martinez Date: Mon, 15 Jul 2024 17:35:23 +0700 Subject: [PATCH 09/11] chore: modified indexer to use new event handler approach and legacy one in parallel during migration --- src/index.ts | 43 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/src/index.ts b/src/index.ts index 0be278aa..9f2fc6ad 100644 --- a/src/index.ts +++ b/src/index.ts @@ -43,6 +43,7 @@ import { decodeJsonWithBigInts } from "./utils/index.js"; import { Block } from "chainsauce/dist/cache.js"; import { createPublicClient, http } from "viem"; import { IndexerEvents } from "chainsauce/dist/indexer.js"; +import { getEventHandler } from "./indexer/utils/getEventHandler.js"; const RESOURCE_MONITOR_INTERVAL_MS = 1 * 60 * 1000; // every minute @@ -539,9 +540,45 @@ async function catchupAndWatchChain( indexer.on("event", async (args) => { try { - // console.time(args.event.name); - // do not await donation inserts as they are write only - if (args.event.name === "Voted") { + const { + event: { contractName, name: eventName }, + } = args; + + // for now we check for handlers based on: + // - protocol name: AlloV1 / AlloV2 + // - contract name example: ProjectRegistry + // - contract version: V1 / V2 + // and then the event name. + // but we can combine in the future this way + // with another object that has event handlers by name + // like generic event handlers that are not depending on Protocol/Name/Version contracts + const handler = getEventHandler(contractName, eventName); + if (handler) { + const changesets = await handler(args); + if (["Voted", "Allocated"].includes(eventName)) { + try { + db.applyChanges(changesets); + } catch (err: unknown) { + if (args.event.name === "Voted") { + indexerLogger.warn({ + msg: "error while processing vote", + err, + }); + } else if (args.event.name === "Allocated") { + indexerLogger.warn({ + msg: "error while processing allocation", + err, + }); + } + } + } else { + for (const changeset of changesets) { + await db.applyChange(changeset); + } + } + } else if (args.event.name === "Voted") { + // console.time(args.event.name); + // do not await donation inserts as they are write only handleAlloV1Event(args) .then((changesets) => db.applyChanges(changesets)) .catch((err: unknown) => { From 6776113c82306f874475b811491eab18832b157e Mon Sep 17 00:00:00 2001 From: Hussein Martinez Date: Mon, 15 Jul 2024 17:36:52 +0700 Subject: [PATCH 10/11] chore: removed ProjectCreated event handling from old approach --- src/indexer/allo/v1/handleEvent.test.ts | 53 ------------------------- src/indexer/allo/v1/handleEvent.ts | 43 -------------------- 2 files changed, 96 deletions(-) diff --git a/src/indexer/allo/v1/handleEvent.test.ts b/src/indexer/allo/v1/handleEvent.test.ts index a36bc328..626c1364 100644 --- a/src/indexer/allo/v1/handleEvent.test.ts +++ b/src/indexer/allo/v1/handleEvent.test.ts @@ -112,59 +112,6 @@ describe("handleEvent", () => { vi.resetAllMocks(); }); - describe("ProjectCreated", () => { - test("should insert project", async () => { - const changesets = await handleEvent({ - ...DEFAULT_ARGS, - event: { - ...DEFAULT_ARGS.event, - contractName: "AlloV1/ProjectRegistry/V2", - name: "ProjectCreated", - params: { - projectID: 1n, - owner: addressTwo, - }, - }, - context: { - ...DEFAULT_ARGS.context, - rpcClient: MOCK_RPC_CLIENT(), - }, - }); - - expect(changesets).toHaveLength(2); - - expect(changesets[0]).toEqual({ - type: "InsertProject", - project: { - chainId: 1, - createdByAddress: addressTwo, - createdAtBlock: 1n, - updatedAtBlock: 1n, - id: "0xe31382b762a33e568e1e9ef38d64f4a2b4dbb51ec0f79ec41779fc5be79ead32", - name: "", - metadata: null, - metadataCid: null, - projectNumber: 1, - registryAddress: addressOne, - tags: ["allo-v1"], - projectType: "canonical", - }, - }); - - expect(changesets[1]).toEqual({ - type: "InsertProjectRole", - projectRole: { - chainId: 1, - projectId: - "0xe31382b762a33e568e1e9ef38d64f4a2b4dbb51ec0f79ec41779fc5be79ead32", - address: addressTwo, - role: "owner", - createdAtBlock: 1n, - }, - }); - }); - }); - describe("MetadataUpdated", () => { test("should fetch and update metadata", async () => { const changesets = await handleEvent({ diff --git a/src/indexer/allo/v1/handleEvent.ts b/src/indexer/allo/v1/handleEvent.ts index 08f54386..f684c223 100644 --- a/src/indexer/allo/v1/handleEvent.ts +++ b/src/indexer/allo/v1/handleEvent.ts @@ -65,49 +65,6 @@ export async function handleEvent( switch (event.name) { // -- PROJECTS - case "ProjectCreated": { - const projectId = fullProjectId( - chainId, - Number(event.params.projectID), - event.address - ); - - const tx = await rpcClient.getTransaction({ - hash: event.transactionHash, - }); - - const createdBy = tx.from; - - return [ - { - type: "InsertProject", - project: { - tags: ["allo-v1"], - chainId, - registryAddress: parseAddress(event.address), - id: projectId, - name: "", - projectNumber: Number(event.params.projectID), - metadataCid: null, - metadata: null, - createdByAddress: parseAddress(createdBy), - createdAtBlock: event.blockNumber, - updatedAtBlock: event.blockNumber, - projectType: "canonical", - }, - }, - { - type: "InsertProjectRole", - projectRole: { - chainId, - projectId, - address: parseAddress(event.params.owner), - role: "owner", - createdAtBlock: event.blockNumber, - }, - }, - ]; - } case "MetadataUpdated": { const projectId = fullProjectId( From f24fbbfe6bf44c8247d064db592c6c903df8b6f9 Mon Sep 17 00:00:00 2001 From: Hussein Martinez Date: Mon, 15 Jul 2024 17:52:40 +0700 Subject: [PATCH 11/11] fix: linting errors --- src/index.ts | 2 +- .../contracts/__tests__/eventHandlers.test.ts | 37 +------------------ 2 files changed, 3 insertions(+), 36 deletions(-) diff --git a/src/index.ts b/src/index.ts index 9f2fc6ad..79d5a32a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -557,7 +557,7 @@ async function catchupAndWatchChain( const changesets = await handler(args); if (["Voted", "Allocated"].includes(eventName)) { try { - db.applyChanges(changesets); + await db.applyChanges(changesets); } catch (err: unknown) { if (args.event.name === "Voted") { indexerLogger.warn({ diff --git a/src/indexer/contracts/__tests__/eventHandlers.test.ts b/src/indexer/contracts/__tests__/eventHandlers.test.ts index 01049b40..30a2dceb 100644 --- a/src/indexer/contracts/__tests__/eventHandlers.test.ts +++ b/src/indexer/contracts/__tests__/eventHandlers.test.ts @@ -226,26 +226,10 @@ describe("handleEvent", () => { }); }); - test("MetadataUpdated event, NOT MIGRATED -> getEventHandler should return undefined", async () => { + test("MetadataUpdated event, NOT MIGRATED -> getEventHandler should return undefined", () => { const contractName = "AlloV1/ProjectRegistry/V2"; const eventName = "MetadataUpdated"; - const args = { - ...DEFAULT_ARGS, - event: { - ...DEFAULT_ARGS.event, - contractName, - name: eventName, - params: { - projectID: 1n, - metaPtr: { - pointer: "project-cid", - protocol: 0n, - }, - }, - }, - }; - const handler = getEventHandler(contractName, eventName); expect(handler).toBeUndefined(); @@ -253,27 +237,10 @@ describe("handleEvent", () => { }); describe("Unknown contract name", () => { - test("getEventHandler should return undefined", async () => { + test("getEventHandler should return undefined", () => { const contractName = "Unknown"; const eventName = "ProjectCreated"; - const args = { - ...DEFAULT_ARGS, - event: { - ...DEFAULT_ARGS.event, - contractName, - name: eventName, - params: { - projectID: 1n, - owner: addressTwo, - }, - }, - context: { - ...DEFAULT_ARGS.context, - rpcClient: MOCK_RPC_CLIENT(), - }, - }; - const handler = getEventHandler(contractName, eventName); expect(handler).toBeUndefined();