Skip to content

Commit

Permalink
feat: direct grants simple event handlers
Browse files Browse the repository at this point in the history
  • Loading branch information
0xnigir1 committed Nov 18, 2024
1 parent 3dcffdb commit 6cf441a
Show file tree
Hide file tree
Showing 10 changed files with 681 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { Changeset } from "@grants-stack-indexer/repository";
import { ChainId, ProcessorEvent, StrategyEvent } from "@grants-stack-indexer/shared";

import { ProcessorDependencies, UnsupportedEventException } from "../../../internal.js";
import { BaseDistributedHandler, BaseStrategyHandler } from "../index.js";
import { DGSimpleRegisteredHandler, DGSimpleTimestampsUpdatedHandler } from "./handlers/index.js";

const STRATEGY_NAME = "allov2.DirectGrantsSimpleStrategy";

/**
* This handler is responsible for processing events related to the
* Direct Grants Simple strategy.
*
* The following events are currently handled by this strategy:
* - TimestampsUpdated
* - RegisteredWithSender
* - DistributedWithRecipientAddress
*/
export class DirectGrantsSimpleStrategyHandler extends BaseStrategyHandler {
constructor(
private readonly chainId: ChainId,
private readonly dependencies: ProcessorDependencies,
) {
super(STRATEGY_NAME);
}

/** @inheritdoc */
async handle(event: ProcessorEvent<"Strategy", StrategyEvent>): Promise<Changeset[]> {
switch (event.eventName) {
case "TimestampsUpdated":
return new DGSimpleTimestampsUpdatedHandler(
event as ProcessorEvent<"Strategy", "TimestampsUpdated">,
this.chainId,
this.dependencies,
).handle();
case "RegisteredWithSender":
return new DGSimpleRegisteredHandler(
event as ProcessorEvent<"Strategy", "RegisteredWithSender">,
this.chainId,
this.dependencies,
).handle();
case "DistributedWithRecipientAddress":
return new BaseDistributedHandler(
event as ProcessorEvent<"Strategy", "DistributedWithRecipientAddress">,
this.chainId,
this.dependencies,
).handle();
default:
throw new UnsupportedEventException("Strategy", event.eventName, this.name);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./timestampsUpdated.handler.js";
export * from "./registered.handler.js";
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { getAddress } from "viem";

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

import { IEventHandler, ProcessorDependencies } from "../../../../internal.js";
import { decodeDGApplicationData } from "../../helpers/index.js";

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

/**
* Handles the Registered event for the Donation Voting Merkle Distribution Direct Transfer strategy.
*
* This handler performs the following core actions when a project registers for a round:
* - Validates that both the project and round exist
* - Decodes the application data from the event
* - Retrieves the application metadata
* - Creates a new application record with PENDING status
* - Links the application to both the project and round
*/

export class DGSimpleRegisteredHandler
implements IEventHandler<"Strategy", "RegisteredWithSender">
{
constructor(
readonly event: ProcessorEvent<"Strategy", "RegisteredWithSender">,
private readonly chainId: ChainId,
private readonly dependencies: Dependencies,
) {}

/** @inheritdoc */
async handle(): Promise<Changeset[]> {
const { projectRepository, roundRepository, metadataProvider } = this.dependencies;
const { data: encodedData, recipientId, sender } = this.event.params;
const { blockNumber, blockTimestamp } = this.event;

const anchorAddress = getAddress(recipientId);
const project = await projectRepository.getProjectByAnchorOrThrow(
this.chainId,
anchorAddress,
);

const strategyAddress = getAddress(this.event.srcAddress);
const round = await roundRepository.getRoundByStrategyAddressOrThrow(
this.chainId,
strategyAddress,
);

const values = decodeDGApplicationData(encodedData);
const id = recipientId;

const metadata = await metadataProvider.getMetadata(values.metadata.pointer);

const application: NewApplication = {
chainId: this.chainId,
id: id,
projectId: project.id,
anchorAddress,
roundId: round.id,
status: "PENDING",
metadataCid: values.metadata.pointer,
metadata: metadata ?? null,
createdAtBlock: BigInt(blockNumber),
createdByAddress: getAddress(sender),
statusUpdatedAtBlock: BigInt(blockNumber),
statusSnapshots: [
{
status: "PENDING",
updatedAtBlock: blockNumber.toString(),
updatedAt: new Date(blockTimestamp * 1000), // timestamp is in seconds, convert to ms
},
],
distributionTransaction: null,
totalAmountDonatedInUsd: 0,
totalDonationsCount: 0,
uniqueDonorsCount: 0,
tags: ["allo-v2"],
};

return [
{
type: "InsertApplication",
args: application,
},
];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { getAddress } from "viem";

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

import { getDateFromTimestamp } from "../../../../helpers/index.js";
import { IEventHandler, ProcessorDependencies } from "../../../../internal.js";

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

/**
* Handles the TimestampsUpdated event for the Direct Grants Simple strategy.
*
* This handler processes updates to the round timestamps:
* - Validates the round exists for the strategy address
* - Converts the updated registration timestamps to dates
* - Returns a changeset to update the round's application timestamps
*/
export class DGSimpleTimestampsUpdatedHandler
implements IEventHandler<"Strategy", "TimestampsUpdated">
{
constructor(
readonly event: ProcessorEvent<"Strategy", "TimestampsUpdated">,
private readonly chainId: ChainId,
private readonly dependencies: Dependencies,
) {}

/**
* Handles the TimestampsUpdated event for the Direct Grants Simple strategy.
* @returns The changeset with an UpdateRound operation.
* @throws RoundNotFound if the round is not found.
*/
async handle(): Promise<Changeset[]> {
const strategyAddress = getAddress(this.event.srcAddress);
const round = await this.dependencies.roundRepository.getRoundByStrategyAddressOrThrow(
this.chainId,
strategyAddress,
);

const { startTime: strStartTime, endTime: strEndTime } = this.event.params;

const applicationsStartTime = getDateFromTimestamp(BigInt(strStartTime));
const applicationsEndTime = getDateFromTimestamp(BigInt(strEndTime));

return [
{
type: "UpdateRound",
args: {
chainId: this.chainId,
roundId: round.id,
round: {
applicationsStartTime,
applicationsEndTime,
},
},
},
];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./handlers/index.js";
export * from "./directGrantsSimple.handler.js";
40 changes: 40 additions & 0 deletions packages/processors/src/processors/strategy/helpers/decoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,30 @@ const DVMD_DATA_DECODER = [
},
] as const;

const DG_DATA_DECODER = [
{ name: "recipientId", type: "address" },
{ name: "registryAnchor", type: "address" },
{ name: "grantAmount", type: "uint256" },
{
name: "metadata",
type: "tuple",
components: [
{ name: "protocol", type: "uint256" },
{ name: "pointer", type: "string" },
],
},
] as const;

export type DGApplicationData = {
recipientAddress: string;
anchorAddress: string;
grantAmount: bigint;
metadata: {
protocol: number;
pointer: string;
};
};

export const decodeDVMDApplicationData = (encodedData: Hex): DVMDApplicationData => {
const decodedData = decodeAbiParameters(DVMD_DATA_DECODER, encodedData);

Expand Down Expand Up @@ -50,3 +74,19 @@ export const decodeDVMDExtendedApplicationData = (
recipientsCounter: values[1].toString(),
};
};

export const decodeDGApplicationData = (encodedData: Hex): DGApplicationData => {
const decodedData = decodeAbiParameters(DG_DATA_DECODER, encodedData);

const results: DGApplicationData = {
recipientAddress: decodedData[0],
anchorAddress: decodedData[1],
grantAmount: decodedData[2],
metadata: {
protocol: Number(decodedData[3].protocol),
pointer: decodedData[3].pointer,
},
};

return results;
};
5 changes: 5 additions & 0 deletions packages/processors/src/processors/strategy/mapping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Hex } from "viem";

import type { StrategyHandlerConstructor } from "../../internal.js";
import { DirectAllocationStrategyHandler } from "./directAllocation/index.js";
import { DirectGrantsSimpleStrategyHandler } from "./directGrantsSimple/index.js";
import { DVMDDirectTransferStrategyHandler } from "./donationVotingMerkleDistributionDirectTransfer/dvmdDirectTransfer.handler.js";

/**
Expand All @@ -21,6 +22,10 @@ const strategyIdToHandler: Readonly<Record<string, StrategyHandlerConstructor>>
DVMDDirectTransferStrategyHandler, // DonationVotingMerkleDistributionDirectTransferStrategyv2.1
"0x4cd0051913234cdd7d165b208851240d334786d6e5afbb4d0eec203515a9c6f3":
DirectAllocationStrategyHandler,
"0x263cb916541b6fc1fb5543a244829ccdba75264b097726e6ecc3c3cfce824bf5":
DirectGrantsSimpleStrategyHandler,
"0x53fb9d3bce0956ca2db5bb1441f5ca23050cb1973b33789e04a5978acfd9ca93":
DirectGrantsSimpleStrategyHandler,
} as const;

/**
Expand Down
Loading

0 comments on commit 6cf441a

Please sign in to comment.