From ba485ab2736649e043d1dc28c3d9ae254e6cdc1c Mon Sep 17 00:00:00 2001 From: 0xmad <0xmad@users.noreply.github.com> Date: Thu, 3 Oct 2024 16:02:54 -0500 Subject: [PATCH] feat(contracts): add tally results - [x] Allow to add tally results and keep them onchain - [x] Update cli commands --- .../contracts/interfaces/IPayoutStrategy.sol | 61 ++ .../contracts/contracts/interfaces/IPoll.sol | 7 +- packages/contracts/contracts/maci/Poll.sol | 10 +- packages/contracts/contracts/maci/Tally.sol | 321 +++++++++++ .../contracts/contracts/maci/TallyFactory.sol | 24 + .../contracts/contracts/mocks/MockERC20.sol | 12 + packages/contracts/package.json | 7 +- .../tasks/deploy/maci/07-tallyFactory.ts | 53 ++ .../contracts/tasks/deploy/poll/01-poll.ts | 11 +- packages/contracts/tests/Maci.test.ts | 12 +- packages/contracts/tests/Poll.test.ts | 40 +- packages/contracts/tests/Tally.test.ts | 530 ++++++++++++++++++ packages/contracts/tests/constants.ts | 45 +- packages/contracts/tests/utils.ts | 69 ++- pnpm-lock.yaml | 207 +++++-- 15 files changed, 1292 insertions(+), 117 deletions(-) create mode 100644 packages/contracts/contracts/interfaces/IPayoutStrategy.sol create mode 100644 packages/contracts/contracts/maci/Tally.sol create mode 100644 packages/contracts/contracts/maci/TallyFactory.sol create mode 100644 packages/contracts/contracts/mocks/MockERC20.sol create mode 100644 packages/contracts/tasks/deploy/maci/07-tallyFactory.ts create mode 100644 packages/contracts/tests/Tally.test.ts diff --git a/packages/contracts/contracts/interfaces/IPayoutStrategy.sol b/packages/contracts/contracts/interfaces/IPayoutStrategy.sol new file mode 100644 index 00000000..0b5e2bd0 --- /dev/null +++ b/packages/contracts/contracts/interfaces/IPayoutStrategy.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import { IPoll } from "./IPoll.sol"; + +/// @title IPayoutStrategy +/// @notice Interface responsible for payout strategy +interface IPayoutStrategy { + /// @notice Strategy initialization params + struct StrategyInit { + /// @notice The cooldown duration for withdrawal extra funds + uint256 cooldownTime; + /// @notice The max contribution amount + uint256 maxContribution; + /// @notice The payout token + address payoutToken; + /// @notice The poll address + address poll; + } + + /// @notice Claim params + struct Claim { + /// @notice The index of the vote option to verify the correctness of the tally + uint256 index; + /// @notice The voice credit options received for recipient + uint256 voiceCreditsPerOption; + /// @notice Flattened array of the tally + uint256 tallyResult; + /// @notice The total amount of voice credits spent + uint256 totalSpent; + /// @notice Corresponding proof of the tally result + uint256[][] tallyResultProof; + /// @notice The respective salt in the results object in the tally.json + uint256 tallyResultSalt; + /// @notice Depth of the vote option tree + uint8 voteOptionTreeDepth; + /// @notice hashLeftRight(number of spent voice credits, spent salt) + uint256 spentVoiceCreditsHash; + /// @notice hashLeftRight(merkle root of the no spent voice + uint256 perVOSpentVoiceCreditsHash; + } + + /// @notice Total deposited amount + function totalAmount() external view returns (uint256); + + /// @notice The cooldown timeout + function cooldown() external view returns (uint256); + + /// @notice Deposit amount + /// @param amount The amount + function deposit(uint256 amount) external; + + /// @notice Withdraw extra amount + /// @param receivers The receivers addresses + /// @param amounts The amounts + function withdrawExtra(address[] calldata receivers, uint256[] calldata amounts) external; + + /// @notice Claim funds for recipient + /// @param params The claim params + function claim(Claim calldata params) external; +} diff --git a/packages/contracts/contracts/interfaces/IPoll.sol b/packages/contracts/contracts/interfaces/IPoll.sol index 04c2696c..fcd3c0d4 100644 --- a/packages/contracts/contracts/interfaces/IPoll.sol +++ b/packages/contracts/contracts/interfaces/IPoll.sol @@ -4,8 +4,9 @@ pragma solidity ^0.8.20; import { IPoll as IPollBase } from "maci-contracts/contracts/interfaces/IPoll.sol"; import { IOwnable } from "./IOwnable.sol"; +import { IRecipientRegistry } from "./IRecipientRegistry.sol"; -/// @title IPollBase +/// @title IPoll /// @notice Poll interface interface IPoll is IPollBase, IOwnable { /// @notice The initialization function. @@ -14,4 +15,8 @@ interface IPoll is IPollBase, IOwnable { /// @notice Set the poll registry. /// @param registryAddress The registry address function setRegistry(address registryAddress) external; + + /// @notice Get the poll registry. + /// @return registry The poll registry + function getRegistry() external returns (IRecipientRegistry); } diff --git a/packages/contracts/contracts/maci/Poll.sol b/packages/contracts/contracts/maci/Poll.sol index 9389234d..a889a5fc 100644 --- a/packages/contracts/contracts/maci/Poll.sol +++ b/packages/contracts/contracts/maci/Poll.sol @@ -5,6 +5,7 @@ import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; import { Poll as BasePoll } from "maci-contracts/contracts/Poll.sol"; import { ICommon } from "../interfaces/ICommon.sol"; +import { IRecipientRegistry } from "../interfaces/IRecipientRegistry.sol"; /// @title Poll /// @notice A Poll contract allows voters to submit encrypted messages @@ -90,8 +91,7 @@ contract Poll is Ownable, BasePoll, ICommon { _; } - /// @notice A modifier that causes the function to revert if the voting period is - /// over + /// @notice A modifier that causes the function to revert if the voting period is over modifier isWithinVotingDeadline() override { uint256 secondsPassed = block.timestamp - initTime; @@ -110,6 +110,12 @@ contract Poll is Ownable, BasePoll, ICommon { emit SetRegistry(registryAddress); } + /// @notice Get the poll registry. + /// @return registry The poll registry + function getRegistry() public view returns (IRecipientRegistry) { + return IRecipientRegistry(registry); + } + /// @notice The initialization function. function init() public override onlyOwner isRegistryInitialized { initTime = block.timestamp; diff --git a/packages/contracts/contracts/maci/Tally.sol b/packages/contracts/contracts/maci/Tally.sol new file mode 100644 index 00000000..5c094b7f --- /dev/null +++ b/packages/contracts/contracts/maci/Tally.sol @@ -0,0 +1,321 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; +import { Pausable } from "@openzeppelin/contracts/utils/Pausable.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { Tally as TallyBase } from "maci-contracts/contracts/Tally.sol"; + +import { IPoll } from "../interfaces/IPoll.sol"; +import { IPayoutStrategy } from "../interfaces/IPayoutStrategy.sol"; +import { IRecipientRegistry } from "../interfaces/IRecipientRegistry.sol"; + +/// @title Tally - poll tally and payout strategy +/// @notice The Tally contract is used during votes tallying and by users to verify the tally results. +/// @notice Allows users to deposit and claim rewards for recipients +contract Tally is TallyBase, IPayoutStrategy, Pausable { + using SafeERC20 for IERC20; + + /// @notice The max voice credits (MACI allows 2 ** 32 voice credits max) + uint256 private constant MAX_VOICE_CREDITS = 10 ** 9; + + /// @notice The alpha precision (needed for allocated amount calculation) + uint256 private constant ALPHA_PRECISION = 10 ** 18; + + /// @notice The payout token + IERC20 public token; + + /// @notice The poll registry + IRecipientRegistry public registry; + + /// @notice The total amount of funds deposited + uint256 public totalAmount; + + /// @notice The max contribution amount + uint256 public maxContributionAmount; + + /// @notice The voice credit factor (needed for allocated amount calculation) + uint256 public voiceCreditFactor; + + /// @notice The cooldown duration for withdrawal extra funds + uint256 public cooldown; + + /// @notice The sum of tally result squares + uint256 public totalVotesSquares; + + /// @notice Fixed recipient count for claim + uint256 public recipientCount; + + /// @notice Initialized or not + bool internal initialized; + + /// @notice custom errors + error CooldownPeriodNotOver(); + error VotingPeriodNotOver(); + error VotingPeriodOver(); + error InvalidBudget(); + error NoProjectHasMoreThanOneVote(); + error InvalidWithdrawal(); + error AlreadyInitialized(); + error NotInitialized(); + error InvalidPoll(); + error NotCompletedResults(); + error TooManyResults(); + + /// @notice Create a new Tally contract + /// @param verifierContract The Verifier contract + /// @param vkRegistryContract The VkRegistry contract + /// @param pollContract The Poll contract + /// @param mpContract The MessageProcessor contract + /// @param tallyOwner The owner of the Tally contract + /// @param pollMode The mode of the poll + constructor( + address verifierContract, + address vkRegistryContract, + address pollContract, + address mpContract, + address tallyOwner, + Mode pollMode + ) payable TallyBase(verifierContract, vkRegistryContract, pollContract, mpContract, tallyOwner, pollMode) {} + + /// @notice A modifier that causes the function to revert if the cooldown period is not over + modifier afterCooldown() { + (uint256 deployTime, uint256 duration) = poll.getDeployTimeAndDuration(); + uint256 secondsPassed = block.timestamp - deployTime; + + if (secondsPassed <= duration + cooldown) { + revert CooldownPeriodNotOver(); + } + + _; + } + + /// @notice A modifier that causes the function to revert if the voting period is over + modifier beforeVotingDeadline() { + (uint256 deployTime, uint256 duration) = poll.getDeployTimeAndDuration(); + uint256 secondsPassed = block.timestamp - deployTime; + + if (secondsPassed > duration) { + revert VotingPeriodOver(); + } + + _; + } + + /// @notice A modifier that causes the function to revert if the voting period is not over + modifier afterVotingDeadline() { + (uint256 deployTime, uint256 duration) = poll.getDeployTimeAndDuration(); + uint256 secondsPassed = block.timestamp - deployTime; + + if (secondsPassed <= duration) { + revert VotingPeriodNotOver(); + } + + _; + } + + /// @notice A modifier that causes the function to revert if the strategy is not initialized + modifier isInitialized() { + if (!initialized) { + revert NotInitialized(); + } + + _; + } + + /// @notice Initialize tally with strategy params + /// @param params The strategy initialization params + function init(IPayoutStrategy.StrategyInit calldata params) public onlyOwner { + if (initialized) { + revert AlreadyInitialized(); + } + + if (params.poll != address(poll)) { + revert InvalidPoll(); + } + + initialized = true; + cooldown = params.cooldownTime; + registry = IPoll(params.poll).getRegistry(); + token = IERC20(params.payoutToken); + maxContributionAmount = params.maxContribution; + voiceCreditFactor = params.maxContribution / MAX_VOICE_CREDITS; + } + + /// @notice Pause contract calls (deposit, claim, withdraw) + function pause() public onlyOwner { + _pause(); + } + + /// @notice Unpause contract calls (deposit, claim, withdraw) + function unpause() public onlyOwner { + _unpause(); + } + + /// @inheritdoc IPayoutStrategy + function deposit(uint256 amount) public isInitialized whenNotPaused beforeVotingDeadline { + totalAmount += amount; + + token.safeTransferFrom(msg.sender, address(this), amount); + } + + /// @inheritdoc IPayoutStrategy + function withdrawExtra( + address[] calldata receivers, + uint256[] calldata amounts + ) public override isInitialized onlyOwner whenNotPaused afterCooldown { + uint256 amountLength = amounts.length; + uint256 totalFunds = totalAmount; + uint256 sum = 0; + + for (uint256 index = 0; index < amountLength; ) { + uint256 amount = amounts[index]; + sum += amount; + + if (sum > totalFunds) { + revert InvalidWithdrawal(); + } + + totalAmount -= amount; + + unchecked { + index++; + } + } + + for (uint256 index = 0; index < amountLength; ) { + uint256 amount = amounts[index]; + address receiver = receivers[index]; + + if (amount > 0) { + token.safeTransfer(receiver, amount); + } + + unchecked { + index++; + } + } + } + + /// @inheritdoc TallyBase + function addTallyResults( + uint256[] calldata voteOptionIndices, + uint256[] calldata results, + uint256[][][] calldata tallyResultProofs, + uint256 tallyResultSalt, + uint256 spentVoiceCreditsHashes, + uint256 perVOSpentVoiceCreditsHashes + ) public override isInitialized whenNotPaused afterVotingDeadline onlyOwner { + if (recipientCount == 0) { + recipientCount = registry.recipientCount(); + } + + if (recipientCount == totalTallyResults) { + revert TooManyResults(); + } + + super.addTallyResults( + voteOptionIndices, + results, + tallyResultProofs, + tallyResultSalt, + spentVoiceCreditsHashes, + perVOSpentVoiceCreditsHashes + ); + + if (recipientCount < totalTallyResults) { + revert TooManyResults(); + } + } + + /// @inheritdoc TallyBase + function addTallyResult( + uint256 voteOptionIndex, + uint256 tallyResult, + uint256[][] calldata tallyResultProof, + uint256 tallyResultSalt, + uint256 spentVoiceCreditsHash, + uint256 perVOSpentVoiceCreditsHash, + uint8 voteOptionTreeDepth + ) internal override { + super.addTallyResult( + voteOptionIndex, + tallyResult, + tallyResultProof, + tallyResultSalt, + spentVoiceCreditsHash, + perVOSpentVoiceCreditsHash, + voteOptionTreeDepth + ); + + totalVotesSquares += tallyResult ** 2; + } + + /// @inheritdoc IPayoutStrategy + function claim( + IPayoutStrategy.Claim calldata params + ) public override isInitialized whenNotPaused afterVotingDeadline { + uint256 amount = getAllocatedAmount(params.voiceCreditsPerOption, params.totalSpent, params.tallyResult); + totalAmount -= amount; + + bool isValid = verifyTallyResult( + params.index, + params.tallyResult, + params.tallyResultProof, + params.tallyResultSalt, + params.voteOptionTreeDepth, + params.spentVoiceCreditsHash, + params.perVOSpentVoiceCreditsHash + ); + + if (!isValid) { + revert InvalidTallyVotesProof(); + } + + IRecipientRegistry.Recipient memory recipient = registry.getRecipient(params.index); + + token.safeTransfer(recipient.recipient, amount); + } + + /// @notice Get allocated token amount (without verification). + /// @param voiceCreditsPerOption The voice credit options received for recipient + /// @param totalSpent The total amount of voice credits spent + /// @param tallyResult The tally result for recipient + function getAllocatedAmount( + uint256 voiceCreditsPerOption, + uint256 totalSpent, + uint256 tallyResult + ) internal view returns (uint256) { + uint256 alpha = calculateAlpha(totalSpent); + uint256 quadratic = alpha * voiceCreditFactor * tallyResult * tallyResult; + uint256 totalSpentCredits = voiceCreditFactor * voiceCreditsPerOption; + uint256 linearPrecision = ALPHA_PRECISION * totalSpentCredits; + uint256 linearAlpha = alpha * totalSpentCredits; + + return ((quadratic + linearPrecision) - linearAlpha) / ALPHA_PRECISION; + } + + /// @notice Calculate the alpha for the capital constrained quadratic formula + /// @dev page 17 of https://arxiv.org/pdf/1809.06421.pdf + /// @param totalSpent The total amount of voice credits spent + function calculateAlpha(uint256 totalSpent) internal view returns (uint256) { + uint256 budget = token.balanceOf(address(this)); + + uint256 contributions = totalSpent * voiceCreditFactor; + + if (budget < contributions) { + revert InvalidBudget(); + } + + if (totalVotesSquares <= totalSpent) { + revert NoProjectHasMoreThanOneVote(); + } + + if (recipientCount != totalTallyResults) { + revert NotCompletedResults(); + } + + return ((budget - contributions) * ALPHA_PRECISION) / (voiceCreditFactor * (totalVotesSquares - totalSpent)); + } +} diff --git a/packages/contracts/contracts/maci/TallyFactory.sol b/packages/contracts/contracts/maci/TallyFactory.sol new file mode 100644 index 00000000..57d05f79 --- /dev/null +++ b/packages/contracts/contracts/maci/TallyFactory.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import { TallyFactory as BaseTallyFactory } from "maci-contracts/contracts/TallyFactory.sol"; + +import { Tally } from "./Tally.sol"; + +/// @title TallyFactory +/// @notice A factory contract which deploys Tally contracts. +contract TallyFactory is BaseTallyFactory { + /// @inheritdoc BaseTallyFactory + function deploy( + address verifier, + address vkRegistry, + address poll, + address messageProcessor, + address owner, + Mode mode + ) public virtual override returns (address tallyAddress) { + // deploy Tally for this Poll + Tally tally = new Tally(verifier, vkRegistry, poll, messageProcessor, owner, mode); + tallyAddress = address(tally); + } +} diff --git a/packages/contracts/contracts/mocks/MockERC20.sol b/packages/contracts/contracts/mocks/MockERC20.sol new file mode 100644 index 00000000..62fedec7 --- /dev/null +++ b/packages/contracts/contracts/mocks/MockERC20.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +/// @title MockERC20 +/// @notice A mock ERC20 contract that mints 100,000,000,000,000,000 tokens to the deployer +contract MockERC20 is ERC20 { + constructor(string memory name, string memory symbol) ERC20(name, symbol) { + _mint(msg.sender, 100e21); + } +} diff --git a/packages/contracts/package.json b/packages/contracts/package.json index d2594c5c..1c44e1b4 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -54,9 +54,10 @@ "ethers": "^6.13.2", "hardhat": "^2.22.8", "lowdb": "^1.0.0", - "maci-contracts": "^2.3.0", - "maci-core": "^2.2.0", - "maci-domainobjs": "^2.2.0", + "maci-contracts": "0.0.0-ci.07552f4", + "maci-core": "^2.4.0", + "maci-crypto": "^2.4.0", + "maci-domainobjs": "^2.4.0", "solidity-docgen": "^0.6.0-beta.36" }, "devDependencies": { diff --git a/packages/contracts/tasks/deploy/maci/07-tallyFactory.ts b/packages/contracts/tasks/deploy/maci/07-tallyFactory.ts new file mode 100644 index 00000000..ca41cacf --- /dev/null +++ b/packages/contracts/tasks/deploy/maci/07-tallyFactory.ts @@ -0,0 +1,53 @@ +import { ContractStorage, Deployment, type IDeployParams } from "maci-contracts"; + +import { EDeploySteps, EContracts } from "../../helpers/constants"; + +const deployment = Deployment.getInstance(); +const storage = ContractStorage.getInstance(); + +/** + * Deploy step registration and task itself + */ +deployment.deployTask(EDeploySteps.TallyFactory, "Deploy tally factory").then((task) => + task.setAction(async ({ incremental }: IDeployParams, hre) => { + deployment.setHre(hre); + const deployer = await deployment.getDeployer(); + + const tallyFactoryContractAddress = storage.getAddress(EContracts.TallyFactory, hre.network.name); + + if (incremental && tallyFactoryContractAddress) { + // eslint-disable-next-line no-console + console.log(`Skipping deployment of the ${EContracts.TallyFactory} contract`); + return; + } + + const poseidonT3ContractAddress = storage.mustGetAddress(EContracts.PoseidonT3, hre.network.name); + const poseidonT4ContractAddress = storage.mustGetAddress(EContracts.PoseidonT4, hre.network.name); + const poseidonT5ContractAddress = storage.mustGetAddress(EContracts.PoseidonT5, hre.network.name); + const poseidonT6ContractAddress = storage.mustGetAddress(EContracts.PoseidonT6, hre.network.name); + + const linkedTallyFactoryContract = await hre.ethers.getContractFactory( + "contracts/maci/TallyFactory.sol:TallyFactory", + { + signer: deployer, + libraries: { + PoseidonT3: poseidonT3ContractAddress, + PoseidonT4: poseidonT4ContractAddress, + PoseidonT5: poseidonT5ContractAddress, + PoseidonT6: poseidonT6ContractAddress, + }, + }, + ); + + const tallyFactoryContract = await deployment.deployContractWithLinkedLibraries({ + contractFactory: linkedTallyFactoryContract, + }); + + await storage.register({ + id: EContracts.TallyFactory, + contract: tallyFactoryContract, + args: [], + network: hre.network.name, + }); + }), +); diff --git a/packages/contracts/tasks/deploy/poll/01-poll.ts b/packages/contracts/tasks/deploy/poll/01-poll.ts index 8677b5a1..9e742ada 100644 --- a/packages/contracts/tasks/deploy/poll/01-poll.ts +++ b/packages/contracts/tasks/deploy/poll/01-poll.ts @@ -2,7 +2,7 @@ import { ContractStorage, Deployment, EMode } from "maci-contracts"; import { PubKey } from "maci-domainobjs"; -import { type Poll, type MACI } from "../../../typechain-types"; +import { type Poll, type MACI, Tally } from "../../../typechain-types"; import { EContracts, EDeploySteps, REGISTRY_TYPES, TRegistryManager, TRegistry } from "../../helpers/constants"; const deployment = Deployment.getInstance(); @@ -31,7 +31,11 @@ deployment.deployTask(EDeploySteps.Poll, "Deploy poll").then((task) => throw new Error("Need to deploy VkRegistry contract first"); } - const { MACI__factory: MACIFactory, Poll__factory: PollFactory } = await import("../../../typechain-types"); + const { + MACI__factory: MACIFactory, + Poll__factory: PollFactory, + Tally__factory: TallyFactory, + } = await import("../../../typechain-types"); const maciContract = await deployment.getContract({ name: EContracts.MACI, abi: MACIFactory.abi }); const pollId = await maciContract.nextPollId(); @@ -113,8 +117,9 @@ deployment.deployTask(EDeploySteps.Poll, "Deploy poll").then((task) => address: messageProcessorContractAddress, }); - const tallyContract = await deployment.getContract({ + const tallyContract = await deployment.getContract({ name: EContracts.Tally, + abi: TallyFactory.abi, address: tallyContractAddress, }); diff --git a/packages/contracts/tests/Maci.test.ts b/packages/contracts/tests/Maci.test.ts index 2df89b5c..e03d4591 100644 --- a/packages/contracts/tests/Maci.test.ts +++ b/packages/contracts/tests/Maci.test.ts @@ -9,9 +9,9 @@ import { MACI, Poll__factory as PollFactory, Poll as PollContract } from "../typ import { NOTHING_UP_MY_SLEEVE, STATE_TREE_DEPTH, - duration, - initialVoiceCreditBalance, - messageBatchSize, + DURATION, + INITIAL_VOICE_CREDIT_BALANCE, + MESSAGE_BATCH_SIZE, treeDepths, } from "./constants"; import { deployTestContracts } from "./utils"; @@ -36,7 +36,7 @@ describe("Maci", () => { [owner, user] = await getSigners(); const contracts = await deployTestContracts({ - initialVoiceCreditBalance, + initialVoiceCreditBalance: INITIAL_VOICE_CREDIT_BALANCE, stateTreeDepth: STATE_TREE_DEPTH, signer: owner, }); @@ -46,7 +46,7 @@ describe("Maci", () => { // deploy on chain poll const tx = await maciContract.deployPoll( - duration, + DURATION, treeDepths, coordinator.pubKey.asContractParam(), verifierContract, @@ -66,7 +66,7 @@ describe("Maci", () => { pollContract = PollFactory.connect(pollContracts.poll, owner); // deploy local poll - const p = maciState.deployPoll(BigInt(deployTime + duration), treeDepths, messageBatchSize, coordinator); + const p = maciState.deployPoll(BigInt(deployTime + DURATION), treeDepths, MESSAGE_BATCH_SIZE, coordinator); expect(p.toString()).to.eq(pollId.toString()); // publish the NOTHING_UP_MY_SLEEVE message const messageData = [NOTHING_UP_MY_SLEEVE]; diff --git a/packages/contracts/tests/Poll.test.ts b/packages/contracts/tests/Poll.test.ts index 299a4406..75580e26 100644 --- a/packages/contracts/tests/Poll.test.ts +++ b/packages/contracts/tests/Poll.test.ts @@ -1,24 +1,20 @@ import { expect } from "chai"; import { encodeBytes32String, ZeroAddress, type Signer } from "ethers"; -import hardhat from "hardhat"; -import { deployContract, Deployment, deployPoseidonContracts, genEmptyBallotRoots, getSigners } from "maci-contracts"; +import { deployContract, genEmptyBallotRoots, getSigners } from "maci-contracts"; import { Keypair, Message, PubKey } from "maci-domainobjs"; -import { type Poll, Poll__factory as PollFactory, PollFactory as PollFactoryContract } from "../typechain-types"; +import type { Poll } from "../typechain-types"; import { DEFAULT_SR_QUEUE_OPS, STATE_TREE_DEPTH, treeDepths } from "./constants"; -import { timeTravel } from "./utils"; +import { deployTestPoll, timeTravel } from "./utils"; describe("Poll", () => { - let pollFactory: PollFactoryContract; let pollContract: Poll; let owner: Signer; let user: Signer; const { pubKey: coordinatorPubKey } = new Keypair(); - const deployment = Deployment.getInstance(hardhat); - const emptyBallotRoots = genEmptyBallotRoots(STATE_TREE_DEPTH); const emptyBallotRoot = emptyBallotRoots[treeDepths.voteOptionTreeDepth]; const duration = 100; @@ -32,34 +28,8 @@ describe("Poll", () => { before(async () => { [owner, user] = await getSigners(); - const { PoseidonT3Contract, PoseidonT4Contract, PoseidonT5Contract, PoseidonT6Contract } = - await deployPoseidonContracts(owner); - - const contractFactory = await hardhat.ethers.getContractFactory("contracts/maci/PollFactory.sol:PollFactory", { - signer: owner, - libraries: { - PoseidonT3: PoseidonT3Contract, - PoseidonT4: PoseidonT4Contract, - PoseidonT5: PoseidonT5Contract, - PoseidonT6: PoseidonT6Contract, - }, - }); - - pollFactory = await deployment.deployContractWithLinkedLibraries({ contractFactory, signer: owner }); - - const pollAddress = await pollFactory.deploy.staticCall( - duration, - treeDepths, - coordinatorPubKey.asContractParam(), - await owner.getAddress(), - emptyBallotRoot, - ); - await pollFactory - .deploy("100", treeDepths, coordinatorPubKey.asContractParam(), await owner.getAddress(), emptyBallotRoot) - .then((tx) => tx.wait()); - - pollContract = PollFactory.connect(pollAddress, owner); + pollContract = await deployTestPoll({ signer: owner, emptyBallotRoot, treeDepths, duration, coordinatorPubKey }); }); it("should fail if unauthorized user tries to set the poll registry", async () => { @@ -105,6 +75,8 @@ describe("Poll", () => { .to.emit(pollContract, "SetRegistry") .withArgs(address); + expect(await pollContract.getRegistry()).to.equal(address); + await expect(pollContract.connect(owner).setRegistry(address)).to.be.revertedWithCustomError( pollContract, "RegistryAlreadyInitialized", diff --git a/packages/contracts/tests/Tally.test.ts b/packages/contracts/tests/Tally.test.ts new file mode 100644 index 00000000..15cf1379 --- /dev/null +++ b/packages/contracts/tests/Tally.test.ts @@ -0,0 +1,530 @@ +import { expect } from "chai"; +import { encodeBytes32String, parseUnits, Signer } from "ethers"; +import { + getSigners, + deployContract, + EMode, + MessageProcessor, + MockVerifier, + VkRegistry, + MessageProcessor__factory as MessageProcessorFactory, + IVerifyingKeyStruct, +} from "maci-contracts"; +import { genTreeProof } from "maci-crypto"; +import { Keypair, Message, PCommand, PubKey } from "maci-domainobjs"; + +import { + Tally, + ERC20, + Poll, + IRecipientRegistry, + MACI, + Poll__factory as PollFactory, + Tally__factory as TallyFactory, +} from "../typechain-types"; + +import { + INITIAL_VOICE_CREDIT_BALANCE, + MESSAGE_BATCH_SIZE, + PER_VO_SPENT_VOICE_CREDITS, + STATE_TREE_DEPTH, + TALLY_RESULTS, + TEST_PROCESS_VK, + TEST_TALLY_VK, + TOTAL_SPENT_VOICE_CREDITS, + treeDepths, +} from "./constants"; +import { deployTestContracts, timeTravel } from "./utils"; + +describe("Tally", () => { + let payoutToken: ERC20; + let poll: Poll; + let tally: Tally; + let maci: MACI; + let messageProcessor: MessageProcessor; + let registry: IRecipientRegistry; + let verifier: MockVerifier; + let vkRegistry: VkRegistry; + let owner: Signer; + let user: Signer; + let project: Signer; + + let ownerAddress: string; + let projectAddress: string; + + const maxRecipients = TALLY_RESULTS.tally.length; + + const cooldownTime = 1_000; + const metadataUrl = encodeBytes32String("url"); + const maxContribution = parseUnits("5", 18); + const duration = 100; + const keypair = new Keypair(); + + const emptyClaimParams = { + index: 0, + voiceCreditsPerOption: 0, + tallyResult: 0, + totalSpent: 0, + tallyResultProof: [], + tallyResultSalt: 0, + voteOptionTreeDepth: 0, + spentVoiceCreditsHash: 0, + perVOSpentVoiceCreditsHash: 0, + }; + + before(async () => { + [owner, user, project] = await getSigners(); + [ownerAddress, , projectAddress] = await Promise.all([owner.getAddress(), user.getAddress(), project.getAddress()]); + + payoutToken = await deployContract("MockERC20", owner, true, "Payout token", "PT"); + + const contracts = await deployTestContracts({ + initialVoiceCreditBalance: INITIAL_VOICE_CREDIT_BALANCE, + stateTreeDepth: STATE_TREE_DEPTH, + signer: owner, + }); + maci = contracts.maciContract; + verifier = contracts.mockVerifierContract; + vkRegistry = contracts.vkRegistryContract; + + await vkRegistry.setVerifyingKeys( + STATE_TREE_DEPTH, + treeDepths.intStateTreeDepth, + treeDepths.messageTreeDepth, + treeDepths.voteOptionTreeDepth, + MESSAGE_BATCH_SIZE, + EMode.QV, + TEST_PROCESS_VK.asContractParam() as IVerifyingKeyStruct, + TEST_TALLY_VK.asContractParam() as IVerifyingKeyStruct, + ); + + await maci + .deployPoll(duration, treeDepths, keypair.pubKey.asContractParam(), verifier, vkRegistry, EMode.QV) + .then((tx) => tx.wait()); + + registry = await deployContract("SimpleRegistry", owner, true, maxRecipients, metadataUrl, ownerAddress); + + await maci.setPollRegistry(0n, registry).then((tx) => tx.wait()); + await maci.initPoll(0n).then((tx) => tx.wait()); + + const pollContracts = await maci.getPoll(0n); + poll = PollFactory.connect(pollContracts.poll, owner); + tally = TallyFactory.connect(pollContracts.tally, owner); + messageProcessor = MessageProcessorFactory.connect(pollContracts.messageProcessor, owner); + + const messages: [Message, PubKey][] = []; + for (let i = 0; i < 2; i += 1) { + const command = new PCommand(1n, keypair.pubKey, 0n, 9n, 1n, 0n, BigInt(i)); + const signature = command.sign(keypair.privKey); + const sharedKey = Keypair.genEcdhSharedKey(keypair.privKey, keypair.pubKey); + const message = command.encrypt(signature, sharedKey); + messages.push([message, keypair.pubKey]); + } + + await poll + .publishMessageBatch( + messages.map(([m]) => m.asContractParam()), + messages.map(([, k]) => k.asContractParam()), + ) + .then((tx) => tx.wait()); + }); + + it("should not allow to deposit/claim/withdraw/addTallyResults before initialization", async () => { + await expect(tally.deposit(1n)).to.be.revertedWithCustomError(tally, "NotInitialized"); + await expect(tally.withdrawExtra([], [])).to.be.revertedWithCustomError(tally, "NotInitialized"); + await expect(tally.claim(emptyClaimParams)).to.be.revertedWithCustomError(tally, "NotInitialized"); + await expect( + tally.addTallyResults( + [], + [], + [], + TALLY_RESULTS.salt, + TOTAL_SPENT_VOICE_CREDITS.commitment, + PER_VO_SPENT_VOICE_CREDITS.commitment, + ), + ).to.be.revertedWithCustomError(tally, "NotInitialized"); + }); + + it("should not allow non-owner to initialize tally", async () => { + await expect( + tally.connect(user).init({ + cooldownTime, + maxContribution: parseUnits("5", await payoutToken.decimals()), + poll, + payoutToken, + }), + ).to.be.revertedWithCustomError(tally, "OwnableUnauthorizedAccount"); + }); + + it("should not allow to initialize with different poll", async () => { + await expect( + tally.init({ + cooldownTime, + poll: project, + maxContribution: parseUnits("5", await payoutToken.decimals()), + payoutToken, + }), + ).to.be.revertedWithCustomError(tally, "InvalidPoll"); + }); + + it("should initialize tally properly", async () => { + const receipt = await tally + .init({ + cooldownTime, + poll, + maxContribution: parseUnits("5", await payoutToken.decimals()), + payoutToken, + }) + .then((tx) => tx.wait()); + + expect(receipt?.status).to.equal(1); + }); + + it("should not allow to initialize tally twice", async () => { + await expect( + tally.init({ + cooldownTime, + poll, + maxContribution: parseUnits("5", await payoutToken.decimals()), + payoutToken, + }), + ).to.be.revertedWithCustomError(tally, "AlreadyInitialized"); + }); + + it("should deposit funds properly", async () => { + const [decimals, initialBalance] = await Promise.all([payoutToken.decimals(), payoutToken.balanceOf(owner)]); + const ownerAmount = parseUnits(TOTAL_SPENT_VOICE_CREDITS.spent, decimals); + const userAmount = parseUnits("2", decimals); + + await payoutToken.approve(user, userAmount).then((tx) => tx.wait()); + await payoutToken.transfer(user, userAmount); + + await payoutToken.approve(tally, ownerAmount).then((tx) => tx.wait()); + await tally.deposit(ownerAmount).then((tx) => tx.wait()); + + await payoutToken + .connect(user) + .approve(tally, userAmount) + .then((tx) => tx.wait()); + await tally + .connect(user) + .deposit(userAmount) + .then((tx) => tx.wait()); + + const [tokenBalance, totalAmount] = await Promise.all([payoutToken.balanceOf(tally), tally.totalAmount()]); + + expect(totalAmount).to.equal(tokenBalance); + expect(initialBalance - tokenBalance).to.equal(initialBalance - ownerAmount - userAmount); + }); + + it("should not withdraw extra if cooldown period is not over", async () => { + await expect(tally.withdrawExtra([owner, user], [1n])).to.be.revertedWithCustomError( + tally, + "CooldownPeriodNotOver", + ); + }); + + it("should not allow non-owner to withdraw funds", async () => { + await expect(tally.connect(user).withdrawExtra([owner, user], [1n])).to.be.revertedWithCustomError( + tally, + "OwnableUnauthorizedAccount", + ); + }); + + it("should not allow non-owner to pause/unpause", async () => { + await expect(tally.connect(user).pause()).to.be.revertedWithCustomError(tally, "OwnableUnauthorizedAccount"); + + await expect(tally.connect(user).unpause()).to.be.revertedWithCustomError(tally, "OwnableUnauthorizedAccount"); + }); + + it("should not allow to call functions if contract is paused", async () => { + try { + await tally.pause().then((tx) => tx.wait()); + + await expect(tally.deposit(1n)).to.be.revertedWithCustomError(tally, "EnforcedPause"); + + await expect(tally.withdrawExtra([owner, user], [1n])).to.be.revertedWithCustomError(tally, "EnforcedPause"); + + await expect(tally.claim(emptyClaimParams)).to.be.revertedWithCustomError(tally, "EnforcedPause"); + + await expect( + tally.addTallyResults( + [], + [], + [], + TALLY_RESULTS.salt, + TOTAL_SPENT_VOICE_CREDITS.commitment, + PER_VO_SPENT_VOICE_CREDITS.commitment, + ), + ).to.be.revertedWithCustomError(tally, "EnforcedPause"); + } finally { + await tally.unpause().then((tx) => tx.wait()); + } + }); + + it("should not allow to add tally results before voting deadline", async () => { + await expect( + tally.addTallyResults( + [], + [], + [], + TALLY_RESULTS.salt, + TOTAL_SPENT_VOICE_CREDITS.commitment, + PER_VO_SPENT_VOICE_CREDITS.commitment, + ), + ).to.be.revertedWithCustomError(tally, "VotingPeriodNotOver"); + }); + + it("should not allow to claim before voting deadline", async () => { + await expect(tally.claim(emptyClaimParams)).to.be.revertedWithCustomError(tally, "VotingPeriodNotOver"); + }); + + it("should not allow to claim funds if there are no any votes", async () => { + await timeTravel(cooldownTime + duration, owner); + + await expect(tally.claim(emptyClaimParams).then((tx) => tx.wait())).to.be.revertedWithCustomError( + tally, + "NoProjectHasMoreThanOneVote", + ); + }); + + it("should not allow non-owner to add tally results", async () => { + await expect( + tally + .connect(user) + .addTallyResults( + [], + [], + [], + TALLY_RESULTS.salt, + TOTAL_SPENT_VOICE_CREDITS.commitment, + PER_VO_SPENT_VOICE_CREDITS.commitment, + ), + ).to.be.revertedWithCustomError(tally, "OwnableUnauthorizedAccount"); + }); + + it("should add tally results properly", async () => { + // eslint-disable-next-line @typescript-eslint/prefer-for-of + for (let index = 0; index < TALLY_RESULTS.tally.length; index += 1) { + // eslint-disable-next-line no-await-in-loop + await registry + .addRecipient({ + id: encodeBytes32String(index.toString()), + metadataUrl, + recipient: projectAddress, + }) + .then((tx) => tx.wait()); + } + + await poll.mergeMaciState().then((tx) => tx.wait()); + await poll.mergeMessageAqSubRoots(4).then((tx) => tx.wait()); + await poll.mergeMessageAq().then((tx) => tx.wait()); + + await messageProcessor.processMessages(0n, [0, 0, 0, 0, 0, 0, 0, 0]); + await tally.tallyVotes( + 14233750085429709079626724186351653560210151525264608035972235393198738498917n, + [0, 0, 0, 0, 0, 0, 0, 0], + ); + + const tallyResults = TALLY_RESULTS.tally.map((x) => BigInt(x)); + const indices = TALLY_RESULTS.tally.map((_, index) => index); + const tallyResultProofs = TALLY_RESULTS.tally.map((_, index) => + genTreeProof(index, tallyResults, Number(treeDepths.voteOptionTreeDepth)), + ); + const invalidProof = [ + [0n, 0n, 0n, 0n], + [0n, 0n, 0n, 0n], + [0n, 0n, 0n, 0n], + ]; + + await expect( + tally.addTallyResults( + [0], + [TALLY_RESULTS.tally[0]], + [invalidProof], + TALLY_RESULTS.salt, + TOTAL_SPENT_VOICE_CREDITS.commitment, + PER_VO_SPENT_VOICE_CREDITS.commitment, + ), + ).to.be.revertedWithCustomError(tally, "InvalidTallyVotesProof"); + + const firstReceipt = await tally + .addTallyResults( + indices.slice(1), + tallyResults.slice(1), + tallyResultProofs.slice(1), + TALLY_RESULTS.salt, + TOTAL_SPENT_VOICE_CREDITS.commitment, + PER_VO_SPENT_VOICE_CREDITS.commitment, + ) + .then((tx) => tx.wait()); + + expect(firstReceipt?.status).to.equal(1); + + await expect( + tally.claim({ + index: 0, + voiceCreditsPerOption: PER_VO_SPENT_VOICE_CREDITS.tally[0], + tallyResult: tallyResults[0], + totalSpent: TOTAL_SPENT_VOICE_CREDITS.spent, + tallyResultProof: tallyResultProofs[0], + tallyResultSalt: TALLY_RESULTS.salt, + voteOptionTreeDepth: treeDepths.voteOptionTreeDepth, + spentVoiceCreditsHash: TOTAL_SPENT_VOICE_CREDITS.commitment, + perVOSpentVoiceCreditsHash: PER_VO_SPENT_VOICE_CREDITS.commitment, + }), + ).to.be.revertedWithCustomError(tally, "NotCompletedResults"); + + const additionalProof = genTreeProof(tallyResults.length, tallyResults.concat(0n), treeDepths.voteOptionTreeDepth); + + await expect( + tally.addTallyResults( + [indices[0], tallyResults.length], + [tallyResults[0], 0n], + [tallyResultProofs[0], additionalProof], + TALLY_RESULTS.salt, + TOTAL_SPENT_VOICE_CREDITS.commitment, + PER_VO_SPENT_VOICE_CREDITS.commitment, + ), + ).to.be.revertedWithCustomError(tally, "TooManyResults"); + + const lastReceipt = await tally + .addTallyResults( + indices.slice(0, 1), + tallyResults.slice(0, 1), + tallyResultProofs.slice(0, 1), + TALLY_RESULTS.salt, + TOTAL_SPENT_VOICE_CREDITS.commitment, + PER_VO_SPENT_VOICE_CREDITS.commitment, + ) + .then((tx) => tx.wait()); + + expect(lastReceipt?.status).to.equal(1); + }); + + it("should not allow to add tally results twice", async () => { + const invalidProof = [ + [0n, 0n, 0n, 0n], + [0n, 0n, 0n, 0n], + [0n, 0n, 0n, 0n], + ]; + + await expect( + tally.addTallyResults( + [0], + [TALLY_RESULTS.tally[0]], + [invalidProof], + TALLY_RESULTS.salt, + TOTAL_SPENT_VOICE_CREDITS.commitment, + PER_VO_SPENT_VOICE_CREDITS.commitment, + ), + ).to.be.revertedWithCustomError(tally, "TooManyResults"); + }); + + it("should claim funds properly for the project", async () => { + const tallyResults = TALLY_RESULTS.tally.map((x) => BigInt(x)); + const tallyResultProofs = TALLY_RESULTS.tally.map((_, index) => + genTreeProof(index, tallyResults, Number(treeDepths.voteOptionTreeDepth)), + ); + + // eslint-disable-next-line @typescript-eslint/prefer-for-of + for (let index = 0; index < TALLY_RESULTS.tally.length; index += 1) { + const tallyResultProof = tallyResultProofs[index]; + + const params = { + index, + voiceCreditsPerOption: PER_VO_SPENT_VOICE_CREDITS.tally[index], + tallyResult: tallyResults[index], + totalSpent: TOTAL_SPENT_VOICE_CREDITS.spent, + tallyResultProof, + tallyResultSalt: TALLY_RESULTS.salt, + voteOptionTreeDepth: treeDepths.voteOptionTreeDepth, + spentVoiceCreditsHash: TOTAL_SPENT_VOICE_CREDITS.commitment, + perVOSpentVoiceCreditsHash: PER_VO_SPENT_VOICE_CREDITS.commitment, + }; + + // eslint-disable-next-line no-await-in-loop + await tally.claim(params).then((tx) => tx.wait()); + } + }); + + it("should not claim funds for the project if proof generation is failed", async () => { + const voteOptionTreeDepth = 3; + const invalidProof = [ + [0n, 0n, 0n, 0n], + [0n, 0n, 0n, 0n], + [0n, 0n, 0n, 0n], + ]; + + // eslint-disable-next-line @typescript-eslint/prefer-for-of + for (let index = 0; index < TALLY_RESULTS.tally.length; index += 1) { + const params = { + index, + voiceCreditsPerOption: PER_VO_SPENT_VOICE_CREDITS.tally[index], + tallyResult: TALLY_RESULTS.tally[index], + totalSpent: TOTAL_SPENT_VOICE_CREDITS.spent, + tallyResultProof: invalidProof, + tallyResultSalt: TALLY_RESULTS.salt, + voteOptionTreeDepth, + spentVoiceCreditsHash: TOTAL_SPENT_VOICE_CREDITS.commitment, + perVOSpentVoiceCreditsHash: PER_VO_SPENT_VOICE_CREDITS.commitment, + }; + + // eslint-disable-next-line no-await-in-loop + await expect(tally.claim(params)).to.be.revertedWithCustomError(tally, "InvalidTallyVotesProof"); + } + }); + + it("should not allow to claim funds if there are not enough funds", async () => { + const total = await payoutToken.balanceOf(tally); + + const params = { + index: 0n, + voiceCreditsPerOption: 0n, + tallyResult: 1n, + totalSpent: total + 1n, + tallyResultProof: [], + tallyResultSalt: 0n, + voteOptionTreeDepth: 2, + spentVoiceCreditsHash: 0n, + perVOSpentVoiceCreditsHash: 0n, + }; + + await expect(tally.claim({ ...params }).then((tx) => tx.wait())).to.be.revertedWithCustomError( + tally, + "InvalidBudget", + ); + }); + + it("should withdraw extra after cooldown properly", async () => { + const [contractBalance, initialOwnerBalance, totalAmount] = await Promise.all([ + payoutToken.balanceOf(tally), + payoutToken.balanceOf(owner), + tally.totalAmount(), + ]); + + await tally.withdrawExtra([owner, user], [totalAmount, 0]).then((tx) => tx.wait()); + + const [balance, ownerBalance, totalExtraFunds] = await Promise.all([ + payoutToken.balanceOf(tally), + payoutToken.balanceOf(owner), + tally.totalAmount(), + ]); + + expect(balance).to.equal(0n); + expect(totalExtraFunds).to.equal(0n); + expect(initialOwnerBalance + totalAmount).to.equal(ownerBalance); + expect(contractBalance).to.equal(totalAmount); + }); + + it("should not withdraw extra if there is no enough funds", async () => { + await expect(tally.withdrawExtra([owner], [maxContribution])).to.be.revertedWithCustomError( + tally, + "InvalidWithdrawal", + ); + }); + + it("should not deposit after voting period is over", async () => { + await expect(tally.deposit(1n)).to.be.revertedWithCustomError(tally, "VotingPeriodOver"); + }); +}); diff --git a/packages/contracts/tests/constants.ts b/packages/contracts/tests/constants.ts index 821c0bd2..aa0ee36b 100644 --- a/packages/contracts/tests/constants.ts +++ b/packages/contracts/tests/constants.ts @@ -1,15 +1,32 @@ -import { TreeDepths, STATE_TREE_ARITY, MESSAGE_TREE_ARITY } from "maci-core"; - -export const duration = 2_000; +import { TreeDepths, MESSAGE_TREE_ARITY } from "maci-core"; +import { G1Point, G2Point } from "maci-crypto"; +import { VerifyingKey } from "maci-domainobjs"; export const STATE_TREE_DEPTH = 10; export const MESSAGE_TREE_DEPTH = 2; export const MESSAGE_TREE_SUBDEPTH = 1; -export const messageBatchSize = MESSAGE_TREE_ARITY ** MESSAGE_TREE_SUBDEPTH; +export const MESSAGE_BATCH_SIZE = MESSAGE_TREE_ARITY ** MESSAGE_TREE_SUBDEPTH; export const NOTHING_UP_MY_SLEEVE = 8370432830353022751713833565135785980866757267633941821328460903436894336785n; export const DEFAULT_SR_QUEUE_OPS = 4; -export const initialVoiceCreditBalance = 100; +export const DURATION = 2_000; +export const INITIAL_VOICE_CREDIT_BALANCE = 1000; + +export const TEST_PROCESS_VK = new VerifyingKey( + new G1Point(BigInt(0), BigInt(1)), + new G2Point([BigInt(2), BigInt(3)], [BigInt(4), BigInt(5)]), + new G2Point([BigInt(6), BigInt(7)], [BigInt(8), BigInt(9)]), + new G2Point([BigInt(10), BigInt(11)], [BigInt(12), BigInt(13)]), + [new G1Point(BigInt(14), BigInt(15)), new G1Point(BigInt(16), BigInt(17))], +); + +export const TEST_TALLY_VK = new VerifyingKey( + new G1Point(BigInt(0), BigInt(1)), + new G2Point([BigInt(2), BigInt(3)], [BigInt(4), BigInt(5)]), + new G2Point([BigInt(6), BigInt(7)], [BigInt(8), BigInt(9)]), + new G2Point([BigInt(10), BigInt(11)], [BigInt(12), BigInt(13)]), + [new G1Point(BigInt(14), BigInt(15)), new G1Point(BigInt(16), BigInt(17))], +); export const treeDepths: TreeDepths = { intStateTreeDepth: 1, @@ -18,4 +35,20 @@ export const treeDepths: TreeDepths = { voteOptionTreeDepth: 2, }; -export const tallyBatchSize = STATE_TREE_ARITY ** treeDepths.intStateTreeDepth; +export const TALLY_RESULTS = { + tally: ["12", "107", "159", "67", "26", "35", "53", "17", "114", "158", "65", "60", "0"], + salt: "0xed6956a31ec7a8d7c71e4d9bb90f70281e45e7977644268af3cc061c384feae", + commitment: "0x1f6ebdc3299a1c2eef28cfa14822c4a1e0a607a4044f6aa4434b1b80f1b79ef7", +}; + +export const TOTAL_SPENT_VOICE_CREDITS = { + spent: "12391", + salt: "0x269f6c1a1a34bc13d7369fb93323590504f37edf41d952d5f9e8cf814381c475", + commitment: "0x1f5c444c5dbc821ad8eb76bcfd8966468eca73c08b98cf1504d312751d61b908", +}; + +export const PER_VO_SPENT_VOICE_CREDITS = { + tally: ["86", "1459", "3039", "587", "136", "491", "627", "79", "1644", "2916", "721", "606", "0"], + salt: "0x1f31c9ed6fb5f2beb54c32edbc922a87d467c31d0178c8427fbb7d978048ade7", + commitment: "0xdf601b181267173f055d90e5fb286637634317c29aa57478800077a0f9bc839", +}; diff --git a/packages/contracts/tests/utils.ts b/packages/contracts/tests/utils.ts index de1ecbc3..097edac3 100644 --- a/packages/contracts/tests/utils.ts +++ b/packages/contracts/tests/utils.ts @@ -3,6 +3,7 @@ import { deployConstantInitialVoiceCreditProxy, deployFreeForAllSignUpGatekeeper, deployMaci, + Deployment, deployMockVerifier, deployPoseidonContracts, deployVkRegistry, @@ -13,8 +14,10 @@ import { } from "maci-contracts"; import type { ContractFactory, Signer, JsonRpcProvider } from "ethers"; +import type { TreeDepths } from "maci-core"; +import type { PubKey } from "maci-domainobjs"; -import { type MACI } from "../typechain-types"; +import { Poll, PollFactory as PollFactoryContract, Poll__factory as PollFactory, type MACI } from "../typechain-types"; /** * An interface that represents argument for deployment test contracts @@ -94,7 +97,7 @@ export const deployTestContracts = async ({ "contracts/maci/MACI.sol:MACI", "contracts/maci/PollFactory.sol:PollFactory", "MessageProcessorFactory", - "TallyFactory", + "contracts/maci/TallyFactory.sol:TallyFactory", ].map((factory) => ethers.getContractFactory(factory, { libraries: { @@ -126,6 +129,68 @@ export const deployTestContracts = async ({ }; }; +/** + * An interface that represents arguments for test poll deployment + */ +export interface IDeployTestPollArgs { + signer: Signer; + emptyBallotRoot: bigint | number; + treeDepths: TreeDepths; + duration: bigint | number; + coordinatorPubKey: PubKey; +} + +/** + * Deploy a test poll using poll factory. + * + * @param signer - the signer to use + * @param emptyBallotRoot - the empty ballot root + * @param treeDepths - the tree depths + * @param duration - the poll duration + * @param coordinatorPubKey - the coordinator public key + * @returns the deployed poll contract + */ +export const deployTestPoll = async ({ + signer, + emptyBallotRoot, + treeDepths, + duration, + coordinatorPubKey, +}: IDeployTestPollArgs): Promise => { + const { PoseidonT3Contract, PoseidonT4Contract, PoseidonT5Contract, PoseidonT6Contract } = + await deployPoseidonContracts(signer); + + const contractFactory = await ethers.getContractFactory("contracts/maci/PollFactory.sol:PollFactory", { + signer, + libraries: { + PoseidonT3: PoseidonT3Contract, + PoseidonT4: PoseidonT4Contract, + PoseidonT5: PoseidonT5Contract, + PoseidonT6: PoseidonT6Contract, + }, + }); + + const deployment = Deployment.getInstance(); + const pollFactory = await deployment.deployContractWithLinkedLibraries({ + contractFactory, + signer, + }); + + const pollAddress = await pollFactory.deploy.staticCall( + duration, + treeDepths, + coordinatorPubKey.asContractParam(), + await signer.getAddress(), + emptyBallotRoot, + ); + + await pollFactory + .deploy("100", treeDepths, coordinatorPubKey.asContractParam(), await signer.getAddress(), emptyBallotRoot) + .then((tx) => tx.wait()); + + return PollFactory.connect(pollAddress, signer); +}; + /** * Utility to travel in time when using a local blockchain * @param seconds - the number of seconds to travel in time diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7ff3112a..84a2a3d6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -105,14 +105,17 @@ importers: specifier: ^1.0.0 version: 1.0.0 maci-contracts: - specifier: ^2.3.0 - version: 2.3.0(scbmzgron6bryzn5ew3pd77ohu) + specifier: 0.0.0-ci.07552f4 + version: 0.0.0-ci.07552f4(scbmzgron6bryzn5ew3pd77ohu) maci-core: - specifier: ^2.2.0 - version: 2.2.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) + specifier: ^2.4.0 + version: 2.4.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) + maci-crypto: + specifier: ^2.4.0 + version: 2.4.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) maci-domainobjs: - specifier: ^2.2.0 - version: 2.2.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) + specifier: ^2.4.0 + version: 2.4.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) solidity-docgen: specifier: ^0.6.0-beta.36 version: 0.6.0-beta.36(hardhat@2.22.8(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@22.2.0)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)) @@ -544,7 +547,7 @@ importers: version: 9.1.0(eslint@8.57.0) eslint-import-resolver-typescript: specifier: ^3.6.1 - version: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-plugin-import@2.30.0)(eslint@8.57.0) + version: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0))(eslint@8.57.0) eslint-plugin-import: specifier: ^2.30.0 version: 2.30.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) @@ -4693,6 +4696,9 @@ packages: '@zk-kit/poseidon-cipher@0.3.1': resolution: {integrity: sha512-3plpr4Dk0EADSRPJ0NLNt7x+QG8zlJhT264zVGRxgl4yhraE2C/wAxrclUx1mcw8I04hYoXf1BTd0noAIwd5/A==} + '@zk-kit/poseidon-cipher@0.3.2': + resolution: {integrity: sha512-Ezz1e0mj/GRDlHdU5m0uhj5iHY72zWJU0BP8DsCCvPubU7LPI2tVaPpxrAjT4JQqatbVRQHLIhixW7F7BPzaFg==} + '@zk-kit/utils@1.0.0': resolution: {integrity: sha512-v5UjrZiaRNAN2UJmTFHvlMktaA2Efc2qN1Mwd4060ExX12yRhY8ZhzdlDODhnuHkvW5zPukuBHgQhHMScNP3Pg==} @@ -9607,6 +9613,9 @@ packages: resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} hasBin: true + maci-circuits@0.0.0-ci.07552f4: + resolution: {integrity: sha512-kRaiJqVdLD+cHjNGF2TUAhDK0O5c29lDcTYBwwxtci/Jbqos9uzrH2Ua2QxB60j6PnoQr/chs7cjg2vAwUKc0A==} + maci-circuits@2.3.0: resolution: {integrity: sha512-dq7pwfK9b8+VPbJ++ytLnMoZIZZYQ2ENRBC5rHEAlcyViEhNhj44k3/MmcfGBNbKYsu8y9GG+esdN8hyLt4jAw==} @@ -9621,6 +9630,10 @@ packages: resolution: {integrity: sha512-G7W4VGTqv96Zf808wyTZbwXRKtWTCRsxkagof4Cedup4AL0ywcDVzQumhVo/Nur3Jn9rYdfa+9FtjOSax5xU/w==} hasBin: true + maci-contracts@0.0.0-ci.07552f4: + resolution: {integrity: sha512-iiIwLd5p/QpBaTWawlB1J6rTevgHDqhrMEiHiZhPzXAn+khlz9oDqJwBo6+otufjoloEFJkC8Zoq1xficWHoxg==} + hasBin: true + maci-contracts@2.3.0: resolution: {integrity: sha512-9G/aEA3CpdKgBhzGcqCVfJqI6Tjeo6r9MXaAKMejFFoobIPWqjE8m/g+IKI+QbhLjI5PTNLaja5ji4ZuOfB8Yg==} hasBin: true @@ -9629,12 +9642,18 @@ packages: resolution: {integrity: sha512-BTjP8Ml0NfZco24LIGXwPCZC8m06D9g0yKnRYZmuTplY69KqBNyPwxM0z5B8niZv+Tid+IrOteJVKYlX+AQsQg==} hasBin: true + maci-core@0.0.0-ci.07552f4: + resolution: {integrity: sha512-Yp9mP6s5Z7r097LTvAlMCtskI+oge5fRYbO+b5IGmLrqv0x/IY3Cp2xOcwDHqukHsYtJL4ei0jKa8NO2ApF61w==} + maci-core@2.2.0: resolution: {integrity: sha512-jHS40/uGJZMYvslfDls3LUPXK8gAijVrc8L8o51SJQX44iocgR3aWpWycD8df9rBCGBxScZPbtn04CmtFT0lhQ==} maci-core@2.4.0: resolution: {integrity: sha512-+x2rUL5h1uHtjogLRA3AMveQ7pv4P8gCy4Er25Stmj1yJF77dI53bozb6jgqmPS9sNDsViFcCDUBiTebwo5bSg==} + maci-crypto@0.0.0-ci.07552f4: + resolution: {integrity: sha512-pTHpDmMNx8G6dRg1lFGwnG+pP2uxF0Og8FCWwCO7hYz1HJ97/jDo8danP5rl4qFpwBvgexuWUQEFbRSGAThfBA==} + maci-crypto@2.0.0: resolution: {integrity: sha512-bkgOoDA1ABG49MXDzzsQPsFVEijAkLk8ocJKGyeNQS7YpNhC3YEVVz/SE4g0td+N4xJhD3PbXsyHeaTM3ApIjw==} @@ -9644,6 +9663,9 @@ packages: maci-crypto@2.4.0: resolution: {integrity: sha512-fQiq7Q/liWzifduQ7ARGJRhA0L2jfPdfQbiiMlQ8R4RdYqU5aSL1BYNkAhfEUEo8g9uPXChu3NIL3sKzszZClA==} + maci-domainobjs@0.0.0-ci.07552f4: + resolution: {integrity: sha512-9Il9eoJedBPClifzbJMORhhE7oqTq2Kt4R17MlaF4HYD4ZZ5JVyWgerDs4o0DkedS7BlPNzZPYO1t9ws65LBWA==} + maci-domainobjs@2.0.0: resolution: {integrity: sha512-FmQdIC1omsWR/98wt8WvEJj0SDfnVTl9/2FMDp3N4WwUy1lzmmlVjUGKSFKj2+dj2Rx26DmBWsmKhbTIQeoPOQ==} @@ -16569,7 +16591,7 @@ snapshots: '@nomicfoundation/hardhat-chai-matchers@2.0.7(@nomicfoundation/hardhat-ethers@3.0.8(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.7(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)))(chai@4.5.0)(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.7(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10))': dependencies: - '@nomicfoundation/hardhat-ethers': 3.0.8(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.8(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)) + '@nomicfoundation/hardhat-ethers': 3.0.8(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.7(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)) '@types/chai-as-promised': 7.1.8 chai: 4.5.0 chai-as-promised: 7.1.2(chai@4.5.0) @@ -16600,6 +16622,15 @@ snapshots: hardhat: 2.22.8(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@22.2.0)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10) ordinal: 1.0.3 + '@nomicfoundation/hardhat-ethers@3.0.8(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.7(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10))': + dependencies: + debug: 4.3.6(supports-color@8.1.1) + ethers: 6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10) + hardhat: 2.22.7(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10) + lodash.isequal: 4.5.0 + transitivePeerDependencies: + - supports-color + '@nomicfoundation/hardhat-ethers@3.0.8(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.8(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10))': dependencies: debug: 4.3.6(supports-color@8.1.1) @@ -16620,7 +16651,7 @@ snapshots: '@nomicfoundation/hardhat-ignition-ethers@0.15.5(@nomicfoundation/hardhat-ethers@3.0.8(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.7(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)))(@nomicfoundation/hardhat-ignition@0.15.5(@nomicfoundation/hardhat-verify@2.0.9(hardhat@2.22.7(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)))(bufferutil@4.0.8)(hardhat@2.22.7(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10))(@nomicfoundation/ignition-core@0.15.5(bufferutil@4.0.8)(utf-8-validate@5.0.10))(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.7(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10))': dependencies: - '@nomicfoundation/hardhat-ethers': 3.0.8(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.8(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)) + '@nomicfoundation/hardhat-ethers': 3.0.8(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.7(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)) '@nomicfoundation/hardhat-ignition': 0.15.5(@nomicfoundation/hardhat-verify@2.0.9(hardhat@2.22.7(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)))(bufferutil@4.0.8)(hardhat@2.22.7(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10) '@nomicfoundation/ignition-core': 0.15.5(bufferutil@4.0.8)(utf-8-validate@5.0.10) ethers: 6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10) @@ -16705,7 +16736,7 @@ snapshots: '@nomicfoundation/hardhat-toolbox@5.0.0(4lzaewf5vhmhgkr3zdkxldxwse)': dependencies: '@nomicfoundation/hardhat-chai-matchers': 2.0.7(@nomicfoundation/hardhat-ethers@3.0.8(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.7(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)))(chai@4.5.0)(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.7(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)) - '@nomicfoundation/hardhat-ethers': 3.0.8(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.8(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)) + '@nomicfoundation/hardhat-ethers': 3.0.8(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.7(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)) '@nomicfoundation/hardhat-ignition-ethers': 0.15.5(@nomicfoundation/hardhat-ethers@3.0.8(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.7(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)))(@nomicfoundation/hardhat-ignition@0.15.5(@nomicfoundation/hardhat-verify@2.0.9(hardhat@2.22.7(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)))(bufferutil@4.0.8)(hardhat@2.22.7(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10))(@nomicfoundation/ignition-core@0.15.5(bufferutil@4.0.8)(utf-8-validate@5.0.10))(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.7(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)) '@nomicfoundation/hardhat-network-helpers': 1.0.11(hardhat@2.22.7(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)) '@nomicfoundation/hardhat-verify': 2.0.9(hardhat@2.22.7(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)) @@ -18978,7 +19009,7 @@ snapshots: '@types/concat-stream@1.6.1': dependencies: - '@types/node': 20.14.14 + '@types/node': 22.2.0 '@types/connect-history-api-fallback@1.5.4': dependencies: @@ -19065,7 +19096,7 @@ snapshots: '@types/form-data@0.0.33': dependencies: - '@types/node': 20.14.14 + '@types/node': 22.2.0 '@types/formidable@3.4.5': dependencies: @@ -20112,6 +20143,11 @@ snapshots: '@zk-kit/baby-jubjub': 1.0.1 '@zk-kit/utils': 1.0.0 + '@zk-kit/poseidon-cipher@0.3.2': + dependencies: + '@zk-kit/baby-jubjub': 1.0.3 + '@zk-kit/utils': 1.2.1 + '@zk-kit/utils@1.0.0': dependencies: buffer: 6.0.3 @@ -20546,7 +20582,7 @@ snapshots: axe-core@4.10.0: {} - axios-debug-log@1.0.0(axios@1.7.3(debug@4.3.6)): + axios-debug-log@1.0.0(axios@1.7.3): dependencies: '@types/debug': 4.1.12 axios: 1.7.3(debug@4.3.6) @@ -22773,7 +22809,7 @@ snapshots: '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.5.4) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.0) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0))(eslint@8.57.0) eslint-plugin-import: 2.30.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) eslint-plugin-jsx-a11y: 6.9.0(eslint@8.57.0) eslint-plugin-react: 7.35.0(eslint@8.57.0) @@ -22796,29 +22832,12 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.0): + eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0))(eslint@8.57.0): dependencies: debug: 4.3.6(supports-color@8.1.1) enhanced-resolve: 5.17.1 eslint: 8.57.0 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.0))(eslint@8.57.0) - eslint-plugin-import: 2.30.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) - fast-glob: 3.3.2 - get-tsconfig: 4.7.6 - is-core-module: 2.15.0 - is-glob: 4.0.3 - transitivePeerDependencies: - - '@typescript-eslint/parser' - - eslint-import-resolver-node - - eslint-import-resolver-webpack - - supports-color - - eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-plugin-import@2.30.0)(eslint@8.57.0): - dependencies: - debug: 4.3.6(supports-color@8.1.1) - enhanced-resolve: 5.17.1 - eslint: 8.57.0 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-plugin-import@2.30.0)(eslint@8.57.0))(eslint@8.57.0) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-plugin-import@2.30.0)(eslint@8.57.0))(eslint@8.57.0) eslint-plugin-import: 2.30.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) fast-glob: 3.3.2 get-tsconfig: 4.7.6 @@ -22854,7 +22873,7 @@ snapshots: '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.5.4) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-plugin-import@2.30.0)(eslint@8.57.0) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0))(eslint@8.57.0) transitivePeerDependencies: - supports-color @@ -22869,24 +22888,14 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.0))(eslint@8.57.0): + eslint-module-utils@2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-plugin-import@2.30.0)(eslint@8.57.0))(eslint@8.57.0): dependencies: debug: 3.2.7(supports-color@8.1.1) optionalDependencies: '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.5.4) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.0) - transitivePeerDependencies: - - supports-color - - eslint-module-utils@2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-plugin-import@2.30.0)(eslint@8.57.0))(eslint@8.57.0): - dependencies: - debug: 3.2.7(supports-color@8.1.1) - optionalDependencies: - '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.5.4) - eslint: 8.57.0 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-plugin-import@2.30.0)(eslint@8.57.0) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0))(eslint@8.57.0) transitivePeerDependencies: - supports-color @@ -26642,6 +26651,20 @@ snapshots: lz-string@1.5.0: {} + maci-circuits@0.0.0-ci.07552f4(@types/snarkjs@0.7.8)(bufferutil@4.0.8)(utf-8-validate@5.0.10): + dependencies: + '@zk-kit/circuits': 0.4.0 + circomkit: 0.2.1(@types/snarkjs@0.7.8)(snarkjs@0.7.4) + circomlib: 2.0.5 + maci-core: 0.0.0-ci.07552f4(bufferutil@4.0.8)(utf-8-validate@5.0.10) + maci-crypto: 0.0.0-ci.07552f4(bufferutil@4.0.8)(utf-8-validate@5.0.10) + maci-domainobjs: 0.0.0-ci.07552f4(bufferutil@4.0.8)(utf-8-validate@5.0.10) + snarkjs: 0.7.4 + transitivePeerDependencies: + - '@types/snarkjs' + - bufferutil + - utf-8-validate + maci-circuits@2.3.0(@types/snarkjs@0.7.8)(bufferutil@4.0.8)(utf-8-validate@5.0.10): dependencies: '@zk-kit/circuits': 0.4.0 @@ -26679,7 +26702,7 @@ snapshots: ethers: 6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10) hardhat: 2.22.8(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10) maci-circuits: 2.3.0(@types/snarkjs@0.7.8)(bufferutil@4.0.8)(utf-8-validate@5.0.10) - maci-contracts: 2.3.0(g3d4wepazdznljietc42e5anoy) + maci-contracts: 2.4.0(g3d4wepazdznljietc42e5anoy) maci-core: 2.2.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) maci-crypto: 2.2.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) maci-domainobjs: 2.2.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) @@ -26744,6 +26767,44 @@ snapshots: - typescript - utf-8-validate + maci-contracts@0.0.0-ci.07552f4(scbmzgron6bryzn5ew3pd77ohu): + dependencies: + '@nomicfoundation/hardhat-ethers': 3.0.8(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.8(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@22.2.0)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)) + '@nomicfoundation/hardhat-toolbox': 5.0.0(6zbd34p7xszhw7fs5zk33ckexe) + '@openzeppelin/contracts': 5.0.2 + '@openzeppelin/merkle-tree': 1.0.7 + circomlibjs: 0.1.7(bufferutil@4.0.8)(utf-8-validate@5.0.10) + ethers: 6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10) + hardhat: 2.22.8(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@22.2.0)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10) + lowdb: 1.0.0 + maci-circuits: 0.0.0-ci.07552f4(@types/snarkjs@0.7.8)(bufferutil@4.0.8)(utf-8-validate@5.0.10) + maci-core: 0.0.0-ci.07552f4(bufferutil@4.0.8)(utf-8-validate@5.0.10) + maci-crypto: 0.0.0-ci.07552f4(bufferutil@4.0.8)(utf-8-validate@5.0.10) + maci-domainobjs: 0.0.0-ci.07552f4(bufferutil@4.0.8)(utf-8-validate@5.0.10) + solidity-docgen: 0.6.0-beta.36(hardhat@2.22.8(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@22.2.0)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)) + uuid: 10.0.0 + transitivePeerDependencies: + - '@nomicfoundation/hardhat-chai-matchers' + - '@nomicfoundation/hardhat-ignition-ethers' + - '@nomicfoundation/hardhat-network-helpers' + - '@nomicfoundation/hardhat-verify' + - '@typechain/ethers-v6' + - '@typechain/hardhat' + - '@types/chai' + - '@types/mocha' + - '@types/node' + - '@types/snarkjs' + - bufferutil + - c-kzg + - chai + - hardhat-gas-reporter + - solidity-coverage + - supports-color + - ts-node + - typechain + - typescript + - utf-8-validate + maci-contracts@2.3.0(g3d4wepazdznljietc42e5anoy): dependencies: '@nomicfoundation/hardhat-ethers': 3.0.8(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.8(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)) @@ -26781,20 +26842,21 @@ snapshots: - typescript - utf-8-validate - maci-contracts@2.3.0(scbmzgron6bryzn5ew3pd77ohu): + maci-contracts@2.4.0(g3d4wepazdznljietc42e5anoy): dependencies: - '@nomicfoundation/hardhat-ethers': 3.0.8(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.8(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@22.2.0)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)) - '@nomicfoundation/hardhat-toolbox': 5.0.0(6zbd34p7xszhw7fs5zk33ckexe) + '@nomicfoundation/hardhat-ethers': 3.0.8(ethers@6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.22.8(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)) + '@nomicfoundation/hardhat-toolbox': 5.0.0(hke34ef7km4fdyz52h5gry5opq) '@openzeppelin/contracts': 5.0.2 + '@openzeppelin/merkle-tree': 1.0.7 circomlibjs: 0.1.7(bufferutil@4.0.8)(utf-8-validate@5.0.10) ethers: 6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10) - hardhat: 2.22.8(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@22.2.0)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10) + hardhat: 2.22.8(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10) lowdb: 1.0.0 - maci-circuits: 2.3.0(@types/snarkjs@0.7.8)(bufferutil@4.0.8)(utf-8-validate@5.0.10) - maci-core: 2.2.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) - maci-crypto: 2.2.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) - maci-domainobjs: 2.2.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) - solidity-docgen: 0.6.0-beta.36(hardhat@2.22.8(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@22.2.0)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)) + maci-circuits: 2.4.0(@types/snarkjs@0.7.8)(bufferutil@4.0.8)(utf-8-validate@5.0.10) + maci-core: 2.4.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) + maci-crypto: 2.4.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) + maci-domainobjs: 2.4.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) + solidity-docgen: 0.6.0-beta.36(hardhat@2.22.8(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@20.14.14)(typescript@5.5.4))(typescript@5.5.4)(utf-8-validate@5.0.10)) uuid: 10.0.0 transitivePeerDependencies: - '@nomicfoundation/hardhat-chai-matchers' @@ -26856,10 +26918,18 @@ snapshots: - typescript - utf-8-validate + maci-core@0.0.0-ci.07552f4(bufferutil@4.0.8)(utf-8-validate@5.0.10): + dependencies: + maci-crypto: 0.0.0-ci.07552f4(bufferutil@4.0.8)(utf-8-validate@5.0.10) + maci-domainobjs: 0.0.0-ci.07552f4(bufferutil@4.0.8)(utf-8-validate@5.0.10) + transitivePeerDependencies: + - bufferutil + - utf-8-validate + maci-core@2.2.0(bufferutil@4.0.8)(utf-8-validate@5.0.10): dependencies: maci-crypto: 2.2.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) - maci-domainobjs: 2.2.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) + maci-domainobjs: 2.4.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) transitivePeerDependencies: - bufferutil - utf-8-validate @@ -26872,6 +26942,16 @@ snapshots: - bufferutil - utf-8-validate + maci-crypto@0.0.0-ci.07552f4(bufferutil@4.0.8)(utf-8-validate@5.0.10): + dependencies: + '@zk-kit/baby-jubjub': 1.0.3 + '@zk-kit/eddsa-poseidon': 1.0.3 + '@zk-kit/poseidon-cipher': 0.3.2 + ethers: 6.13.2(bufferutil@4.0.8)(utf-8-validate@5.0.10) + transitivePeerDependencies: + - bufferutil + - utf-8-validate + maci-crypto@2.0.0(bufferutil@4.0.8)(utf-8-validate@5.0.10): dependencies: '@zk-kit/baby-jubjub': 1.0.1 @@ -26902,6 +26982,13 @@ snapshots: - bufferutil - utf-8-validate + maci-domainobjs@0.0.0-ci.07552f4(bufferutil@4.0.8)(utf-8-validate@5.0.10): + dependencies: + maci-crypto: 0.0.0-ci.07552f4(bufferutil@4.0.8)(utf-8-validate@5.0.10) + transitivePeerDependencies: + - bufferutil + - utf-8-validate + maci-domainobjs@2.0.0(bufferutil@4.0.8)(utf-8-validate@5.0.10): dependencies: maci-crypto: 2.0.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) @@ -26927,7 +27014,7 @@ snapshots: dependencies: '@graphprotocol/graph-cli': 0.80.0(@types/node@20.14.14)(bufferutil@4.0.8)(encoding@0.1.13)(node-fetch@2.7.0(encoding@0.1.13))(typescript@5.5.4)(utf-8-validate@5.0.10) '@graphprotocol/graph-ts': 0.35.1 - maci-contracts: 2.3.0(g3d4wepazdznljietc42e5anoy) + maci-contracts: 2.4.0(g3d4wepazdznljietc42e5anoy) transitivePeerDependencies: - '@nomicfoundation/hardhat-chai-matchers' - '@nomicfoundation/hardhat-ignition-ethers' @@ -29414,7 +29501,7 @@ snapshots: resolve@1.22.8: dependencies: - is-core-module: 2.15.0 + is-core-module: 2.15.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 @@ -29886,7 +29973,7 @@ snapshots: '@aduh95/viz.js': 3.7.0 '@solidity-parser/parser': 0.16.2 axios: 1.7.3(debug@4.3.6) - axios-debug-log: 1.0.0(axios@1.7.3(debug@4.3.6)) + axios-debug-log: 1.0.0(axios@1.7.3) cli-color: 2.0.4 commander: 11.1.0 convert-svg-to-png: 0.6.4(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)