Skip to content
Open
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
1 change: 1 addition & 0 deletions packages/contracts/hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
getEtherscanApiKeys,
getNetworkRpcUrls,
} from "./tasks/helpers/constants";
import "./tasks/runner/claim";
import "./tasks/runner/initPoll";
import "./tasks/runner/merge";
import "./tasks/runner/prove";
Expand Down
3 changes: 3 additions & 0 deletions packages/contracts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@
"submitOnChain": "hardhat submitOnChain",
"submitOnChain:localhost": "pnpm run submitOnChain",
"submitOnChain:optimism-sepolia": "pnpm run submitOnChain --network optimism_sepolia",
"claim": "hardhat claim",
"claim:localhost": "pnpm run claim",
"claim:optimism-sepolia": "pnpm run claim --network optimism_sepolia",
"upload-round-metadata": "ts-node ./scripts/uploadRoundMetadata.ts"
},
"dependencies": {
Expand Down
139 changes: 139 additions & 0 deletions packages/contracts/tasks/runner/claim.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/* eslint-disable no-console */
import { task, types } from "hardhat/config";
import { ContractStorage, Deployment, type TallyData } from "maci-contracts";
import { genTreeProof } from "maci-crypto";

import fs from "fs";

import { type MACI, type Poll, type Tally } from "../../typechain-types";
import { EContracts } from "../helpers/constants";

/**
* Interface that represents claim task arguments
*/
interface IClaimParams {
poll: string;
tallyFile: string;
index: number;
}
Comment on lines +11 to +18

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Types should not be defined here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There were types in initPoll task as well, where should I put them?


/**
* Claim hardhat task for claiming payout for a specific project
*/
task("claim", "Command to claim payout for a project")
.addParam("poll", "The poll id", undefined, types.string)
.addParam("tallyFile", "The file containing the tally data", undefined, types.string)
.addParam("index", "The index of the project to claim for", undefined, types.int)
.setAction(async ({ poll, tallyFile, index }: IClaimParams, hre) => {
const deployment = Deployment.getInstance();

deployment.setHre(hre);
deployment.setContractNames(EContracts);

const storage = ContractStorage.getInstance();
const signer = await deployment.getDeployer();
const { network } = hre;

const startBalance = await signer.provider.getBalance(signer);

console.log("Start balance: ", Number(startBalance / 10n ** 12n) / 1e6);

const {
MACI__factory: MACIFactory,
Poll__factory: PollFactory,
Tally__factory: TallyFactory,
} = await import("../../typechain-types");

// Get MACI contract
const maciContractAddress = storage.mustGetAddress(EContracts.MACI, network.name);
const maciContract = await deployment.getContract<MACI>({
name: EContracts.MACI,
address: maciContractAddress,
abi: MACIFactory.abi,
});

// Get Poll and Tally contracts
const pollContracts = await maciContract.polls(poll);
const [pollContract, tallyContract] = await Promise.all([
deployment.getContract<Poll>({
name: EContracts.Poll,
address: pollContracts.poll,
abi: PollFactory.abi,
}),
deployment.getContract<Tally>({
name: EContracts.Tally,
address: pollContracts.tally,
abi: TallyFactory.abi,
}),
]);

// Check if already claimed
const isClaimed = await tallyContract.claimed(index);

if (isClaimed) {
throw new Error(`Project at index ${index} has already claimed their payout`);
}

// Read tally data from file
if (!fs.existsSync(tallyFile)) {
throw new Error(`Tally file ${tallyFile} does not exist`);
}

const tallyData = await fs.promises
.readFile(tallyFile, "utf8")
.then((result) => JSON.parse(result) as unknown as TallyData);

// Get tree depths from poll contract
const treeDepths = await pollContract.treeDepths();
const voteOptionTreeDepth = Number(treeDepths[3]);

// Prepare claim parameters
const tallyResults = tallyData.results.tally.map((x) => BigInt(x));

// Validate index
if (index < 0 || index >= tallyResults.length) {
throw new Error(`Invalid project index ${index}. Must be between 0 and ${tallyResults.length - 1}`);
}

// Generate merkle proof for the tally result
const tallyResultProof = genTreeProof(index, tallyResults, voteOptionTreeDepth);

const claimParams = {
index,
voiceCreditsPerOption: tallyData.perVOSpentVoiceCredits!.tally[index],
tallyResultProof,
tallyResultSalt: tallyData.results.salt,
voteOptionTreeDepth,
spentVoiceCreditsHash: tallyData.totalSpentVoiceCredits.commitment,
perVOSpentVoiceCreditsHash: tallyData.perVOSpentVoiceCredits!.commitment,
};

console.log(`\nClaiming payout for project at index ${index}...`);
console.log(`Tally result: ${tallyResults[index]}`);
console.log(`Voice credits per option: ${claimParams.voiceCreditsPerOption}`);

// Get allocated amount before claiming
const allocatedAmount = await tallyContract.getAllocatedAmount(
claimParams.index,
claimParams.voiceCreditsPerOption,
);

console.log(`Allocated amount: ${allocatedAmount.toString()}`);

// Execute claim transaction
const tx = await tallyContract.claim(claimParams);
const receipt = await tx.wait();

if (receipt?.status === 1) {
console.log(`\n✅ Successfully claimed payout for project at index ${index}`);
console.log(`Transaction hash: ${receipt.hash}`);
console.log(`Amount claimed: ${allocatedAmount.toString()}`);
} else {
console.log(`\n❌ Claim transaction failed`);
}

const endBalance = await signer.provider.getBalance(signer);

console.log("\nEnd balance: ", Number(endBalance / 10n ** 12n) / 1e6);
console.log("Claim expenses: ", Number((startBalance - endBalance) / 10n ** 12n) / 1e6);
});
Loading