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

fix: payout issue in v2 #3440

Merged
merged 6 commits into from
May 16, 2024
Merged
Show file tree
Hide file tree
Changes from 5 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
2 changes: 1 addition & 1 deletion packages/common/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"test:watch": "vitest watch"
},
"dependencies": {
"@allo-team/allo-v2-sdk": "^1.0.71",
"@allo-team/allo-v2-sdk": "^1.0.72",
"@ethersproject/abstract-signer": "^5.7.0",
"@ethersproject/providers": "^5.7.2",
"@gitcoinco/passport-sdk-types": "^0.2.0",
Expand Down
87 changes: 68 additions & 19 deletions packages/common/src/allo/backends/allo-v2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ import { PermitSignature, getPermitType } from "../voting";
import Erc20ABI from "../abis/erc20";
import { StandardMerkleTree } from "@openzeppelin/merkle-tree";
import { buildUpdatedRowsOfApplicationStatuses } from "../application";
import { generateMerkleTree } from "./allo-v1";
import { BigNumber, utils } from "ethers";
import { Distribution } from "@allo-team/allo-v2-sdk/dist/strategies/DonationVotingMerkleDistributionStrategy/types";

function getStrategyAddress(strategy: RoundCategory, chainId: ChainId): string {
let strategyAddresses;
Expand Down Expand Up @@ -1181,46 +1181,58 @@ export class AlloV2 implements Allo {
);

// Generate merkle tree
const { tree, matchingResults } = generateMerkleTree(args.allProjects);
const { tree, matchingResults } = generateMerkleTreeV2(args.allProjects);

// Filter projects to be paid from matching results
const projectsToBePaid = matchingResults.filter((project) =>
args.projectIdsToBePaid.includes(project.projectId)
args.projectIdsToBePaid.includes(project.anchorAddress ?? "")
);

const projectsWithMerkleProof: ProjectWithMerkleProof[] = [];
const projectsWithMerkleProof: Distribution[] = [];

projectsToBePaid.forEach((project) => {
if (!project.index) {
throw new AlloError("Project index is required");
if (project.index === 0) {
// do nothing
} else {
throw new AlloError("Project index is required");
}
}
if (!project.anchorAddress) {
throw new AlloError("Anchor address is required");
}
const distribution: [number, string, BigNumber, string] = [
const distribution: [number, string, string, BigNumber] = [
project.index,
project.applicationId,
project.anchorAddress,
project.projectPayoutAddress,
project.matchAmountInToken,
project.projectId,
];

// Generate merkle proof
const validMerkleProof = tree.getProof(distribution);

projectsWithMerkleProof.push({
index: distribution[0],
recipientId: distribution[1],
amount: distribution[2],
merkleProof: validMerkleProof,
index: BigInt(distribution[0]),
recipientId: distribution[1] as Address,
amount: BigInt(distribution[3].toString()),
merkleProof: validMerkleProof as Address[],
});
});

const projectsWithMerkleProofBytes = serializeProjects(
projectsWithMerkleProof
const strategy = new DonationVotingMerkleDistributionStrategy({
chain: this.chainId,
poolId: poolId,
});

const txData = strategy.distribute(
recipientIds,
projectsWithMerkleProof,
);

const txResult = await sendTransaction(this.transactionSender, {
address: this.allo.address(),
abi: AlloAbi,
functionName: "distribute",
args: [poolId, recipientIds, projectsWithMerkleProofBytes],
const txResult = await sendRawTransaction(this.transactionSender, {
to: txData.to,
data: txData.data,
value: BigInt(txData.value),
});

emit("transaction", txResult);
Expand Down Expand Up @@ -1340,3 +1352,40 @@ export type ProjectWithMerkleProof = {
amount: BigNumber;
merkleProof: string[];
};

/**
* Generate merkle tree
*
* To get merkle Proof: tree.getProof(distributions[0]);
* @param matchingResults MatchingStatsData[]
* @returns
*/
export const generateMerkleTreeV2 = (
bhargavaparoksham marked this conversation as resolved.
Show resolved Hide resolved
matchingResults: MatchingStatsData[]
): {
distribution: [number, string, string, BigNumber][];
tree: StandardMerkleTree<[number, string, string, BigNumber]>;
matchingResults: MatchingStatsData[];
} => {
const distribution: [number, string, string, BigNumber][] = [];

matchingResults.forEach((matchingResult, index) => {
matchingResults[index].index = index;

distribution.push([
index,
matchingResult.anchorAddress ?? "",
matchingResult.projectPayoutAddress,
matchingResult.matchAmountInToken, // TODO: FIX
]);
});

const tree = StandardMerkleTree.of(distribution, [
"uint256",
"address",
"address",
"uint256",
]);

return { distribution, tree, matchingResults };
};
3 changes: 2 additions & 1 deletion packages/common/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export type MatchingStatsData = {
matchPoolPercentage: number;
projectId: string;
applicationId: string;
anchorAddress?: string;
matchAmountInToken: BigNumber;
originalMatchAmountInToken: BigNumber;
projectPayoutAddress: string;
Expand Down Expand Up @@ -110,4 +111,4 @@ export type VotingToken = {
//TODO: split PayoutTokens and VotingTokens in
// 2 different types/lists and remove the following attribute
canVote: boolean;
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ export const useGroupProjectsByPaymentStatus = (
matchPoolPercentage: matchingStatsData.matchPoolPercentage,
projectId: matchingStatsData.projectId,
applicationId: matchingStatsData.applicationId,
anchorAddress: applications?.find(
(application) =>
application.projectId === matchingStatsData.projectId
)?.anchorAddress,
matchAmountInToken: BigNumber.from(
matchingStatsData.matchAmountInToken
),
Expand All @@ -81,7 +85,7 @@ export const useGroupProjectsByPaymentStatus = (
};
}
) ?? [],
[round.matchingDistribution?.matchingDistribution]
[round.matchingDistribution?.matchingDistribution, applications]
);

useEffect(() => {
Expand Down
1 change: 1 addition & 0 deletions packages/round-manager/src/features/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ export type MatchingStatsData = {
matchPoolPercentage: number;
projectId: string;
applicationId: string;
anchorAddress?: string;
matchAmountInToken: BigNumber;
originalMatchAmountInToken: BigNumber;
projectPayoutAddress: string;
Expand Down
12 changes: 7 additions & 5 deletions packages/round-manager/src/features/round/ViewFundGrantees.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,10 @@ export function PayProjectsTable(props: {
? getAddress(props.round.payoutStrategy.id)
: props.round.id,
allProjects: props.allProjects,
projectIdsToBePaid: selectedProjects.map((p) => p.projectId),
projectIdsToBePaid:
alloVersion === "allo-v1"
? selectedProjects.map((p) => p.projectId)
: selectedProjects.map((p) => p.anchorAddress ?? ""),
})
.on("transaction", (result) => {
if (result.type === "error") {
Expand Down Expand Up @@ -494,12 +497,11 @@ export function PayProjectsTable(props: {
<button
type="button"
data-testid="pay-out-funds-button"
className="block m-3 rounded-md bg-indigo-600 py-1.5 px-3 text-center text-sm font-semibold leading-6 text-white shadow-sm hover:bg-indigo-500 disabled:text-slate-500 disabled:bg-gray-100 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
// disabled={selectedProjects.length === 0}
disabled={true}
className="block m-3 rounded-md bg-indigo-600 py-1.5 px-3 text-center text-sm font-semibold leading-6 text-white shadow-sm hover:bg-indigo-500 disabled:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
disabled={selectedProjects.length === 0}
onClick={() => handlePayOutFunds()}
>
Payout funds (Disabled)
Payout funds
</button>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ describe("View Fund Grantees", () => {
expect(screen.getByText("Wallet Address")).toBeInTheDocument();
expect(screen.getByText("Matching %")).toBeInTheDocument();
expect(screen.getByText("Payout Amount")).toBeInTheDocument();
expect(screen.getByText("Payout funds (Disabled)")).toBeInTheDocument();
expect(screen.getByText("Payout funds")).toBeInTheDocument();
});

it("displays exact list of projects in table which are to be paid", async () => {
Expand All @@ -246,7 +246,7 @@ describe("View Fund Grantees", () => {
).toBeInTheDocument();
});

it.skip("Should show the confirmation modal and close on cancel", async () => {
it("Should show the confirmation modal and close on cancel", async () => {
(useBalance as jest.Mock).mockImplementation(() => ({
data: { formatted: "0", value: ethers.utils.parseEther("1000") },
error: null,
Expand All @@ -272,7 +272,7 @@ describe("View Fund Grantees", () => {
expect(screen.queryByText("Confirm Decision")).not.toBeInTheDocument();
});

it.skip("Should show the progress modal", async () => {
it("Should show the progress modal", async () => {
(useBalance as jest.Mock).mockImplementation(() => ({
data: { formatted: "0", value: ethers.utils.parseEther("1000") },
error: null,
Expand All @@ -296,7 +296,7 @@ describe("View Fund Grantees", () => {
});
});

it.skip("Should show the warning when not enough funds in contract", async () => {
it("Should show the warning when not enough funds in contract", async () => {
(useBalance as jest.Mock).mockImplementation(() => ({
data: { formatted: "0", value: "0" },
error: null,
Expand Down
Loading
Loading