Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] initial strategy adapters test implementation #3426

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions packages/common/src/allo-adapters/adapter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Address, PublicClient } from "viem";
import { AdapterErrorWrapper } from "./errorWrapper";

export type AdapterResponse<T> =
| { type: "success"; value: T }
| { type: "error"; error: AdapterErrorWrapper };

export interface AllocationAdapter {
// Returns true if the allocator can allocate to the pool.
// If the pool has token gated allocations,
// this function should check if the allocator has the required tokens.
// allocatorAddress is undefined if the user's wallet is not connected.
// In some cases the adapter doesn't need the address to determine if the allocator can allocate.
canAllocate: (
client: PublicClient,
poolId: string,
allocatorAddress: Address | undefined
) => Promise<AdapterResponse<boolean>>;

// Returns true if the pool requires an amount
// to be specified by the allocator in the UI.
requiresAmount: (client: PublicClient, poolId: string) => Promise<boolean>;
}

export interface Adapter {
allocation?: AllocationAdapter;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Address, PublicClient } from "viem";
import { AdapterResponse } from "../../adapter";

export function canAllocate(
_client: PublicClient,
_poolId: string,
_allocatorAddress: Address | undefined
): Promise<AdapterResponse<boolean>> {
// This is a test to simulate a delay in the response.
return new Promise((resolve) => {
// A normal Donation Strategy would return true here;
// anyone can vote.
// return resolve({
// type: "success",
// value: true,
// });

// We simulate here something like a token-gated pool
setTimeout(() => {
// Simulate an async check.
// A real example can be:
// - Check if the user has a specific token in their wallet.
// - return success if they have the token.
// - return error if they don't have the token.
return resolve({
type: "error",

error: {
type: "ADAPTER_ALLOCATION_UNAUTHORIZED",
error: new Error("you don't have a badge to vote."),
},
});
}, 2000);
});
}

export function requiresAmount(
_client: PublicClient,
_poolId: string
): Promise<boolean> {
return Promise.resolve(true);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import * as allocationAdapter from "./allocation";

export default {
allocation: allocationAdapter,
};
20 changes: 20 additions & 0 deletions packages/common/src/allo-adapters/errorWrapper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export type AdapterNotFoundError = {
type: "ADAPTER_NOT_FOUND";
strategyName: string;
error: Error;
};

export type AdapterAllocationUnauthorizedError = {
type: "ADAPTER_ALLOCATION_UNAUTHORIZED";
error: Error;
};

export type AdapterUnknownError = {
type: "ADAPTER_UNKNOWN_ERROR";
error: Error;
};

export type AdapterErrorWrapper =
| AdapterNotFoundError
| AdapterAllocationUnauthorizedError
| AdapterUnknownError;
63 changes: 63 additions & 0 deletions packages/common/src/allo-adapters/hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { Address } from "wagmi";
import { PublicClient } from "viem";
import { useState, useEffect } from "react";
import { getAllocationAdapter } from "./index";
import { AdapterErrorWrapper } from "./errorWrapper";

type AdapterResponseWrapper<T> = {
loading: boolean;
error: AdapterErrorWrapper | undefined;
value: T | undefined;
};

export function useAdapterCanAllocate(
client: PublicClient | undefined,
poolId: string | undefined,
strategyName: string | undefined,
address: Address | undefined
): AdapterResponseWrapper<boolean> {
const [loading, setLoading] = useState(false);
const [error, setError] = useState<AdapterErrorWrapper>();
const [value, setValue] = useState<boolean>();

useEffect(() => {
if (
client === undefined ||
poolId === undefined ||
strategyName === undefined
) {
return;
}

setLoading(true);

const adapter = getAllocationAdapter(strategyName);
if (adapter === undefined) {
setError({
type: "ADAPTER_NOT_FOUND",
strategyName,
error: new Error(`Adapter not found for strategy: ${strategyName}`),
});
setLoading(false);
return;
}

adapter
.canAllocate(client, poolId, address)
.then((resp) => {
if (resp.type === "success") {
setValue(resp.value);
} else {
setError(resp.error);
}
})
.catch((e: AdapterErrorWrapper) => {
setError(e);
})
.finally(() => {
setLoading(false);
});
}, [client, poolId, strategyName]);

Check warning on line 60 in packages/common/src/allo-adapters/hooks.ts

View workflow job for this annotation

GitHub Actions / lint-test-typecheck

React Hook useEffect has a missing dependency: 'address'. Either include it or remove the dependency array

return { loading, error, value };
}
12 changes: 12 additions & 0 deletions packages/common/src/allo-adapters/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { AllocationAdapter, Adapter } from "./adapter";
import alloV2QF from "./adapters/allov2.QF";

export const adapters: { [key: string]: Adapter } = {
"allov2.DonationVotingMerkleDistributionDirectTransferStrategy": alloV2QF,
};

export function getAllocationAdapter(
strategyName: string
): AllocationAdapter | undefined {
return adapters[strategyName]?.allocation;
}
114 changes: 0 additions & 114 deletions packages/data-layer/src/backends/legacy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,125 +5,11 @@ import {
MetadataPointer,
PayoutStrategy,
Project,
Round,
RoundOverview,
RoundVisibilityType,
TimeFilterVariables,
} from "../data.types";

export const getRoundById = async (
{ roundId, chainId }: { roundId: string; chainId: number },
{
graphqlEndpoint,
ipfsGateway,
}: { ipfsGateway: string; graphqlEndpoint: string },
): Promise<Round> => {
try {
// get the subgraph for round by $roundId
const res: GetRoundByIdResult = await graphql_fetch(
`
query GetRoundById($roundId: String) {
rounds(where: {
id: $roundId
}) {
id
program {
id
}
roundMetaPtr {
protocol
pointer
}
applicationMetaPtr {
protocol
pointer
}
applicationsStartTime
applicationsEndTime
roundStartTime
roundEndTime
token
payoutStrategy {
id
strategyName
}
votingStrategy
projectsMetaPtr {
pointer
}
projects(
first: 1000
where:{
status: 1
}
) {
id
project
status
applicationIndex
metaPtr {
protocol
pointer
}
}
}
}
`,
graphqlEndpoint,
{ roundId: roundId.toLowerCase() },
);

const round: RoundResult = res.data.rounds[0];

const roundMetadata: RoundMetadata = await fetchFromIPFS(
round.roundMetaPtr.pointer,
{ ipfsGateway },
);

round.projects = round.projects.map((project) => {
return {
...project,
status: convertStatus(project.status),
};
});

const approvedProjectsWithMetadata = await loadApprovedProjectsMetadata(
round,
chainId,
{ graphqlEndpoint, ipfsGateway },
);

return {
id: roundId,
roundMetadata,
applicationsStartTime: new Date(
parseInt(round.applicationsStartTime) * 1000,
),
applicationsEndTime: new Date(parseInt(round.applicationsEndTime) * 1000),
roundStartTime: new Date(parseInt(round.roundStartTime) * 1000),
roundEndTime: new Date(parseInt(round.roundEndTime) * 1000),
token: round.token,
payoutStrategy: round.payoutStrategy,
votingStrategy: round.votingStrategy,
ownedBy: round.program.id,
approvedProjects: approvedProjectsWithMetadata,
};
} catch (error) {
throw Error(`Unable to fetch round ${roundId} on chain ${chainId}`, {
cause: error,
});
}
};

/**
* Shape of subgraph response
*/
interface GetRoundByIdResult {
data: {
rounds: RoundResult[];
};
}

const graphql_fetch = async (
query: string,
endpoint: string,
Expand Down
1 change: 1 addition & 0 deletions packages/data-layer/src/data-layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -640,6 +640,7 @@ export class DataLayer {
return {
round: {
id: round.id,
strategyName: round.strategyName,
chainId: round.chainId,
applicationsStartTime: new Date(round.applicationsStartTime),
applicationsEndTime: new Date(round.applicationsEndTime),
Expand Down
2 changes: 2 additions & 0 deletions packages/data-layer/src/data.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,8 @@ export interface Round {
*/
token: string;

strategyName: RoundPayoutType;

/**
* Contract address of the program to which the round belongs
*/
Expand Down
6 changes: 3 additions & 3 deletions packages/grant-explorer/src/context/RoundContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,9 @@ function fetchRoundsById(
dispatch({ type: ActionType.ADD_ROUND, payload: round });
}
})
.catch((error) =>
dispatch({ type: ActionType.SET_ERROR_GET_ROUND, payload: error })
)
.catch((error) => {
dispatch({ type: ActionType.SET_ERROR_GET_ROUND, payload: error });
})
.finally(() => dispatch({ type: ActionType.FINISH_LOADING }));
}

Expand Down
Loading
Loading