diff --git a/.eslintrc.cjs b/.eslintrc.cjs index c907a20..179fcf9 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -26,7 +26,11 @@ module.exports = { "@typescript-eslint/no-unsafe-return": "error", "@typescript-eslint/no-unused-vars": [ "error", - { argsIgnorePattern: "_+", ignoreRestSiblings: true }, + { + argsIgnorePattern: "_+", + ignoreRestSiblings: true, + destructuredArrayIgnorePattern: "^_", + }, ], "@typescript-eslint/prefer-as-const": "warn", }, diff --git a/packages/data-flow/test/unit/eventsRegistry.spec.ts b/packages/data-flow/test/unit/eventsRegistry.spec.ts index 453245e..1ddf8b7 100644 --- a/packages/data-flow/test/unit/eventsRegistry.spec.ts +++ b/packages/data-flow/test/unit/eventsRegistry.spec.ts @@ -30,12 +30,12 @@ describe("InMemoryEventsRegistry", () => { srcAddress: "0x123", strategyId: "0xstrategy", params: { - poolId: 1n, + poolId: "1", profileId: "0x456", strategy: "0x789", token: "0xtoken", - amount: 0n, - metadata: [1n, "0xmetadata"], + amount: "0", + metadata: ["1", "0xmetadata"], }, transactionFields: { hash: "0xabc", @@ -62,12 +62,12 @@ describe("InMemoryEventsRegistry", () => { srcAddress: "0x123", strategyId: "0xstrategy", params: { - poolId: 1n, + poolId: "1", profileId: "0x456", strategy: "0x789", token: "0xtoken", - amount: 0n, - metadata: [1n, "0xmetadata"], + amount: "0", + metadata: ["1", "0xmetadata"], }, transactionFields: { hash: "0xabc", diff --git a/packages/data-flow/test/unit/orchestrator.spec.ts b/packages/data-flow/test/unit/orchestrator.spec.ts index 4392da5..6943c78 100644 --- a/packages/data-flow/test/unit/orchestrator.spec.ts +++ b/packages/data-flow/test/unit/orchestrator.spec.ts @@ -206,11 +206,11 @@ describe("Orchestrator", { sequential: true }, () => { "0x6f9291df02b2664139cec5703c124e4ebce32879c74b6297faa1468aa5ff9ebf" as Hex; const mockEvent = createMockEvent("Allo", "PoolCreated", 1, { strategy: strategyAddress, - poolId: 1n, + poolId: "1", profileId: "0x123", token: "0x123", - amount: 100n, - metadata: [1n, "1"], + amount: "100", + metadata: ["1", "1"], }); const eventsProcessorSpy = vi.spyOn(orchestrator["eventsProcessor"], "processEvent"); @@ -396,11 +396,11 @@ describe("Orchestrator", { sequential: true }, () => { "0x6f9291df02b2664139cec5703c124e4ebce32879c74b6297faa1468aa5ff9ebf" as Hex; const poolCreatedEvent = createMockEvent("Allo", "PoolCreated", 1, { strategy: strategyAddress, - poolId: 1n, + poolId: "1", profileId: "0x123", token: "0x123", - amount: 100n, - metadata: [1n, "1"], + amount: "100", + metadata: ["1", "1"], }); const registeredEvent = createMockEvent( "Strategy", diff --git a/packages/processors/src/processors/allo/allo.processor.ts b/packages/processors/src/processors/allo/allo.processor.ts index fa783c8..c0638c9 100644 --- a/packages/processors/src/processors/allo/allo.processor.ts +++ b/packages/processors/src/processors/allo/allo.processor.ts @@ -3,7 +3,13 @@ import { AlloEvent, ChainId, ProcessorEvent } from "@grants-stack-indexer/shared import type { IProcessor, ProcessorDependencies } from "../../internal.js"; import { UnsupportedEventException } from "../../internal.js"; -import { PoolCreatedHandler } from "./handlers/index.js"; +import { + PoolCreatedHandler, + PoolFundedHandler, + PoolMetadataUpdatedHandler, + RoleGrantedHandler, + RoleRevokedHandler, +} from "./handlers/index.js"; /** * AlloProcessor handles the processing of Allo V2 events from the Allo contract by delegating them to the appropriate handler @@ -17,7 +23,35 @@ export class AlloProcessor implements IProcessor<"Allo", AlloEvent> { async process(event: ProcessorEvent<"Allo", AlloEvent>): Promise { switch (event.eventName) { case "PoolCreated": - return new PoolCreatedHandler(event, this.chainId, this.dependencies).handle(); + return new PoolCreatedHandler( + event as ProcessorEvent<"Allo", "PoolCreated">, + this.chainId, + this.dependencies, + ).handle(); + case "PoolFunded": + return new PoolFundedHandler( + event as ProcessorEvent<"Allo", "PoolFunded">, + this.chainId, + this.dependencies, + ).handle(); + case "RoleGranted": + return new RoleGrantedHandler( + event as ProcessorEvent<"Allo", "RoleGranted">, + this.chainId, + this.dependencies, + ).handle(); + case "PoolMetadataUpdated": + return new PoolMetadataUpdatedHandler( + event as ProcessorEvent<"Allo", "PoolMetadataUpdated">, + this.chainId, + this.dependencies, + ).handle(); + case "RoleRevoked": + return new RoleRevokedHandler( + event as ProcessorEvent<"Allo", "RoleRevoked">, + this.chainId, + this.dependencies, + ).handle(); default: throw new UnsupportedEventException("Allo", event.eventName); } diff --git a/packages/processors/src/processors/allo/handlers/index.ts b/packages/processors/src/processors/allo/handlers/index.ts index 7f0b311..c8e3084 100644 --- a/packages/processors/src/processors/allo/handlers/index.ts +++ b/packages/processors/src/processors/allo/handlers/index.ts @@ -1 +1,5 @@ export * from "./poolCreated.handler.js"; +export * from "./poolFunded.handler.js"; +export * from "./poolMetadataUpdated.handler.js"; +export * from "./roleGranted.handler.js"; +export * from "./roleRevoked.handler.js"; diff --git a/packages/processors/src/processors/allo/handlers/poolCreated.handler.ts b/packages/processors/src/processors/allo/handlers/poolCreated.handler.ts index 9979c72..8db23a6 100644 --- a/packages/processors/src/processors/allo/handlers/poolCreated.handler.ts +++ b/packages/processors/src/processors/allo/handlers/poolCreated.handler.ts @@ -39,8 +39,9 @@ export class PoolCreatedHandler implements IEventHandler<"Allo", "PoolCreated"> poolId, token: tokenAddress, strategy: strategyAddress, - amount: fundedAmount, + amount, } = this.event.params; + const fundedAmount = BigInt(amount); const { hash: txHash, from: txFrom } = this.event.transactionFields; const strategyId = this.event.strategyId; @@ -99,7 +100,7 @@ export class PoolCreatedHandler implements IEventHandler<"Allo", "PoolCreated"> // transaction sender const createdBy = txFrom ?? (await evmProvider.getTransaction(txHash)).from; - const roundRoles = getRoundRoles(poolId); + const roundRoles = getRoundRoles(BigInt(poolId)); const newRound: NewRound = { chainId: this.chainId, diff --git a/packages/processors/src/processors/allo/handlers/poolFunded.handler.ts b/packages/processors/src/processors/allo/handlers/poolFunded.handler.ts new file mode 100644 index 0000000..116ea4d --- /dev/null +++ b/packages/processors/src/processors/allo/handlers/poolFunded.handler.ts @@ -0,0 +1,55 @@ +import type { Changeset } from "@grants-stack-indexer/repository"; +import type { ChainId, ProcessorEvent } from "@grants-stack-indexer/shared"; +import { getToken, UnknownToken } from "@grants-stack-indexer/shared"; + +import type { IEventHandler, ProcessorDependencies } from "../../../internal.js"; +import { getTokenAmountInUsd } from "../../../helpers/index.js"; + +type Dependencies = Pick; + +/** + * Handles the PoolFunded event for the Allo protocol. + * + * This handler performs the following core actions when a pool is funded: + * - Fetches the round metadata from the metadata provider. + * - Returns the changeset to update the round with the new metadata. + */ +export class PoolFundedHandler implements IEventHandler<"Allo", "PoolFunded"> { + constructor( + readonly event: ProcessorEvent<"Allo", "PoolFunded">, + private readonly chainId: ChainId, + private readonly dependencies: Dependencies, + ) {} + /* @inheritdoc */ + async handle(): Promise { + const poolId = this.event.params.poolId.toString(); + const fundedAmount = BigInt(this.event.params.amount); + const { roundRepository, pricingProvider } = this.dependencies; + + const round = await roundRepository.getRoundByIdOrThrow(this.chainId, poolId); + + const token = getToken(this.chainId, round.matchTokenAddress); + + //TODO: Review this on Advace Recovery Milestone + if (!token) throw new UnknownToken(round.matchTokenAddress, this.chainId); + + const { amountInUsd } = await getTokenAmountInUsd( + pricingProvider, + token, + fundedAmount, + this.event.blockTimestamp, + ); + + return [ + { + type: "IncrementRoundFundedAmount", + args: { + chainId: this.chainId, + roundId: round.id, + fundedAmount, + fundedAmountInUsd: amountInUsd, + }, + }, + ]; + } +} diff --git a/packages/processors/src/processors/allo/handlers/poolMetadataUpdated.handler.ts b/packages/processors/src/processors/allo/handlers/poolMetadataUpdated.handler.ts new file mode 100644 index 0000000..fd8bd11 --- /dev/null +++ b/packages/processors/src/processors/allo/handlers/poolMetadataUpdated.handler.ts @@ -0,0 +1,83 @@ +import { parseUnits } from "viem"; + +import type { Changeset } from "@grants-stack-indexer/repository"; +import type { ChainId, ProcessorEvent } from "@grants-stack-indexer/shared"; +import { getToken } from "@grants-stack-indexer/shared"; + +import type { IEventHandler, ProcessorDependencies } from "../../../internal.js"; +import { getTokenAmountInUsd } from "../../../helpers/index.js"; +import { RoundMetadataSchema } from "../../../schemas/index.js"; + +type Dependencies = Pick< + ProcessorDependencies, + "metadataProvider" | "roundRepository" | "pricingProvider" +>; + +/** + * Handles the PoolMetadataUpdated event for the Allo protocol. + * + * This handler performs the following core actions when a pool metadata is updated: + * - Fetches the round metadata from the metadata provider. + * - Returns the changeset to update the round with the new metadata. + */ +export class PoolMetadataUpdatedHandler implements IEventHandler<"Allo", "PoolMetadataUpdated"> { + constructor( + readonly event: ProcessorEvent<"Allo", "PoolMetadataUpdated">, + private readonly chainId: ChainId, + private readonly dependencies: Dependencies, + ) {} + /* @inheritdoc */ + async handle(): Promise { + const [_protocol, metadataPointer] = this.event.params.metadata; + const { metadataProvider, pricingProvider, roundRepository } = this.dependencies; + + const metadata = await metadataProvider.getMetadata<{ + round?: unknown; + application?: unknown; + }>(metadataPointer); + + const round = await roundRepository.getRoundByIdOrThrow( + this.chainId, + this.event.params.poolId.toString(), + ); + + let matchAmount = round.matchAmount; + let matchAmountInUsd = round.matchAmountInUsd; + + const parsedRoundMetadata = RoundMetadataSchema.safeParse(metadata?.round); + const token = getToken(this.chainId, round.matchTokenAddress); + + if (parsedRoundMetadata.success && token) { + matchAmount = parseUnits( + parsedRoundMetadata.data.quadraticFundingConfig.matchingFundsAvailable.toString(), + token.decimals, + ); + matchAmountInUsd = ( + await getTokenAmountInUsd( + pricingProvider, + token, + matchAmount, + this.event.blockTimestamp, + ) + ).amountInUsd; + } + + return [ + { + type: "UpdateRound", + args: { + chainId: this.chainId, + roundId: this.event.params.poolId.toString(), + round: { + matchAmount, + matchAmountInUsd, + applicationMetadataCid: metadataPointer, + applicationMetadata: metadata?.application ?? {}, + roundMetadataCid: metadataPointer, + roundMetadata: metadata?.round ?? {}, + }, + }, + }, + ]; + } +} diff --git a/packages/processors/src/processors/allo/handlers/roleGranted.handler.ts b/packages/processors/src/processors/allo/handlers/roleGranted.handler.ts new file mode 100644 index 0000000..b950212 --- /dev/null +++ b/packages/processors/src/processors/allo/handlers/roleGranted.handler.ts @@ -0,0 +1,84 @@ +import { getAddress } from "viem"; + +import type { Changeset, Round } from "@grants-stack-indexer/repository"; +import type { ChainId, ProcessorEvent } from "@grants-stack-indexer/shared"; + +import type { IEventHandler, ProcessorDependencies } from "../../../internal.js"; + +type Dependencies = Pick; + +/** + * Handles the RoleGranted event for the Allo protocol. + * + * This handler performs the following core actions when a new role is granted: + * - Insert a new round role if the role granted is admin or manager. + * - Insert a new pending round role if the role granted is not admin or manager. + * - Return the changeset. + */ +export class RoleGrantedHandler implements IEventHandler<"Allo", "RoleGranted"> { + constructor( + readonly event: ProcessorEvent<"Allo", "RoleGranted">, + private readonly chainId: ChainId, + private readonly dependencies: Dependencies, + ) {} + /* @inheritdoc */ + async handle(): Promise { + const role = this.event.params.role.toLowerCase(); + const account = getAddress(this.event.params.account); + const { roundRepository } = this.dependencies; + + let round: Round | undefined = undefined; + + // search for a round where the admin role is the role granted + round = await roundRepository.getRoundByRole(this.chainId, "admin", role); + if (round) { + return [ + { + type: "InsertRoundRole", + args: { + roundRole: { + chainId: this.chainId, + roundId: round.id, + role: "admin", + address: account, + createdAtBlock: BigInt(this.event.blockNumber), + }, + }, + }, + ]; + } + + // search for a round where the manager role is the role granted + round = await roundRepository.getRoundByRole(this.chainId, "manager", role); + if (round) { + return [ + { + type: "InsertRoundRole", + args: { + roundRole: { + chainId: this.chainId, + roundId: round.id, + role: "manager", + address: account, + createdAtBlock: BigInt(this.event.blockNumber), + }, + }, + }, + ]; + } + + return [ + { + type: "InsertPendingRoundRole", + args: { + pendingRoundRole: { + chainId: this.chainId, + role: role, + address: account, + createdAtBlock: BigInt(this.event.blockNumber), + }, + }, + }, + ]; + } +} diff --git a/packages/processors/src/processors/allo/handlers/roleRevoked.handler.ts b/packages/processors/src/processors/allo/handlers/roleRevoked.handler.ts new file mode 100644 index 0000000..47b238b --- /dev/null +++ b/packages/processors/src/processors/allo/handlers/roleRevoked.handler.ts @@ -0,0 +1,69 @@ +import { getAddress } from "viem"; + +import type { Changeset, Round } from "@grants-stack-indexer/repository"; +import type { ChainId, ProcessorEvent } from "@grants-stack-indexer/shared"; + +import type { IEventHandler, ProcessorDependencies } from "../../../internal.js"; + +type Dependencies = Pick; + +/** + * Handles the RoleRevoked event for the Allo protocol. + * + * This handler performs the following core actions when a new role is revoked: + * - Delete the round role if the role revoked is admin or manager. + * - Return the changeset. + */ +export class RoleRevokedHandler implements IEventHandler<"Allo", "RoleRevoked"> { + constructor( + readonly event: ProcessorEvent<"Allo", "RoleRevoked">, + private readonly chainId: ChainId, + private readonly dependencies: Dependencies, + ) {} + /* @inheritdoc */ + async handle(): Promise { + const role = this.event.params.role.toLowerCase(); + const account = getAddress(this.event.params.account); + const { roundRepository, logger } = this.dependencies; + let round: Round | undefined = undefined; + + // search for a round where the admin role is the role granted + round = await roundRepository.getRoundByRole(this.chainId, "admin", role); + if (round) { + return [ + { + type: "DeleteAllRoundRolesByRoleAndAddress", + args: { + roundRole: { + chainId: this.chainId, + roundId: round.id, + role: "admin", + address: account, + }, + }, + }, + ]; + } + + // search for a round where the manager role is the role granted + round = await roundRepository.getRoundByRole(this.chainId, "manager", role); + if (round) { + return [ + { + type: "DeleteAllRoundRolesByRoleAndAddress", + args: { + roundRole: { + chainId: this.chainId, + roundId: round.id, + role: "manager", + address: account, + }, + }, + }, + ]; + } + + logger.warn(`No round found for role ${role} on chain ${this.chainId}`); + return []; + } +} diff --git a/packages/processors/src/processors/strategy/common/baseDistributionUpdated.handler.ts b/packages/processors/src/processors/strategy/common/baseDistributionUpdated.handler.ts index e8b4c64..350402c 100644 --- a/packages/processors/src/processors/strategy/common/baseDistributionUpdated.handler.ts +++ b/packages/processors/src/processors/strategy/common/baseDistributionUpdated.handler.ts @@ -37,7 +37,6 @@ export class BaseDistributionUpdatedHandler /* @inheritdoc */ async handle(): Promise { 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); diff --git a/packages/processors/test/allo/handlers/poolCreated.handler.spec.ts b/packages/processors/test/allo/handlers/poolCreated.handler.spec.ts index 6109242..67c23e0 100644 --- a/packages/processors/test/allo/handlers/poolCreated.handler.spec.ts +++ b/packages/processors/test/allo/handlers/poolCreated.handler.spec.ts @@ -24,11 +24,11 @@ function createMockEvent( srcAddress: "0x1133eA7Af70876e64665ecD07C0A0476d09465a1", params: { strategy: "0xD545fbA3f43EcA447CC7FBF41D4A8F0f575F2491", - poolId: 10n, + poolId: "10", profileId: "0xcc3509068dfb6604965939f100e57dde21e9d764d8ce4b34284bbe9364b1f5ed", - amount: 0n, + amount: "0", token: "0x4200000000000000000000000000000000000042", - metadata: [1n, "bafkreihrjyu5tney6wia2hmkertc74nzfpsgxw2epvnxm72bxj6ifnd4ku"], + metadata: ["1", "bafkreihrjyu5tney6wia2hmkertc74nzfpsgxw2epvnxm72bxj6ifnd4ku"], }, transactionFields: { hash: "0xd2352acdcd59e312370831ea927d51a1917654697a72434cd905a60897a5bb8b", @@ -72,7 +72,7 @@ describe("PoolCreatedHandler", () => { it("process an event with initial funds", async () => { const fundedAmount = parseUnits("10", 18); const mockEvent = createMockEvent({ - params: { amount: fundedAmount }, + params: { amount: fundedAmount.toString() }, strategyId: "0xunknown", }); @@ -308,7 +308,7 @@ describe("PoolCreatedHandler", () => { }); it("throws an error if token price fetch fails", async () => { - const mockEvent = createMockEvent({ params: { amount: 1n }, strategyId: "0xunknown" }); + const mockEvent = createMockEvent({ params: { amount: "1" }, strategyId: "0xunknown" }); vi.spyOn(mockMetadataProvider, "getMetadata").mockResolvedValue(undefined); @@ -469,7 +469,7 @@ describe("PoolCreatedHandler", () => { const fundedAmount = parseUnits("10", 18); const mockEvent = createMockEvent({ params: { - amount: fundedAmount, + amount: fundedAmount.toString(), token: "0x95aD61b0a150d79219dCF64E1E6Cc01f0B64C4cE", }, strategyId: "0xunknown", diff --git a/packages/processors/test/allo/handlers/poolFunded.handler.spec.ts b/packages/processors/test/allo/handlers/poolFunded.handler.spec.ts new file mode 100644 index 0000000..7588472 --- /dev/null +++ b/packages/processors/test/allo/handlers/poolFunded.handler.spec.ts @@ -0,0 +1,146 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +import type { ChainId, ILogger, ProcessorEvent, Token } from "@grants-stack-indexer/shared"; +import { IPricingProvider } from "@grants-stack-indexer/pricing"; +import { IRoundReadRepository, Round } from "@grants-stack-indexer/repository"; +import { TOKENS, UnknownToken } from "@grants-stack-indexer/shared"; + +import { calculateAmountInUsd } from "../../../src/helpers/index.js"; +import { PoolFundedHandler } from "../../../src/processors/allo/handlers/index.js"; + +function createMockEvent( + overrides: Partial> = {}, +): ProcessorEvent<"Allo", "PoolFunded"> { + return { + blockNumber: 116385567, + blockTimestamp: 1708369911, + chainId: 10 as ChainId, + contractName: "Allo", + eventName: "PoolFunded", + logIndex: 123, + srcAddress: "0x1133eA7Af70876e64665ecD07C0A0476d09465a1", + params: { + poolId: "1", + amount: "100", + fee: "10", + }, + transactionFields: { + hash: "0xtransactionhash", + transactionIndex: 5, + from: "0xsenderaddress", + }, + ...overrides, + }; +} + +describe("PoolFundedHandler", () => { + let mockPricingProvider: IPricingProvider; + let mockRoundRepository: IRoundReadRepository; + let mockLogger: ILogger; + let handler: PoolFundedHandler; + + const mockDependencies = (): { + roundRepository: IRoundReadRepository; + pricingProvider: IPricingProvider; + logger: ILogger; + } => ({ + roundRepository: mockRoundRepository, + pricingProvider: mockPricingProvider, + logger: mockLogger, + }); + + beforeEach(() => { + mockRoundRepository = { + getRoundById: vi.fn(), + getRoundByIdOrThrow: vi.fn(), + } as unknown as IRoundReadRepository; + mockPricingProvider = { + getTokenPrice: vi.fn(), + }; + mockLogger = { + error: vi.fn(), + warn: vi.fn(), + info: vi.fn(), + } as unknown as ILogger; + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it("returns a changeset with funded amount and USD value", async () => { + const mockEvent = createMockEvent(); + const mockToken = Object.values( + TOKENS["10"] as { + [tokenAddress: `0x${string}`]: Token; + }, + )[0]; + const mockPrice = { + priceUsd: 2.5, + timestampMs: 1708369911, + }; + const round = { + id: "1", + matchTokenAddress: mockToken?.address, + }; + vi.spyOn(mockRoundRepository, "getRoundByIdOrThrow").mockResolvedValue(round as Round); + vi.spyOn(mockPricingProvider, "getTokenPrice").mockResolvedValue(mockPrice); + + handler = new PoolFundedHandler( + mockEvent, + mockEvent.chainId as ChainId, + mockDependencies(), + ); + + const result = await handler.handle(); + + expect(mockRoundRepository.getRoundByIdOrThrow).toHaveBeenCalledWith(10, "1"); + expect(mockPricingProvider.getTokenPrice).toHaveBeenCalled(); + expect(result).toEqual([ + { + type: "IncrementRoundFundedAmount", + args: { + chainId: 10, + roundId: "1", + fundedAmount: BigInt(mockEvent.params.amount), + fundedAmountInUsd: calculateAmountInUsd( + BigInt(mockEvent.params.amount), + mockPrice.priceUsd, + mockToken?.decimals as number, + ), + }, + }, + ]); + }); + + it("throw if the round does not exist", async () => { + const mockEvent = createMockEvent(); + const roundError = new Error("Round not found"); + vi.spyOn(mockRoundRepository, "getRoundByIdOrThrow").mockRejectedValue(roundError); + + handler = new PoolFundedHandler( + mockEvent, + mockEvent.chainId as ChainId, + mockDependencies(), + ); + + await expect(handler.handle()).rejects.toThrowError(roundError); + }); + + it("throws an error for an unknown token", async () => { + const mockEvent = createMockEvent(); + const mockRound = { + id: "1", + matchTokenAddress: "0xUnknownToken", + }; + vi.spyOn(mockRoundRepository, "getRoundByIdOrThrow").mockResolvedValue(mockRound as Round); + + handler = new PoolFundedHandler( + mockEvent, + mockEvent.chainId as ChainId, + mockDependencies(), + ); + + await expect(handler.handle()).rejects.toThrow(UnknownToken); + }); +}); diff --git a/packages/processors/test/allo/handlers/poolMetadataUpdated.handler.spec.ts b/packages/processors/test/allo/handlers/poolMetadataUpdated.handler.spec.ts new file mode 100644 index 0000000..b33441e --- /dev/null +++ b/packages/processors/test/allo/handlers/poolMetadataUpdated.handler.spec.ts @@ -0,0 +1,241 @@ +import { parseUnits } from "viem"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +import type { IRoundReadRepository, Round } from "@grants-stack-indexer/repository"; +import type { ChainId, ILogger, ProcessorEvent, Token } from "@grants-stack-indexer/shared"; +import { IMetadataProvider } from "@grants-stack-indexer/metadata"; +import { IPricingProvider } from "@grants-stack-indexer/pricing"; +import { TOKENS } from "@grants-stack-indexer/shared"; + +import { calculateAmountInUsd } from "../../../src/helpers/index.js"; +import { TokenPriceNotFoundError } from "../../../src/internal.js"; +import { PoolMetadataUpdatedHandler } from "../../../src/processors/allo/handlers/index.js"; + +function createMockEvent( + overrides: Partial> = {}, +): ProcessorEvent<"Allo", "PoolMetadataUpdated"> { + return { + blockNumber: 116385567, + blockTimestamp: 1708369911, + chainId: 10 as ChainId, + contractName: "Allo", + eventName: "PoolMetadataUpdated", + logIndex: 456, + srcAddress: "0x1133eA7Af70876e64665ecD07C0A0476d09465a1", + params: { + poolId: "1", + metadata: ["1", "bafkreihrjyu5tney6wia2hmkertc74nzfpsgxw2epvnxm72bxj6ifnd4ku"], + }, + transactionFields: { + hash: "0xtransactionhash", + transactionIndex: 7, + from: "0xsenderaddress", + }, + ...overrides, + }; +} + +describe("PoolMetadataUpdatedHandler", () => { + let mockMetadataProvider: IMetadataProvider; + let mockRoundRepository: IRoundReadRepository; + let mockLogger: ILogger; + let mockPricingProvider: IPricingProvider; + let handler: PoolMetadataUpdatedHandler; + + const mockDependencies = (): { + metadataProvider: IMetadataProvider; + roundRepository: IRoundReadRepository; + logger: ILogger; + pricingProvider: IPricingProvider; + } => ({ + metadataProvider: mockMetadataProvider, + roundRepository: mockRoundRepository, + logger: mockLogger, + pricingProvider: mockPricingProvider, + }); + + beforeEach(() => { + mockMetadataProvider = { + getMetadata: vi.fn(), + }; + mockRoundRepository = { + getRoundById: vi.fn(), + getRoundByIdOrThrow: vi.fn(), + } as unknown as IRoundReadRepository; + mockLogger = { + error: vi.fn(), + info: vi.fn(), + } as unknown as ILogger; + mockPricingProvider = { + getTokenPrice: vi.fn(), + }; + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it("returns a changeset with updated round metadata", async () => { + const mockEvent = createMockEvent(); + const metadata = { + round: { + name: "asd", + roundType: "public", + quadraticFundingConfig: { + matchingFundsAvailable: 100, + }, + }, + application: {}, + }; + const mockToken = Object.values( + TOKENS["10"] as { + [tokenAddress: `0x${string}`]: Token; + }, + )[0]; + const round = { + id: "1", + matchTokenAddress: mockToken?.address, + matchAmount: 0n, + matchAmountInUsd: "0", + }; + const mockTokenPrice = { + priceUsd: 2.5, + timestampMs: 1708369911, + }; + vi.spyOn(mockMetadataProvider, "getMetadata").mockResolvedValue(metadata); + vi.spyOn(mockRoundRepository, "getRoundByIdOrThrow").mockResolvedValue(round as Round); + vi.spyOn(mockPricingProvider, "getTokenPrice").mockResolvedValue(mockTokenPrice); + + handler = new PoolMetadataUpdatedHandler( + mockEvent, + mockEvent.chainId as ChainId, + mockDependencies(), + ); + + const result = await handler.handle(); + const matchAmountResult = parseUnits( + metadata.round.quadraticFundingConfig.matchingFundsAvailable.toString(), + mockToken?.decimals as number, + ); + expect(mockMetadataProvider.getMetadata).toHaveBeenCalledWith( + "bafkreihrjyu5tney6wia2hmkertc74nzfpsgxw2epvnxm72bxj6ifnd4ku", + ); + expect(mockRoundRepository.getRoundByIdOrThrow).toHaveBeenCalledWith(10, "1"); + expect(result).toEqual([ + { + type: "UpdateRound", + args: { + chainId: 10, + roundId: "1", + round: { + matchAmount: matchAmountResult, + matchAmountInUsd: calculateAmountInUsd( + matchAmountResult, + mockTokenPrice.priceUsd, + mockToken?.decimals as number, + ), + applicationMetadataCid: + "bafkreihrjyu5tney6wia2hmkertc74nzfpsgxw2epvnxm72bxj6ifnd4ku", + applicationMetadata: metadata.application, + roundMetadataCid: + "bafkreihrjyu5tney6wia2hmkertc74nzfpsgxw2epvnxm72bxj6ifnd4ku", + roundMetadata: metadata.round, + }, + }, + }, + ]); + }); + + it("throws if tokenPrice is not found", async () => { + const mockEvent = createMockEvent(); + const mockToken = Object.values( + TOKENS["10"] as { + [tokenAddress: `0x${string}`]: Token; + }, + )[0]; + const metadata = { + round: { + name: "asd", + roundType: "public", + quadraticFundingConfig: { + matchingFundsAvailable: 100, + }, + }, + }; + const round = { + id: "1", + matchTokenAddress: mockToken?.address, + matchAmount: 0n, + matchAmountInUsd: "0", + }; + + vi.spyOn(mockMetadataProvider, "getMetadata").mockResolvedValue(metadata); + vi.spyOn(mockRoundRepository, "getRoundByIdOrThrow").mockResolvedValue(round as Round); + vi.spyOn(mockPricingProvider, "getTokenPrice").mockResolvedValue(undefined); + + handler = new PoolMetadataUpdatedHandler(mockEvent, 10 as ChainId, mockDependencies()); + + await expect(handler.handle()).rejects.toThrowError(TokenPriceNotFoundError); + expect(mockPricingProvider.getTokenPrice).toHaveBeenCalled(); + }); + + it("throws if round is not found", async () => { + const mockEvent = createMockEvent(); + const metadata = { + round: { + name: "asd", + roundType: "public", + quadraticFundingConfig: { + matchingFundsAvailable: 100, + }, + }, + }; + const roundError = new Error("Round not found"); + vi.spyOn(mockMetadataProvider, "getMetadata").mockResolvedValue(metadata); + vi.spyOn(mockRoundRepository, "getRoundByIdOrThrow").mockRejectedValue(roundError); + vi.spyOn(mockPricingProvider, "getTokenPrice").mockResolvedValue(undefined); + + handler = new PoolMetadataUpdatedHandler(mockEvent, 10 as ChainId, mockDependencies()); + + await expect(handler.handle()).rejects.toThrowError(roundError); + }); + + it("returns a changeset with empty metadata if metadata parsing fails", async () => { + const mockEvent = createMockEvent(); + const metadata = { round: null }; + const round = { + id: "1", + matchTokenAddress: "0xTokenAddress", + matchAmount: 0n, + matchAmountInUsd: "0", + }; + + vi.spyOn(mockMetadataProvider, "getMetadata").mockResolvedValue(metadata); + vi.spyOn(mockRoundRepository, "getRoundByIdOrThrow").mockResolvedValue(round as Round); + + handler = new PoolMetadataUpdatedHandler(mockEvent, 10 as ChainId, mockDependencies()); + + const result = await handler.handle(); + + expect(mockMetadataProvider.getMetadata).toHaveBeenCalled(); + expect(result).toEqual([ + { + type: "UpdateRound", + args: { + chainId: 10, + roundId: "1", + round: { + matchAmount: 0n, + matchAmountInUsd: "0", + applicationMetadataCid: + "bafkreihrjyu5tney6wia2hmkertc74nzfpsgxw2epvnxm72bxj6ifnd4ku", + applicationMetadata: {}, + roundMetadataCid: + "bafkreihrjyu5tney6wia2hmkertc74nzfpsgxw2epvnxm72bxj6ifnd4ku", + roundMetadata: {}, + }, + }, + }, + ]); + }); +}); diff --git a/packages/processors/test/allo/handlers/roleGranted.handler.spec.ts b/packages/processors/test/allo/handlers/roleGranted.handler.spec.ts new file mode 100644 index 0000000..59ba93c --- /dev/null +++ b/packages/processors/test/allo/handlers/roleGranted.handler.spec.ts @@ -0,0 +1,158 @@ +import { getAddress } from "viem"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +import type { Bytes32String, ChainId, ProcessorEvent } from "@grants-stack-indexer/shared"; +import { IRoundReadRepository, Round } from "@grants-stack-indexer/repository"; + +import { RoleGrantedHandler } from "../../../src/processors/allo/handlers/index.js"; + +function createMockEvent( + overrides: Partial> = {}, +): ProcessorEvent<"Allo", "RoleGranted"> { + return { + blockNumber: 116385567, + blockTimestamp: 1708369911, + chainId: 10 as ChainId, + contractName: "Allo", + eventName: "RoleGranted", + logIndex: 123, + srcAddress: "0x1133eA7Af70876e64665ecD07C0A0476d09465a1", + params: { + role: "admin" as Bytes32String, + account: "0x95222290DD7278Aa3Ddd389Cc1E1d165CC4BAfe5", + sender: "0x95222290DD7278Aa3Ddd389Cc1E1d165CC4BAfe5Address", + }, + transactionFields: { + hash: "0x6e5a7115323ac1712f7c27adff46df2216324a4ad615a8c9ce488c32a1f3a035", + transactionIndex: 6, + from: "0x95222290DD7278Aa3Ddd389Cc1E1d165CC4BAfe5Address", + }, + ...overrides, + }; +} + +describe("RoleGrantedHandler", () => { + let mockRoundRepository: IRoundReadRepository; + let handler: RoleGrantedHandler; + + const mockDependencies = (): { roundRepository: IRoundReadRepository } => ({ + roundRepository: mockRoundRepository, + }); + + beforeEach(() => { + mockRoundRepository = { + getRoundByRole: vi.fn(), + } as unknown as IRoundReadRepository; + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it("returns a changeset for an admin role in an existing round", async () => { + const mockEvent = createMockEvent(); + const round = { id: "1" }; + + vi.spyOn(mockRoundRepository, "getRoundByRole") + .mockResolvedValueOnce(round as Round) + .mockResolvedValueOnce(undefined); + + handler = new RoleGrantedHandler(mockEvent, 10 as ChainId, mockDependencies()); + + const result = await handler.handle(); + + expect(mockRoundRepository.getRoundByRole).toHaveBeenCalledWith(10, "admin", "admin"); + expect(result).toEqual([ + { + type: "InsertRoundRole", + args: { + roundRole: { + chainId: 10, + roundId: "1", + role: "admin", + address: getAddress("0x95222290DD7278Aa3Ddd389Cc1E1d165CC4BAfe5"), + createdAtBlock: BigInt(116385567), + }, + }, + }, + ]); + }); + + it("returns a changeset for a manager role in an existing round", async () => { + const mockEvent = createMockEvent({ + params: { + role: "manager" as Bytes32String, + account: "0x95222290DD7278Aa3Ddd389Cc1E1d165CC4BAfe5", + sender: "0x95222290DD7278Aa3Ddd389Cc1E1d165CC4BAfe5", + }, + }); + const round = { id: "2" }; + + vi.spyOn(mockRoundRepository, "getRoundByRole") + .mockResolvedValueOnce(undefined) + .mockResolvedValueOnce(round as Round); + + handler = new RoleGrantedHandler(mockEvent, 10 as ChainId, mockDependencies()); + + const result = await handler.handle(); + + expect(mockRoundRepository.getRoundByRole).toHaveBeenCalledWith( + 10, + "admin", + mockEvent.params.role, + ); + expect(mockRoundRepository.getRoundByRole).toHaveBeenCalledWith( + 10, + "manager", + mockEvent.params.role, + ); + expect(result).toEqual([ + { + type: "InsertRoundRole", + args: { + roundRole: { + chainId: 10, + roundId: "2", + role: "manager", + address: getAddress("0x95222290DD7278Aa3Ddd389Cc1E1d165CC4BAfe5"), + createdAtBlock: BigInt(116385567), + }, + }, + }, + ]); + }); + + it("returns a changeset for a pending role if no matching round is found", async () => { + const mockEvent = createMockEvent({ + params: { + role: "otherRole" as Bytes32String, + account: "0x95222290DD7278Aa3Ddd389Cc1E1d165CC4BAfe5", + sender: "0x95222290DD7278Aa3Ddd389Cc1E1d165CC4BAfe5", + }, + }); + + vi.spyOn(mockRoundRepository, "getRoundByRole") + .mockResolvedValue(undefined) + .mockResolvedValue(undefined); + + handler = new RoleGrantedHandler(mockEvent, 10 as ChainId, mockDependencies()); + + const result = await handler.handle(); + + expect(mockRoundRepository.getRoundByRole).toHaveBeenCalledWith(10, "admin", "otherrole"); + expect(mockRoundRepository.getRoundByRole).toHaveBeenCalledWith(10, "manager", "otherrole"); + expect(result).toEqual([ + { + type: "InsertPendingRoundRole", + args: { + pendingRoundRole: { + chainId: 10, + role: "otherrole", + address: getAddress("0x95222290DD7278Aa3Ddd389Cc1E1d165CC4BAfe5"), + createdAtBlock: BigInt(116385567), + }, + }, + }, + ]); + }); +}); diff --git a/packages/processors/test/allo/handlers/roleRevoked.handler.spec.ts b/packages/processors/test/allo/handlers/roleRevoked.handler.spec.ts new file mode 100644 index 0000000..c89b774 --- /dev/null +++ b/packages/processors/test/allo/handlers/roleRevoked.handler.spec.ts @@ -0,0 +1,160 @@ +import { getAddress } from "viem"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +import type { Bytes32String, ChainId, ILogger, ProcessorEvent } from "@grants-stack-indexer/shared"; +import { IRoundReadRepository, Round } from "@grants-stack-indexer/repository"; + +import { RoleRevokedHandler } from "../../../src/processors/allo/handlers/roleRevoked.handler.js"; + +function createMockEvent( + overrides: Partial> = {}, +): ProcessorEvent<"Allo", "RoleRevoked"> { + return { + blockNumber: 116385567, + blockTimestamp: 1708369911, + chainId: 10 as ChainId, + contractName: "Allo", + eventName: "RoleRevoked", + logIndex: 123, + srcAddress: "0x1133eA7Af70876e64665ecD07C0A0476d09465a1", + params: { + role: "admin" as Bytes32String, + account: "0x95222290DD7278Aa3Ddd389Cc1E1d165CC4BAfe5", + sender: "0x95222290DD7278Aa3Ddd389Cc1E1d165CC4BAfe5Address", + }, + transactionFields: { + hash: "0x6e5a7115323ac1712f7c27adff46df2216324a4ad615a8c9ce488c32a1f3a035", + transactionIndex: 6, + from: "0x95222290DD7278Aa3Ddd389Cc1E1d165CC4BAfe5Address", + }, + ...overrides, + }; +} + +describe("RoleRevokedHandler", () => { + let mockRoundRepository: IRoundReadRepository; + let mockLogger: ILogger; + let handler: RoleRevokedHandler; + + const mockDependencies = (): { roundRepository: IRoundReadRepository; logger: ILogger } => ({ + roundRepository: mockRoundRepository, + logger: mockLogger, + }); + + beforeEach(() => { + mockRoundRepository = { + getRoundByRole: vi.fn(), + } as unknown as IRoundReadRepository; + + mockLogger = { + warn: vi.fn(), + } as unknown as ILogger; + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it("returns a changeset for an admin role in an existing round", async () => { + const mockEvent = createMockEvent(); + const round = { id: "1" }; + + vi.spyOn(mockRoundRepository, "getRoundByRole") + .mockResolvedValueOnce(round as Round) + .mockResolvedValueOnce(undefined); + + handler = new RoleRevokedHandler(mockEvent, 10 as ChainId, mockDependencies()); + + const result = await handler.handle(); + + expect(mockRoundRepository.getRoundByRole).toHaveBeenCalledWith(10, "admin", "admin"); + expect(result).toEqual([ + { + type: "DeleteAllRoundRolesByRoleAndAddress", + args: { + roundRole: { + chainId: 10, + roundId: "1", + role: "admin", + address: getAddress("0x95222290DD7278Aa3Ddd389Cc1E1d165CC4BAfe5"), + }, + }, + }, + ]); + }); + + it("returns a changeset for a manager role in an existing round", async () => { + const mockEvent = createMockEvent({ + params: { + role: "manager" as Bytes32String, + account: "0x95222290DD7278Aa3Ddd389Cc1E1d165CC4BAfe5", + sender: "0x95222290DD7278Aa3Ddd389Cc1E1d165CC4BAfe5", + }, + }); + const round = { id: "2" }; + + vi.spyOn(mockRoundRepository, "getRoundByRole") + .mockResolvedValueOnce(undefined) + .mockResolvedValueOnce(round as Round); + + handler = new RoleRevokedHandler(mockEvent, 10 as ChainId, mockDependencies()); + + const result = await handler.handle(); + + expect(mockRoundRepository.getRoundByRole).toHaveBeenCalledWith( + 10, + "manager", + mockEvent.params.role, + ); + expect(result).toEqual([ + { + type: "DeleteAllRoundRolesByRoleAndAddress", + args: { + roundRole: { + chainId: 10, + roundId: "2", + role: "manager", + address: getAddress("0x95222290DD7278Aa3Ddd389Cc1E1d165CC4BAfe5"), + }, + }, + }, + ]); + }); + + it("returns an empty array if no matching round is found", async () => { + const mockEvent = createMockEvent({ + params: { + role: "otherRole" as Bytes32String, + account: "0x95222290DD7278Aa3Ddd389Cc1E1d165CC4BAfe5", + sender: "0x95222290DD7278Aa3Ddd389Cc1E1d165CC4BAfe5", + }, + }); + + vi.spyOn(mockRoundRepository, "getRoundByRole") + .mockResolvedValue(undefined) + .mockResolvedValue(undefined); + + handler = new RoleRevokedHandler(mockEvent, 10 as ChainId, mockDependencies()); + + const result = await handler.handle(); + + expect(mockRoundRepository.getRoundByRole).toHaveBeenCalledWith(10, "admin", "otherrole"); + expect(mockRoundRepository.getRoundByRole).toHaveBeenCalledWith(10, "manager", "otherrole"); + expect(result).toEqual([]); + }); + + it("logs a warning if no round is found", async () => { + const mockEvent = createMockEvent(); + vi.spyOn(mockRoundRepository, "getRoundByRole").mockResolvedValue(undefined); + + const logger = { warn: vi.fn() } as unknown as ILogger; + handler = new RoleRevokedHandler(mockEvent, 10 as ChainId, { + roundRepository: mockRoundRepository, + logger, + }); + + await handler.handle(); + + expect(logger.warn).toHaveBeenCalledWith(`No round found for role admin on chain 10`); + }); +}); diff --git a/packages/repository/src/exceptions/roundNotFound.exception.ts b/packages/repository/src/exceptions/roundNotFound.exception.ts index 6b68c91..5705522 100644 --- a/packages/repository/src/exceptions/roundNotFound.exception.ts +++ b/packages/repository/src/exceptions/roundNotFound.exception.ts @@ -5,3 +5,9 @@ export class RoundNotFound extends Error { super(`Round not found for chainId: ${chainId} and strategyAddress: ${strategyAddress}`); } } + +export class RoundNotFoundForId extends Error { + constructor(chainId: ChainId, roundId: string) { + super(`Round not found for chainId: ${chainId} and roundId: ${roundId}`); + } +} diff --git a/packages/repository/src/interfaces/roundRepository.interface.ts b/packages/repository/src/interfaces/roundRepository.interface.ts index 12ac0ba..e94fe2c 100644 --- a/packages/repository/src/interfaces/roundRepository.interface.ts +++ b/packages/repository/src/interfaces/roundRepository.interface.ts @@ -27,6 +27,14 @@ export interface IRoundReadRepository { */ getRoundById(chainId: ChainId, roundId: string): Promise; + /** + * Retrieves a specific round by its ID and chain ID. + * @param chainId The chain ID of the round. + * @param roundId The ID of the round to fetch. + * @returns A promise that resolves to a Round object + * @throws {RoundNotFoundForId} if the round does not exist + */ + getRoundByIdOrThrow(chainId: ChainId, roundId: string): Promise; /** * Retrieves a round by its strategy address and chain ID. * @param chainId The chain ID of the round. diff --git a/packages/repository/src/repositories/kysely/round.repository.ts b/packages/repository/src/repositories/kysely/round.repository.ts index dbed809..400ab72 100644 --- a/packages/repository/src/repositories/kysely/round.repository.ts +++ b/packages/repository/src/repositories/kysely/round.repository.ts @@ -12,6 +12,7 @@ import { PendingRoundRole, Round, RoundNotFound, + RoundNotFoundForId, RoundRole, RoundRoleNames, } from "../../internal.js"; @@ -45,6 +46,16 @@ export class KyselyRoundRepository implements IRoundRepository { .executeTakeFirst(); } + /* @inheritdoc */ + async getRoundByIdOrThrow(chainId: ChainId, roundId: string): Promise { + const round = await this.getRoundById(chainId, roundId); + + if (!round) { + throw new RoundNotFoundForId(chainId, roundId); + } + return round; + } + /* @inheritdoc */ async getRoundByStrategyAddress( chainId: ChainId, diff --git a/packages/shared/src/types/events/allo.ts b/packages/shared/src/types/events/allo.ts index 3b8e325..4b2d643 100644 --- a/packages/shared/src/types/events/allo.ts +++ b/packages/shared/src/types/events/allo.ts @@ -1,9 +1,16 @@ import { Address, AnyEvent, ContractName, ProcessorEvent } from "../../internal.js"; +import { RoleGrantedParams, RoleRevokedParams } from "./index.js"; /** * This array is used to represent all Allo events. */ -const AlloEventArray = ["PoolCreated"] as const; +const AlloEventArray = [ + "PoolCreated", + "PoolFunded", + "PoolMetadataUpdated", + "RoleGranted", + "RoleRevoked", +] as const; /** * This type is used to represent a Allo events. @@ -15,18 +22,37 @@ export type AlloEvent = (typeof AlloEventArray)[number]; */ export type AlloEventParams = T extends "PoolCreated" ? PoolCreatedParams - : never; + : T extends "PoolMetadataUpdated" + ? PoolMetadataUpdatedParams + : T extends "PoolFunded" + ? PoolFundedParams + : T extends "RoleGranted" + ? RoleGrantedParams + : T extends "RoleRevoked" + ? RoleRevokedParams + : never; // ============================================================================= // =============================== Event Parameters ============================ // ============================================================================= export type PoolCreatedParams = { strategy: Address; - poolId: bigint; + poolId: string; //uint256 profileId: Address; token: Address; - amount: bigint; - metadata: [protocol: bigint, pointer: string]; + amount: string; //uint256 + metadata: [protocol: string /*uint256*/, pointer: string]; +}; + +export type PoolMetadataUpdatedParams = { + poolId: string; //uint256 + metadata: [protocol: string /*uint256*/, pointer: string]; +}; + +export type PoolFundedParams = { + poolId: string; //uint256 + amount: string; //uint256 + fee: string; //uint256 }; /** diff --git a/packages/shared/src/types/events/index.ts b/packages/shared/src/types/events/index.ts index dfe6730..3d3adf6 100644 --- a/packages/shared/src/types/events/index.ts +++ b/packages/shared/src/types/events/index.ts @@ -2,3 +2,4 @@ export * from "./allo.js"; export * from "./common.js"; export * from "./registry.js"; export * from "./strategy.js"; +export * from "./sharedEvents.js"; diff --git a/packages/shared/src/types/events/registry.ts b/packages/shared/src/types/events/registry.ts index 6bcec46..a686d18 100644 --- a/packages/shared/src/types/events/registry.ts +++ b/packages/shared/src/types/events/registry.ts @@ -1,6 +1,14 @@ // TODO: Should we validate event params in runtime ? How should we approach that ? -import { Address, AnyEvent, Bytes32String, ContractName, ProcessorEvent } from "../../internal.js"; +import { + Address, + AnyEvent, + Bytes32String, + ContractName, + ProcessorEvent, + RoleGrantedParams, + RoleRevokedParams, +} from "../../internal.js"; /** * This array is used to represent all Registry events. @@ -61,18 +69,6 @@ export type ProfileOwnerUpdatedParams = { owner: Address; }; -export type RoleGrantedParams = { - role: Bytes32String; - account: Address; - sender: Address; -}; - -export type RoleRevokedParams = { - role: Bytes32String; - account: Address; - sender: Address; -}; - /** * Type guard for Registry events. * @param event The event to check. diff --git a/packages/shared/src/types/events/sharedEvents.ts b/packages/shared/src/types/events/sharedEvents.ts new file mode 100644 index 0000000..22a19c0 --- /dev/null +++ b/packages/shared/src/types/events/sharedEvents.ts @@ -0,0 +1,13 @@ +import { Address, Bytes32String } from "../../internal.js"; + +export type RoleGrantedParams = { + role: Bytes32String; + account: Address; + sender: Address; +}; + +export type RoleRevokedParams = { + role: Bytes32String; + account: Address; + sender: Address; +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1fdb47b..eeca826 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1257,146 +1257,146 @@ packages: } engines: { node: ^12.20.0 || ^14.18.0 || >=16.0.0 } - "@rollup/rollup-android-arm-eabi@4.27.4": + "@rollup/rollup-android-arm-eabi@4.27.3": resolution: { - integrity: sha512-2Y3JT6f5MrQkICUyRVCw4oa0sutfAsgaSsb0Lmmy1Wi2y7X5vT9Euqw4gOsCyy0YfKURBg35nhUKZS4mDcfULw==, + integrity: sha512-EzxVSkIvCFxUd4Mgm4xR9YXrcp976qVaHnqom/Tgm+vU79k4vV4eYTjmRvGfeoW8m9LVcsAy/lGjcgVegKEhLQ==, } cpu: [arm] os: [android] - "@rollup/rollup-android-arm64@4.27.4": + "@rollup/rollup-android-arm64@4.27.3": resolution: { - integrity: sha512-wzKRQXISyi9UdCVRqEd0H4cMpzvHYt1f/C3CoIjES6cG++RHKhrBj2+29nPF0IB5kpy9MS71vs07fvrNGAl/iA==, + integrity: sha512-LJc5pDf1wjlt9o/Giaw9Ofl+k/vLUaYsE2zeQGH85giX2F+wn/Cg8b3c5CDP3qmVmeO5NzwVUzQQxwZvC2eQKw==, } cpu: [arm64] os: [android] - "@rollup/rollup-darwin-arm64@4.27.4": + "@rollup/rollup-darwin-arm64@4.27.3": resolution: { - integrity: sha512-PlNiRQapift4LNS8DPUHuDX/IdXiLjf8mc5vdEmUR0fF/pyy2qWwzdLjB+iZquGr8LuN4LnUoSEvKRwjSVYz3Q==, + integrity: sha512-OuRysZ1Mt7wpWJ+aYKblVbJWtVn3Cy52h8nLuNSzTqSesYw1EuN6wKp5NW/4eSre3mp12gqFRXOKTcN3AI3LqA==, } cpu: [arm64] os: [darwin] - "@rollup/rollup-darwin-x64@4.27.4": + "@rollup/rollup-darwin-x64@4.27.3": resolution: { - integrity: sha512-o9bH2dbdgBDJaXWJCDTNDYa171ACUdzpxSZt+u/AAeQ20Nk5x+IhA+zsGmrQtpkLiumRJEYef68gcpn2ooXhSQ==, + integrity: sha512-xW//zjJMlJs2sOrCmXdB4d0uiilZsOdlGQIC/jjmMWT47lkLLoB1nsNhPUcnoqyi5YR6I4h+FjBpILxbEy8JRg==, } cpu: [x64] os: [darwin] - "@rollup/rollup-freebsd-arm64@4.27.4": + "@rollup/rollup-freebsd-arm64@4.27.3": resolution: { - integrity: sha512-NBI2/i2hT9Q+HySSHTBh52da7isru4aAAo6qC3I7QFVsuhxi2gM8t/EI9EVcILiHLj1vfi+VGGPaLOUENn7pmw==, + integrity: sha512-58E0tIcwZ+12nK1WiLzHOD8I0d0kdrY/+o7yFVPRHuVGY3twBwzwDdTIBGRxLmyjciMYl1B/U515GJy+yn46qw==, } cpu: [arm64] os: [freebsd] - "@rollup/rollup-freebsd-x64@4.27.4": + "@rollup/rollup-freebsd-x64@4.27.3": resolution: { - integrity: sha512-wYcC5ycW2zvqtDYrE7deary2P2UFmSh85PUpAx+dwTCO9uw3sgzD6Gv9n5X4vLaQKsrfTSZZ7Z7uynQozPVvWA==, + integrity: sha512-78fohrpcVwTLxg1ZzBMlwEimoAJmY6B+5TsyAZ3Vok7YabRBUvjYTsRXPTjGEvv/mfgVBepbW28OlMEz4w8wGA==, } cpu: [x64] os: [freebsd] - "@rollup/rollup-linux-arm-gnueabihf@4.27.4": + "@rollup/rollup-linux-arm-gnueabihf@4.27.3": resolution: { - integrity: sha512-9OwUnK/xKw6DyRlgx8UizeqRFOfi9mf5TYCw1uolDaJSbUmBxP85DE6T4ouCMoN6pXw8ZoTeZCSEfSaYo+/s1w==, + integrity: sha512-h2Ay79YFXyQi+QZKo3ISZDyKaVD7uUvukEHTOft7kh00WF9mxAaxZsNs3o/eukbeKuH35jBvQqrT61fzKfAB/Q==, } cpu: [arm] os: [linux] - "@rollup/rollup-linux-arm-musleabihf@4.27.4": + "@rollup/rollup-linux-arm-musleabihf@4.27.3": resolution: { - integrity: sha512-Vgdo4fpuphS9V24WOV+KwkCVJ72u7idTgQaBoLRD0UxBAWTF9GWurJO9YD9yh00BzbkhpeXtm6na+MvJU7Z73A==, + integrity: sha512-Sv2GWmrJfRY57urktVLQ0VKZjNZGogVtASAgosDZ1aUB+ykPxSi3X1nWORL5Jk0sTIIwQiPH7iE3BMi9zGWfkg==, } cpu: [arm] os: [linux] - "@rollup/rollup-linux-arm64-gnu@4.27.4": + "@rollup/rollup-linux-arm64-gnu@4.27.3": resolution: { - integrity: sha512-pleyNgyd1kkBkw2kOqlBx+0atfIIkkExOTiifoODo6qKDSpnc6WzUY5RhHdmTdIJXBdSnh6JknnYTtmQyobrVg==, + integrity: sha512-FPoJBLsPW2bDNWjSrwNuTPUt30VnfM8GPGRoLCYKZpPx0xiIEdFip3dH6CqgoT0RnoGXptaNziM0WlKgBc+OWQ==, } cpu: [arm64] os: [linux] - "@rollup/rollup-linux-arm64-musl@4.27.4": + "@rollup/rollup-linux-arm64-musl@4.27.3": resolution: { - integrity: sha512-caluiUXvUuVyCHr5DxL8ohaaFFzPGmgmMvwmqAITMpV/Q+tPoaHZ/PWa3t8B2WyoRcIIuu1hkaW5KkeTDNSnMA==, + integrity: sha512-TKxiOvBorYq4sUpA0JT+Fkh+l+G9DScnG5Dqx7wiiqVMiRSkzTclP35pE6eQQYjP4Gc8yEkJGea6rz4qyWhp3g==, } cpu: [arm64] os: [linux] - "@rollup/rollup-linux-powerpc64le-gnu@4.27.4": + "@rollup/rollup-linux-powerpc64le-gnu@4.27.3": resolution: { - integrity: sha512-FScrpHrO60hARyHh7s1zHE97u0KlT/RECzCKAdmI+LEoC1eDh/RDji9JgFqyO+wPDb86Oa/sXkily1+oi4FzJQ==, + integrity: sha512-v2M/mPvVUKVOKITa0oCFksnQQ/TqGrT+yD0184/cWHIu0LoIuYHwox0Pm3ccXEz8cEQDLk6FPKd1CCm+PlsISw==, } cpu: [ppc64] os: [linux] - "@rollup/rollup-linux-riscv64-gnu@4.27.4": + "@rollup/rollup-linux-riscv64-gnu@4.27.3": resolution: { - integrity: sha512-qyyprhyGb7+RBfMPeww9FlHwKkCXdKHeGgSqmIXw9VSUtvyFZ6WZRtnxgbuz76FK7LyoN8t/eINRbPUcvXB5fw==, + integrity: sha512-LdrI4Yocb1a/tFVkzmOE5WyYRgEBOyEhWYJe4gsDWDiwnjYKjNs7PS6SGlTDB7maOHF4kxevsuNBl2iOcj3b4A==, } cpu: [riscv64] os: [linux] - "@rollup/rollup-linux-s390x-gnu@4.27.4": + "@rollup/rollup-linux-s390x-gnu@4.27.3": resolution: { - integrity: sha512-PFz+y2kb6tbh7m3A7nA9++eInGcDVZUACulf/KzDtovvdTizHpZaJty7Gp0lFwSQcrnebHOqxF1MaKZd7psVRg==, + integrity: sha512-d4wVu6SXij/jyiwPvI6C4KxdGzuZOvJ6y9VfrcleHTwo68fl8vZC5ZYHsCVPUi4tndCfMlFniWgwonQ5CUpQcA==, } cpu: [s390x] os: [linux] - "@rollup/rollup-linux-x64-gnu@4.27.4": + "@rollup/rollup-linux-x64-gnu@4.27.3": resolution: { - integrity: sha512-Ni8mMtfo+o/G7DVtweXXV/Ol2TFf63KYjTtoZ5f078AUgJTmaIJnj4JFU7TK/9SVWTaSJGxPi5zMDgK4w+Ez7Q==, + integrity: sha512-/6bn6pp1fsCGEY5n3yajmzZQAh+mW4QPItbiWxs69zskBzJuheb3tNynEjL+mKOsUSFK11X4LYF2BwwXnzWleA==, } cpu: [x64] os: [linux] - "@rollup/rollup-linux-x64-musl@4.27.4": + "@rollup/rollup-linux-x64-musl@4.27.3": resolution: { - integrity: sha512-5AeeAF1PB9TUzD+3cROzFTnAJAcVUGLuR8ng0E0WXGkYhp6RD6L+6szYVX+64Rs0r72019KHZS1ka1q+zU/wUw==, + integrity: sha512-nBXOfJds8OzUT1qUreT/en3eyOXd2EH5b0wr2bVB5999qHdGKkzGzIyKYaKj02lXk6wpN71ltLIaQpu58YFBoQ==, } cpu: [x64] os: [linux] - "@rollup/rollup-win32-arm64-msvc@4.27.4": + "@rollup/rollup-win32-arm64-msvc@4.27.3": resolution: { - integrity: sha512-yOpVsA4K5qVwu2CaS3hHxluWIK5HQTjNV4tWjQXluMiiiu4pJj4BN98CvxohNCpcjMeTXk/ZMJBRbgRg8HBB6A==, + integrity: sha512-ogfbEVQgIZOz5WPWXF2HVb6En+kWzScuxJo/WdQTqEgeyGkaa2ui5sQav9Zkr7bnNCLK48uxmmK0TySm22eiuw==, } cpu: [arm64] os: [win32] - "@rollup/rollup-win32-ia32-msvc@4.27.4": + "@rollup/rollup-win32-ia32-msvc@4.27.3": resolution: { - integrity: sha512-KtwEJOaHAVJlxV92rNYiG9JQwQAdhBlrjNRp7P9L8Cb4Rer3in+0A+IPhJC9y68WAi9H0sX4AiG2NTsVlmqJeQ==, + integrity: sha512-ecE36ZBMLINqiTtSNQ1vzWc5pXLQHlf/oqGp/bSbi7iedcjcNb6QbCBNG73Euyy2C+l/fn8qKWEwxr+0SSfs3w==, } cpu: [ia32] os: [win32] - "@rollup/rollup-win32-x64-msvc@4.27.4": + "@rollup/rollup-win32-x64-msvc@4.27.3": resolution: { - integrity: sha512-3j4jx1TppORdTAoBJRd+/wJRGCPC0ETWkXOecJ6PPZLj6SptXkrXcNqdj0oclbKML6FkQltdz7bBA3rUSirZug==, + integrity: sha512-vliZLrDmYKyaUoMzEbMTg2JkerfBjn03KmAw9CykO0Zzkzoyd7o3iZNam/TpyWNjNT+Cz2iO3P9Smv2wgrR+Eg==, } cpu: [x64] os: [win32] @@ -2036,10 +2036,10 @@ packages: } engines: { node: ">=10" } - caniuse-lite@1.0.30001684: + caniuse-lite@1.0.30001683: resolution: { - integrity: sha512-G1LRwLIQjBQoyq0ZJGqGIJUXzJ8irpbjHLpVRXDvBEScFJ9b17sgK6vlx0GAJFE21okD7zXl08rRRUfq6HdoEQ==, + integrity: sha512-iqmNnThZ0n70mNwvxpEC2nBJ037ZHZUoBI5Gorh1Mw6IlEAZujEoU1tXA628iZfzm7R9FvFzxbfdgml82a3k8Q==, } chai@4.3.10: @@ -4196,10 +4196,10 @@ packages: deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true - rollup@4.27.4: + rollup@4.27.3: resolution: { - integrity: sha512-RLKxqHEMjh/RGLsDxAEsaLO3mWgyoU6x9w6n1ikAzet4B3gI2/3yP6PWY2p9QzRTh6MfEIXB3MwsOY0Iv3vNrw==, + integrity: sha512-SLsCOnlmGt9VoZ9Ek8yBK8tAdmPHeppkw+Xa7yDlCEhDTvwYei03JlWo1fdc7YTfLZ4tD8riJCUyAgTbszk1fQ==, } engines: { node: ">=18.0.0", npm: ">=8.0.0" } hasBin: true @@ -4546,10 +4546,10 @@ packages: } engines: { node: ">= 14.0.0" } - ts-api-utils@1.4.1: + ts-api-utils@1.4.0: resolution: { - integrity: sha512-5RU2/lxTA3YUZxju61HO2U6EoZLvBLtmV2mbTvqyu4a/7s7RmJPT+1YekhMVsQhznRWk/czIwDUg+V8Q9ZuG4w==, + integrity: sha512-032cPxaEKwM+GT3vA5JXNzIaizx388rhsSW79vGRNGXfRRAdEAn2mvk36PvK5HnOchyWZ7afLEXqYCvPCrzuzQ==, } engines: { node: ">=16" } peerDependencies: @@ -4713,10 +4713,10 @@ packages: } engines: { node: ">=10" } - type-fest@4.28.0: + type-fest@4.27.0: resolution: { - integrity: sha512-jXMwges/FVbFRe5lTMJZVEZCrO9kI9c8k0PA/z7nF3bo0JSCCLysvokFjNPIUK/itEMas10MQM+AiHoHt/T/XA==, + integrity: sha512-3IMSWgP7C5KSQqmo1wjhKrwsvXAtF33jO3QY+Uy++ia7hqvgSK6iXbbg5PbDBc1P2ZbNEDgejOrN4YooXvhwCw==, } engines: { node: ">=16" } @@ -5609,7 +5609,7 @@ snapshots: string-length: 6.0.0 strip-ansi: 7.1.0 ts-toolbelt: 9.6.0 - type-fest: 4.28.0 + type-fest: 4.27.0 zod: 3.23.8 "@molt/types@0.2.0": @@ -5651,58 +5651,58 @@ snapshots: "@pkgr/core@0.1.1": {} - "@rollup/rollup-android-arm-eabi@4.27.4": + "@rollup/rollup-android-arm-eabi@4.27.3": optional: true - "@rollup/rollup-android-arm64@4.27.4": + "@rollup/rollup-android-arm64@4.27.3": optional: true - "@rollup/rollup-darwin-arm64@4.27.4": + "@rollup/rollup-darwin-arm64@4.27.3": optional: true - "@rollup/rollup-darwin-x64@4.27.4": + "@rollup/rollup-darwin-x64@4.27.3": optional: true - "@rollup/rollup-freebsd-arm64@4.27.4": + "@rollup/rollup-freebsd-arm64@4.27.3": optional: true - "@rollup/rollup-freebsd-x64@4.27.4": + "@rollup/rollup-freebsd-x64@4.27.3": optional: true - "@rollup/rollup-linux-arm-gnueabihf@4.27.4": + "@rollup/rollup-linux-arm-gnueabihf@4.27.3": optional: true - "@rollup/rollup-linux-arm-musleabihf@4.27.4": + "@rollup/rollup-linux-arm-musleabihf@4.27.3": optional: true - "@rollup/rollup-linux-arm64-gnu@4.27.4": + "@rollup/rollup-linux-arm64-gnu@4.27.3": optional: true - "@rollup/rollup-linux-arm64-musl@4.27.4": + "@rollup/rollup-linux-arm64-musl@4.27.3": optional: true - "@rollup/rollup-linux-powerpc64le-gnu@4.27.4": + "@rollup/rollup-linux-powerpc64le-gnu@4.27.3": optional: true - "@rollup/rollup-linux-riscv64-gnu@4.27.4": + "@rollup/rollup-linux-riscv64-gnu@4.27.3": optional: true - "@rollup/rollup-linux-s390x-gnu@4.27.4": + "@rollup/rollup-linux-s390x-gnu@4.27.3": optional: true - "@rollup/rollup-linux-x64-gnu@4.27.4": + "@rollup/rollup-linux-x64-gnu@4.27.3": optional: true - "@rollup/rollup-linux-x64-musl@4.27.4": + "@rollup/rollup-linux-x64-musl@4.27.3": optional: true - "@rollup/rollup-win32-arm64-msvc@4.27.4": + "@rollup/rollup-win32-arm64-msvc@4.27.3": optional: true - "@rollup/rollup-win32-ia32-msvc@4.27.4": + "@rollup/rollup-win32-ia32-msvc@4.27.3": optional: true - "@rollup/rollup-win32-x64-msvc@4.27.4": + "@rollup/rollup-win32-x64-msvc@4.27.3": optional: true "@scure/base@1.1.9": {} @@ -5801,7 +5801,7 @@ snapshots: graphemer: 1.4.0 ignore: 5.3.2 natural-compare: 1.4.0 - ts-api-utils: 1.4.1(typescript@5.5.4) + ts-api-utils: 1.4.0(typescript@5.5.4) optionalDependencies: typescript: 5.5.4 transitivePeerDependencies: @@ -5860,7 +5860,7 @@ snapshots: "@typescript-eslint/utils": 7.18.0(eslint@8.56.0)(typescript@5.5.4) debug: 4.3.7 eslint: 8.56.0 - ts-api-utils: 1.4.1(typescript@5.5.4) + ts-api-utils: 1.4.0(typescript@5.5.4) optionalDependencies: typescript: 5.5.4 transitivePeerDependencies: @@ -5893,7 +5893,7 @@ snapshots: is-glob: 4.0.3 minimatch: 9.0.5 semver: 7.6.3 - ts-api-utils: 1.4.1(typescript@5.5.4) + ts-api-utils: 1.4.0(typescript@5.5.4) optionalDependencies: typescript: 5.5.4 transitivePeerDependencies: @@ -6127,7 +6127,7 @@ snapshots: browserslist@4.24.2: dependencies: - caniuse-lite: 1.0.30001684 + caniuse-lite: 1.0.30001683 electron-to-chromium: 1.5.64 node-releases: 2.0.18 update-browserslist-db: 1.1.1(browserslist@4.24.2) @@ -6140,7 +6140,7 @@ snapshots: camelcase@6.3.0: {} - caniuse-lite@1.0.30001684: {} + caniuse-lite@1.0.30001683: {} chai@4.3.10: dependencies: @@ -7307,28 +7307,28 @@ snapshots: dependencies: glob: 7.2.3 - rollup@4.27.4: + rollup@4.27.3: dependencies: "@types/estree": 1.0.6 optionalDependencies: - "@rollup/rollup-android-arm-eabi": 4.27.4 - "@rollup/rollup-android-arm64": 4.27.4 - "@rollup/rollup-darwin-arm64": 4.27.4 - "@rollup/rollup-darwin-x64": 4.27.4 - "@rollup/rollup-freebsd-arm64": 4.27.4 - "@rollup/rollup-freebsd-x64": 4.27.4 - "@rollup/rollup-linux-arm-gnueabihf": 4.27.4 - "@rollup/rollup-linux-arm-musleabihf": 4.27.4 - "@rollup/rollup-linux-arm64-gnu": 4.27.4 - "@rollup/rollup-linux-arm64-musl": 4.27.4 - "@rollup/rollup-linux-powerpc64le-gnu": 4.27.4 - "@rollup/rollup-linux-riscv64-gnu": 4.27.4 - "@rollup/rollup-linux-s390x-gnu": 4.27.4 - "@rollup/rollup-linux-x64-gnu": 4.27.4 - "@rollup/rollup-linux-x64-musl": 4.27.4 - "@rollup/rollup-win32-arm64-msvc": 4.27.4 - "@rollup/rollup-win32-ia32-msvc": 4.27.4 - "@rollup/rollup-win32-x64-msvc": 4.27.4 + "@rollup/rollup-android-arm-eabi": 4.27.3 + "@rollup/rollup-android-arm64": 4.27.3 + "@rollup/rollup-darwin-arm64": 4.27.3 + "@rollup/rollup-darwin-x64": 4.27.3 + "@rollup/rollup-freebsd-arm64": 4.27.3 + "@rollup/rollup-freebsd-x64": 4.27.3 + "@rollup/rollup-linux-arm-gnueabihf": 4.27.3 + "@rollup/rollup-linux-arm-musleabihf": 4.27.3 + "@rollup/rollup-linux-arm64-gnu": 4.27.3 + "@rollup/rollup-linux-arm64-musl": 4.27.3 + "@rollup/rollup-linux-powerpc64le-gnu": 4.27.3 + "@rollup/rollup-linux-riscv64-gnu": 4.27.3 + "@rollup/rollup-linux-s390x-gnu": 4.27.3 + "@rollup/rollup-linux-x64-gnu": 4.27.3 + "@rollup/rollup-linux-x64-musl": 4.27.3 + "@rollup/rollup-win32-arm64-msvc": 4.27.3 + "@rollup/rollup-win32-ia32-msvc": 4.27.3 + "@rollup/rollup-win32-x64-msvc": 4.27.3 fsevents: 2.3.3 run-parallel@1.2.0: @@ -7500,7 +7500,7 @@ snapshots: triple-beam@1.4.1: {} - ts-api-utils@1.4.1(typescript@5.5.4): + ts-api-utils@1.4.0(typescript@5.5.4): dependencies: typescript: 5.5.4 @@ -7603,7 +7603,7 @@ snapshots: type-fest@0.20.2: {} - type-fest@4.28.0: {} + type-fest@4.27.0: {} typescript@5.2.2: {} @@ -7703,7 +7703,7 @@ snapshots: dependencies: esbuild: 0.21.5 postcss: 8.4.49 - rollup: 4.27.4 + rollup: 4.27.3 optionalDependencies: "@types/node": 20.3.1 fsevents: 2.3.3