Skip to content

Commit

Permalink
feat: base strategy class and refactor strategyId mappings
Browse files Browse the repository at this point in the history
  • Loading branch information
0xnigir1 committed Oct 25, 2024
1 parent 72a6c5a commit 8c25428
Show file tree
Hide file tree
Showing 19 changed files with 635 additions and 295 deletions.
45 changes: 21 additions & 24 deletions packages/processors/src/allo/handlers/poolCreated.handler.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getAddress, parseUnits, zeroAddress } from "viem";
import { getAddress, zeroAddress } from "viem";

import type { Changeset, NewRound, PendingRoundRole } from "@grants-stack-indexer/repository";
import type { ChainId, ProtocolEvent, Token } from "@grants-stack-indexer/shared";
Expand All @@ -7,10 +7,10 @@ import { getToken } from "@grants-stack-indexer/shared/dist/src/internal.js";

import type { IEventHandler, ProcessorDependencies, StrategyTimings } from "../../internal.js";
import { getRoundRoles } from "../../helpers/roles.js";
import { extractStrategyFromId, getStrategyTimings } from "../../helpers/strategy.js";
import { calculateAmountInUsd } from "../../helpers/tokenMath.js";
import { TokenPriceNotFoundError } from "../../internal.js";
import { RoundMetadataSchema } from "../../schemas/index.js";
import { StrategyHandlerFactory } from "../../strategy/strategyHandler.factory.js";

type Dependencies = Pick<
ProcessorDependencies,
Expand Down Expand Up @@ -61,7 +61,13 @@ export class PoolCreatedHandler implements IEventHandler<"Allo", "PoolCreated">
? zeroAddress
: checksummedTokenAddress;

const strategy = extractStrategyFromId(strategyId);
const strategyHandler = StrategyHandlerFactory.createHandler(
this.chainId,
this.dependencies as ProcessorDependencies,
strategyId,
);

// const strategy = extractStrategyFromId(strategyId);

const token = getToken(this.chainId, matchTokenAddress);

Expand All @@ -72,26 +78,17 @@ export class PoolCreatedHandler implements IEventHandler<"Allo", "PoolCreated">
donationsEndTime: null,
};

let matchAmount = 0n;
let matchAmountInUsd = "0";

if (strategy) {
strategyTimings = await getStrategyTimings(evmProvider, strategy, strategyAddress);

//TODO: when creating strategy handlers, should this be moved there?
if (
strategy.name === "allov2.DonationVotingMerkleDistributionDirectTransferStrategy" &&
parsedRoundMetadata.success &&
token
) {
matchAmount = parseUnits(
parsedRoundMetadata.data.quadraticFundingConfig.matchingFundsAvailable.toString(),
token.decimals,
);
let matchAmount = {
matchAmount: 0n,
matchAmountInUsd: "0",
};

matchAmountInUsd = await this.getTokenAmountInUsd(
if (strategyHandler) {
strategyTimings = await strategyHandler.fetchStrategyTimings(strategyAddress);
if (parsedRoundMetadata.success && token) {
matchAmount = await strategyHandler.fetchMatchAmount(
Number(parsedRoundMetadata.data.quadraticFundingConfig.matchingFundsAvailable),
token,
matchAmount,
this.event.blockTimestamp,
);
}
Expand Down Expand Up @@ -120,8 +117,8 @@ export class PoolCreatedHandler implements IEventHandler<"Allo", "PoolCreated">
totalAmountDonatedInUsd: "0",
uniqueDonorsCount: 0,
matchTokenAddress,
matchAmount,
matchAmountInUsd,
matchAmount: matchAmount.matchAmount,
matchAmountInUsd: matchAmount.matchAmountInUsd,
fundedAmount,
fundedAmountInUsd,
applicationMetadataCid: metadataPointer,
Expand All @@ -132,7 +129,7 @@ export class PoolCreatedHandler implements IEventHandler<"Allo", "PoolCreated">
...roundRoles,
strategyAddress,
strategyId,
strategyName: strategy?.name ?? "",
strategyName: strategyHandler?.name ?? "",
createdByAddress: getAddress(createdBy),
createdAtBlock: BigInt(this.event.blockNumber),
updatedAtBlock: BigInt(this.event.blockNumber),
Expand Down
2 changes: 2 additions & 0 deletions packages/processors/src/external.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// Add your external exports here
export { StrategyProcessor, AlloProcessor } from "./internal.js";
export type { IProcessor } from "./internal.js";

export { existsHandler } from "./internal.js";
224 changes: 2 additions & 222 deletions packages/processors/src/helpers/strategy.ts
Original file line number Diff line number Diff line change
@@ -1,231 +1,11 @@
import type { EvmProvider } from "@grants-stack-indexer/chain-providers";
import type { Address, Branded } from "@grants-stack-indexer/shared";
import type { Address } from "@grants-stack-indexer/shared";

import DirectGrantsLiteStrategy from "../abis/allo-v2/v1/DirectGrantsLiteStrategy.js";
import DonationVotingMerkleDistributionDirectTransferStrategy from "../abis/allo-v2/v1/DonationVotingMerkleDistributionDirectTransferStrategy.js";
import { StrategyTimings } from "../internal.js";
import { getDateFromTimestamp } from "./utils.js";

type SanitizedStrategyId = Branded<string, "SanitizedStrategyId">;
type Strategy = {
id: SanitizedStrategyId;
name: string | null;
// TODO: check if groups are required
groups: string[];
};

//TODO: refactor this into a mapping in Shared package from ID to the corresponding handler class
/*
* Extracts the strategy from the ID.
* @param _id - The ID of the strategy.
* @returns The strategy.
*/
export function extractStrategyFromId(_id: Address): Strategy | undefined {
const id = _id.toLowerCase();
/* eslint-disable no-fallthrough */
switch (id) {
// SQFSuperfluidv1
case "0xf8a14294e80ff012e54157ec9d1b2827421f1e7f6bde38c06730b1c031b3f935":
return {
id: id as SanitizedStrategyId,
name: "allov2.SQFSuperFluidStrategy",
groups: ["allov2.SQFSuperFluidStrategy"],
};

// MicroGrantsv1
case "0x697f0592ebd05466d2d24454477e11d69c475d7a7c4134f15ddc1ea9811bb16f":
return {
id: id as SanitizedStrategyId,
name: "allov2.MicroGrantsStrategy",
groups: ["allov2.MicroGrantsStrategy", "allov2.MicroGrantsCommon"],
};

// MicroGrantsGovv1
case "0x741ac1e2f387d83f219f6b5349d35ec34902cf94019d117335e0045d2e0ed912":
return {
id: id as SanitizedStrategyId,
name: "allov2.MicroGrantsGovStrategy",
groups: ["allov2.MicroGrantsGovStrategy", "allov2.MicroGrantsCommon"],
};

// MicroGrantsHatsv1
case "0x5aa24dcfcd55a1e059a172e987b3456736b4856c71e57aaf52e9a965897318dd":
return {
id: id as SanitizedStrategyId,
name: "allov2.MicroGrantsHatsStrategy",
groups: ["allov2.MicroGrantsHatsStrategy", "allov2.MicroGrantsCommon"],
};

// RFPSimpleStrategyv1.0
case "0x0d459e12d9e91d2b2a8fa12be8c7eb2b4f1c35e74573990c34b436613bc2350f":
return {
id: id as SanitizedStrategyId,
name: "allov2.RFPSimpleStrategy",
groups: ["allov2.RFPSimpleStrategy"],
};

// RFPCommitteeStrategyv1.0
case "0x7d143166a83c6a8a303ae32a6ccd287e48d79818f5d15d89e185391199909803":
return {
id: id as SanitizedStrategyId,
name: "allov2.RFPCommitteeStrategy",
groups: ["allov2.RFPCommitteeStrategy"],
};

// QVSimpleStrategyv1.0
case "0x22d006e191d6dc5ff1a25bb0733f47f64a9c34860b6703df88dea7cb3987b4c3":
return {
id: id as SanitizedStrategyId,
name: "allov2.QVSimpleStrategy",
groups: ["allov2.QVSimpleStrategy"],
};

// DonationVotingMerkleDistributionDirectTransferStrategyv1.0
case "0x6f9291df02b2664139cec5703c124e4ebce32879c74b6297faa1468aa5ff9ebf":
// DonationVotingMerkleDistributionDirectTransferStrategyv1.1
case "0x2f46bf157821dc41daa51479e94783bb0c8699eac63bf75ec450508ab03867ce":
// DonationVotingMerkleDistributionDirectTransferStrategyv2.0
case "0x2f0250d534b2d59b8b5cfa5eb0d0848a59ccbf5de2eaf72d2ba4bfe73dce7c6b":
// DonationVotingMerkleDistributionDirectTransferStrategyv2.1
case "0x9fa6890423649187b1f0e8bf4265f0305ce99523c3d11aa36b35a54617bb0ec0":
return {
id: id as SanitizedStrategyId,
name: "allov2.DonationVotingMerkleDistributionDirectTransferStrategy",
groups: ["allov2.DonationVotingMerkleDistributionDirectTransferStrategy"],
};

// DonationVotingMerkleDistributionVaultStrategyv1.0
case "0x7e75375f0a7cd9f7ea159c8b065976e4f764f9dcef1edf692f31dd1842f70c87":
// DonationVotingMerkleDistributionVaultStrategyv1.1
case "0x093072375737c0e8872fef36808849aeba7f865e182d495f2b98308115c9ef13":
return {
id: id as SanitizedStrategyId,
name: "allov2.DonationVotingMerkleDistributionVaultStrategy",
groups: ["allov2.DonationVotingMerkleDistributionVaultStrategy"],
};

// DirectGrantsSimpleStrategyv1.1
case "0x263cb916541b6fc1fb5543a244829ccdba75264b097726e6ecc3c3cfce824bf5":
// DirectGrantsSimpleStrategyv2.1
case "0x53fb9d3bce0956ca2db5bb1441f5ca23050cb1973b33789e04a5978acfd9ca93":
return {
id: id as SanitizedStrategyId,
name: "allov2.DirectGrantsSimpleStrategy",
groups: ["allov2.DirectGrantsSimpleStrategy"],
};

// DirectGrantsLiteStrategyv1.0
case "0x103732a8e473467a510d4128ee11065262bdd978f0d9dad89ba68f2c56127e27":
return {
id: id as SanitizedStrategyId,
name: "allov2.DirectGrantsLiteStrategy",
groups: ["allov2.DirectGrantsLiteStrategy"],
};

// EasyRPGFStrategy1.0
case "0x662f5a0d3ea7e9b6ed1b351a9d96ac636a3c3ed727390aeff4ec931ae760d5ae":
return {
id: id as SanitizedStrategyId,
name: "allov2.EasyRPGFStrategy",
groups: ["allov2.EasyRPGFStrategy"],
};

// DirectAllocationStrategyv1.1
case "0x4cd0051913234cdd7d165b208851240d334786d6e5afbb4d0eec203515a9c6f3":
return {
id: id as SanitizedStrategyId,
name: "allov2.DirectAllocationStrategy",
groups: ["allov2.DirectAllocationStrategy"],
};
}

return undefined;
}

//TODO: refactor this into the StrategyHandler when implemented
// see if we can use a common interface or abstract class for all strategies
// so we don't have to do this switch statement
// most of the strategies don't need to fetch anything and just return null for all the times
export const getStrategyTimings = async (
evmProvider: EvmProvider,
strategy: Strategy,
strategyAddress: Address,
): Promise<StrategyTimings> => {
switch (strategy.name) {
case "allov2.DonationVotingMerkleDistributionDirectTransferStrategy":
return getDonationVotingMerkleDistributionDirectTransferStrategyTimings(
evmProvider,
strategyAddress,
);
case "allov2.DirectGrantsSimpleStrategy":
case "allov2.DirectGrantsLiteStrategy":
return getDirectGrantsStrategyTimings(evmProvider, strategyAddress);
default:
return {
applicationsStartTime: null,
applicationsEndTime: null,
donationsStartTime: null,
donationsEndTime: null,
};
}
};

/**
* Gets the strategy data for the DonationVotingMerkleDistributionDirectTransferStrategy
* @param evmProvider - The evm provider
* @param strategyId - The address of the strategy
* @returns The strategy data
*/
export const getDonationVotingMerkleDistributionDirectTransferStrategyTimings = async (
evmProvider: EvmProvider,
strategyId: Address,
): Promise<StrategyTimings> => {
let results: [bigint, bigint, bigint, bigint] = [0n, 0n, 0n, 0n];

const contractCalls = [
{
abi: DonationVotingMerkleDistributionDirectTransferStrategy,
functionName: "registrationStartTime",
address: strategyId,
},
{
abi: DonationVotingMerkleDistributionDirectTransferStrategy,
functionName: "registrationEndTime",
address: strategyId,
},
{
abi: DonationVotingMerkleDistributionDirectTransferStrategy,
functionName: "allocationStartTime",
address: strategyId,
},
{
abi: DonationVotingMerkleDistributionDirectTransferStrategy,
functionName: "allocationEndTime",
address: strategyId,
},
] as const;

if (evmProvider.getMulticall3Address()) {
results = await evmProvider.multicall({
contracts: contractCalls,
allowFailure: false,
});
} else {
results = (await Promise.all(
contractCalls.map((call) =>
evmProvider.readContract(call.address, call.abi, call.functionName),
),
)) as [bigint, bigint, bigint, bigint];
}

return {
applicationsStartTime: getDateFromTimestamp(results[0]),
applicationsEndTime: getDateFromTimestamp(results[1]),
donationsStartTime: getDateFromTimestamp(results[2]),
donationsEndTime: getDateFromTimestamp(results[3]),
};
};

//TODO: move this to the DirectGrantsStrategyHandler when implemented
/**
* Gets the strategy data for the DirectGrantsStrategy
* @param evmProvider - The evm provider
Expand Down
39 changes: 37 additions & 2 deletions packages/processors/src/interfaces/strategyHandler.interface.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,50 @@
import { Changeset } from "@grants-stack-indexer/repository";
import { ContractToEventName, ProtocolEvent } from "@grants-stack-indexer/shared";
import type { Changeset } from "@grants-stack-indexer/repository";
import type {
Address,
ContractToEventName,
ProtocolEvent,
Token,
} from "@grants-stack-indexer/shared";

import type { StrategyTimings } from "../internal.js";

/**
* Interface for an event handler.
* @template C - The contract name.
* @template E - The event name.
*/
export interface IStrategyHandler<E extends ContractToEventName<"Strategy">> {
/**
* The name of the strategy.
*/
name: string;

/**
* Handles the event.
* @returns A promise that resolves to an array of changesets.
*/
handle(event: ProtocolEvent<"Strategy", E>): Promise<Changeset[]>;

/**
* Fetch the strategy timings data from the strategy contract
* @param strategyAddress - The address of the strategy
* @returns The strategy timings
*/
fetchStrategyTimings(strategyAddress: Address): Promise<StrategyTimings>;

/**
* Fetch the match amount for a strategy
* @param matchingFundsAvailable - The matching funds available
* @param token - The token
* @param blockTimestamp - The block timestamp
* @returns The match amount and match amount in USD
*/
fetchMatchAmount(
matchingFundsAvailable: number,
token: Token,
blockTimestamp: number,
): Promise<{
matchAmount: bigint;
matchAmountInUsd: string;
}>;
}
12 changes: 10 additions & 2 deletions packages/processors/src/internal.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
// Add your internal exports here
// Types and interfaces
export * from "./types/index.js";
export * from "./interfaces/index.js";

// Exceptions
export * from "./exceptions/index.js";

// Allo
export * from "./allo/index.js";

// Strategy
export * from "./strategy/common/index.js";
export * from "./strategy/index.js";
export * from "./strategy/strategyHandler.factory.js";
export * from "./strategy/strategy.processor.js";
export { getHandler, existsHandler } from "./strategy/mapping.js";
Loading

0 comments on commit 8c25428

Please sign in to comment.