From 7288e183c9952aaa9075d9c58fc95d329f3c9245 Mon Sep 17 00:00:00 2001 From: ctrlc03 <93448202+ctrlc03@users.noreply.github.com> Date: Wed, 4 Dec 2024 16:34:50 +0000 Subject: [PATCH] feat: use gatekeeper per poll --- packages/cli/ts/commands/deployPoll.ts | 38 ++- packages/cli/ts/commands/joinPoll.ts | 6 +- packages/cli/ts/index.ts | 2 + packages/cli/ts/utils/interfaces.ts | 11 + packages/contracts/contracts/MACI.sol | 7 +- packages/contracts/contracts/Poll.sol | 18 +- .../contracts/contracts/interfaces/IPoll.sol | 3 +- .../interfaces/ISignUpGatekeeper.sol | 11 + .../contracts/contracts/utilities/Params.sol | 2 + packages/contracts/deploy-config-example.json | 33 +- .../tasks/deploy/poll/01-gatekeepers.ts | 298 ++++++++++++++++++ .../deploy/poll/{01-poll.ts => 02-poll.ts} | 6 + packages/contracts/tasks/helpers/constants.ts | 1 + packages/contracts/tests/MACI.test.ts | 14 +- .../contracts/tests/MessageProcessor.test.ts | 6 +- packages/contracts/tests/Poll.test.ts | 31 +- packages/contracts/tests/PollFactory.test.ts | 7 +- packages/contracts/tests/Tally.test.ts | 8 + packages/contracts/tests/TallyNonQv.test.ts | 4 + packages/contracts/tests/constants.ts | 1 + .../ts/__tests__/maci-keys.test.ts | 3 +- .../ts/__tests__/utils/interfaces.ts | 3 +- .../ts/__tests__/utils/utils.ts | 7 +- 23 files changed, 480 insertions(+), 40 deletions(-) create mode 100644 packages/contracts/contracts/interfaces/ISignUpGatekeeper.sol create mode 100644 packages/contracts/tasks/deploy/poll/01-gatekeepers.ts rename packages/contracts/tasks/deploy/poll/{01-poll.ts => 02-poll.ts} (93%) diff --git a/packages/cli/ts/commands/deployPoll.ts b/packages/cli/ts/commands/deployPoll.ts index bff6d257f2..b69fb1a631 100644 --- a/packages/cli/ts/commands/deployPoll.ts +++ b/packages/cli/ts/commands/deployPoll.ts @@ -1,4 +1,4 @@ -import { MACI__factory as MACIFactory, EMode } from "maci-contracts"; +import { MACI__factory as MACIFactory, EMode, deployFreeForAllSignUpGatekeeper } from "maci-contracts"; import { PubKey } from "maci-domainobjs"; import { @@ -26,6 +26,7 @@ export const deployPoll = async ({ coordinatorPubkey, maciAddress, vkRegistryAddress, + gatekeeperAddress, signer, quiet = true, useQuadraticVoting = false, @@ -49,6 +50,18 @@ export const deployPoll = async ({ const maci = maciAddress || maciContractAddress; + const maciContract = MACIFactory.connect(maci, signer); + const pollId = await maciContract.nextPollId(); + + // check if we have a signupGatekeeper already deployed or passed as arg + let signupGatekeeperContractAddress = + gatekeeperAddress || (await readContractAddress(`SignUpGatekeeper-${pollId.toString()}`, network?.name)); + + if (!signupGatekeeperContractAddress) { + const contract = await deployFreeForAllSignUpGatekeeper(signer, true); + signupGatekeeperContractAddress = await contract.getAddress(); + } + // required arg -> poll duration if (pollDuration <= 0) { logError("Duration cannot be <= 0"); @@ -83,8 +96,6 @@ export const deployPoll = async ({ // get the verifier contract const verifierContractAddress = await readContractAddress("Verifier", network?.name); - const maciContract = MACIFactory.connect(maci, signer); - // deploy the poll let pollAddr = ""; let messageProcessorContractAddress = ""; @@ -103,6 +114,7 @@ export const deployPoll = async ({ verifierContractAddress, vkRegistry, useQuadraticVoting ? EMode.QV : EMode.NON_QV, + signupGatekeeperContractAddress, { gasLimit: 10000000 }, ); @@ -130,8 +142,8 @@ export const deployPoll = async ({ } // eslint-disable-next-line no-underscore-dangle - const pollId = log.args._pollId; - const pollContracts = await maciContract.getPoll(pollId); + const eventPollId = log.args._pollId; + const pollContracts = await maciContract.getPoll(eventPollId); pollAddr = pollContracts.poll; messageProcessorContractAddress = pollContracts.messageProcessor; tallyContractAddress = pollContracts.tally; @@ -142,9 +154,18 @@ export const deployPoll = async ({ logGreen(quiet, info(`Tally contract: ${tallyContractAddress}`)); // store the address - await storeContractAddress(`MessageProcessor-${pollId.toString()}`, messageProcessorContractAddress, network?.name); - await storeContractAddress(`Tally-${pollId.toString()}`, tallyContractAddress, network?.name); - await storeContractAddress(`Poll-${pollId.toString()}`, pollAddr, network?.name); + await storeContractAddress( + `SignUpGatekeeper-${eventPollId.toString()}`, + signupGatekeeperContractAddress, + network?.name, + ); + await storeContractAddress( + `MessageProcessor-${eventPollId.toString()}`, + messageProcessorContractAddress, + network?.name, + ); + await storeContractAddress(`Tally-${eventPollId.toString()}`, tallyContractAddress, network?.name); + await storeContractAddress(`Poll-${eventPollId.toString()}`, pollAddr, network?.name); } catch (error) { logError((error as Error).message); } @@ -154,5 +175,6 @@ export const deployPoll = async ({ messageProcessor: messageProcessorContractAddress, tally: tallyContractAddress, poll: pollAddr, + signupGatekeeper: signupGatekeeperContractAddress, }; }; diff --git a/packages/cli/ts/commands/joinPoll.ts b/packages/cli/ts/commands/joinPoll.ts index c8dc02af6a..a78b52a6dc 100644 --- a/packages/cli/ts/commands/joinPoll.ts +++ b/packages/cli/ts/commands/joinPoll.ts @@ -10,7 +10,7 @@ import fs from "fs"; import type { IJoinPollArgs, IJoinedUserArgs, IParsePollJoinEventsArgs, IJoinPollData } from "../utils"; -import { contractExists, logError, logYellow, info, logGreen, success, BLOCKS_STEP } from "../utils"; +import { contractExists, logError, logYellow, info, logGreen, success, BLOCKS_STEP, DEFAULT_SG_DATA } from "../utils"; import { banner } from "../utils/banner"; /** @@ -195,6 +195,7 @@ export const joinPoll = async ({ rapidsnark, pollWitgen, pollWasm, + sgDataArg, quiet = true, }: IJoinPollArgs): Promise => { banner(quiet); @@ -332,6 +333,8 @@ export const joinPoll = async ({ let pollStateIndex = ""; let receipt: ContractTransactionReceipt | null = null; + const sgData = sgDataArg || DEFAULT_SG_DATA; + try { // generate the proof for this batch const proof = await generateAndVerifyProof( @@ -351,6 +354,7 @@ export const joinPoll = async ({ loadedCreditBalance!, currentStateRootIndex, proof, + sgData, ); receipt = await tx.wait(); logYellow(quiet, info(`Transaction hash: ${receipt!.hash}`)); diff --git a/packages/cli/ts/index.ts b/packages/cli/ts/index.ts index 6051a4f856..c40a5166e9 100644 --- a/packages/cli/ts/index.ts +++ b/packages/cli/ts/index.ts @@ -216,6 +216,7 @@ program .description("join the poll") .requiredOption("-sk, --priv-key ", "the private key") .option("-i, --state-index ", "the user's state index", BigInt) + .requiredOption("-s, --sg-data ", "the signup gateway data") .requiredOption("-esk, --poll-priv-key ", "the user ephemeral private key for the poll") .option( "-nv, --new-voice-credit-balance ", @@ -265,6 +266,7 @@ program useWasm: cmdObj.wasm, rapidsnark: cmdObj.rapidsnark, pollWitgen: cmdObj.pollWitnessgen, + sgDataArg: cmdObj.sgData, }); } catch (error) { program.error((error as Error).message, { exitCode: 1 }); diff --git a/packages/cli/ts/utils/interfaces.ts b/packages/cli/ts/utils/interfaces.ts index f1054c0b83..68467b9f7a 100644 --- a/packages/cli/ts/utils/interfaces.ts +++ b/packages/cli/ts/utils/interfaces.ts @@ -23,6 +23,7 @@ export interface PollContracts { poll: string; messageProcessor: string; tally: string; + signupGatekeeper: string; } /** @@ -318,6 +319,11 @@ export interface DeployPollArgs { * Whether to use quadratic voting or not */ useQuadraticVoting?: boolean; + + /** + * The address of the gatekeeper contract + */ + gatekeeperAddress?: string; } /** @@ -447,6 +453,11 @@ export interface IJoinPollArgs { * Poll private key for the poll */ pollPrivKey: string; + + /** + * The signup gatekeeper data + */ + sgDataArg?: string; } /** diff --git a/packages/contracts/contracts/MACI.sol b/packages/contracts/contracts/MACI.sol index 8a12ee01f3..3575d6a7cf 100644 --- a/packages/contracts/contracts/MACI.sol +++ b/packages/contracts/contracts/MACI.sol @@ -6,6 +6,7 @@ import { IMessageProcessorFactory } from "./interfaces/IMPFactory.sol"; import { ITallyFactory } from "./interfaces/ITallyFactory.sol"; import { IVerifier } from "./interfaces/IVerifier.sol"; import { IVkRegistry } from "./interfaces/IVkRegistry.sol"; +import { ISignUpGatekeeper } from "./interfaces/ISignUpGatekeeper.sol"; import { InitialVoiceCreditProxy } from "./initialVoiceCreditProxy/InitialVoiceCreditProxy.sol"; import { SignUpGatekeeper } from "./gatekeepers/SignUpGatekeeper.sol"; import { IMACI } from "./interfaces/IMACI.sol"; @@ -187,7 +188,8 @@ contract MACI is IMACI, DomainObjs, Params, Utilities { PubKey memory _coordinatorPubKey, address _verifier, address _vkRegistry, - Mode _mode + Mode _mode, + address _gatekeeper ) public virtual { // cache the poll to a local variable so we can increment it uint256 pollId = nextPollId; @@ -208,7 +210,8 @@ contract MACI is IMACI, DomainObjs, Params, Utilities { ExtContracts memory extContracts = ExtContracts({ maci: IMACI(address(this)), verifier: IVerifier(_verifier), - vkRegistry: IVkRegistry(_vkRegistry) + vkRegistry: IVkRegistry(_vkRegistry), + gatekeeper: ISignUpGatekeeper(_gatekeeper) }); address p = pollFactory.deploy( diff --git a/packages/contracts/contracts/Poll.sol b/packages/contracts/contracts/Poll.sol index a183f39e00..d620c03906 100644 --- a/packages/contracts/contracts/Poll.sol +++ b/packages/contracts/contracts/Poll.sol @@ -6,6 +6,7 @@ import { SnarkCommon } from "./crypto/SnarkCommon.sol"; import { LazyIMTData, InternalLazyIMT } from "./trees/LazyIMT.sol"; import { IMACI } from "./interfaces/IMACI.sol"; import { IPoll } from "./interfaces/IPoll.sol"; +import { ISignUpGatekeeper } from "./interfaces/ISignUpGatekeeper.sol"; import { Utilities } from "./utilities/Utilities.sol"; import { CurveBabyJubJub } from "./crypto/BabyJubJub.sol"; @@ -284,28 +285,31 @@ contract Poll is Params, Utilities, SnarkCommon, IPoll { PubKey calldata _pubKey, uint256 _newVoiceCreditBalance, uint256 _stateRootIndex, - uint256[8] calldata _proof + uint256[8] calldata _proof, + bytes memory _signUpGatekeeperData ) public virtual isWithinVotingDeadline { // Whether the user has already joined if (pollNullifier[_nullifier]) { revert UserAlreadyJoined(); } + // Set nullifier for user's private key + pollNullifier[_nullifier] = true; + // Verify user's proof if (!verifyPollProof(_nullifier, _newVoiceCreditBalance, _stateRootIndex, _pubKey, _proof)) { revert InvalidPollProof(); } + // Check if the user is eligible to join the poll + extContracts.gatekeeper.register(msg.sender, _signUpGatekeeperData); + // Store user in the pollStateTree - uint256 timestamp = block.timestamp; - uint256 stateLeaf = hashStateLeaf(StateLeaf(_pubKey, _newVoiceCreditBalance, timestamp)); + uint256 stateLeaf = hashStateLeaf(StateLeaf(_pubKey, _newVoiceCreditBalance, block.timestamp)); InternalLazyIMT._insert(pollStateTree, stateLeaf); - // Set nullifier for user's private key - pollNullifier[_nullifier] = true; - uint256 pollStateIndex = pollStateTree.numberOfLeaves - 1; - emit PollJoined(_pubKey.x, _pubKey.y, _newVoiceCreditBalance, timestamp, _nullifier, pollStateIndex); + emit PollJoined(_pubKey.x, _pubKey.y, _newVoiceCreditBalance, block.timestamp, _nullifier, pollStateIndex); } /// @notice Verify the proof for Poll diff --git a/packages/contracts/contracts/interfaces/IPoll.sol b/packages/contracts/contracts/interfaces/IPoll.sol index 50318db07f..b17491e6ad 100644 --- a/packages/contracts/contracts/interfaces/IPoll.sol +++ b/packages/contracts/contracts/interfaces/IPoll.sol @@ -13,7 +13,8 @@ interface IPoll { DomainObjs.PubKey calldata _pubKey, uint256 _newVoiceCreditBalance, uint256 _stateRootIndex, - uint256[8] calldata _proof + uint256[8] calldata _proof, + bytes memory _signUpGatekeeperData ) external; /// @notice The number of messages which have been processed and the number of signups diff --git a/packages/contracts/contracts/interfaces/ISignUpGatekeeper.sol b/packages/contracts/contracts/interfaces/ISignUpGatekeeper.sol new file mode 100644 index 0000000000..ded9df5cfd --- /dev/null +++ b/packages/contracts/contracts/interfaces/ISignUpGatekeeper.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.10; + +/// @title ISignUpGatekeeper +/// @notice SignUpGatekeeper interface +interface ISignUpGatekeeper { + /// @notice Register a user + /// @param _user User address + /// @param _data Data to register + function register(address _user, bytes memory _data) external; +} diff --git a/packages/contracts/contracts/utilities/Params.sol b/packages/contracts/contracts/utilities/Params.sol index bde214b17e..91d9fae8e0 100644 --- a/packages/contracts/contracts/utilities/Params.sol +++ b/packages/contracts/contracts/utilities/Params.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.20; import { IMACI } from "../interfaces/IMACI.sol"; import { IVerifier } from "../interfaces/IVerifier.sol"; import { IVkRegistry } from "../interfaces/IVkRegistry.sol"; +import { ISignUpGatekeeper } from "../interfaces/ISignUpGatekeeper.sol"; /// @title Params /// @notice This contracts contains a number of structures @@ -24,5 +25,6 @@ contract Params { IMACI maci; IVerifier verifier; IVkRegistry vkRegistry; + ISignUpGatekeeper gatekeeper; } } diff --git a/packages/contracts/deploy-config-example.json b/packages/contracts/deploy-config-example.json index eb3daf87e4..04ab0e3417 100644 --- a/packages/contracts/deploy-config-example.json +++ b/packages/contracts/deploy-config-example.json @@ -65,7 +65,8 @@ "Poll": { "pollDuration": 3600, "coordinatorPubkey": "macipk.9a59264310d95cfd8eb7083aebeba221b5c26e77427f12b7c0f50bc1cc35e621", - "useQuadraticVoting": false + "useQuadraticVoting": false, + "gatekeeper": "FreeForAllGatekeeper" } }, "scroll_sepolia": { @@ -133,7 +134,8 @@ "Poll": { "pollDuration": 10800, "coordinatorPubkey": "macipk.0a1ce79a43fa676ee3d2882c79d9164a24d4a22bb6190e3d8fa25d97bffc069a", - "useQuadraticVoting": false + "useQuadraticVoting": false, + "gatekeeper": "FreeForAllGatekeeper" } }, "optimism": { @@ -202,7 +204,8 @@ "Poll": { "pollDuration": 3600, "coordinatorPubkey": "macipk.9a59264310d95cfd8eb7083aebeba221b5c26e77427f12b7c0f50bc1cc35e621", - "useQuadraticVoting": false + "useQuadraticVoting": false, + "gatekeeper": "FreeForAllGatekeeper" } }, "arbitrum_sepolia": { @@ -271,7 +274,8 @@ "Poll": { "pollDuration": 3600, "coordinatorPubkey": "macipk.9a59264310d95cfd8eb7083aebeba221b5c26e77427f12b7c0f50bc1cc35e621", - "useQuadraticVoting": false + "useQuadraticVoting": false, + "gatekeeper": "FreeForAllGatekeeper" } }, "localhost": { @@ -340,7 +344,8 @@ "Poll": { "pollDuration": 30, "coordinatorPubkey": "macipk.29add77d27341c4cdfc2fb623175ecfd6527a286e3e7ded785d9fd7afbbdf399", - "useQuadraticVoting": false + "useQuadraticVoting": false, + "gatekeeper": "FreeForAllGatekeeper" } }, "base_sepolia": { @@ -409,7 +414,8 @@ "Poll": { "pollDuration": 3600, "coordinatorPubkey": "macipk.9a59264310d95cfd8eb7083aebeba221b5c26e77427f12b7c0f50bc1cc35e621", - "useQuadraticVoting": false + "useQuadraticVoting": false, + "gatekeeper": "FreeForAllGatekeeper" } }, "optimism_sepolia": { @@ -483,7 +489,8 @@ "Poll": { "pollDuration": 3600, "coordinatorPubkey": "macipk.9a59264310d95cfd8eb7083aebeba221b5c26e77427f12b7c0f50bc1cc35e621", - "useQuadraticVoting": false + "useQuadraticVoting": false, + "gatekeeper": "FreeForAllGatekeeper" } }, "gnosis_chiado": { @@ -557,7 +564,8 @@ "Poll": { "pollDuration": 3600, "coordinatorPubkey": "macipk.0a1ce79a43fa676ee3d2882c79d9164a24d4a22bb6190e3d8fa25d97bffc069a", - "useQuadraticVoting": false + "useQuadraticVoting": false, + "gatekeeper": "FreeForAllGatekeeper" } }, "gnosis": { @@ -631,7 +639,8 @@ "Poll": { "pollDuration": 3600, "coordinatorPubkey": "macipk.0a1ce79a43fa676ee3d2882c79d9164a24d4a22bb6190e3d8fa25d97bffc069a", - "useQuadraticVoting": false + "useQuadraticVoting": false, + "gatekeeper": "FreeForAllGatekeeper" } }, "polygon_amoy": { @@ -705,7 +714,8 @@ "Poll": { "pollDuration": 3600, "coordinatorPubkey": "macipk.0a1ce79a43fa676ee3d2882c79d9164a24d4a22bb6190e3d8fa25d97bffc069a", - "useQuadraticVoting": true + "useQuadraticVoting": true, + "gatekeeper": "FreeForAllGatekeeper" } }, "polygon": { @@ -779,7 +789,8 @@ "Poll": { "pollDuration": 3600, "coordinatorPubkey": "macipk.0a1ce79a43fa676ee3d2882c79d9164a24d4a22bb6190e3d8fa25d97bffc069a", - "useQuadraticVoting": true + "useQuadraticVoting": true, + "gatekeeper": "FreeForAllGatekeeper" } } } diff --git a/packages/contracts/tasks/deploy/poll/01-gatekeepers.ts b/packages/contracts/tasks/deploy/poll/01-gatekeepers.ts new file mode 100644 index 0000000000..856ebef3dc --- /dev/null +++ b/packages/contracts/tasks/deploy/poll/01-gatekeepers.ts @@ -0,0 +1,298 @@ +import { hexToBigInt, uuidToBigInt } from "@pcd/util"; + +import { HatsGatekeeperBase, MACI } from "../../../typechain-types"; +import { EDeploySteps, ESupportedChains } from "../../helpers/constants"; +import { ContractStorage } from "../../helpers/ContractStorage"; +import { Deployment } from "../../helpers/Deployment"; +import { EContracts, IDeployParams } from "../../helpers/types"; + +const deployment = Deployment.getInstance(); +const storage = ContractStorage.getInstance(); + +/** + * Deploy step registration and task itself + */ +deployment.deployTask(EDeploySteps.PollGatekeeper, "Deploy Poll gatekeepers").then((task) => + task.setAction(async ({ incremental }: IDeployParams, hre) => { + deployment.setHre(hre); + const deployer = await deployment.getDeployer(); + + const maciContract = await deployment.getContract({ name: EContracts.MACI }); + const pollId = await maciContract.nextPollId(); + + const freeForAllGatekeeperContractAddress = storage.getAddress( + EContracts.FreeForAllGatekeeper, + hre.network.name, + `poll-${pollId}`, + ); + const easGatekeeperContractAddress = storage.getAddress( + EContracts.EASGatekeeper, + hre.network.name, + `poll-${pollId}`, + ); + const hatsGatekeeperContractAddress = storage.getAddress( + EContracts.HatsGatekeeper, + hre.network.name, + `poll-${pollId}`, + ); + const gitcoinGatekeeperContractAddress = storage.getAddress( + EContracts.GitcoinPassportGatekeeper, + hre.network.name, + `poll-${pollId}`, + ); + const zupassGatekeeperContractAddress = storage.getAddress( + EContracts.ZupassGatekeeper, + hre.network.name, + `poll-${pollId}`, + ); + const semaphoreGatekeeperContractAddress = storage.getAddress( + EContracts.SemaphoreGatekeeper, + hre.network.name, + `poll-${pollId}`, + ); + const merkleProofGatekeeperContractAddress = storage.getAddress( + EContracts.MerkleProofGatekeeper, + hre.network.name, + `poll-${pollId}`, + ); + + const gatekeeperToDeploy = + deployment.getDeployConfigField(EContracts.Poll, "gatekeeper") || + EContracts.FreeForAllGatekeeper; + + const skipDeployFreeForAllGatekeeper = gatekeeperToDeploy !== EContracts.FreeForAllGatekeeper; + const skipDeployEASGatekeeper = gatekeeperToDeploy !== EContracts.EASGatekeeper; + const skipDeployGitcoinGatekeeper = gatekeeperToDeploy !== EContracts.GitcoinPassportGatekeeper; + const skipDeployZupassGatekeeper = gatekeeperToDeploy !== EContracts.ZupassGatekeeper; + const skipDeploySemaphoreGatekeeper = gatekeeperToDeploy !== EContracts.SemaphoreGatekeeper; + const skipDeployHatsGatekeeper = gatekeeperToDeploy !== EContracts.HatsGatekeeper; + const skipDeployMerkleProofGatekeeper = gatekeeperToDeploy !== EContracts.MerkleProofGatekeeper; + + const hasGatekeeperAddress = [ + freeForAllGatekeeperContractAddress, + easGatekeeperContractAddress, + gitcoinGatekeeperContractAddress, + zupassGatekeeperContractAddress, + semaphoreGatekeeperContractAddress, + hatsGatekeeperContractAddress, + merkleProofGatekeeperContractAddress, + ].some(Boolean); + + const isSkipable = [ + skipDeployFreeForAllGatekeeper, + skipDeployEASGatekeeper, + skipDeployGitcoinGatekeeper, + skipDeployZupassGatekeeper, + skipDeploySemaphoreGatekeeper, + skipDeployHatsGatekeeper, + skipDeployMerkleProofGatekeeper, + ].some((skip) => !skip); + + const canSkipDeploy = incremental && hasGatekeeperAddress && isSkipable; + + if (canSkipDeploy) { + // eslint-disable-next-line no-console + console.log(`Skipping deployment of the Gatekeeper contract`); + return; + } + + if (!skipDeployFreeForAllGatekeeper) { + const freeForAllGatekeeperContract = await deployment.deployContract({ + name: EContracts.FreeForAllGatekeeper, + signer: deployer, + }); + + await storage.register({ + id: EContracts.FreeForAllGatekeeper, + key: `poll-${pollId}`, + contract: freeForAllGatekeeperContract, + args: [], + network: hre.network.name, + }); + } + + const isSupportedEASGatekeeperNetwork = ![ESupportedChains.Hardhat, ESupportedChains.Coverage].includes( + hre.network.name as ESupportedChains, + ); + + if (!skipDeployEASGatekeeper && isSupportedEASGatekeeperNetwork) { + const easAddress = deployment.getDeployConfigField(EContracts.EASGatekeeper, "easAddress", true); + const encodedSchema = deployment.getDeployConfigField(EContracts.EASGatekeeper, "schema", true); + const attester = deployment.getDeployConfigField(EContracts.EASGatekeeper, "attester", true); + + const easGatekeeperContract = await deployment.deployContract( + { + name: EContracts.EASGatekeeper, + signer: deployer, + }, + easAddress, + attester, + encodedSchema, + ); + + await storage.register({ + id: EContracts.EASGatekeeper, + key: `poll-${pollId}`, + contract: easGatekeeperContract, + args: [easAddress, attester, encodedSchema], + network: hre.network.name, + }); + } + + const isSupportedGitcoinGatekeeperNetwork = ![ + ESupportedChains.Hardhat, + ESupportedChains.Coverage, + ESupportedChains.Sepolia, + ].includes(hre.network.name as ESupportedChains); + + if (!skipDeployGitcoinGatekeeper && isSupportedGitcoinGatekeeperNetwork) { + const gitcoinGatekeeperDecoderAddress = deployment.getDeployConfigField( + EContracts.GitcoinPassportGatekeeper, + "decoderAddress", + true, + ); + const gitcoinGatekeeperPassingScore = deployment.getDeployConfigField( + EContracts.GitcoinPassportGatekeeper, + "passingScore", + true, + ); + const gitcoinGatekeeperContract = await deployment.deployContract( + { + name: EContracts.GitcoinPassportGatekeeper, + signer: deployer, + }, + gitcoinGatekeeperDecoderAddress, + gitcoinGatekeeperPassingScore, + ); + + await storage.register({ + id: EContracts.GitcoinPassportGatekeeper, + key: `poll-${pollId}`, + contract: gitcoinGatekeeperContract, + args: [gitcoinGatekeeperDecoderAddress, gitcoinGatekeeperPassingScore], + network: hre.network.name, + }); + } + + if (!skipDeployZupassGatekeeper) { + const eventId = deployment.getDeployConfigField(EContracts.ZupassGatekeeper, "eventId", true); + const validEventId = uuidToBigInt(eventId); + const signer1 = deployment.getDeployConfigField(EContracts.ZupassGatekeeper, "signer1", true); + const validSigner1 = hexToBigInt(signer1); + const signer2 = deployment.getDeployConfigField(EContracts.ZupassGatekeeper, "signer2", true); + const validSigner2 = hexToBigInt(signer2); + let verifier = deployment.getDeployConfigField(EContracts.ZupassGatekeeper, "zupassVerifier"); + + if (!verifier) { + const verifierContract = await deployment.deployContract({ + name: EContracts.ZupassGroth16Verifier, + signer: deployer, + }); + verifier = await verifierContract.getAddress(); + } + + const ZupassGatekeeperContract = await deployment.deployContract( + { + name: EContracts.ZupassGatekeeper, + signer: deployer, + }, + validEventId, + validSigner1, + validSigner2, + verifier, + ); + await storage.register({ + id: EContracts.ZupassGatekeeper, + key: `poll-${pollId}`, + contract: ZupassGatekeeperContract, + args: [validEventId.toString(), validSigner1.toString(), validSigner2.toString(), verifier], + network: hre.network.name, + }); + } + + if (!skipDeploySemaphoreGatekeeper) { + const semaphoreContractAddress = deployment.getDeployConfigField( + EContracts.SemaphoreGatekeeper, + "semaphoreContract", + true, + ); + const groupId = deployment.getDeployConfigField(EContracts.SemaphoreGatekeeper, "groupId", true); + + const semaphoreGatekeeperContract = await deployment.deployContract( + { + name: EContracts.SemaphoreGatekeeper, + signer: deployer, + }, + semaphoreContractAddress, + groupId, + ); + + await storage.register({ + id: EContracts.SemaphoreGatekeeper, + key: `poll-${pollId}`, + contract: semaphoreGatekeeperContract, + args: [semaphoreContractAddress, groupId.toString()], + network: hre.network.name, + }); + } + + if (!skipDeployHatsGatekeeper) { + // get args + const criterionHats = deployment.getDeployConfigField(EContracts.HatsGatekeeper, "criterionHats", true); + const hatsProtocolAddress = deployment.getDeployConfigField( + EContracts.HatsGatekeeper, + "hatsProtocolAddress", + true, + ); + + let hatsGatekeeperContract: HatsGatekeeperBase; + // if we have one we use the single gatekeeper + if (criterionHats.length === 1) { + hatsGatekeeperContract = await deployment.deployContract( + { + name: EContracts.HatsGatekeeperSingle, + signer: deployer, + }, + hatsProtocolAddress, + criterionHats[0], + ); + } else { + hatsGatekeeperContract = await deployment.deployContract( + { + name: EContracts.HatsGatekeeperMultiple, + signer: deployer, + }, + hatsProtocolAddress, + criterionHats, + ); + } + + await storage.register({ + id: EContracts.HatsGatekeeper, + key: `poll-${pollId}`, + contract: hatsGatekeeperContract, + args: [hatsProtocolAddress, criterionHats.length === 1 ? criterionHats[0] : criterionHats], + network: hre.network.name, + }); + } + + if (!skipDeployMerkleProofGatekeeper) { + const root = deployment.getDeployConfigField(EContracts.MerkleProofGatekeeper, "root", true); + + const MerkleProofGatekeeperContract = await deployment.deployContract( + { + name: EContracts.MerkleProofGatekeeper, + signer: deployer, + }, + root, + ); + await storage.register({ + id: EContracts.MerkleProofGatekeeper, + key: `poll-${pollId}`, + contract: MerkleProofGatekeeperContract, + args: [root], + network: hre.network.name, + }); + } + }), +); diff --git a/packages/contracts/tasks/deploy/poll/01-poll.ts b/packages/contracts/tasks/deploy/poll/02-poll.ts similarity index 93% rename from packages/contracts/tasks/deploy/poll/01-poll.ts rename to packages/contracts/tasks/deploy/poll/02-poll.ts index 4a6459019a..98b48ee5b8 100644 --- a/packages/contracts/tasks/deploy/poll/01-poll.ts +++ b/packages/contracts/tasks/deploy/poll/02-poll.ts @@ -49,6 +49,11 @@ deployment.deployTask(EDeploySteps.Poll, "Deploy poll").then((task) => const unserializedKey = PubKey.deserialize(coordinatorPubkey); const mode = useQuadraticVoting ? EMode.QV : EMode.NON_QV; + const gatekeeper = + deployment.getDeployConfigField(EContracts.Poll, "gatekeeper") || + EContracts.FreeForAllGatekeeper; + const gatekeeperContractAddress = storage.mustGetAddress(gatekeeper, hre.network.name, `poll-${pollId}`); + const tx = await maciContract.deployPoll( pollDuration, { @@ -60,6 +65,7 @@ deployment.deployTask(EDeploySteps.Poll, "Deploy poll").then((task) => verifierContractAddress, vkRegistryContractAddress, mode, + gatekeeperContractAddress, ); const receipt = await tx.wait(); diff --git a/packages/contracts/tasks/helpers/constants.ts b/packages/contracts/tasks/helpers/constants.ts index 76e580ec17..f8bcfec6c2 100644 --- a/packages/contracts/tasks/helpers/constants.ts +++ b/packages/contracts/tasks/helpers/constants.ts @@ -11,6 +11,7 @@ export enum EDeploySteps { TallyFactory = "full:deploy-tally-factory", Maci = "full:deploy-maci", VkRegistry = "full:deploy-vk-registry", + PollGatekeeper = "poll:deploy-gatekeeper", Poll = "poll:deploy-poll", } diff --git a/packages/contracts/tests/MACI.test.ts b/packages/contracts/tests/MACI.test.ts index 19bd9d7b14..8c8d6a03ae 100644 --- a/packages/contracts/tests/MACI.test.ts +++ b/packages/contracts/tests/MACI.test.ts @@ -8,7 +8,14 @@ import { Keypair, PubKey, Message } from "maci-domainobjs"; import { EMode } from "../ts/constants"; import { getDefaultSigner, getSigners } from "../ts/utils"; -import { MACI, Poll as PollContract, Poll__factory as PollFactory, Verifier, VkRegistry } from "../typechain-types"; +import { + MACI, + Poll as PollContract, + Poll__factory as PollFactory, + Verifier, + VkRegistry, + SignUpGatekeeper, +} from "../typechain-types"; import { STATE_TREE_DEPTH, duration, initialVoiceCreditBalance, messageBatchSize, treeDepths } from "./constants"; import { timeTravel, deployTestContracts } from "./utils"; @@ -19,6 +26,7 @@ describe("MACI", function test() { let maciContract: MACI; let vkRegistryContract: VkRegistry; let verifierContract: Verifier; + let signupGatekeeperContract: SignUpGatekeeper; let pollId: bigint; const coordinator = new Keypair(); @@ -40,6 +48,7 @@ describe("MACI", function test() { maciContract = r.maciContract; vkRegistryContract = r.vkRegistryContract; verifierContract = r.mockVerifierContract as Verifier; + signupGatekeeperContract = r.gatekeeperContract; }); it("should have set the correct stateTreeDepth", async () => { @@ -218,6 +227,7 @@ describe("MACI", function test() { verifierContract, vkRegistryContract, EMode.QV, + signupGatekeeperContract, ); const receipt = await tx.wait(); @@ -252,6 +262,7 @@ describe("MACI", function test() { verifierContract, vkRegistryContract, EMode.QV, + signupGatekeeperContract, ); const receipt = await tx.wait(); expect(receipt?.status).to.eq(1); @@ -270,6 +281,7 @@ describe("MACI", function test() { verifierContract, vkRegistryContract, EMode.QV, + signupGatekeeperContract, ); const receipt = await tx.wait(); expect(receipt?.status).to.eq(1); diff --git a/packages/contracts/tests/MessageProcessor.test.ts b/packages/contracts/tests/MessageProcessor.test.ts index 598558d1af..6702b30619 100644 --- a/packages/contracts/tests/MessageProcessor.test.ts +++ b/packages/contracts/tests/MessageProcessor.test.ts @@ -15,6 +15,7 @@ import { MessageProcessor__factory as MessageProcessorFactory, Verifier, VkRegistry, + SignUpGatekeeper, } from "../typechain-types"; import { @@ -34,7 +35,7 @@ describe("MessageProcessor", () => { let verifierContract: Verifier; let vkRegistryContract: VkRegistry; let mpContract: MessageProcessor; - + let signupGatekeeperContract: SignUpGatekeeper; let pollId: bigint; // local poll and maci state @@ -57,7 +58,7 @@ describe("MessageProcessor", () => { signer = await getDefaultSigner(); verifierContract = r.mockVerifierContract as Verifier; vkRegistryContract = r.vkRegistryContract; - + signupGatekeeperContract = r.gatekeeperContract; // deploy on chain poll const tx = await maciContract.deployPoll( duration, @@ -67,6 +68,7 @@ describe("MessageProcessor", () => { verifierContract, vkRegistryContract, EMode.QV, + signupGatekeeperContract, ); let receipt = await tx.wait(); diff --git a/packages/contracts/tests/Poll.test.ts b/packages/contracts/tests/Poll.test.ts index 1bd6b5b4b0..935a731807 100644 --- a/packages/contracts/tests/Poll.test.ts +++ b/packages/contracts/tests/Poll.test.ts @@ -10,7 +10,14 @@ import { Keypair, Message, PCommand, PubKey } from "maci-domainobjs"; import { EMode } from "../ts/constants"; import { IVerifyingKeyStruct } from "../ts/types"; import { getDefaultSigner } from "../ts/utils"; -import { Poll__factory as PollFactory, MACI, Poll as PollContract, Verifier, VkRegistry } from "../typechain-types"; +import { + Poll__factory as PollFactory, + MACI, + Poll as PollContract, + Verifier, + VkRegistry, + SignUpGatekeeper, +} from "../typechain-types"; import { STATE_TREE_DEPTH, @@ -30,6 +37,7 @@ describe("Poll", () => { let pollContract: PollContract; let verifierContract: Verifier; let vkRegistryContract: VkRegistry; + let signupGatekeeperContract: SignUpGatekeeper; let signer: Signer; let deployTime: number; const coordinator = new Keypair(); @@ -51,6 +59,7 @@ describe("Poll", () => { maciContract = r.maciContract; verifierContract = r.mockVerifierContract as Verifier; vkRegistryContract = r.vkRegistryContract; + signupGatekeeperContract = r.gatekeeperContract; for (let i = 0; i < NUM_USERS; i += 1) { const timestamp = Math.floor(Date.now() / 1000); @@ -74,6 +83,7 @@ describe("Poll", () => { verifierContract, vkRegistryContract, EMode.QV, + signupGatekeeperContract, ); const receipt = await tx.wait(); @@ -177,6 +187,7 @@ describe("Poll", () => { r.mockVerifierContract as Verifier, r.vkRegistryContract, EMode.QV, + signupGatekeeperContract, ), ).to.be.revertedWithCustomError(testMaciContract, "InvalidPubKey"); }); @@ -192,7 +203,14 @@ describe("Poll", () => { const mockNullifier = AbiCoder.defaultAbiCoder().encode(["uint256"], [i]); const voiceCreditBalance = AbiCoder.defaultAbiCoder().encode(["uint256"], [i]); - const response = await pollContract.joinPoll(mockNullifier, pubkey, voiceCreditBalance, i, mockProof); + const response = await pollContract.joinPoll( + mockNullifier, + pubkey, + voiceCreditBalance, + i, + mockProof, + AbiCoder.defaultAbiCoder().encode(["uint256"], [1]), + ); const receipt = await response.wait(); const logs = receipt!.logs[0]; const event = iface.parseLog(logs as unknown as { topics: string[]; data: string }) as unknown as { @@ -226,7 +244,14 @@ describe("Poll", () => { const mockProof = [0, 0, 0, 0, 0, 0, 0, 0]; await expect( - pollContract.joinPoll(mockNullifier, pubkey, voiceCreditBalance, 0, mockProof), + pollContract.joinPoll( + mockNullifier, + pubkey, + voiceCreditBalance, + 0, + mockProof, + AbiCoder.defaultAbiCoder().encode(["uint256"], [1]), + ), ).to.be.revertedWithCustomError(pollContract, "UserAlreadyJoined"); }); }); diff --git a/packages/contracts/tests/PollFactory.test.ts b/packages/contracts/tests/PollFactory.test.ts index 89d4565101..55c3586818 100644 --- a/packages/contracts/tests/PollFactory.test.ts +++ b/packages/contracts/tests/PollFactory.test.ts @@ -33,7 +33,12 @@ describe("pollFactory", () => { maciContract = r.maciContract; verifierContract = r.mockVerifierContract as Verifier; vkRegistryContract = r.vkRegistryContract; - extContracts = { maci: maciContract, verifier: verifierContract, vkRegistry: vkRegistryContract }; + extContracts = { + maci: maciContract, + verifier: verifierContract, + vkRegistry: vkRegistryContract, + gatekeeper: r.gatekeeperContract, + }; pollFactory = (await deployPollFactory(signer, undefined, true)) as BaseContract as PollFactory; }); diff --git a/packages/contracts/tests/Tally.test.ts b/packages/contracts/tests/Tally.test.ts index ed76475005..d8a25fd2de 100644 --- a/packages/contracts/tests/Tally.test.ts +++ b/packages/contracts/tests/Tally.test.ts @@ -19,6 +19,7 @@ import { MessageProcessor__factory as MessageProcessorFactory, Poll__factory as PollFactory, Tally__factory as TallyFactory, + SignUpGatekeeper, } from "../typechain-types"; import { @@ -41,6 +42,7 @@ describe("TallyVotes", () => { let mpContract: MessageProcessor; let verifierContract: Verifier; let vkRegistryContract: VkRegistry; + let signupGatekeeperContract: SignUpGatekeeper; const coordinator = new Keypair(); let users: Keypair[]; @@ -63,6 +65,7 @@ describe("TallyVotes", () => { maciContract = r.maciContract; verifierContract = r.mockVerifierContract as Verifier; vkRegistryContract = r.vkRegistryContract; + signupGatekeeperContract = r.gatekeeperContract; // deploy a poll // deploy on chain poll @@ -74,6 +77,7 @@ describe("TallyVotes", () => { verifierContract, vkRegistryContract, EMode.QV, + signupGatekeeperContract, ); const receipt = await tx.wait(); @@ -231,6 +235,7 @@ describe("TallyVotes", () => { verifierContract, vkRegistryContract, EMode.QV, + signupGatekeeperContract, ); const receipt = await tx.wait(); @@ -308,6 +313,7 @@ describe("TallyVotes", () => { BigInt(initialVoiceCreditBalance), i, [0, 0, 0, 0, 0, 0, 0, 0], + AbiCoder.defaultAbiCoder().encode(["uint256"], [1]), ); } @@ -538,6 +544,7 @@ describe("TallyVotes", () => { verifierContract, vkRegistryContract, EMode.QV, + signupGatekeeperContract, ); const receipt = await tx.wait(); @@ -616,6 +623,7 @@ describe("TallyVotes", () => { BigInt(initialVoiceCreditBalance), i, [0, 0, 0, 0, 0, 0, 0, 0], + AbiCoder.defaultAbiCoder().encode(["uint256"], [1]), ); } diff --git a/packages/contracts/tests/TallyNonQv.test.ts b/packages/contracts/tests/TallyNonQv.test.ts index 7d4e218e40..9187925de8 100644 --- a/packages/contracts/tests/TallyNonQv.test.ts +++ b/packages/contracts/tests/TallyNonQv.test.ts @@ -19,6 +19,7 @@ import { MessageProcessor__factory as MessageProcessorFactory, Poll__factory as PollFactory, Tally__factory as TallyFactory, + SignUpGatekeeper, } from "../typechain-types"; import { STATE_TREE_DEPTH, duration, messageBatchSize, testProcessVk, testTallyVk, treeDepths } from "./constants"; @@ -32,6 +33,7 @@ describe("TallyVotesNonQv", () => { let mpContract: MessageProcessor; let verifierContract: Verifier; let vkRegistryContract: VkRegistry; + let gatekeeperContract: SignUpGatekeeper; const coordinator = new Keypair(); let maciState: MaciState; @@ -50,6 +52,7 @@ describe("TallyVotesNonQv", () => { maciContract = r.maciContract; verifierContract = r.mockVerifierContract as Verifier; vkRegistryContract = r.vkRegistryContract; + gatekeeperContract = r.gatekeeperContract; // deploy a poll // deploy on chain poll @@ -61,6 +64,7 @@ describe("TallyVotesNonQv", () => { verifierContract, vkRegistryContract, EMode.NON_QV, + gatekeeperContract, ); const receipt = await tx.wait(); diff --git a/packages/contracts/tests/constants.ts b/packages/contracts/tests/constants.ts index 855b0e102e..ac2b747dcf 100644 --- a/packages/contracts/tests/constants.ts +++ b/packages/contracts/tests/constants.ts @@ -7,6 +7,7 @@ export interface ExtContractsStruct { maci: AddressLike; verifier: AddressLike; vkRegistry: AddressLike; + gatekeeper: AddressLike; } export const duration = 2_000; diff --git a/packages/integrationTests/ts/__tests__/maci-keys.test.ts b/packages/integrationTests/ts/__tests__/maci-keys.test.ts index 52a86fcfb4..081553e52f 100644 --- a/packages/integrationTests/ts/__tests__/maci-keys.test.ts +++ b/packages/integrationTests/ts/__tests__/maci-keys.test.ts @@ -72,7 +72,7 @@ describe("integration tests private/public/keypair", () => { before(async () => { signer = await getDefaultSigner(); - const { maci, verifier, vkRegistry } = await deployTestContracts( + const { maci, verifier, vkRegistry, gatekeeper } = await deployTestContracts( initialVoiceCredits, STATE_TREE_DEPTH, signer, @@ -91,6 +91,7 @@ describe("integration tests private/public/keypair", () => { verifier, vkRegistry, EMode.NON_QV, + gatekeeper, ); // we know it's the first poll so id is 0 diff --git a/packages/integrationTests/ts/__tests__/utils/interfaces.ts b/packages/integrationTests/ts/__tests__/utils/interfaces.ts index 9bfd6297ce..647a0b6ee5 100644 --- a/packages/integrationTests/ts/__tests__/utils/interfaces.ts +++ b/packages/integrationTests/ts/__tests__/utils/interfaces.ts @@ -1,4 +1,4 @@ -import { MACI, Verifier, VkRegistry } from "maci-contracts"; +import { MACI, Verifier, VkRegistry, FreeForAllGatekeeper } from "maci-contracts"; /** * A util interface that represents a vote object @@ -47,4 +47,5 @@ export interface IDeployedTestContracts { maci: MACI; verifier: Verifier; vkRegistry: VkRegistry; + gatekeeper: FreeForAllGatekeeper; } diff --git a/packages/integrationTests/ts/__tests__/utils/utils.ts b/packages/integrationTests/ts/__tests__/utils/utils.ts index 2859b7ecf1..5b59cb923b 100644 --- a/packages/integrationTests/ts/__tests__/utils/utils.ts +++ b/packages/integrationTests/ts/__tests__/utils/utils.ts @@ -198,5 +198,10 @@ export const deployTestContracts = async ( quiet, }); - return { maci: maciContract, verifier: mockVerifierContract as Verifier, vkRegistry: vkRegistryContract }; + return { + maci: maciContract, + verifier: mockVerifierContract as Verifier, + vkRegistry: vkRegistryContract, + gatekeeper: gatekeeperContract, + }; };