From 579a53f1f9a501e95fbfb064d39272c57a9e4f21 Mon Sep 17 00:00:00 2001 From: yu-zhen Date: Sat, 13 Jan 2024 00:19:04 +0800 Subject: [PATCH] feat: add interfaces and factory contracts with Natspec --- .../scripts/ceremony-param-tests-c-witness.sh | 5 +- cli/package.json | 6 +- cli/testScript.sh | 4 +- cli/tests/e2e/e2e.subsidy.test.ts | 24 ++- cli/tests/e2e/e2e.test.ts | 67 ++++--- cli/tests/e2e/keyChange.test.ts | 17 +- cli/tests/unit/topup.test.ts | 3 - cli/ts/commands/deploy.ts | 1 - cli/ts/commands/deployPoll.ts | 87 +++------ cli/ts/commands/proveOnChain.ts | 38 ++-- cli/ts/commands/verify.ts | 18 +- cli/ts/index.ts | 21 +++ cli/ts/utils/interfaces.ts | 4 +- contracts/contracts/MACI.sol | 75 +++++--- contracts/contracts/MessageProcessor.sol | 85 +++++---- .../contracts/MessageProcessorFactory.sol | 24 +++ contracts/contracts/Poll.sol | 68 +++----- contracts/contracts/PollFactory.sol | 20 +-- contracts/contracts/Subsidy.sol | 56 +++--- contracts/contracts/SubsidyFactory.sol | 23 +++ contracts/contracts/Tally.sol | 58 +++--- contracts/contracts/TallyFactory.sol | 23 +++ contracts/contracts/VkRegistry.sol | 23 +-- contracts/contracts/crypto/IVerifier.sol | 11 -- contracts/contracts/crypto/MockVerifier.sol | 5 +- contracts/contracts/crypto/Verifier.sol | 5 +- contracts/contracts/interfaces/IMACI.sol | 25 +-- contracts/contracts/interfaces/IMPFactory.sol | 14 ++ .../interfaces/IMessageProcessor.sol | 14 ++ contracts/contracts/interfaces/IPoll.sol | 101 +++++++++++ .../contracts/interfaces/IPollFactory.sol | 32 ++++ .../interfaces/ITallySubsidyFactory.sol | 21 +++ contracts/contracts/interfaces/IVerifier.sol | 22 +++ .../contracts/interfaces/IVkRegistry.sol | 43 +++++ .../contracts/utilities/CommonUtilities.sol | 4 +- contracts/tests/MACI.test.ts | 34 +++- contracts/tests/MessageProcessor.test.ts | 52 +++--- contracts/tests/Poll.test.ts | 21 ++- contracts/tests/Subsidy.test.ts | 94 +++++----- contracts/tests/Tally.test.ts | 79 +++++---- contracts/tests/constants.ts | 2 +- contracts/tests/utils.ts | 61 +------ contracts/ts/deploy.ts | 165 ++++-------------- contracts/ts/index.ts | 3 - contracts/ts/types.ts | 6 - .../ts/__tests__/integration.test.ts | 3 + .../ts/__tests__/maci-keys.test.ts | 18 +- .../ts/__tests__/utils/interfaces.ts | 11 ++ integrationTests/ts/__tests__/utils/utils.ts | 28 ++- 49 files changed, 935 insertions(+), 689 deletions(-) create mode 100644 contracts/contracts/MessageProcessorFactory.sol create mode 100644 contracts/contracts/SubsidyFactory.sol create mode 100644 contracts/contracts/TallyFactory.sol delete mode 100644 contracts/contracts/crypto/IVerifier.sol create mode 100644 contracts/contracts/interfaces/IMPFactory.sol create mode 100644 contracts/contracts/interfaces/IMessageProcessor.sol create mode 100644 contracts/contracts/interfaces/IPoll.sol create mode 100644 contracts/contracts/interfaces/IPollFactory.sol create mode 100644 contracts/contracts/interfaces/ITallySubsidyFactory.sol create mode 100644 contracts/contracts/interfaces/IVerifier.sol create mode 100644 contracts/contracts/interfaces/IVkRegistry.sol diff --git a/.github/scripts/ceremony-param-tests-c-witness.sh b/.github/scripts/ceremony-param-tests-c-witness.sh index f3d00157f8..5434611899 100755 --- a/.github/scripts/ceremony-param-tests-c-witness.sh +++ b/.github/scripts/ceremony-param-tests-c-witness.sh @@ -25,6 +25,7 @@ HARDHAT_CONFIG=./build/hardhat.config.js node build/ts/index.js deployPoll \ --msg-tree-depth 8 \ --msg-batch-depth 2 \ --vote-option-tree-depth 3 \ + --subsidy-enabled false \ -q true HARDHAT_CONFIG=./build/hardhat.config.js node build/ts/index.js signup \ --pubkey macipk.e743ffb5298ef0f5c1f63b6464a48fea19ea7ee5a885c67ae1b24a1d04f03f07 \ @@ -64,8 +65,10 @@ HARDHAT_CONFIG=./build/hardhat.config.js node build/ts/index.js genProofs \ HARDHAT_CONFIG=./build/hardhat.config.js node build/ts/index.js proveOnChain \ --poll-id 0 \ --proof-dir proofs/ \ + --subsidy-enabled false \ -q true HARDHAT_CONFIG=./build/hardhat.config.js node build/ts/index.js verify \ --poll-id 0 \ + --subsidy-enabled false \ --tally-file tally.json \ - -q true + -q true \ No newline at end of file diff --git a/cli/package.json b/cli/package.json index 65db33b830..57982819f8 100644 --- a/cli/package.json +++ b/cli/package.json @@ -15,9 +15,9 @@ "postbuild": "cp package.json ./build", "types": "tsc -p tsconfig.json --noEmit", "test": "nyc ts-mocha --exit tests/**/*.test.ts", - "test:e2e": "ts-mocha --exit tests/e2e.test.ts", - "test:e2e-subsidy": "ts-mocha --exit tests/e2e.subsidy.test.ts", - "test:keyChange": "ts-mocha --exit tests/keyChange.test.ts", + "test:e2e": "ts-mocha --exit tests/e2e/e2e.test.ts", + "test:e2e-subsidy": "ts-mocha --exit tests/e2e/e2e.subsidy.test.ts", + "test:keyChange": "ts-mocha --exit tests/e2e/keyChange.test.ts", "test:unit": "nyc ts-mocha --exit tests/unit/*.test.ts", "test:airdrop": "nyc ts-mocha --exit tests/unit/airdrop.test.ts", "test:genPubKey": "ts-mocha --exit tests/unit/genPubKey.test.ts", diff --git a/cli/testScript.sh b/cli/testScript.sh index 7c64431381..2f986faacd 100644 --- a/cli/testScript.sh +++ b/cli/testScript.sh @@ -13,7 +13,7 @@ HARDHAT_CONFIG=./build/hardhat.config.js node build/ts/index.js setVerifyingKeys HARDHAT_CONFIG=./build/hardhat.config.js node build/ts/index.js create -s 10 HARDHAT_CONFIG=./build/hardhat.config.js node build/ts/index.js deployPoll \ --pubkey macipk.ea638a3366ed91f2e955110888573861f7c0fc0bb5fb8b8dca9cd7a08d7d6b93 \ - -t 30 -g 25 -mv 25 -i 1 -m 2 -b 1 -v 2 + -t 30 -g 25 -mv 25 -i 1 -m 2 -b 1 -v 2 -se false HARDHAT_CONFIG=./build/hardhat.config.js node build/ts/index.js signup \ --pubkey macipk.e743ffb5298ef0f5c1f63b6464a48fea19ea7ee5a885c67ae1b24a1d04f03f07 HARDHAT_CONFIG=./build/hardhat.config.js node build/ts/index.js publish \ @@ -48,6 +48,8 @@ HARDHAT_CONFIG=./build/hardhat.config.js node build/ts/index.js genProofs \ HARDHAT_CONFIG=./build/hardhat.config.js node build/ts/index.js proveOnChain \ --poll-id 0 \ --proof-dir proofs/ + --subsidy-enabled false HARDHAT_CONFIG=./build/hardhat.config.js node build/ts/index.js verify \ --poll-id 0 \ + --subsidy-enabled false \ --tally-file tally.json diff --git a/cli/tests/e2e/e2e.subsidy.test.ts b/cli/tests/e2e/e2e.subsidy.test.ts index fc8d3afbf2..f5e35fd937 100644 --- a/cli/tests/e2e/e2e.subsidy.test.ts +++ b/cli/tests/e2e/e2e.subsidy.test.ts @@ -52,6 +52,8 @@ describe("e2e with Subsidy tests", function test() { let pollAddresses: PollContracts; let vkRegistryContractAddress: string; + const subsidyEnabled = true; + before(async () => { // we deploy the vk registry contract vkRegistryContractAddress = await deployVkRegistryContract(true); @@ -89,6 +91,7 @@ describe("e2e with Subsidy tests", function test() { MSG_TREE_DEPTH, VOTE_OPTION_TREE_DEPTH, coordinatorPubKey, + subsidyEnabled, ); }); @@ -193,9 +196,10 @@ describe("e2e with Subsidy tests", function test() { testSubsidyWasmPath, useWasm, ); - await proveOnChain("0", testProofsDirPath); + await proveOnChain("0", testProofsDirPath, subsidyEnabled); await verify( "0", + subsidyEnabled, testTallyFilePath, tallyData, maciAddresses.maciAddress, @@ -236,6 +240,7 @@ describe("e2e with Subsidy tests", function test() { MSG_TREE_DEPTH, VOTE_OPTION_TREE_DEPTH, coordinatorPubKey, + subsidyEnabled, ); }); @@ -285,9 +290,10 @@ describe("e2e with Subsidy tests", function test() { testSubsidyWasmPath, useWasm, ); - await proveOnChain("0", testProofsDirPath); + await proveOnChain("0", testProofsDirPath, subsidyEnabled); await verify( "0", + subsidyEnabled, testTallyFilePath, tallyData, maciAddresses.maciAddress, @@ -318,6 +324,7 @@ describe("e2e with Subsidy tests", function test() { MSG_TREE_DEPTH, VOTE_OPTION_TREE_DEPTH, coordinatorPubKey, + subsidyEnabled, ); }); @@ -369,9 +376,10 @@ describe("e2e with Subsidy tests", function test() { testSubsidyWasmPath, useWasm, ); - await proveOnChain("0", testProofsDirPath); + await proveOnChain("0", testProofsDirPath, subsidyEnabled); await verify( "0", + subsidyEnabled, testTallyFilePath, tallyData, maciAddresses.maciAddress, @@ -415,6 +423,7 @@ describe("e2e with Subsidy tests", function test() { MSG_TREE_DEPTH, VOTE_OPTION_TREE_DEPTH, coordinatorPubKey, + subsidyEnabled, ); // signup // eslint-disable-next-line @typescript-eslint/prefer-for-of @@ -459,9 +468,10 @@ describe("e2e with Subsidy tests", function test() { testSubsidyWasmPath, useWasm, ); - await proveOnChain("0", testProofsDirPath); + await proveOnChain("0", testProofsDirPath, subsidyEnabled); await verify( "0", + subsidyEnabled, testTallyFilePath, tallyData, maciAddresses.maciAddress, @@ -483,6 +493,7 @@ describe("e2e with Subsidy tests", function test() { MSG_TREE_DEPTH, VOTE_OPTION_TREE_DEPTH, coordinatorPubKey, + subsidyEnabled, ); secondPollAddresses = await deployPoll( pollDuration, @@ -493,6 +504,7 @@ describe("e2e with Subsidy tests", function test() { MSG_TREE_DEPTH, VOTE_OPTION_TREE_DEPTH, coordinatorPubKey, + subsidyEnabled, ); }); @@ -597,6 +609,7 @@ describe("e2e with Subsidy tests", function test() { await proveOnChain( "1", testProofsDirPath, + subsidyEnabled, maciAddresses.maciAddress, pollAddresses.messageProcessor, pollAddresses.tally, @@ -604,6 +617,7 @@ describe("e2e with Subsidy tests", function test() { ); await verify( "1", + subsidyEnabled, testTallyFilePath, tallyData, maciAddresses.maciAddress, @@ -640,6 +654,7 @@ describe("e2e with Subsidy tests", function test() { await proveOnChain( "2", testProofsDirPath, + subsidyEnabled, maciAddresses.maciAddress, secondPollAddresses.messageProcessor, secondPollAddresses.tally, @@ -647,6 +662,7 @@ describe("e2e with Subsidy tests", function test() { ); await verify( "2", + subsidyEnabled, testTallyFilePath, tallyData, maciAddresses.maciAddress, diff --git a/cli/tests/e2e/e2e.test.ts b/cli/tests/e2e/e2e.test.ts index 13d0ab394e..1236222a06 100644 --- a/cli/tests/e2e/e2e.test.ts +++ b/cli/tests/e2e/e2e.test.ts @@ -64,6 +64,8 @@ describe("e2e tests", function test() { let maciAddresses: DeployedContracts; let pollAddresses: PollContracts; + const subsidyEnabled = false; + // before all tests we deploy the vk registry contract and set the verifying keys before(async () => { // we deploy the vk registry contract @@ -100,6 +102,7 @@ describe("e2e tests", function test() { MSG_TREE_DEPTH, VOTE_OPTION_TREE_DEPTH, coordinatorPubKey, + subsidyEnabled, ); }); @@ -145,14 +148,14 @@ describe("e2e tests", function test() { undefined, useWasm, ); - await proveOnChain("0", testProofsDirPath); + await proveOnChain("0", testProofsDirPath, subsidyEnabled); await verify( "0", + subsidyEnabled, testTallyFilePath, tallyFileData, maciAddresses.maciAddress, pollAddresses.tally, - pollAddresses.subsidy, ); }); }); @@ -177,6 +180,7 @@ describe("e2e tests", function test() { MSG_TREE_DEPTH, VOTE_OPTION_TREE_DEPTH, coordinatorPubKey, + subsidyEnabled, ); }); @@ -259,14 +263,14 @@ describe("e2e tests", function test() { undefined, useWasm, ); - await proveOnChain("0", testProofsDirPath); + await proveOnChain("0", testProofsDirPath, subsidyEnabled); await verify( "0", + subsidyEnabled, testTallyFilePath, tallyFileData, maciAddresses.maciAddress, pollAddresses.tally, - pollAddresses.subsidy, ); }); }); @@ -291,6 +295,7 @@ describe("e2e tests", function test() { MSG_TREE_DEPTH, VOTE_OPTION_TREE_DEPTH, coordinatorPubKey, + subsidyEnabled, ); }); @@ -395,14 +400,14 @@ describe("e2e tests", function test() { undefined, useWasm, ); - await proveOnChain("0", testProofsDirPath); + await proveOnChain("0", testProofsDirPath, subsidyEnabled); await verify( "0", + subsidyEnabled, testTallyFilePath, tallyFileData, maciAddresses.maciAddress, pollAddresses.tally, - pollAddresses.subsidy, ); }); }); @@ -437,6 +442,7 @@ describe("e2e tests", function test() { MSG_TREE_DEPTH, VOTE_OPTION_TREE_DEPTH, coordinatorPubKey, + subsidyEnabled, ); }); @@ -486,14 +492,14 @@ describe("e2e tests", function test() { undefined, useWasm, ); - await proveOnChain("0", testProofsDirPath); + await proveOnChain("0", testProofsDirPath, subsidyEnabled); await verify( "0", + subsidyEnabled, testTallyFilePath, tallyFileData, maciAddresses.maciAddress, pollAddresses.tally, - pollAddresses.subsidy, ); }); }); @@ -518,6 +524,7 @@ describe("e2e tests", function test() { MSG_TREE_DEPTH, VOTE_OPTION_TREE_DEPTH, coordinatorPubKey, + subsidyEnabled, ); }); @@ -569,14 +576,14 @@ describe("e2e tests", function test() { undefined, useWasm, ); - await proveOnChain("0", testProofsDirPath); + await proveOnChain("0", testProofsDirPath, subsidyEnabled); await verify( "0", + subsidyEnabled, testTallyFilePath, tallyFileData, maciAddresses.maciAddress, pollAddresses.tally, - pollAddresses.subsidy, ); }); }); @@ -619,6 +626,7 @@ describe("e2e tests", function test() { MSG_TREE_DEPTH, VOTE_OPTION_TREE_DEPTH, coordinatorPubKey, + subsidyEnabled, ); // signup await signup(user.pubKey.serialize()); @@ -659,8 +667,8 @@ describe("e2e tests", function test() { undefined, useWasm, ); - await proveOnChain("0", testProofsDirPath); - await verify("0", testTallyFilePath, tallyFileData); + await proveOnChain("0", testProofsDirPath, subsidyEnabled); + await verify("0", subsidyEnabled, testTallyFilePath, tallyFileData); cleanVanilla(); }); @@ -674,6 +682,7 @@ describe("e2e tests", function test() { MSG_TREE_DEPTH, VOTE_OPTION_TREE_DEPTH, coordinatorPubKey, + subsidyEnabled, ); }); it("should publish a new message", async () => { @@ -713,8 +722,8 @@ describe("e2e tests", function test() { undefined, useWasm, ); - await proveOnChain("1", testProofsDirPath); - await verify("1", testTallyFilePath); + await proveOnChain("1", testProofsDirPath, subsidyEnabled); + await verify("1", subsidyEnabled, testTallyFilePath); }); }); @@ -751,6 +760,7 @@ describe("e2e tests", function test() { MSG_TREE_DEPTH, VOTE_OPTION_TREE_DEPTH, coordinatorPubKey, + subsidyEnabled, ); // signup @@ -797,8 +807,8 @@ describe("e2e tests", function test() { undefined, useWasm, ); - await proveOnChain("0", testProofsDirPath); - await verify("0", testTallyFilePath, tallyFileData); + await proveOnChain("0", testProofsDirPath, subsidyEnabled); + await verify("0", subsidyEnabled, testTallyFilePath, tallyFileData); cleanVanilla(); }); @@ -813,6 +823,7 @@ describe("e2e tests", function test() { MSG_TREE_DEPTH, VOTE_OPTION_TREE_DEPTH, coordinatorPubKey, + subsidyEnabled, ); secondPollAddresses = await deployPoll( pollDuration, @@ -823,6 +834,7 @@ describe("e2e tests", function test() { MSG_TREE_DEPTH, VOTE_OPTION_TREE_DEPTH, coordinatorPubKey, + subsidyEnabled, ); }); @@ -927,11 +939,12 @@ describe("e2e tests", function test() { await proveOnChain( "1", testProofsDirPath, + subsidyEnabled, maciAddresses.maciAddress, pollAddresses.messageProcessor, pollAddresses.tally, ); - await verify("1", testTallyFilePath, tallyData, maciAddresses.maciAddress, pollAddresses.tally); + await verify("1", subsidyEnabled, testTallyFilePath, tallyData, maciAddresses.maciAddress, pollAddresses.tally); cleanVanilla(); }); @@ -961,11 +974,19 @@ describe("e2e tests", function test() { await proveOnChain( "2", testProofsDirPath, + subsidyEnabled, maciAddresses.maciAddress, secondPollAddresses.messageProcessor, secondPollAddresses.tally, ); - await verify("2", testTallyFilePath, tallyData, maciAddresses.maciAddress, secondPollAddresses.tally); + await verify( + "2", + subsidyEnabled, + testTallyFilePath, + tallyData, + maciAddresses.maciAddress, + secondPollAddresses.tally, + ); }); }); @@ -995,6 +1016,7 @@ describe("e2e tests", function test() { MSG_TREE_DEPTH, VOTE_OPTION_TREE_DEPTH, coordinatorPubKey, + subsidyEnabled, ); }); @@ -1051,8 +1073,8 @@ describe("e2e tests", function test() { useWasm, stateOutPath, ); - await proveOnChain("0", testProofsDirPath); - await verify("0", testTallyFilePath); + await proveOnChain("0", testProofsDirPath, subsidyEnabled); + await verify("0", subsidyEnabled, testTallyFilePath); }); }); @@ -1078,6 +1100,7 @@ describe("e2e tests", function test() { MSG_TREE_DEPTH, VOTE_OPTION_TREE_DEPTH, coordinatorPubKey, + subsidyEnabled, ); }); @@ -1131,8 +1154,8 @@ describe("e2e tests", function test() { undefined, useWasm, ); - await proveOnChain("0", testProofsDirPath); - await verify("0", testTallyFilePath); + await proveOnChain("0", testProofsDirPath, subsidyEnabled); + await verify("0", subsidyEnabled, testTallyFilePath); }); }); }); diff --git a/cli/tests/e2e/keyChange.test.ts b/cli/tests/e2e/keyChange.test.ts index 360068357a..7b583a1cec 100644 --- a/cli/tests/e2e/keyChange.test.ts +++ b/cli/tests/e2e/keyChange.test.ts @@ -45,6 +45,8 @@ describe("keyChange tests", function test() { let maciAddresses: DeployedContracts; + const subsidyEnabled = false; + // before all tests we deploy the vk registry contract and set the verifying keys before(async () => { // we deploy the vk registry contract @@ -89,6 +91,7 @@ describe("keyChange tests", function test() { MSG_TREE_DEPTH, VOTE_OPTION_TREE_DEPTH, coordinatorPubKey, + subsidyEnabled, ); stateIndex = Number.parseInt(await signup(keypair1.pubKey.serialize()), 10); await publish( @@ -142,8 +145,8 @@ describe("keyChange tests", function test() { undefined, useWasm, ); - await proveOnChain("0", testProofsDirPath); - await verify("0", testTallyFilePath); + await proveOnChain("0", testProofsDirPath, subsidyEnabled); + await verify("0", subsidyEnabled, testTallyFilePath); }); it("should confirm the tally is correct", () => { @@ -181,6 +184,7 @@ describe("keyChange tests", function test() { MSG_TREE_DEPTH, VOTE_OPTION_TREE_DEPTH, coordinatorPubKey, + subsidyEnabled, ); stateIndex = Number.parseInt(await signup(keypair1.pubKey.serialize()), 10); await publish( @@ -234,8 +238,8 @@ describe("keyChange tests", function test() { undefined, useWasm, ); - await proveOnChain("0", testProofsDirPath); - await verify("0", testTallyFilePath); + await proveOnChain("0", testProofsDirPath, subsidyEnabled); + await verify("0", subsidyEnabled, testTallyFilePath); }); it("should confirm the tally is correct", () => { @@ -273,6 +277,7 @@ describe("keyChange tests", function test() { MSG_TREE_DEPTH, VOTE_OPTION_TREE_DEPTH, coordinatorPubKey, + subsidyEnabled, ); stateIndex = Number.parseInt(await signup(keypair1.pubKey.serialize()), 10); await publish( @@ -326,8 +331,8 @@ describe("keyChange tests", function test() { undefined, useWasm, ); - await proveOnChain("0", testProofsDirPath); - await verify("0", testTallyFilePath); + await proveOnChain("0", testProofsDirPath, subsidyEnabled); + await verify("0", subsidyEnabled, testTallyFilePath); }); it("should confirm the tally is correct", () => { diff --git a/cli/tests/unit/topup.test.ts b/cli/tests/unit/topup.test.ts index 2090973608..1f0add0afd 100644 --- a/cli/tests/unit/topup.test.ts +++ b/cli/tests/unit/topup.test.ts @@ -4,7 +4,6 @@ import { deployConstantInitialVoiceCreditProxy, deployFreeForAllSignUpGatekeeper, deployMaci, - deployMockVerifier, deployTopupCredit, getDefaultSigner, } from "maci-contracts"; @@ -18,13 +17,11 @@ describe("topup", () => { before(async () => { signer = await getDefaultSigner(); const signupGatekepper = await deployFreeForAllSignUpGatekeeper(signer, true); - const verifier = await deployMockVerifier(signer, true); const topupCredit = await deployTopupCredit(signer, true); const initialVoiceCreditProxyAddress = await deployConstantInitialVoiceCreditProxy(100, signer, true); const maciContracts = await deployMaci( await signupGatekepper.getAddress(), await initialVoiceCreditProxyAddress.getAddress(), - await verifier.getAddress(), await topupCredit.getAddress(), signer, 10, diff --git a/cli/ts/commands/deploy.ts b/cli/ts/commands/deploy.ts index 0ef370e737..abd8819a9d 100644 --- a/cli/ts/commands/deploy.ts +++ b/cli/ts/commands/deploy.ts @@ -71,7 +71,6 @@ export const deploy = async ( const { maciContract, stateAqContract, pollFactoryContract, poseidonAddrs } = await deployMaci( signupGatekeeperContractAddress, initialVoiceCreditProxyContractAddress!, - verifierContractAddress, topUpCreditAddress, signer, stateTreeDepth, diff --git a/cli/ts/commands/deployPoll.ts b/cli/ts/commands/deployPoll.ts index bc7fe6368e..8b60ccbdf7 100644 --- a/cli/ts/commands/deployPoll.ts +++ b/cli/ts/commands/deployPoll.ts @@ -1,12 +1,5 @@ import { BaseContract } from "ethers"; -import { - type MACI, - deployMessageProcessor, - deploySubsidy, - deployTally, - getDefaultSigner, - parseArtifact, -} from "maci-contracts"; +import { type MACI, getDefaultSigner, parseArtifact } from "maci-contracts"; import { PubKey } from "maci-domainobjs"; import type { PollContracts } from "../utils/interfaces"; @@ -26,6 +19,7 @@ import { info, logError, logGreen } from "../utils/theme"; * @param messageTreeDepth - the depth of the message tree * @param voteOptionTreeDepth - the depth of the vote option tree * @param coordinatorPubkey - the coordinator's public key + * @param subsidyEnabled - whether to deploy subsidy contract * @param maciAddress - the MACI contract address * @param vkRegistryAddress - the vkRegistry contract address * @param quiet - whether to log the output to the console @@ -40,6 +34,7 @@ export const deployPoll = async ( messageTreeDepth: number, voteOptionTreeDepth: number, coordinatorPubkey: string, + subsidyEnabled: boolean, maciAddress?: string, vkRegistryAddress?: string, quiet = true, @@ -105,62 +100,17 @@ export const deployPoll = async ( const unserializedKey = PubKey.deserialize(coordinatorPubkey); - // get the poseidon contracts addresses - const poseidonT3 = readContractAddress("PoseidonT3"); - const poseidonT4 = readContractAddress("PoseidonT4"); - const poseidonT5 = readContractAddress("PoseidonT5"); - const poseidonT6 = readContractAddress("PoseidonT6"); - // get the verifier contract const verifierContractAddress = readContractAddress("Verifier"); - // deploy the message processor - const messageProcessorContract = await deployMessageProcessor( - verifierContractAddress, - vkRegistry, - poseidonT3, - poseidonT4, - poseidonT5, - poseidonT6, - signer, - true, - ); - - // deploy the tally contract - const tallyContract = await deployTally( - verifierContractAddress, - vkRegistry, - poseidonT3, - poseidonT4, - poseidonT5, - poseidonT6, - signer, - true, - ); - - // deploy the subsidy contract - const subsidyContract = await deploySubsidy( - verifierContractAddress, - vkRegistry, - poseidonT3, - poseidonT4, - poseidonT5, - poseidonT6, - signer, - true, - ); - const maciAbi = parseArtifact("MACI")[0]; const maciContract = new BaseContract(maci, maciAbi, signer) as MACI; // deploy the poll let pollAddr = ""; - - const [messageProcessorContractAddress, tallyContractAddress, subsidyContractAddress] = await Promise.all([ - messageProcessorContract.getAddress(), - tallyContract.getAddress(), - subsidyContract.getAddress(), - ]); + let messageProcessorContractAddress = ""; + let tallyContractAddress = ""; + let subsidyContractAddress; try { // deploy the poll contract via the maci contract @@ -174,6 +124,9 @@ export const deployPoll = async ( voteOptionTreeDepth, }, unserializedKey.asContractParam(), + verifierContractAddress, + vkRegistry, + subsidyEnabled, { gasLimit: 10000000 }, ); @@ -196,16 +149,32 @@ export const deployPoll = async ( const pollId = log!.args._pollId as number; // eslint-disable-next-line no-underscore-dangle pollAddr = log!.args._pollAddr as string; + // eslint-disable-next-line no-underscore-dangle + messageProcessorContractAddress = log!.args._mpAddr as string; + // eslint-disable-next-line no-underscore-dangle + tallyContractAddress = log!.args._tallyAddr as string; + + if (subsidyEnabled) { + const receiptLogSubsidy = receipt!.logs[receipt!.logs.length - 2]; + const logSubsidy = iface.parseLog(receiptLogSubsidy as unknown as { topics: string[]; data: string }); + if (logSubsidy?.name !== "DeploySubsidy") { + logError("Invalid event log"); + } + // eslint-disable-next-line no-underscore-dangle + subsidyContractAddress = logSubsidy!.args._subsidyAddr as string; + } logGreen(quiet, info(`Poll ID: ${pollId.toString()}`)); logGreen(quiet, info(`Poll contract: ${pollAddr}`)); - logGreen(quiet, info(`Message processor contract: ${messageProcessorContractAddress}`)); + logGreen(quiet, info(`Message Processor contract: ${messageProcessorContractAddress}`)); logGreen(quiet, info(`Tally contract: ${tallyContractAddress}`)); - logGreen(quiet, info(`Subsidy contract: ${subsidyContractAddress}`)); + if (subsidyEnabled && subsidyContractAddress) { + logGreen(quiet, info(`Subsidy contract: ${subsidyContractAddress}`)); + storeContractAddress(`Subsidy-${pollId.toString()}`, subsidyContractAddress); + } // store the addresss storeContractAddress(`MessageProcessor-${pollId.toString()}`, messageProcessorContractAddress); storeContractAddress(`Tally-${pollId.toString()}`, tallyContractAddress); - storeContractAddress(`Subsidy-${pollId.toString()}`, subsidyContractAddress); storeContractAddress(`Poll-${pollId.toString()}`, pollAddr); } catch (error) { logError((error as Error).message); diff --git a/cli/ts/commands/proveOnChain.ts b/cli/ts/commands/proveOnChain.ts index 9d8437d582..47782073ba 100644 --- a/cli/ts/commands/proveOnChain.ts +++ b/cli/ts/commands/proveOnChain.ts @@ -39,6 +39,7 @@ import { Proof } from "../utils/interfaces"; * Command to prove the result of a poll on-chain * @param pollId - the id of the poll * @param proofDir - the directory containing the proofs + * @param subsidyEnabled - whether to deploy subsidy contract * @param maciAddress - the address of the MACI contract * @param messageProcessorAddress - the address of the MessageProcessor contract * @param tallyAddress - the address of the Tally contract @@ -48,6 +49,7 @@ import { Proof } from "../utils/interfaces"; export const proveOnChain = async ( pollId: string, proofDir: string, + subsidyEnabled: boolean, maciAddress?: string, messageProcessorAddress?: string, tallyAddress?: string, @@ -67,7 +69,7 @@ export const proveOnChain = async ( if (!readContractAddress(`Tally-${pollId}`) && !tallyAddress) { logError("Tally contract address is empty"); } - if (!readContractAddress(`Subsidy-${pollId}`) && !subsidyAddress) { + if (subsidyEnabled && !readContractAddress(`Subsidy-${pollId}`) && !subsidyAddress) { logError("Subsidy contract address is empty"); } @@ -90,7 +92,7 @@ export const proveOnChain = async ( logError("Tally contract does not exist"); } - if (!(await contractExists(signer.provider!, subsidyContractAddress))) { + if (subsidyEnabled && !(await contractExists(signer.provider!, subsidyContractAddress))) { logError("Subsidy contract does not exist"); } @@ -146,11 +148,6 @@ export const proveOnChain = async ( const verifierContract = new BaseContract(verifierContractAddress, parseArtifact("Verifier")[0], signer) as Verifier; - const [pollContractAddress, mpContractAddress] = await Promise.all([ - pollContract.getAddress(), - mpContract.getAddress(), - ]); - const data = { processProofs: [] as Proof[], tallyProofs: [] as Proof[], @@ -178,9 +175,11 @@ export const proveOnChain = async ( return; } - match = filename.match(/subsidy_(\d+)/); - if (match) { - data.subsidyProofs[Number(match[1])] = JSON.parse(fs.readFileSync(filepath).toString()) as Proof; + if (subsidyEnabled) { + match = filename.match(/subsidy_(\d+)/); + if (match) { + data.subsidyProofs[Number(match[1])] = JSON.parse(fs.readFileSync(filepath).toString()) as Proof; + } } }); @@ -287,7 +286,7 @@ export const proveOnChain = async ( } const packedValsOnChain = BigInt( - await mpContract.genProcessMessagesPackedVals(pollContractAddress, currentMessageBatchIndex, numSignUps), + await mpContract.genProcessMessagesPackedVals(currentMessageBatchIndex, numSignUps), ).toString(); if (circuitInputs.packedVals !== packedValsOnChain) { @@ -298,7 +297,6 @@ export const proveOnChain = async ( const publicInputHashOnChain = BigInt( await mpContract.genProcessMessagesPublicInputHash( - pollContractAddress, currentMessageBatchIndex, messageRootOnChain.toString(), numSignUps, @@ -334,11 +332,7 @@ export const proveOnChain = async ( try { // validate process messaging proof and store the new state and ballot root commitment - const tx = await mpContract.processMessages( - pollContractAddress, - asHex(circuitInputs.newSbCommitment as BigNumberish), - formattedProof, - ); + const tx = await mpContract.processMessages(asHex(circuitInputs.newSbCommitment as BigNumberish), formattedProof); const receipt = await tx.wait(); if (receipt?.status !== 1) { @@ -362,7 +356,7 @@ export const proveOnChain = async ( } // subsidy calculations if any subsidy proofs are provided - if (Object.keys(data.subsidyProofs).length !== 0) { + if (subsidyEnabled && Object.keys(data.subsidyProofs).length !== 0) { let rbi = Number(await subsidyContract.rbi()); let cbi = Number(await subsidyContract.cbi()); const num1DBatches = Math.ceil(numSignUps / subsidyBatchSize); @@ -374,7 +368,7 @@ export const proveOnChain = async ( // process all batches for (let i = subsidyBatchNum; i < totalBatchNum; i += 1) { if (i === 0) { - await subsidyContract.updateSbCommitment(mpContractAddress); + await subsidyContract.updateSbCommitment(); } const { proof, circuitInputs, publicInputs } = data.subsidyProofs[i]; @@ -416,8 +410,6 @@ export const proveOnChain = async ( // verify the proof on chain and set the new subsidy commitment const tx = await subsidyContract.updateSubsidy( - pollContractAddress, - mpContractAddress, circuitInputs.newSubsidyCommitment as BigNumberish, formattedProof, ); @@ -463,7 +455,7 @@ export const proveOnChain = async ( for (let i = tallyBatchNum; i < totalTallyBatches; i += 1) { if (i === 0) { - await tallyContract.updateSbCommitment(mpContractAddress); + await tallyContract.updateSbCommitment(); } const batchStartIndex = i * tallyBatchSize; @@ -508,8 +500,6 @@ export const proveOnChain = async ( // verify the proof on chain const tx = await tallyContract.tallyVotes( - pollContractAddress, - mpContractAddress, asHex(circuitInputs.newTallyCommitment as BigNumberish), formattedProof, ); diff --git a/cli/ts/commands/verify.ts b/cli/ts/commands/verify.ts index 69866c2c87..2a1d0b33b9 100644 --- a/cli/ts/commands/verify.ts +++ b/cli/ts/commands/verify.ts @@ -12,6 +12,7 @@ import { verifyPerVOSpentVoiceCredits, verifyTallyResults } from "../utils/verif /** * Verify the results of a poll and optionally the subsidy results * @param pollId - the id of the poll + * @param subsidyEnabled - whether to deploy subsidy contract * @param tallyFile - the path to the tally file * @param maciAddress - the address of the MACI contract * @param tallyAddress - the address of the Tally contract @@ -21,6 +22,7 @@ import { verifyPerVOSpentVoiceCredits, verifyTallyResults } from "../utils/verif */ export const verify = async ( pollId: string, + subsidyEnabled: boolean, tallyFile?: string, tallyData?: TallyData, maciAddress?: string, @@ -41,13 +43,17 @@ export const verify = async ( logError("Tally contract address is empty"); } - if (!readContractAddress(`Subsidy-${pollId}`) && !subsidyAddress) { + if (subsidyEnabled && !readContractAddress(`Subsidy-${pollId}`) && !subsidyAddress) { logError("Subsidy contract address is empty"); } const maciContractAddress = maciAddress || readContractAddress("MACI"); const tallyContractAddress = tallyAddress || readContractAddress(`Tally-${pollId}`); - const subsidyContractAddress = subsidyAddress || readContractAddress(`Subsidy-${pollId}`); + + let subsidyContractAddress = ""; + if (subsidyEnabled) { + subsidyContractAddress = subsidyAddress || readContractAddress(`Subsidy-${pollId}`); + } if (!(await contractExists(signer.provider!, maciContractAddress))) { logError(`Error: there is no contract deployed at ${maciContractAddress}.`); @@ -57,7 +63,7 @@ export const verify = async ( logError(`Error: there is no contract deployed at ${tallyContractAddress}.`); } - if (!(await contractExists(signer.provider!, subsidyContractAddress))) { + if (subsidyEnabled && !(await contractExists(signer.provider!, subsidyContractAddress))) { logError(`Error: there is no contract deployed at ${subsidyContractAddress}.`); } @@ -68,7 +74,9 @@ export const verify = async ( const tallyContract = new BaseContract(tallyContractAddress, parseArtifact("Tally")[0], signer) as Tally; - const subsidyContract = new BaseContract(subsidyContractAddress, parseArtifact("Subsidy")[0], signer) as Subsidy; + const subsidyContract = subsidyEnabled + ? (new BaseContract(subsidyContractAddress, parseArtifact("Subsidy")[0], signer) as Subsidy) + : undefined; // verification const onChainTallycomment = BigInt(await tallyContract.tallyCommitment()); @@ -197,7 +205,7 @@ export const verify = async ( } // verify subsidy result if subsidy file is provided - if (subsidyFile) { + if (subsidyEnabled && subsidyFile && subsidyContract !== undefined) { const onChainSubsidyCommitment = BigInt(await subsidyContract.subsidyCommitment()); logYellow(quiet, info(`on-chain subsidy commitment: ${onChainSubsidyCommitment.toString(16)}`)); diff --git a/cli/ts/index.ts b/cli/ts/index.ts index 9bc5bbdd86..90eda32765 100644 --- a/cli/ts/index.ts +++ b/cli/ts/index.ts @@ -160,6 +160,12 @@ program .requiredOption("-m, --msg-tree-depth ", "the message tree depth", parseInt) .requiredOption("-v, --vote-option-tree-depth ", "the vote option tree depth", parseInt) .requiredOption("-pk, --pubkey ", "the coordinator public key") + .requiredOption( + "-se, --subsidy-enabled ", + "whether to deploy subsidy contract", + (value) => value === "true", + false, + ) .option("-x, --maci-address ", "the MACI contract address") .option("-q, --quiet ", "whether to print values to the console", (value) => value === "true", false) .option("-r, --rpc-provider ", "the rpc provider URL") @@ -174,6 +180,7 @@ program cmdObj.msgTreeDepth, cmdObj.voteOptionTreeDepth, cmdObj.pubkey, + cmdObj.subsidyEnabled, cmdObj.maciAddress, cmdObj.vkRegistryAddress, cmdObj.quiet, @@ -343,6 +350,12 @@ program .description("verify the results of a poll and optionally the subsidy results") .requiredOption("-o, --poll-id ", "the poll id", parseInt) .requiredOption("-t, --tally-file ", "the tally file") + .requiredOption( + "-se, --subsidy-enabled ", + "whether to deploy subsidy contract", + (value) => value === "true", + false, + ) .option("-s, --subsidy-file ", "the subsidy file") .option("-x, --contract ", "the MACI contract address") .option("-tc, --tally-contract ", "the tally contract address") @@ -353,6 +366,7 @@ program try { await verify( cmdObj.pollId.toString(), + cmdObj.subsidyEnabled, cmdObj.tallyFile, undefined, cmdObj.contract, @@ -460,6 +474,12 @@ program .command("proveOnChain") .description("prove the results of a poll on chain") .requiredOption("-o, --poll-id ", "the poll id", parseInt) + .requiredOption( + "-se, --subsidy-enabled ", + "whether to deploy subsidy contract", + (value) => value === "true", + false, + ) .option("-q, --quiet ", "whether to print values to the console", (value) => value === "true", false) .option("-r, --rpc-provider ", "the rpc provider URL") .option("-x, --contract ", "the MACI contract address") @@ -472,6 +492,7 @@ program await proveOnChain( cmdObj.pollId.toString(), cmdObj.proofDir, + cmdObj.subsidyEnabled, cmdObj.contract, cmdObj.messageProcessorAddress, cmdObj.tallyContract, diff --git a/cli/ts/utils/interfaces.ts b/cli/ts/utils/interfaces.ts index 0857761e34..e993c61a05 100644 --- a/cli/ts/utils/interfaces.ts +++ b/cli/ts/utils/interfaces.ts @@ -17,10 +17,10 @@ export interface DeployedContracts { } export interface PollContracts { + poll: string; messageProcessor: string; tally: string; - subsidy: string; - poll: string; + subsidy?: string; } /** diff --git a/contracts/contracts/MACI.sol b/contracts/contracts/MACI.sol index 300f0e08a1..2c70bf7f26 100644 --- a/contracts/contracts/MACI.sol +++ b/contracts/contracts/MACI.sol @@ -1,8 +1,9 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.10; -import { Poll } from "./Poll.sol"; -import { PollFactory } from "./PollFactory.sol"; +import { IPollFactory } from "./interfaces/IPollFactory.sol"; +import { IMessageProcessorFactory } from "./interfaces/IMPFactory.sol"; +import { ITallySubsidyFactory } from "./interfaces/ITallySubsidyFactory.sol"; import { InitialVoiceCreditProxy } from "./initialVoiceCreditProxy/InitialVoiceCreditProxy.sol"; import { SignUpGatekeeper } from "./gatekeepers/SignUpGatekeeper.sol"; import { AccQueue } from "./trees/AccQueue.sol"; @@ -35,7 +36,7 @@ contract MACI is IMACI, Params, Utilities, Ownable { uint256 public nextPollId; /// @notice A mapping of poll IDs to Poll contracts. - mapping(uint256 => Poll) public polls; + mapping(uint256 => address) public polls; /// @notice The number of signups uint256 public override numSignUps; @@ -43,10 +44,20 @@ contract MACI is IMACI, Params, Utilities, Ownable { /// @notice A mapping of block timestamps to the number of state leaves mapping(uint256 => uint256) public numStateLeaves; - // ERC20 contract that hold topup credits + /// @notice ERC20 contract that hold topup credits TopupCredit public topupCredit; - PollFactory public pollFactory; + /// @notice Factory contract that deploy a Poll contract + IPollFactory public pollFactory; + + /// @notice Factory contract that deploy a MessageProcessor contract + IMessageProcessorFactory public messageProcessorFactory; + + /// @notice Factory contract that deploy a Tally contract + ITallySubsidyFactory public tallyFactory; + + /// @notice Factory contract that deploy a Subsidy contract + ITallySubsidyFactory public subsidyFactory; /// @notice The state AccQueue. Represents a mapping between each user's public key /// and their voice credit balance. @@ -66,7 +77,8 @@ contract MACI is IMACI, Params, Utilities, Ownable { // Events event SignUp(uint256 _stateIndex, PubKey _userPubKey, uint256 _voiceCreditBalance, uint256 _timestamp); - event DeployPoll(uint256 _pollId, address _pollAddr, PubKey _pubKey); + event DeployPoll(uint256 _pollId, address _pollAddr, PubKey _pubKey, address _mpAddr, address _tallyAddr); + event DeploySubsidy(address _subsidyAddr); /// @notice Only allow a Poll contract to call the modified function. modifier onlyPoll(uint256 _pollId) { @@ -84,11 +96,17 @@ contract MACI is IMACI, Params, Utilities, Ownable { /// @notice Create a new instance of the MACI contract. /// @param _pollFactory The PollFactory contract + /// @param _messageProcessorFactory The MessageProcessorFactory contract + /// @param _tallyFactory The TallyFactory contract + /// @param _subsidyFactory The SubsidyFactory contract /// @param _signUpGatekeeper The SignUpGatekeeper contract /// @param _initialVoiceCreditProxy The InitialVoiceCreditProxy contract /// @param _stateTreeDepth The depth of the state tree constructor( - PollFactory _pollFactory, + IPollFactory _pollFactory, + IMessageProcessorFactory _messageProcessorFactory, + ITallySubsidyFactory _tallyFactory, + ITallySubsidyFactory _subsidyFactory, SignUpGatekeeper _signUpGatekeeper, InitialVoiceCreditProxy _initialVoiceCreditProxy, TopupCredit _topupCredit, @@ -105,6 +123,9 @@ contract MACI is IMACI, Params, Utilities, Ownable { } pollFactory = _pollFactory; + messageProcessorFactory = _messageProcessorFactory; + tallyFactory = _tallyFactory; + subsidyFactory = _subsidyFactory; topupCredit = _topupCredit; signUpGatekeeper = _signUpGatekeeper; initialVoiceCreditProxy = _initialVoiceCreditProxy; @@ -116,8 +137,7 @@ contract MACI is IMACI, Params, Utilities, Ownable { if (hash2([uint256(1), uint256(1)]) == 0) revert PoseidonHashLibrariesNotLinked(); } - /// @notice Get the depth of the state tree - /// @return The depth of the state tree + /// @inheritdoc IMACI function stateTreeDepth() external view returns (uint8) { return STATE_TREE_DEPTH; } @@ -173,12 +193,18 @@ contract MACI is IMACI, Params, Utilities, Ownable { /// @param _maxValues The maximum number of vote options, and messages /// @param _treeDepths The depth of the Merkle trees /// @param _coordinatorPubKey The coordinator's public key + /// @param _verifier The Verifier Contract + /// @param _vkRegistry The VkRegistry Contract + /// @param useSubsidy If true, the Poll will use the Subsidy contract /// @return pollAddr a new Poll contract address function deployPoll( uint256 _duration, MaxValues memory _maxValues, TreeDepths memory _treeDepths, - PubKey memory _coordinatorPubKey + PubKey memory _coordinatorPubKey, + address _verifier, + address _vkRegistry, + bool useSubsidy ) public onlyOwner returns (address pollAddr) { // cache the poll to a local variable so we can increment it uint256 pollId = nextPollId; @@ -200,7 +226,9 @@ contract MACI is IMACI, Params, Utilities, Ownable { uint24(TREE_ARITY) ** _treeDepths.intStateTreeDepth ); - Poll p = pollFactory.deploy( + address _owner = owner(); + + address p = pollFactory.deploy( _duration, _maxValues, _treeDepths, @@ -208,32 +236,35 @@ contract MACI is IMACI, Params, Utilities, Ownable { _coordinatorPubKey, this, topupCredit, - owner() + _owner ); + address mp = messageProcessorFactory.deploy(_verifier, _vkRegistry, p, _owner); + address tally = tallyFactory.deploy(_verifier, _vkRegistry, p, mp, _owner); + + if (useSubsidy) { + address subsidy = subsidyFactory.deploy(_verifier, _vkRegistry, p, mp, _owner); + emit DeploySubsidy(subsidy); + } + polls[pollId] = p; pollAddr = address(p); - emit DeployPoll(pollId, pollAddr, _coordinatorPubKey); + emit DeployPoll(pollId, pollAddr, _coordinatorPubKey, mp, tally); } - /// @notice Allow Poll contracts to merge the state subroots - /// @param _numSrQueueOps Number of operations - /// @param _pollId The active Poll ID + /// @inheritdoc IMACI function mergeStateAqSubRoots(uint256 _numSrQueueOps, uint256 _pollId) public override onlyPoll(_pollId) { stateAq.mergeSubRoots(_numSrQueueOps); } - /// @notice Allow Poll contracts to merge the state root - /// @param _pollId The active Poll ID - /// @return root The calculated Merkle root + /// @inheritdoc IMACI function mergeStateAq(uint256 _pollId) public override onlyPoll(_pollId) returns (uint256 root) { root = stateAq.merge(STATE_TREE_DEPTH); } - /// @notice Return the main root of the StateAq contract - /// @return root The Merkle root + /// @inheritdoc IMACI function getStateAqRoot() public view override returns (uint256 root) { root = stateAq.getMainRoot(STATE_TREE_DEPTH); } @@ -241,7 +272,7 @@ contract MACI is IMACI, Params, Utilities, Ownable { /// @notice Get the Poll details /// @param _pollId The identifier of the Poll to retrieve /// @return poll The Poll contract object - function getPoll(uint256 _pollId) public view returns (Poll poll) { + function getPoll(uint256 _pollId) public view returns (address poll) { if (_pollId >= nextPollId) revert PollDoesNotExist(_pollId); poll = polls[_pollId]; } diff --git a/contracts/contracts/MessageProcessor.sol b/contracts/contracts/MessageProcessor.sol index 1214b7264b..42a2d5fcc3 100644 --- a/contracts/contracts/MessageProcessor.sol +++ b/contracts/contracts/MessageProcessor.sol @@ -4,18 +4,19 @@ pragma solidity ^0.8.10; import { AccQueue } from "./trees/AccQueue.sol"; import { IMACI } from "./interfaces/IMACI.sol"; import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; -import { Poll } from "./Poll.sol"; +import { IPoll } from "./interfaces/IPoll.sol"; import { SnarkCommon } from "./crypto/SnarkCommon.sol"; import { Hasher } from "./crypto/Hasher.sol"; +import { IVerifier } from "./interfaces/IVerifier.sol"; +import { IVkRegistry } from "./interfaces/IVkRegistry.sol"; +import { IMessageProcessor } from "./interfaces/IMessageProcessor.sol"; import { CommonUtilities } from "./utilities/CommonUtilities.sol"; -import { Verifier } from "./crypto/Verifier.sol"; -import { VkRegistry } from "./VkRegistry.sol"; /// @title MessageProcessor /// @dev MessageProcessor is used to process messages published by signup users. /// It will process message by batch due to large size of messages. /// After it finishes processing, the sbCommitment will be used for Tally and Subsidy contracts. -contract MessageProcessor is Ownable, SnarkCommon, CommonUtilities, Hasher { +contract MessageProcessor is Ownable, SnarkCommon, Hasher, CommonUtilities, IMessageProcessor { /// @notice custom errors error NoMoreMessages(); error StateAqNotMerged(); @@ -27,51 +28,57 @@ contract MessageProcessor is Ownable, SnarkCommon, CommonUtilities, Hasher { error CurrentMessageBatchIndexTooLarge(); error BatchEndIndexTooLarge(); - /// @notice Whether there are unprocessed messages left + /// @inheritdoc IMessageProcessor bool public processingComplete; + /// @notice The number of batches processed uint256 public numBatchesProcessed; + /// @notice The current message batch index. When the coordinator runs /// processMessages(), this action relates to messages /// currentMessageBatchIndex to currentMessageBatchIndex + messageBatchSize. uint256 public currentMessageBatchIndex; - /// @notice The commitment to the state and ballot roots + + /// @inheritdoc IMessageProcessor uint256 public sbCommitment; - Verifier public verifier; - VkRegistry public vkRegistry; + IPoll public poll; + IVerifier public verifier; + IVkRegistry public vkRegistry; /// @notice Create a new instance /// @param _verifier The Verifier contract address /// @param _vkRegistry The VkRegistry contract address - constructor(Verifier _verifier, VkRegistry _vkRegistry) payable { - verifier = _verifier; - vkRegistry = _vkRegistry; + /// @param _poll The Poll contract address + constructor(address _verifier, address _vkRegistry, address _poll) payable { + verifier = IVerifier(_verifier); + vkRegistry = IVkRegistry(_vkRegistry); + poll = IPoll(_poll); } /// @notice Update the Poll's currentSbCommitment if the proof is valid. - /// @param _poll The poll to update /// @param _newSbCommitment The new state root and ballot root commitment /// after all messages are processed /// @param _proof The zk-SNARK proof - function processMessages(Poll _poll, uint256 _newSbCommitment, uint256[8] memory _proof) external onlyOwner { - _votingPeriodOver(_poll); + function processMessages(uint256 _newSbCommitment, uint256[8] memory _proof) external onlyOwner { + _votingPeriodOver(poll); + // There must be unprocessed messages if (processingComplete) { revert NoMoreMessages(); } // The state AccQueue must be merged - if (!_poll.stateAqMerged()) { + if (!poll.stateAqMerged()) { revert StateAqNotMerged(); } // Retrieve stored vals - (, , uint8 messageTreeDepth, ) = _poll.treeDepths(); - (uint256 messageBatchSize, , ) = _poll.batchSizes(); + (, , uint8 messageTreeDepth, ) = poll.treeDepths(); + (uint256 messageBatchSize, , ) = poll.batchSizes(); AccQueue messageAq; - (, messageAq, ) = _poll.extContracts(); + (, messageAq, ) = poll.extContracts(); // Require that the message queue has been merged uint256 messageRoot = messageAq.getMainRoot(messageTreeDepth); @@ -82,9 +89,9 @@ contract MessageProcessor is Ownable, SnarkCommon, CommonUtilities, Hasher { // Copy the state and ballot commitment and set the batch index if this // is the first batch to process if (numBatchesProcessed == 0) { - uint256 currentSbCommitment = _poll.currentSbCommitment(); + uint256 currentSbCommitment = poll.currentSbCommitment(); sbCommitment = currentSbCommitment; - (, uint256 numMessages) = _poll.numSignUpsAndMessages(); + (, uint256 numMessages) = poll.numSignUpsAndMessages(); uint256 r = numMessages % messageBatchSize; if (r == 0) { @@ -102,20 +109,13 @@ contract MessageProcessor is Ownable, SnarkCommon, CommonUtilities, Hasher { } } - bool isValid = verifyProcessProof( - _poll, - currentMessageBatchIndex, - messageRoot, - sbCommitment, - _newSbCommitment, - _proof - ); + bool isValid = verifyProcessProof(currentMessageBatchIndex, messageRoot, sbCommitment, _newSbCommitment, _proof); if (!isValid) { revert InvalidProcessMessageProof(); } { - (, uint256 numMessages) = _poll.numSignUpsAndMessages(); + (, uint256 numMessages) = poll.numSignUpsAndMessages(); // Decrease the message batch start index to ensure that each // message batch is processed in order if (currentMessageBatchIndex > 0) { @@ -132,7 +132,6 @@ contract MessageProcessor is Ownable, SnarkCommon, CommonUtilities, Hasher { /// @notice Verify the proof for processMessage /// @dev used to update the sbCommitment - /// @param _poll The Poll contract address /// @param _currentMessageBatchIndex The batch index of current message batch /// @param _messageRoot The message tree root /// @param _currentSbCommitment The current sbCommitment (state and ballot) @@ -140,21 +139,19 @@ contract MessageProcessor is Ownable, SnarkCommon, CommonUtilities, Hasher { /// @param _proof The zk-SNARK proof /// @return isValid Whether the proof is valid function verifyProcessProof( - Poll _poll, uint256 _currentMessageBatchIndex, uint256 _messageRoot, uint256 _currentSbCommitment, uint256 _newSbCommitment, uint256[8] memory _proof ) internal view returns (bool isValid) { - (, , uint8 messageTreeDepth, uint8 voteOptionTreeDepth) = _poll.treeDepths(); - (uint256 messageBatchSize, , ) = _poll.batchSizes(); - (uint256 numSignUps, ) = _poll.numSignUpsAndMessages(); - (IMACI maci, , ) = _poll.extContracts(); + (, , uint8 messageTreeDepth, uint8 voteOptionTreeDepth) = poll.treeDepths(); + (uint256 messageBatchSize, , ) = poll.batchSizes(); + (uint256 numSignUps, ) = poll.numSignUpsAndMessages(); + (IMACI maci, , ) = poll.extContracts(); // Calculate the public input hash (a SHA256 hash of several values) uint256 publicInputHash = genProcessMessagesPublicInputHash( - _poll, _currentMessageBatchIndex, _messageRoot, numSignUps, @@ -180,25 +177,23 @@ contract MessageProcessor is Ownable, SnarkCommon, CommonUtilities, Hasher { /// as a single public input and the preimage as private inputs, we reduce /// its verification gas cost though the number of constraints will be /// higher and proving time will be longer. - /// @param _poll The Poll contract address /// @param _currentMessageBatchIndex The batch index of current message batch /// @param _numSignUps The number of users that signup /// @param _currentSbCommitment The current sbCommitment (state and ballot root) /// @param _newSbCommitment The new sbCommitment after we update this message batch /// @return inputHash Returns the SHA256 hash of the packed values function genProcessMessagesPublicInputHash( - Poll _poll, uint256 _currentMessageBatchIndex, uint256 _messageRoot, uint256 _numSignUps, uint256 _currentSbCommitment, uint256 _newSbCommitment ) public view returns (uint256 inputHash) { - uint256 coordinatorPubKeyHash = _poll.coordinatorPubKeyHash(); + uint256 coordinatorPubKeyHash = poll.coordinatorPubKeyHash(); - uint256 packedVals = genProcessMessagesPackedVals(_poll, _currentMessageBatchIndex, _numSignUps); + uint256 packedVals = genProcessMessagesPackedVals(_currentMessageBatchIndex, _numSignUps); - (uint256 deployTime, uint256 duration) = _poll.getDeployTimeAndDuration(); + (uint256 deployTime, uint256 duration) = poll.getDeployTimeAndDuration(); uint256[] memory input = new uint256[](6); input[0] = packedVals; @@ -215,18 +210,16 @@ contract MessageProcessor is Ownable, SnarkCommon, CommonUtilities, Hasher { /// 250-bit value, which consists of the maximum number of vote options, the /// number of signups, the current message batch index, and the end index of /// the current batch. - /// @param _poll the poll contract /// @param _currentMessageBatchIndex batch index of current message batch /// @param _numSignUps number of users that signup /// @return result The packed value function genProcessMessagesPackedVals( - Poll _poll, uint256 _currentMessageBatchIndex, uint256 _numSignUps ) public view returns (uint256 result) { - (, uint256 maxVoteOptions) = _poll.maxValues(); - (, uint256 numMessages) = _poll.numSignUpsAndMessages(); - (uint24 mbs, , ) = _poll.batchSizes(); + (, uint256 maxVoteOptions) = poll.maxValues(); + (, uint256 numMessages) = poll.numSignUpsAndMessages(); + (uint24 mbs, , ) = poll.batchSizes(); uint256 messageBatchSize = uint256(mbs); uint256 batchEndIndex = _currentMessageBatchIndex + messageBatchSize; diff --git a/contracts/contracts/MessageProcessorFactory.sol b/contracts/contracts/MessageProcessorFactory.sol new file mode 100644 index 0000000000..b9fe35b5a8 --- /dev/null +++ b/contracts/contracts/MessageProcessorFactory.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.10; + +import { Params } from "./utilities/Params.sol"; +import { DomainObjs } from "./utilities/DomainObjs.sol"; +import { MessageProcessor } from "./MessageProcessor.sol"; +import { IMessageProcessorFactory } from "./interfaces/IMPFactory.sol"; + +/// @title MessageProcessorFactory +/// @notice A factory contract which deploys MessageProcessor contracts. +contract MessageProcessorFactory is Params, DomainObjs, IMessageProcessorFactory { + /// @inheritdoc IMessageProcessorFactory + function deploy( + address _verifier, + address _vkRegistry, + address _poll, + address _owner + ) public returns (address messageProcessorAddr) { + // deploy MessageProcessor for this Poll + MessageProcessor messageProcessor = new MessageProcessor(_verifier, _vkRegistry, _poll); + messageProcessor.transferOwnership(_owner); + messageProcessorAddr = address(messageProcessor); + } +} diff --git a/contracts/contracts/Poll.sol b/contracts/contracts/Poll.sol index 938ba5156b..23740e7ad4 100644 --- a/contracts/contracts/Poll.sol +++ b/contracts/contracts/Poll.sol @@ -7,6 +7,7 @@ import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { EmptyBallotRoots } from "./trees/EmptyBallotRoots.sol"; +import { IPoll } from "./interfaces/IPoll.sol"; import { Utilities } from "./utilities/Utilities.sol"; /// @title Poll @@ -14,41 +15,47 @@ import { Utilities } from "./utilities/Utilities.sol"; /// which can be either votes, key change messages or topup messages. /// @dev Do not deploy this directly. Use PollFactory.deploy() which performs some /// checks on the Poll constructor arguments. -contract Poll is Params, Utilities, SnarkCommon, Ownable, EmptyBallotRoots { +contract Poll is Params, Utilities, SnarkCommon, Ownable, EmptyBallotRoots, IPoll { using SafeERC20 for ERC20; bool internal isInit; // The coordinator's public key PubKey public coordinatorPubKey; - uint256 public mergedStateRoot; + /// @notice Hash of the coordinator's public key uint256 public coordinatorPubKeyHash; + uint256 public mergedStateRoot; + // The timestamp of the block at which the Poll was deployed uint256 internal deployTime; // The duration of the polling period, in seconds uint256 internal duration; - // Whether the MACI contract's stateAq has been merged by this contract + /// @notice Whether the MACI contract's stateAq has been merged by this contract bool public stateAqMerged; - // The commitment to the state leaves and the ballots. This is - // hash3(stateRoot, ballotRoot, salt). - // Its initial value should be - // hash(maciStateRootSnapshot, emptyBallotRoot, 0) - // Each successful invocation of processMessages() should use a different - // salt to update this value, so that an external observer cannot tell in - // the case that none of the messages are valid. + /// @notice Get the commitment to the state leaves and the ballots. This is + /// hash3(stateRoot, ballotRoot, salt). + /// Its initial value should be + /// hash(maciStateRootSnapshot, emptyBallotRoot, 0) + /// Each successful invocation of processMessages() should use a different + /// salt to update this value, so that an external observer cannot tell in + /// the case that none of the messages are valid. uint256 public currentSbCommitment; uint256 public numMessages; + /// @notice Max values for the poll MaxValues public maxValues; + + /// @notice Depths of the merkle trees TreeDepths public treeDepths; + + /// @notice Batch sizes for processing BatchSizes public batchSizes; - /// @notice custom errors error VotingPeriodOver(); error VotingPeriodNotOver(); error PollAlreadyInit(); @@ -64,12 +71,13 @@ contract Poll is Params, Utilities, SnarkCommon, Ownable, EmptyBallotRoots { event MergeMessageAqSubRoots(uint256 _numSrQueueOps); event MergeMessageAq(uint256 _messageRoot); + /// @notice External contracts ExtContracts public extContracts; /// @notice Each MACI instance can have multiple Polls. /// When a Poll is deployed, its voting period starts immediately. /// @param _duration The duration of the voting period, in seconds - /// @param _maxValues The maximum number of signups and messages + /// @param _maxValues The maximum number of messages and vote options /// @param _treeDepths The depths of the merkle trees /// @param _batchSizes The batch sizes for processing /// @param _coordinatorPubKey The coordinator's public key @@ -133,9 +141,7 @@ contract Poll is Params, Utilities, SnarkCommon, Ownable, EmptyBallotRoots { emit PublishMessage(_message, _padKey); } - /// @notice Allows to publish a Topup message - /// @param stateIndex The index of user in the state queue - /// @param amount The amount of credits to topup + /// @inheritdoc IPoll function topup(uint256 stateIndex, uint256 amount) public isWithinVotingDeadline { // we check that we do not exceed the max number of messages if (numMessages == maxValues.maxMessages) revert TooManyMessages(); @@ -154,12 +160,7 @@ contract Poll is Params, Utilities, SnarkCommon, Ownable, EmptyBallotRoots { emit TopupMessage(_message); } - /// @notice Allows anyone to publish a message (an encrypted command and signature). - /// This function also enqueues the message. - /// @param _message The message to publish - /// @param _encPubKey An epheremal public key which can be combined with the - /// coordinator's private key to generate an ECDH shared key with which - /// to encrypt the message. + /// @inheritdoc IPoll function publishMessage(Message memory _message, PubKey calldata _encPubKey) public isWithinVotingDeadline { // we check that we do not exceed the max number of messages if (numMessages == maxValues.maxMessages) revert TooManyMessages(); @@ -181,9 +182,7 @@ contract Poll is Params, Utilities, SnarkCommon, Ownable, EmptyBallotRoots { emit PublishMessage(_message, _encPubKey); } - /// @notice The first step of merging the MACI state AccQueue. This allows the - /// ProcessMessages circuit to access the latest state tree and ballots via - /// currentSbCommitment. + /// @inheritdoc IPoll function mergeMaciStateAqSubRoots(uint256 _numSrQueueOps, uint256 _pollId) public onlyOwner isAfterVotingDeadline { // This function cannot be called after the stateAq was merged if (stateAqMerged) revert StateAqAlreadyMerged(); @@ -195,10 +194,7 @@ contract Poll is Params, Utilities, SnarkCommon, Ownable, EmptyBallotRoots { emit MergeMaciStateAqSubRoots(_numSrQueueOps); } - /// @notice The second step of merging the MACI state AccQueue. This allows the - /// ProcessMessages circuit to access the latest state tree and ballots via - /// currentSbCommitment. - /// @param _pollId The ID of the Poll + /// @inheritdoc IPoll function mergeMaciStateAq(uint256 _pollId) public onlyOwner isAfterVotingDeadline { // This function can only be called once per Poll after the voting // deadline @@ -220,33 +216,25 @@ contract Poll is Params, Utilities, SnarkCommon, Ownable, EmptyBallotRoots { emit MergeMaciStateAq(mergedStateRoot); } - /// @notice The first step in merging the message AccQueue so that the - /// ProcessMessages circuit can access the message root. - /// @param _numSrQueueOps The number of subroot queue operations to perform + /// @inheritdoc IPoll function mergeMessageAqSubRoots(uint256 _numSrQueueOps) public onlyOwner isAfterVotingDeadline { extContracts.messageAq.mergeSubRoots(_numSrQueueOps); emit MergeMessageAqSubRoots(_numSrQueueOps); } - /// @notice The second step in merging the message AccQueue so that the - /// ProcessMessages circuit can access the message root. + /// @inheritdoc IPoll function mergeMessageAq() public onlyOwner isAfterVotingDeadline { uint256 root = extContracts.messageAq.merge(treeDepths.messageTreeDepth); emit MergeMessageAq(root); } - /// @notice Returns the Poll's deploy time and duration - /// @return _deployTime The deployment timestamp - /// @return _duration The duration of the poll + /// @inheritdoc IPoll function getDeployTimeAndDuration() public view returns (uint256 _deployTime, uint256 _duration) { _deployTime = deployTime; _duration = duration; } - /// @notice The number of messages which have been processed and the number of - /// signups - /// @return numSignups The number of signups - /// @return numMsgs The number of messages sent by voters + /// @inheritdoc IPoll function numSignUpsAndMessages() public view returns (uint256 numSignups, uint256 numMsgs) { numSignups = extContracts.maci.numSignUps(); numMsgs = numMessages; diff --git a/contracts/contracts/PollFactory.sol b/contracts/contracts/PollFactory.sol index abaf244aeb..a5bbd19d3c 100644 --- a/contracts/contracts/PollFactory.sol +++ b/contracts/contracts/PollFactory.sol @@ -8,11 +8,12 @@ import { TopupCredit } from "./TopupCredit.sol"; import { Params } from "./utilities/Params.sol"; import { DomainObjs } from "./utilities/DomainObjs.sol"; import { Poll } from "./Poll.sol"; +import { IPollFactory } from "./interfaces/IPollFactory.sol"; /// @title PollFactory /// @notice A factory contract which deploys Poll contracts. It allows the MACI contract /// size to stay within the limit set by EIP-170. -contract PollFactory is Params, DomainObjs { +contract PollFactory is Params, DomainObjs, IPollFactory { // The number of children each node in the message tree has uint256 internal constant TREE_ARITY = 5; @@ -23,16 +24,7 @@ contract PollFactory is Params, DomainObjs { // solhint-disable-next-line no-empty-blocks constructor() payable {} - /// @notice Deploy a new Poll contract and AccQueue contract for messages. - /// @param _duration The duration of the poll - /// @param _maxValues The max values for the poll - /// @param _treeDepths The depths of the merkle trees - /// @param _batchSizes The batch sizes for processing - /// @param _coordinatorPubKey The coordinator's public key - /// @param _maci The MACI contract interface reference - /// @param _topupCredit The TopupCredit contract - /// @param _pollOwner The owner of the poll - /// @return poll The deployed Poll contract + /// @inheritdoc IPollFactory function deploy( uint256 _duration, MaxValues calldata _maxValues, @@ -42,7 +34,7 @@ contract PollFactory is Params, DomainObjs { IMACI _maci, TopupCredit _topupCredit, address _pollOwner - ) public returns (Poll poll) { + ) public returns (address pollAddr) { /// @notice Validate _maxValues /// maxVoteOptions must be less than 2 ** 50 due to circuit limitations; /// it will be packed as a 50-bit value along with other values as one @@ -64,7 +56,7 @@ contract PollFactory is Params, DomainObjs { ExtContracts memory extContracts = ExtContracts({ maci: _maci, messageAq: messageAq, topupCredit: _topupCredit }); // deploy the poll - poll = new Poll(_duration, _maxValues, _treeDepths, _batchSizes, _coordinatorPubKey, extContracts); + Poll poll = new Poll(_duration, _maxValues, _treeDepths, _batchSizes, _coordinatorPubKey, extContracts); // Make the Poll contract own the messageAq contract, so only it can // run enqueue/merge @@ -74,5 +66,7 @@ contract PollFactory is Params, DomainObjs { poll.init(); poll.transferOwnership(_pollOwner); + + pollAddr = address(poll); } } diff --git a/contracts/contracts/Subsidy.sol b/contracts/contracts/Subsidy.sol index 5b702b1257..d46035502f 100644 --- a/contracts/contracts/Subsidy.sol +++ b/contracts/contracts/Subsidy.sol @@ -2,14 +2,14 @@ pragma solidity ^0.8.10; import { IMACI } from "./interfaces/IMACI.sol"; -import { MessageProcessor } from "./MessageProcessor.sol"; +import { IMessageProcessor } from "./interfaces/IMessageProcessor.sol"; import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; -import { Poll } from "./Poll.sol"; +import { IPoll } from "./interfaces/IPoll.sol"; import { SnarkCommon } from "./crypto/SnarkCommon.sol"; import { Hasher } from "./crypto/Hasher.sol"; import { CommonUtilities } from "./utilities/CommonUtilities.sol"; -import { Verifier } from "./crypto/Verifier.sol"; -import { VkRegistry } from "./VkRegistry.sol"; +import { IVerifier } from "./interfaces/IVerifier.sol"; +import { IVkRegistry } from "./interfaces/IVkRegistry.sol"; /// @title Subsidy /// @notice This contract is used to verify that the subsidy calculations @@ -36,27 +36,32 @@ contract Subsidy is Ownable, CommonUtilities, Hasher, SnarkCommon { error RbiTooLarge(); error CbiTooLarge(); - Verifier public verifier; - VkRegistry public vkRegistry; + IVerifier public verifier; + IVkRegistry public vkRegistry; + IPoll public poll; + IMessageProcessor public mp; /// @notice Create a new Subsidy contract /// @param _verifier The Verifier contract /// @param _vkRegistry The VkRegistry contract - constructor(Verifier _verifier, VkRegistry _vkRegistry) payable { - verifier = _verifier; - vkRegistry = _vkRegistry; + /// @param _poll The Poll contract + /// @param _mp The MessageProcessor contract + constructor(address _verifier, address _vkRegistry, address _poll, address _mp) payable { + verifier = IVerifier(_verifier); + vkRegistry = IVkRegistry(_vkRegistry); + poll = IPoll(_poll); + mp = IMessageProcessor(_mp); } /// @notice Update the currentSbCommitment if the proof is valid. /// @dev currentSbCommitment is the commitment to the state and ballot roots - /// @param _mp The MessageProcessor contract - function updateSbCommitment(MessageProcessor _mp) public onlyOwner { + function updateSbCommitment() public onlyOwner { // Require that all messages have been processed - if (!_mp.processingComplete()) { + if (!mp.processingComplete()) { revert ProcessingNotComplete(); } if (sbCommitment == 0) { - sbCommitment = _mp.sbCommitment(); + sbCommitment = mp.sbCommitment(); } } @@ -88,31 +93,24 @@ contract Subsidy is Ownable, CommonUtilities, Hasher, SnarkCommon { } /// @notice Update the subsidy commitment if the proof is valid - /// @param _poll The Poll contract - /// @param _mp The MessageProcessor contract /// @param _newSubsidyCommitment The new subsidy commitment /// @param _proof The proof - function updateSubsidy( - Poll _poll, - MessageProcessor _mp, - uint256 _newSubsidyCommitment, - uint256[8] calldata _proof - ) external onlyOwner { - _votingPeriodOver(_poll); - updateSbCommitment(_mp); + function updateSubsidy(uint256 _newSubsidyCommitment, uint256[8] calldata _proof) external onlyOwner { + _votingPeriodOver(poll); + updateSbCommitment(); - (uint8 intStateTreeDepth, , , ) = _poll.treeDepths(); + (uint8 intStateTreeDepth, , , ) = poll.treeDepths(); uint256 subsidyBatchSize = uint256(TREE_ARITY) ** intStateTreeDepth; - (uint256 numSignUps, ) = _poll.numSignUpsAndMessages(); + (uint256 numSignUps, ) = poll.numSignUpsAndMessages(); // Require that there are unfinished ballots left if (rbi * subsidyBatchSize > numSignUps) { revert AllSubsidyCalculated(); } - bool isValid = verifySubsidyProof(_poll, _proof, numSignUps, _newSubsidyCommitment); + bool isValid = verifySubsidyProof(_proof, numSignUps, _newSubsidyCommitment); if (!isValid) { revert InvalidSubsidyProof(); } @@ -136,19 +134,17 @@ contract Subsidy is Ownable, CommonUtilities, Hasher, SnarkCommon { } /// @notice Verify the subsidy proof using the Groth16 on chain verifier - /// @param _poll The Poll contract /// @param _proof The proof /// @param _numSignUps The number of signups /// @param _newSubsidyCommitment The new subsidy commitment /// @return isValid True if the proof is valid function verifySubsidyProof( - Poll _poll, uint256[8] calldata _proof, uint256 _numSignUps, uint256 _newSubsidyCommitment ) public view returns (bool isValid) { - (uint8 intStateTreeDepth, , , uint8 voteOptionTreeDepth) = _poll.treeDepths(); - (IMACI maci, , ) = _poll.extContracts(); + (uint8 intStateTreeDepth, , , uint8 voteOptionTreeDepth) = poll.treeDepths(); + (IMACI maci, , ) = poll.extContracts(); // Get the verifying key VerifyingKey memory vk = vkRegistry.getSubsidyVk(maci.stateTreeDepth(), intStateTreeDepth, voteOptionTreeDepth); diff --git a/contracts/contracts/SubsidyFactory.sol b/contracts/contracts/SubsidyFactory.sol new file mode 100644 index 0000000000..0be338de52 --- /dev/null +++ b/contracts/contracts/SubsidyFactory.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.10; + +import { Subsidy } from "./Subsidy.sol"; +import { ITallySubsidyFactory } from "./interfaces/ITallySubsidyFactory.sol"; + +/// @title SubsidyFactory +/// @notice A factory contract which deploys Subsidy contracts. +contract SubsidyFactory is ITallySubsidyFactory { + /// @inheritdoc ITallySubsidyFactory + function deploy( + address _verifier, + address _vkRegistry, + address _poll, + address _messageProcessor, + address _owner + ) public returns (address subsidyAddr) { + /// @notice deploy Subsidy for this Poll + Subsidy subsidy = new Subsidy(_verifier, _vkRegistry, _poll, _messageProcessor); + subsidy.transferOwnership(_owner); + subsidyAddr = address(subsidy); + } +} diff --git a/contracts/contracts/Tally.sol b/contracts/contracts/Tally.sol index 9b09a3c3d5..37b92708c8 100644 --- a/contracts/contracts/Tally.sol +++ b/contracts/contracts/Tally.sol @@ -4,11 +4,11 @@ pragma solidity ^0.8.10; import { IMACI } from "./interfaces/IMACI.sol"; import { Hasher } from "./crypto/Hasher.sol"; import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; -import { Poll } from "./Poll.sol"; -import { MessageProcessor } from "./MessageProcessor.sol"; +import { IPoll } from "./interfaces/IPoll.sol"; +import { IMessageProcessor } from "./interfaces/IMessageProcessor.sol"; import { SnarkCommon } from "./crypto/SnarkCommon.sol"; -import { Verifier } from "./crypto/Verifier.sol"; -import { VkRegistry } from "./VkRegistry.sol"; +import { IVerifier } from "./interfaces/IVerifier.sol"; +import { IVkRegistry } from "./interfaces/IVkRegistry.sol"; import { CommonUtilities } from "./utilities/CommonUtilities.sol"; /// @title Tally @@ -44,15 +44,21 @@ contract Tally is Ownable, SnarkCommon, CommonUtilities, Hasher { // The final commitment to the state and ballot roots uint256 public sbCommitment; - Verifier public verifier; - VkRegistry public vkRegistry; + IVerifier public verifier; + IVkRegistry public vkRegistry; + IPoll public poll; + IMessageProcessor public mp; /// @notice Create a new Tally contract /// @param _verifier The Verifier contract /// @param _vkRegistry The VkRegistry contract - constructor(Verifier _verifier, VkRegistry _vkRegistry) payable { - verifier = _verifier; - vkRegistry = _vkRegistry; + /// @param _poll The Poll contract + /// @param _mp The MessageProcessor contract + constructor(address _verifier, address _vkRegistry, address _poll, address _mp) payable { + verifier = IVerifier(_verifier); + vkRegistry = IVkRegistry(_vkRegistry); + poll = IPoll(_poll); + mp = IMessageProcessor(_mp); } /// @notice Pack the batch start index and number of signups into a 100-bit value. @@ -94,41 +100,33 @@ contract Tally is Ownable, SnarkCommon, CommonUtilities, Hasher { } /// @notice Update the state and ballot root commitment - /// @param _mp the address of the MessageProcessor contract - function updateSbCommitment(MessageProcessor _mp) public onlyOwner { + function updateSbCommitment() public onlyOwner { // Require that all messages have been processed - if (!_mp.processingComplete()) { + if (!mp.processingComplete()) { revert ProcessingNotComplete(); } if (sbCommitment == 0) { - sbCommitment = _mp.sbCommitment(); + sbCommitment = mp.sbCommitment(); } } /// @notice Verify the result of a tally batch - /// @param _poll contract address of the poll proof to be verified - /// @param _mp the address of the MessageProcessor contract /// @param _newTallyCommitment the new tally commitment to be verified /// @param _proof the proof generated after tallying this batch - function tallyVotes( - Poll _poll, - MessageProcessor _mp, - uint256 _newTallyCommitment, - uint256[8] calldata _proof - ) public onlyOwner { - _votingPeriodOver(_poll); - updateSbCommitment(_mp); - - (, uint256 tallyBatchSize, ) = _poll.batchSizes(); + function tallyVotes(uint256 _newTallyCommitment, uint256[8] calldata _proof) public onlyOwner { + _votingPeriodOver(poll); + updateSbCommitment(); + + (, uint256 tallyBatchSize, ) = poll.batchSizes(); uint256 batchStartIndex = tallyBatchNum * tallyBatchSize; - (uint256 numSignUps, ) = _poll.numSignUpsAndMessages(); + (uint256 numSignUps, ) = poll.numSignUpsAndMessages(); // Require that there are untalied ballots left if (batchStartIndex > numSignUps) { revert AllBallotsTallied(); } - bool isValid = verifyTallyProof(_poll, _proof, numSignUps, batchStartIndex, tallyBatchSize, _newTallyCommitment); + bool isValid = verifyTallyProof(_proof, numSignUps, batchStartIndex, tallyBatchSize, _newTallyCommitment); if (!isValid) { revert InvalidTallyVotesProof(); } @@ -139,7 +137,6 @@ contract Tally is Ownable, SnarkCommon, CommonUtilities, Hasher { } /// @notice Verify the tally proof using the verifying key - /// @param _poll contract address of the poll proof to be verified /// @param _proof the proof generated after processing all messages /// @param _numSignUps number of signups for a given poll /// @param _batchStartIndex the number of batches multiplied by the size of the batch @@ -147,16 +144,15 @@ contract Tally is Ownable, SnarkCommon, CommonUtilities, Hasher { /// @param _newTallyCommitment the tally commitment to be verified at a given batch index /// @return isValid whether the proof is valid function verifyTallyProof( - Poll _poll, uint256[8] calldata _proof, uint256 _numSignUps, uint256 _batchStartIndex, uint256 _tallyBatchSize, uint256 _newTallyCommitment ) public view returns (bool isValid) { - (uint8 intStateTreeDepth, , , uint8 voteOptionTreeDepth) = _poll.treeDepths(); + (uint8 intStateTreeDepth, , , uint8 voteOptionTreeDepth) = poll.treeDepths(); - (IMACI maci, , ) = _poll.extContracts(); + (IMACI maci, , ) = poll.extContracts(); // Get the verifying key VerifyingKey memory vk = vkRegistry.getTallyVk(maci.stateTreeDepth(), intStateTreeDepth, voteOptionTreeDepth); diff --git a/contracts/contracts/TallyFactory.sol b/contracts/contracts/TallyFactory.sol new file mode 100644 index 0000000000..f269f16ff4 --- /dev/null +++ b/contracts/contracts/TallyFactory.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.10; + +import { Tally } from "./Tally.sol"; +import { ITallySubsidyFactory } from "./interfaces/ITallySubsidyFactory.sol"; + +/// @title TallyFactory +/// @notice A factory contract which deploys Tally contracts. +contract TallyFactory is ITallySubsidyFactory { + /// @inheritdoc ITallySubsidyFactory + function deploy( + address _verifier, + address _vkRegistry, + address _poll, + address _messageProcessor, + address _owner + ) public returns (address tallyAddr) { + // deploy Tally for this Poll + Tally tally = new Tally(_verifier, _vkRegistry, _poll, _messageProcessor); + tally.transferOwnership(_owner); + tallyAddr = address(tally); + } +} diff --git a/contracts/contracts/VkRegistry.sol b/contracts/contracts/VkRegistry.sol index 09447a1738..a4a9a50a61 100644 --- a/contracts/contracts/VkRegistry.sol +++ b/contracts/contracts/VkRegistry.sol @@ -2,14 +2,14 @@ pragma solidity ^0.8.10; import { SnarkCommon } from "./crypto/SnarkCommon.sol"; - import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; +import { IVkRegistry } from "./interfaces/IVkRegistry.sol"; /// @title VkRegistry /// @notice Stores verifying keys for the circuits. /// Each circuit has a signature which is its compile-time constants represented /// as a uint256. -contract VkRegistry is Ownable, SnarkCommon { +contract VkRegistry is Ownable, SnarkCommon, IVkRegistry { mapping(uint256 => VerifyingKey) internal processVks; mapping(uint256 => bool) internal processVkSet; @@ -200,12 +200,7 @@ contract VkRegistry is Ownable, SnarkCommon { vk = processVks[_sig]; } - /// @notice Get the process verifying key - /// @param _stateTreeDepth The state tree depth - /// @param _messageTreeDepth The message tree depth - /// @param _voteOptionTreeDepth The vote option tree depth - /// @param _messageBatchSize The message batch size - /// @return vk The verifying key + /// @inheritdoc IVkRegistry function getProcessVk( uint256 _stateTreeDepth, uint256 _messageTreeDepth, @@ -241,11 +236,7 @@ contract VkRegistry is Ownable, SnarkCommon { vk = tallyVks[_sig]; } - /// @notice Get the tally verifying key - /// @param _stateTreeDepth The state tree depth - /// @param _intStateTreeDepth The intermediate state tree depth - /// @param _voteOptionTreeDepth The vote option tree depth - /// @return vk The verifying key + /// @inheritdoc IVkRegistry function getTallyVk( uint256 _stateTreeDepth, uint256 _intStateTreeDepth, @@ -280,11 +271,7 @@ contract VkRegistry is Ownable, SnarkCommon { vk = subsidyVks[_sig]; } - /// @notice Get the subsidy verifying key - /// @param _stateTreeDepth The state tree depth - /// @param _intStateTreeDepth The intermediate state tree depth - /// @param _voteOptionTreeDepth The vote option tree depth - /// @return vk The verifying key + /// @inheritdoc IVkRegistry function getSubsidyVk( uint256 _stateTreeDepth, uint256 _intStateTreeDepth, diff --git a/contracts/contracts/crypto/IVerifier.sol b/contracts/contracts/crypto/IVerifier.sol deleted file mode 100644 index 6e8cfe21b5..0000000000 --- a/contracts/contracts/crypto/IVerifier.sol +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import { SnarkCommon } from "./SnarkCommon.sol"; - -/// @title IVerifier -/// @notice an interface for a Groth16 verifier contract -abstract contract IVerifier is SnarkCommon { - /// @notice Verify a zk-SNARK proof - function verify(uint256[8] memory, VerifyingKey memory, uint256) public view virtual returns (bool); -} diff --git a/contracts/contracts/crypto/MockVerifier.sol b/contracts/contracts/crypto/MockVerifier.sol index f471fa4435..d9a8b00edc 100644 --- a/contracts/contracts/crypto/MockVerifier.sol +++ b/contracts/contracts/crypto/MockVerifier.sol @@ -2,11 +2,12 @@ pragma solidity ^0.8.10; import { SnarkConstants } from "./SnarkConstants.sol"; -import { IVerifier } from "./IVerifier.sol"; +import { SnarkCommon } from "./SnarkCommon.sol"; +import { IVerifier } from "../interfaces/IVerifier.sol"; /// @title MockVerifier /// @notice a MockVerifier to be used for testing -contract MockVerifier is IVerifier, SnarkConstants { +contract MockVerifier is IVerifier, SnarkConstants, SnarkCommon { /// @notice Verify a zk-SNARK proof (test only return always true) /// @return result Whether the proof is valid given the verifying key and public function verify(uint256[8] memory, VerifyingKey memory, uint256) public pure override returns (bool result) { diff --git a/contracts/contracts/crypto/Verifier.sol b/contracts/contracts/crypto/Verifier.sol index 6fe010c49a..7654fe6bb0 100644 --- a/contracts/contracts/crypto/Verifier.sol +++ b/contracts/contracts/crypto/Verifier.sol @@ -3,11 +3,12 @@ pragma solidity ^0.8.10; import { Pairing } from "./Pairing.sol"; import { SnarkConstants } from "./SnarkConstants.sol"; -import { IVerifier } from "./IVerifier.sol"; +import { SnarkCommon } from "./SnarkCommon.sol"; +import { IVerifier } from "../interfaces/IVerifier.sol"; /// @title Verifier /// @notice a Groth16 verifier contract -contract Verifier is IVerifier, SnarkConstants { +contract Verifier is IVerifier, SnarkConstants, SnarkCommon { struct Proof { Pairing.G1Point a; Pairing.G2Point b; diff --git a/contracts/contracts/interfaces/IMACI.sol b/contracts/contracts/interfaces/IMACI.sol index 752797e1fc..3060bc9ab5 100644 --- a/contracts/contracts/interfaces/IMACI.sol +++ b/contracts/contracts/interfaces/IMACI.sol @@ -3,31 +3,32 @@ pragma solidity ^0.8.10; import { AccQueue } from "../trees/AccQueue.sol"; -/// @title IMACI interface +/// @title IMACI +/// @notice MACI interface interface IMACI { /// @notice Get the depth of the state tree /// @return The depth of the state tree function stateTreeDepth() external view returns (uint8); - /// @notice Get the root of the state accumulator queue - /// @return The root of the state accumulator queue + /// @notice Return the main root of the StateAq contract + /// @return The Merkle root function getStateAqRoot() external view returns (uint256); - /// @notice Merge the sub roots of the state accumulator queue - /// @param _numSrQueueOps The number of queue operations - /// @param _pollId The poll identifier + /// @notice Allow Poll contracts to merge the state subroots + /// @param _numSrQueueOps Number of operations + /// @param _pollId The ID of the active Poll function mergeStateAqSubRoots(uint256 _numSrQueueOps, uint256 _pollId) external; - /// @notice Merge the state accumulator queue - /// @param _pollId The poll identifier - /// @return The new root of the state accumulator queue after merging + /// @notice Allow Poll contracts to merge the state root + /// @param _pollId The active Poll ID + /// @return The calculated Merkle root function mergeStateAq(uint256 _pollId) external returns (uint256); /// @notice Get the number of signups - /// @return The number of signups + /// @return numsignUps The number of signups function numSignUps() external view returns (uint256); - /// @notice Get the state accumulator queue - /// @return The state accumulator queue + /// @notice Get the state AccQueue + /// @return The state AccQueue function stateAq() external view returns (AccQueue); } diff --git a/contracts/contracts/interfaces/IMPFactory.sol b/contracts/contracts/interfaces/IMPFactory.sol new file mode 100644 index 0000000000..362c4a7e1d --- /dev/null +++ b/contracts/contracts/interfaces/IMPFactory.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.10; + +/// @title IMessageProcessorFactory +/// @notice MessageProcessorFactory interface +interface IMessageProcessorFactory { + /// @notice Deploy a new MessageProcessor contract and return the address. + /// @param _verifier Verifier contract + /// @param _vkRegistry VkRegistry contract + /// @param _poll Poll contract + /// @param _owner Owner of the MessageProcessor contract + /// @return The deployed MessageProcessor contract + function deploy(address _verifier, address _vkRegistry, address _poll, address _owner) external returns (address); +} diff --git a/contracts/contracts/interfaces/IMessageProcessor.sol b/contracts/contracts/interfaces/IMessageProcessor.sol new file mode 100644 index 0000000000..95bc363435 --- /dev/null +++ b/contracts/contracts/interfaces/IMessageProcessor.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.10; + +/// @title IMessageProcessor +/// @notice MessageProcessor interface +interface IMessageProcessor { + /// @notice Get the result of whether there are unprocessed messages left + /// @return Whether there are unprocessed messages left + function processingComplete() external view returns (bool); + + /// @notice Get the commitment to the state and ballot roots + /// @return The commitment to the state and ballot roots + function sbCommitment() external view returns (uint256); +} diff --git a/contracts/contracts/interfaces/IPoll.sol b/contracts/contracts/interfaces/IPoll.sol new file mode 100644 index 0000000000..5e0cc44239 --- /dev/null +++ b/contracts/contracts/interfaces/IPoll.sol @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.10; + +import { DomainObjs } from "../utilities/DomainObjs.sol"; +import { IMACI } from "./IMACI.sol"; +import { AccQueue } from "../trees/AccQueue.sol"; +import { TopupCredit } from "../TopupCredit.sol"; + +/// @title IPoll +/// @notice Poll interface +interface IPoll { + /// @notice The number of messages which have been processed and the number of signups + /// @return numSignups The number of signups + /// @return numMsgs The number of messages sent by voters + function numSignUpsAndMessages() external view returns (uint256 numSignups, uint256 numMsgs); + + /// @notice Allows to publish a Topup message + /// @param stateIndex The index of user in the state queue + /// @param amount The amount of credits to topup + function topup(uint256 stateIndex, uint256 amount) external; + + /// @notice Allows anyone to publish a message (an encrypted command and signature). + /// This function also enqueues the message. + /// @param _message The message to publish + /// @param _encPubKey An epheremal public key which can be combined with the + /// coordinator's private key to generate an ECDH shared key with which + /// to encrypt the message. + function publishMessage(DomainObjs.Message memory _message, DomainObjs.PubKey memory _encPubKey) external; + + /// @notice The first step of merging the MACI state AccQueue. This allows the + /// ProcessMessages circuit to access the latest state tree and ballots via + /// currentSbCommitment. + /// @param _numSrQueueOps Number of operations + /// @param _pollId The ID of the active Poll + function mergeMaciStateAqSubRoots(uint256 _numSrQueueOps, uint256 _pollId) external; + + /// @notice The second step of merging the MACI state AccQueue. This allows the + /// ProcessMessages circuit to access the latest state tree and ballots via + /// currentSbCommitment. + /// @param _pollId The ID of the active Poll + function mergeMaciStateAq(uint256 _pollId) external; + + /// @notice The first step in merging the message AccQueue so that the + /// ProcessMessages circuit can access the message root. + /// @param _numSrQueueOps The number of subroot queue operations to perform + function mergeMessageAqSubRoots(uint256 _numSrQueueOps) external; + + /// @notice The second step in merging the message AccQueue so that the + /// ProcessMessages circuit can access the message root. + function mergeMessageAq() external; + + /// @notice Returns the Poll's deploy time and duration + /// @return _deployTime The deployment timestamp + /// @return _duration The duration of the poll + function getDeployTimeAndDuration() external view returns (uint256 _deployTime, uint256 _duration); + + /// @notice Get the result of whether the MACI contract's stateAq has been merged by this contract + /// @return Whether the MACI contract's stateAq has been merged by this contract + function stateAqMerged() external view returns (bool); + + /// @notice Get the depths of the merkle trees + /// @return intStateTreeDepth The depth of the state tree + /// @return messageTreeSubDepth The subdepth of the message tree + /// @return messageTreeDepth The depth of the message tree + /// @return voteOptionTreeDepth The subdepth of the vote option tree + function treeDepths() + external + view + returns (uint8 intStateTreeDepth, uint8 messageTreeSubDepth, uint8 messageTreeDepth, uint8 voteOptionTreeDepth); + + /// @notice Get the batch sizes for processing + /// @return messageBatchSize The batch size for message processing + /// @return tallyBatchSize The batch size for tally processing + /// @return subsidyBatchSize The batch size for subsidy processing + function batchSizes() external view returns (uint24 messageBatchSize, uint24 tallyBatchSize, uint24 subsidyBatchSize); + + /// @notice Get the max values for the poll + /// @return maxMessages The maximum number of messages + /// @return maxVoteOptions The maximum number of vote options + function maxValues() external view returns (uint256 maxMessages, uint256 maxVoteOptions); + + /// @notice Get the external contracts + /// @return maci The IMACI contract + /// @return messageAq The AccQueue contract + /// @return topupCredit The TopupCredit contract + function extContracts() external view returns (IMACI maci, AccQueue messageAq, TopupCredit topupCredit); + + /// @notice Get the hash of coordinator's public key + /// @return _coordinatorPubKeyHash the hash of coordinator's public key + function coordinatorPubKeyHash() external view returns (uint256 _coordinatorPubKeyHash); + + /// @notice Get the commitment to the state leaves and the ballots. This is + /// hash3(stateRoot, ballotRoot, salt). + /// Its initial value should be + /// hash(maciStateRootSnapshot, emptyBallotRoot, 0) + /// Each successful invocation of processMessages() should use a different + /// salt to update this value, so that an external observer cannot tell in + /// the case that none of the messages are valid. + /// @return The commitment to the state leaves and the ballots + function currentSbCommitment() external view returns (uint256); +} diff --git a/contracts/contracts/interfaces/IPollFactory.sol b/contracts/contracts/interfaces/IPollFactory.sol new file mode 100644 index 0000000000..5e06b2a6a3 --- /dev/null +++ b/contracts/contracts/interfaces/IPollFactory.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.10; + +import { IMACI } from "./IMACI.sol"; +import { TopupCredit } from "../TopupCredit.sol"; +import { Params } from "../utilities/Params.sol"; +import { DomainObjs } from "../utilities/DomainObjs.sol"; + +/// @title IPollFactory +/// @notice PollFactory interface +interface IPollFactory { + /// @notice Deploy a new Poll contract and AccQueue contract for messages. + /// @param _duration The duration of the poll + /// @param _maxValues The max values for the poll + /// @param _treeDepths The depths of the merkle trees + /// @param _batchSizes The batch sizes for processing + /// @param _coordinatorPubKey The coordinator's public key + /// @param _maci The MACI contract interface reference + /// @param _topupCredit The TopupCredit contract + /// @param _pollOwner The owner of the poll + /// @return The deployed Poll contract + function deploy( + uint256 _duration, + Params.MaxValues memory _maxValues, + Params.TreeDepths memory _treeDepths, + Params.BatchSizes memory _batchSizes, + DomainObjs.PubKey memory _coordinatorPubKey, + IMACI _maci, + TopupCredit _topupCredit, + address _pollOwner + ) external returns (address); +} diff --git a/contracts/contracts/interfaces/ITallySubsidyFactory.sol b/contracts/contracts/interfaces/ITallySubsidyFactory.sol new file mode 100644 index 0000000000..c772329db4 --- /dev/null +++ b/contracts/contracts/interfaces/ITallySubsidyFactory.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.10; + +/// @title ITallySubsidyFactory +/// @notice TallySubsidyFactory interface +interface ITallySubsidyFactory { + /// @notice Deploy a new Tally or Subsidy contract and return the address. + /// @param _verifier Verifier contract + /// @param _vkRegistry VkRegistry contract + /// @param _poll Poll contract + /// @param _messageProcessor MessageProcessor contract + /// @param _owner Owner of the contract + /// @return The deployed contract + function deploy( + address _verifier, + address _vkRegistry, + address _poll, + address _messageProcessor, + address _owner + ) external returns (address); +} diff --git a/contracts/contracts/interfaces/IVerifier.sol b/contracts/contracts/interfaces/IVerifier.sol new file mode 100644 index 0000000000..aaf5891b3d --- /dev/null +++ b/contracts/contracts/interfaces/IVerifier.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.10; + +import { SnarkCommon } from "../crypto/SnarkCommon.sol"; + +/// @title IVerifier +/// @notice an interface for a Groth16 verifier contract +interface IVerifier { + /// @notice Verify a zk-SNARK proof + /// @param _proof The proof + /// @param vk The verifying key + /// @param input The public inputs to the circuit + /// @return Whether the proof is valid given the verifying key and public + /// input. Note that this function only supports one public input. + /// Refer to the Semaphore source code for a verifier that supports + /// multiple public inputs. + function verify( + uint256[8] memory _proof, + SnarkCommon.VerifyingKey memory vk, + uint256 input + ) external view returns (bool); +} diff --git a/contracts/contracts/interfaces/IVkRegistry.sol b/contracts/contracts/interfaces/IVkRegistry.sol new file mode 100644 index 0000000000..24c2f5ab97 --- /dev/null +++ b/contracts/contracts/interfaces/IVkRegistry.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.10; + +import { SnarkCommon } from "../crypto/SnarkCommon.sol"; + +/// @title IVkRegistry +/// @notice VkRegistry interface +interface IVkRegistry { + /// @notice Get the tally verifying key + /// @param _stateTreeDepth The state tree depth + /// @param _intStateTreeDepth The intermediate state tree depth + /// @param _voteOptionTreeDepth The vote option tree depth + /// @return The verifying key + function getTallyVk( + uint256 _stateTreeDepth, + uint256 _intStateTreeDepth, + uint256 _voteOptionTreeDepth + ) external view returns (SnarkCommon.VerifyingKey memory); + + /// @notice Get the process verifying key + /// @param _stateTreeDepth The state tree depth + /// @param _messageTreeDepth The message tree depth + /// @param _voteOptionTreeDepth The vote option tree depth + /// @param _messageBatchSize The message batch size + /// @return The verifying key + function getProcessVk( + uint256 _stateTreeDepth, + uint256 _messageTreeDepth, + uint256 _voteOptionTreeDepth, + uint256 _messageBatchSize + ) external view returns (SnarkCommon.VerifyingKey memory); + + /// @notice Get the subsidy verifying key + /// @param _stateTreeDepth The state tree depth + /// @param _intStateTreeDepth The intermediate state tree depth + /// @param _voteOptionTreeDepth The vote option tree depth + /// @return The verifying key + function getSubsidyVk( + uint256 _stateTreeDepth, + uint256 _intStateTreeDepth, + uint256 _voteOptionTreeDepth + ) external view returns (SnarkCommon.VerifyingKey memory); +} diff --git a/contracts/contracts/utilities/CommonUtilities.sol b/contracts/contracts/utilities/CommonUtilities.sol index 4268366417..08ade0d0ef 100644 --- a/contracts/contracts/utilities/CommonUtilities.sol +++ b/contracts/contracts/utilities/CommonUtilities.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.10; -import { Poll } from "../Poll.sol"; +import { IPoll } from "../interfaces/IPoll.sol"; /// @title CommonUtilities /// @notice A contract that holds common utilities @@ -12,7 +12,7 @@ contract CommonUtilities { /// @notice common function for MessageProcessor, Tally and Subsidy /// @param _poll the poll to be checked - function _votingPeriodOver(Poll _poll) internal view { + function _votingPeriodOver(IPoll _poll) internal view { (uint256 deployTime, uint256 duration) = _poll.getDeployTimeAndDuration(); // Require that the voting period is over uint256 secondsPassed = block.timestamp - deployTime; diff --git a/contracts/tests/MACI.test.ts b/contracts/tests/MACI.test.ts index 278ffa8500..e6867c4e7b 100644 --- a/contracts/tests/MACI.test.ts +++ b/contracts/tests/MACI.test.ts @@ -8,7 +8,7 @@ import { Keypair, PubKey, Message } from "maci-domainobjs"; import { parseArtifact } from "../ts/abi"; import { getDefaultSigner, getSigners } from "../ts/utils"; -import { AccQueueQuinaryMaci, MACI, Poll as PollContract } from "../typechain-types"; +import { AccQueueQuinaryMaci, MACI, Poll as PollContract, Verifier, VkRegistry } from "../typechain-types"; import { STATE_TREE_DEPTH, @@ -23,6 +23,8 @@ import { timeTravel, deployTestContracts } from "./utils"; describe("MACI", () => { let maciContract: MACI; let stateAqContract: AccQueueQuinaryMaci; + let vkRegistryContract: VkRegistry; + let verifierContract: Verifier; let pollId: number; const coordinator = new Keypair(); @@ -41,6 +43,8 @@ describe("MACI", () => { maciContract = r.maciContract; stateAqContract = r.stateAqContract; + vkRegistryContract = r.vkRegistryContract; + verifierContract = r.mockVerifierContract as Verifier; }); it("should have set the correct stateTreeDepth", async () => { @@ -154,9 +158,16 @@ describe("MACI", () => { it("should deploy a poll", async () => { // Create the poll and get the poll ID from the tx event logs - const tx = await maciContract.deployPoll(duration, maxValues, treeDepths, coordinator.pubKey.asContractParam(), { - gasLimit: 8000000, - }); + const tx = await maciContract.deployPoll( + duration, + maxValues, + treeDepths, + coordinator.pubKey.asContractParam() as { x: BigNumberish; y: BigNumberish }, + verifierContract, + vkRegistryContract, + false, + { gasLimit: 10000000 }, + ); const receipt = await tx.wait(); const block = await signer.provider!.getBlock(receipt!.blockHash); @@ -194,9 +205,18 @@ describe("MACI", () => { it("should prevent deploying a second poll before the first has finished", async () => { await expect( - maciContract.deployPoll(duration, maxValues, treeDepths, coordinator.pubKey.asContractParam(), { - gasLimit: 8000000, - }), + maciContract.deployPoll( + duration, + maxValues, + treeDepths, + coordinator.pubKey.asContractParam(), + verifierContract, + vkRegistryContract, + false, + { + gasLimit: 10000000, + }, + ), ) .to.be.revertedWithCustomError(maciContract, "PreviousPollNotCompleted") .withArgs(1); diff --git a/contracts/tests/MessageProcessor.test.ts b/contracts/tests/MessageProcessor.test.ts index 4ee175088d..f5b700f7f6 100644 --- a/contracts/tests/MessageProcessor.test.ts +++ b/contracts/tests/MessageProcessor.test.ts @@ -9,7 +9,7 @@ import { Keypair, Message, PubKey } from "maci-domainobjs"; import { parseArtifact } from "../ts/abi"; import { IVerifyingKeyStruct } from "../ts/types"; import { getDefaultSigner } from "../ts/utils"; -import { MACI, MessageProcessor, Poll as PollContract } from "../typechain-types"; +import { MACI, MessageProcessor, Poll as PollContract, Verifier, VkRegistry } from "../typechain-types"; import { STATE_TREE_DEPTH, @@ -27,8 +27,12 @@ describe("MessageProcessor", () => { // contracts let maciContract: MACI; let pollContract: PollContract; + let verifierContract: Verifier; + let vkRegistryContract: VkRegistry; let mpContract: MessageProcessor; + const [pollAbi] = parseArtifact("Poll"); + const [mpAbi] = parseArtifact("MessageProcessor"); let pollId: number; @@ -48,12 +52,22 @@ describe("MessageProcessor", () => { const r = await deployTestContracts(initialVoiceCreditBalance, STATE_TREE_DEPTH, signer, true); maciContract = r.maciContract; signer = await getDefaultSigner(); - mpContract = r.mpContract; + verifierContract = r.mockVerifierContract as Verifier; + vkRegistryContract = r.vkRegistryContract; // deploy on chain poll - const tx = await maciContract.deployPoll(duration, maxValues, treeDepths, coordinator.pubKey.asContractParam(), { - gasLimit: 8000000, - }); + const tx = await maciContract.deployPoll( + duration, + maxValues, + treeDepths, + coordinator.pubKey.asContractParam(), + verifierContract, + vkRegistryContract, + false, + { + gasLimit: 10000000, + }, + ); let receipt = await tx.wait(); // extract poll id @@ -61,13 +75,15 @@ describe("MessageProcessor", () => { const iface = maciContract.interface; const logs = receipt!.logs[receipt!.logs.length - 1]; const event = iface.parseLog(logs as unknown as { topics: string[]; data: string }) as unknown as { - args: { _pollId: number }; + args: { _pollId: number; _mpAddr: string }; }; pollId = event.args._pollId; const pollContractAddress = await maciContract.getPoll(pollId); pollContract = new BaseContract(pollContractAddress, pollAbi, signer) as PollContract; + mpContract = new BaseContract(event.args._mpAddr, mpAbi, signer) as MessageProcessor; + const block = await signer.provider!.getBlock(receipt!.blockHash); const deployTime = block!.timestamp; @@ -91,8 +107,7 @@ describe("MessageProcessor", () => { generatedInputs = poll.processMessages(pollId); // set the verification keys on the vk smart contract - const vkContract = r.vkRegistryContract; - await vkContract.setVerifyingKeys( + vkRegistryContract.setVerifyingKeys( STATE_TREE_DEPTH, treeDepths.intStateTreeDepth, treeDepths.messageTreeDepth, @@ -112,11 +127,10 @@ describe("MessageProcessor", () => { }); it("processMessages() should fail if the state AQ has not been merged", async () => { - const pollContractAddress = await maciContract.getPoll(pollId); - - await expect( - mpContract.processMessages(pollContractAddress, 0, [0, 0, 0, 0, 0, 0, 0, 0]), - ).to.be.revertedWithCustomError(mpContract, "StateAqNotMerged"); + await expect(mpContract.processMessages(0, [0, 0, 0, 0, 0, 0, 0, 0])).to.be.revertedWithCustomError( + mpContract, + "StateAqNotMerged", + ); }); }); @@ -136,21 +150,13 @@ describe("MessageProcessor", () => { 0, poll.messages.length, ); - const onChainPackedVals = BigInt( - await mpContract.genProcessMessagesPackedVals(await pollContract.getAddress(), 0, users.length), - ); + const onChainPackedVals = BigInt(await mpContract.genProcessMessagesPackedVals(0, users.length)); expect(packedVals.toString()).to.eq(onChainPackedVals.toString()); }); it("processMessages() should update the state and ballot root commitment", async () => { - const pollContractAddress = await maciContract.getPoll(pollId); - // Submit the proof - const tx = await mpContract.processMessages( - pollContractAddress, - generatedInputs.newSbCommitment, - [0, 0, 0, 0, 0, 0, 0, 0], - ); + const tx = await mpContract.processMessages(generatedInputs.newSbCommitment, [0, 0, 0, 0, 0, 0, 0, 0]); const receipt = await tx.wait(); expect(receipt?.status).to.eq(1); diff --git a/contracts/tests/Poll.test.ts b/contracts/tests/Poll.test.ts index 2a1f903a0c..dc3934992d 100644 --- a/contracts/tests/Poll.test.ts +++ b/contracts/tests/Poll.test.ts @@ -8,7 +8,7 @@ import { Keypair, Message, PCommand, PubKey } from "maci-domainobjs"; import { parseArtifact } from "../ts/abi"; import { getDefaultSigner, getSigners } from "../ts/utils"; -import { AccQueue, MACI, Poll as PollContract, TopupCredit } from "../typechain-types"; +import { AccQueue, MACI, Poll as PollContract, TopupCredit, Verifier, VkRegistry } from "../typechain-types"; import { MESSAGE_TREE_DEPTH, @@ -26,6 +26,8 @@ describe("Poll", () => { let maciContract: MACI; let pollId: number; let pollContract: PollContract; + let verifierContract: Verifier; + let vkRegistryContract: VkRegistry; let topupCreditContract: TopupCredit; let signer: Signer; let deployTime: number; @@ -40,12 +42,23 @@ describe("Poll", () => { signer = await getDefaultSigner(); const r = await deployTestContracts(initialVoiceCreditBalance, STATE_TREE_DEPTH, signer, true); maciContract = r.maciContract; + verifierContract = r.mockVerifierContract as Verifier; + vkRegistryContract = r.vkRegistryContract; topupCreditContract = r.topupCreditContract; // deploy on chain poll - const tx = await maciContract.deployPoll(duration, maxValues, treeDepths, coordinator.pubKey.asContractParam(), { - gasLimit: 8000000, - }); + const tx = await maciContract.deployPoll( + duration, + maxValues, + treeDepths, + coordinator.pubKey.asContractParam(), + verifierContract, + vkRegistryContract, + false, + { + gasLimit: 10000000, + }, + ); const receipt = await tx.wait(); const block = await signer.provider!.getBlock(receipt!.blockHash); diff --git a/contracts/tests/Subsidy.test.ts b/contracts/tests/Subsidy.test.ts index 4bde80c2b3..f61e505270 100644 --- a/contracts/tests/Subsidy.test.ts +++ b/contracts/tests/Subsidy.test.ts @@ -9,7 +9,7 @@ import { Keypair, Message, PubKey } from "maci-domainobjs"; import { parseArtifact } from "../ts/abi"; import { IVerifyingKeyStruct } from "../ts/types"; import { getDefaultSigner } from "../ts/utils"; -import { MACI, Poll as PollContract, MessageProcessor, Subsidy } from "../typechain-types"; +import { MACI, Poll as PollContract, MessageProcessor, Subsidy, Verifier, VkRegistry } from "../typechain-types"; import { STATE_TREE_DEPTH, @@ -28,11 +28,15 @@ describe("Subsidy", () => { let pollContract: PollContract; let subsidyContract: Subsidy; let mpContract: MessageProcessor; + let verifierContract: Verifier; + let vkRegistryContract: VkRegistry; const coordinator = new Keypair(); const maciState = new MaciState(STATE_TREE_DEPTH); const [pollAbi] = parseArtifact("Poll"); + const [mpAbi] = parseArtifact("MessageProcessor"); + const [subsidyAbi] = parseArtifact("Subsidy"); let pollId: number; let poll: Poll; @@ -44,14 +48,23 @@ describe("Subsidy", () => { const r = await deployTestContracts(100, STATE_TREE_DEPTH, signer, true); maciContract = r.maciContract; - mpContract = r.mpContract; - subsidyContract = r.subsidyContract!; + verifierContract = r.mockVerifierContract as Verifier; + vkRegistryContract = r.vkRegistryContract; // deploy a poll // deploy on chain poll - const tx = await maciContract.deployPoll(duration, maxValues, treeDepths, coordinator.pubKey.asContractParam(), { - gasLimit: 8000000, - }); + const tx = await maciContract.deployPoll( + duration, + maxValues, + treeDepths, + coordinator.pubKey.asContractParam(), + verifierContract, + vkRegistryContract, + true, + { + gasLimit: 10000000, + }, + ); const receipt = await tx.wait(); const block = await signer.provider!.getBlock(receipt!.blockHash); @@ -59,14 +72,31 @@ describe("Subsidy", () => { expect(receipt?.status).to.eq(1); const iface = maciContract.interface; - const logs = receipt!.logs[receipt!.logs.length - 1]; - const event = iface.parseLog(logs as unknown as { topics: string[]; data: string }) as unknown as { - args: { _pollId: number }; + + // parse DeploySubsidy log + const logSubsidy = receipt!.logs[receipt!.logs.length - 2]; + const subsidyEvent = iface.parseLog(logSubsidy as unknown as { topics: string[]; data: string }) as unknown as { + args: { _subsidyAddr: string }; + name: string; + }; + expect(subsidyEvent.name).to.eq("DeploySubsidy"); + + // parse DeployPoll log + const logMPTally = receipt!.logs[receipt!.logs.length - 1]; + const MPTallyEvent = iface.parseLog(logMPTally as unknown as { topics: string[]; data: string }) as unknown as { + args: { _pollId: number; _mpAddr: string; _tallyAddr: string }; + name: string; }; - pollId = event.args._pollId; + expect(MPTallyEvent.name).to.eq("DeployPoll"); + + pollId = MPTallyEvent.args._pollId; const pollContractAddress = await maciContract.getPoll(pollId); pollContract = new BaseContract(pollContractAddress, pollAbi, signer) as PollContract; + const mpContractAddress = MPTallyEvent.args._mpAddr; + mpContract = new BaseContract(mpContractAddress, mpAbi, signer) as MessageProcessor; + const subsidyContractAddress = subsidyEvent.args._subsidyAddr; + subsidyContract = new BaseContract(subsidyContractAddress, subsidyAbi, signer) as Subsidy; // deploy local poll const p = maciState.deployPoll(BigInt(deployTime + duration), maxValues, treeDepths, messageBatchSize, coordinator); @@ -90,8 +120,7 @@ describe("Subsidy", () => { generatedInputs = poll.processMessages(pollId); // set the verification keys on the vk smart contract - const vkContract = r.vkRegistryContract; - await vkContract.setVerifyingKeys( + await vkRegistryContract.setVerifyingKeys( STATE_TREE_DEPTH, treeDepths.intStateTreeDepth, treeDepths.messageTreeDepth, @@ -101,7 +130,7 @@ describe("Subsidy", () => { testTallyVk.asContractParam() as IVerifyingKeyStruct, { gasLimit: 1000000 }, ); - await vkContract.setSubsidyKeys( + await vkRegistryContract.setSubsidyKeys( STATE_TREE_DEPTH, treeDepths.intStateTreeDepth, treeDepths.voteOptionTreeDepth, @@ -110,15 +139,11 @@ describe("Subsidy", () => { ); }); - it("should not be possible to tally votes before the poll has ended", async () => { - await expect( - subsidyContract.updateSubsidy( - await pollContract.getAddress(), - await mpContract.getAddress(), - 0, - [0, 0, 0, 0, 0, 0, 0, 0], - ), - ).to.be.revertedWithCustomError(subsidyContract, "VotingPeriodNotPassed"); + it("should not be possible to update subsidy before the poll has ended", async () => { + await expect(subsidyContract.updateSubsidy(0, [0, 0, 0, 0, 0, 0, 0, 0])).to.be.revertedWithCustomError( + subsidyContract, + "VotingPeriodNotPassed", + ); }); it("genSubsidyPackedVals() should generate the correct value", async () => { @@ -131,17 +156,17 @@ describe("Subsidy", () => { // go forward in time await timeTravel(signer.provider! as unknown as EthereumProvider, duration + 1); - await expect(subsidyContract.updateSbCommitment(await mpContract.getAddress())).to.be.revertedWithCustomError( + await expect(subsidyContract.updateSbCommitment()).to.be.revertedWithCustomError( subsidyContract, "ProcessingNotComplete", ); }); it("updateSubsidy() should fail as the messages have not been processed yet", async () => { - const pollContractAddress = await maciContract.getPoll(pollId); - await expect( - subsidyContract.updateSubsidy(pollContractAddress, await mpContract.getAddress(), 0, [0, 0, 0, 0, 0, 0, 0, 0]), - ).to.be.revertedWithCustomError(subsidyContract, "ProcessingNotComplete"); + await expect(subsidyContract.updateSubsidy(0, [0, 0, 0, 0, 0, 0, 0, 0])).to.be.revertedWithCustomError( + subsidyContract, + "ProcessingNotComplete", + ); }); describe("after merging acc queues", () => { @@ -156,15 +181,9 @@ describe("Subsidy", () => { }); it("updateSubsidy() should update the tally commitment", async () => { // do the processing on the message processor contract - await mpContract.processMessages( - await pollContract.getAddress(), - generatedInputs.newSbCommitment, - [0, 0, 0, 0, 0, 0, 0, 0], - ); + await mpContract.processMessages(generatedInputs.newSbCommitment, [0, 0, 0, 0, 0, 0, 0, 0]); const tx = await subsidyContract.updateSubsidy( - await pollContract.getAddress(), - await mpContract.getAddress(), subsidyGeneratedInputs.newSubsidyCommitment, [0, 0, 0, 0, 0, 0, 0, 0], ); @@ -178,12 +197,7 @@ describe("Subsidy", () => { }); it("updateSubsidy() should revert when votes have already been tallied", async () => { await expect( - subsidyContract.updateSubsidy( - await pollContract.getAddress(), - await mpContract.getAddress(), - subsidyGeneratedInputs.newSubsidyCommitment, - [0, 0, 0, 0, 0, 0, 0, 0], - ), + subsidyContract.updateSubsidy(subsidyGeneratedInputs.newSubsidyCommitment, [0, 0, 0, 0, 0, 0, 0, 0]), ).to.be.revertedWithCustomError(subsidyContract, "AllSubsidyCalculated"); }); }); diff --git a/contracts/tests/Tally.test.ts b/contracts/tests/Tally.test.ts index 80fde86c59..3081632d8b 100644 --- a/contracts/tests/Tally.test.ts +++ b/contracts/tests/Tally.test.ts @@ -15,7 +15,7 @@ import { Keypair, Message, PubKey } from "maci-domainobjs"; import { parseArtifact } from "../ts/abi"; import { IVerifyingKeyStruct } from "../ts/types"; import { getDefaultSigner } from "../ts/utils"; -import { Tally, MACI, Poll as PollContract, MessageProcessor } from "../typechain-types"; +import { Tally, MACI, Poll as PollContract, MessageProcessor, Verifier, VkRegistry } from "../typechain-types"; import { STATE_TREE_DEPTH, @@ -35,12 +35,16 @@ describe("TallyVotes", () => { let pollContract: PollContract; let tallyContract: Tally; let mpContract: MessageProcessor; + let verifierContract: Verifier; + let vkRegistryContract: VkRegistry; const coordinator = new Keypair(); const users = [new Keypair(), new Keypair()]; const maciState = new MaciState(STATE_TREE_DEPTH); const [pollAbi] = parseArtifact("Poll"); + const [mpAbi] = parseArtifact("MessageProcessor"); + const [tallyAbi] = parseArtifact("Tally"); let pollId: number; let poll: Poll; @@ -52,14 +56,23 @@ describe("TallyVotes", () => { const r = await deployTestContracts(100, STATE_TREE_DEPTH, signer, true); maciContract = r.maciContract; - mpContract = r.mpContract; - tallyContract = r.tallyContract; + verifierContract = r.mockVerifierContract as Verifier; + vkRegistryContract = r.vkRegistryContract; // deploy a poll // deploy on chain poll - const tx = await maciContract.deployPoll(duration, maxValues, treeDepths, coordinator.pubKey.asContractParam(), { - gasLimit: 8000000, - }); + const tx = await maciContract.deployPoll( + duration, + maxValues, + treeDepths, + coordinator.pubKey.asContractParam(), + verifierContract, + vkRegistryContract, + false, + { + gasLimit: 10000000, + }, + ); const receipt = await tx.wait(); const block = await signer.provider!.getBlock(receipt!.blockHash); @@ -69,12 +82,17 @@ describe("TallyVotes", () => { const iface = maciContract.interface; const logs = receipt!.logs[receipt!.logs.length - 1]; const event = iface.parseLog(logs as unknown as { topics: string[]; data: string }) as unknown as { - args: { _pollId: number }; + args: { _pollId: number; _mpAddr: string; _tallyAddr: string }; + name: string; }; + expect(event.name).to.eq("DeployPoll"); + pollId = event.args._pollId; const pollContractAddress = await maciContract.getPoll(pollId); pollContract = new BaseContract(pollContractAddress, pollAbi, signer) as PollContract; + mpContract = new BaseContract(event.args._mpAddr, mpAbi, signer) as MessageProcessor; + tallyContract = new BaseContract(event.args._tallyAddr, tallyAbi, signer) as Tally; // deploy local poll const p = maciState.deployPoll(BigInt(deployTime + duration), maxValues, treeDepths, messageBatchSize, coordinator); @@ -98,8 +116,7 @@ describe("TallyVotes", () => { generatedInputs = poll.processMessages(pollId); // set the verification keys on the vk smart contract - const vkContract = r.vkRegistryContract; - await vkContract.setVerifyingKeys( + await vkRegistryContract.setVerifyingKeys( STATE_TREE_DEPTH, treeDepths.intStateTreeDepth, treeDepths.messageTreeDepth, @@ -112,14 +129,10 @@ describe("TallyVotes", () => { }); it("should not be possible to tally votes before the poll has ended", async () => { - await expect( - tallyContract.tallyVotes( - await pollContract.getAddress(), - await mpContract.getAddress(), - 0, - [0, 0, 0, 0, 0, 0, 0, 0], - ), - ).to.be.revertedWithCustomError(tallyContract, "VotingPeriodNotPassed"); + await expect(tallyContract.tallyVotes(0, [0, 0, 0, 0, 0, 0, 0, 0])).to.be.revertedWithCustomError( + tallyContract, + "VotingPeriodNotPassed", + ); }); it("genTallyVotesPackedVals() should generate the correct value", async () => { @@ -132,17 +145,17 @@ describe("TallyVotes", () => { // go forward in time await timeTravel(signer.provider! as unknown as EthereumProvider, duration + 1); - await expect(tallyContract.updateSbCommitment(await mpContract.getAddress())).to.be.revertedWithCustomError( + await expect(tallyContract.updateSbCommitment()).to.be.revertedWithCustomError( tallyContract, "ProcessingNotComplete", ); }); it("tallyVotes() should fail as the messages have not been processed yet", async () => { - const pollContractAddress = await maciContract.getPoll(pollId); - await expect( - tallyContract.tallyVotes(pollContractAddress, await mpContract.getAddress(), 0, [0, 0, 0, 0, 0, 0, 0, 0]), - ).to.be.revertedWithCustomError(tallyContract, "ProcessingNotComplete"); + await expect(tallyContract.tallyVotes(0, [0, 0, 0, 0, 0, 0, 0, 0])).to.be.revertedWithCustomError( + tallyContract, + "ProcessingNotComplete", + ); }); describe("after merging acc queues", () => { @@ -157,18 +170,9 @@ describe("TallyVotes", () => { }); it("tallyVotes() should update the tally commitment", async () => { // do the processing on the message processor contract - await mpContract.processMessages( - await pollContract.getAddress(), - generatedInputs.newSbCommitment, - [0, 0, 0, 0, 0, 0, 0, 0], - ); - - const tx = await tallyContract.tallyVotes( - await pollContract.getAddress(), - await mpContract.getAddress(), - tallyGeneratedInputs.newTallyCommitment, - [0, 0, 0, 0, 0, 0, 0, 0], - ); + await mpContract.processMessages(generatedInputs.newSbCommitment, [0, 0, 0, 0, 0, 0, 0, 0]); + + const tx = await tallyContract.tallyVotes(tallyGeneratedInputs.newTallyCommitment, [0, 0, 0, 0, 0, 0, 0, 0]); const receipt = await tx.wait(); expect(receipt?.status).to.eq(1); @@ -179,12 +183,7 @@ describe("TallyVotes", () => { }); it("tallyVotes() should revert when votes have already been tallied", async () => { await expect( - tallyContract.tallyVotes( - await pollContract.getAddress(), - await mpContract.getAddress(), - tallyGeneratedInputs.newTallyCommitment, - [0, 0, 0, 0, 0, 0, 0, 0], - ), + tallyContract.tallyVotes(tallyGeneratedInputs.newTallyCommitment, [0, 0, 0, 0, 0, 0, 0, 0]), ).to.be.revertedWithCustomError(tallyContract, "AllBallotsTallied"); }); }); diff --git a/contracts/tests/constants.ts b/contracts/tests/constants.ts index f2fcc4ce81..3ca72750d2 100644 --- a/contracts/tests/constants.ts +++ b/contracts/tests/constants.ts @@ -2,7 +2,7 @@ import { MaxValues, TreeDepths } from "maci-core"; import { G1Point, G2Point } from "maci-crypto"; import { VerifyingKey } from "maci-domainobjs"; -export const duration = 15; +export const duration = 20; export const messageBatchSize = 25; export const STATE_TREE_DEPTH = 10; diff --git a/contracts/tests/utils.ts b/contracts/tests/utils.ts index 0a6fc19f90..6656bd543a 100644 --- a/contracts/tests/utils.ts +++ b/contracts/tests/utils.ts @@ -11,11 +11,8 @@ import { deployConstantInitialVoiceCreditProxy, deployFreeForAllSignUpGatekeeper, deployMaci, - deployMessageProcessor, deployMockVerifier, deployPoseidonContracts, - deploySubsidy, - deployTally, deployTopupCredit, deployVkRegistry, linkPoseidonLibraries, @@ -505,60 +502,21 @@ export const deployTestContracts = async ( // VkRegistry const vkRegistryContract = await deployVkRegistry(signer, true); const topupCreditContract = await deployTopupCredit(signer, true); - const [ - gatekeeperContractAddress, - mockVerifierContractAddress, - constantIntialVoiceCreditProxyContractAddress, - vkRegistryContractAddress, - topupCreditContractAddress, - ] = await Promise.all([ - gatekeeperContract.getAddress(), - mockVerifierContract.getAddress(), - constantIntialVoiceCreditProxyContract.getAddress(), - vkRegistryContract.getAddress(), - topupCreditContract.getAddress(), - ]); - - const { maciContract, stateAqContract, poseidonAddrs } = await deployMaci( + const [gatekeeperContractAddress, constantIntialVoiceCreditProxyContractAddress, topupCreditContractAddress] = + await Promise.all([ + gatekeeperContract.getAddress(), + constantIntialVoiceCreditProxyContract.getAddress(), + topupCreditContract.getAddress(), + ]); + + const { maciContract, stateAqContract } = await deployMaci( gatekeeperContractAddress, constantIntialVoiceCreditProxyContractAddress, - mockVerifierContractAddress, topupCreditContractAddress, signer, stateTreeDepth, quiet, ); - const mpContract = await deployMessageProcessor( - mockVerifierContractAddress, - vkRegistryContractAddress, - poseidonAddrs[0], - poseidonAddrs[1], - poseidonAddrs[2], - poseidonAddrs[3], - signer, - true, - ); - const tallyContract = await deployTally( - mockVerifierContractAddress, - vkRegistryContractAddress, - poseidonAddrs[0], - poseidonAddrs[1], - poseidonAddrs[2], - poseidonAddrs[3], - signer, - true, - ); - - const subsidyContract = await deploySubsidy( - mockVerifierContractAddress, - vkRegistryContractAddress, - poseidonAddrs[0], - poseidonAddrs[1], - poseidonAddrs[2], - poseidonAddrs[3], - signer, - true, - ); return { mockVerifierContract, @@ -567,9 +525,6 @@ export const deployTestContracts = async ( maciContract, stateAqContract, vkRegistryContract, - mpContract, - tallyContract, - subsidyContract, topupCreditContract, }; }; diff --git a/contracts/ts/deploy.ts b/contracts/ts/deploy.ts index 16f7db6b1c..4fca400efb 100644 --- a/contracts/ts/deploy.ts +++ b/contracts/ts/deploy.ts @@ -1,4 +1,4 @@ -import { type Contract, type ContractFactory, type Signer, BaseContract } from "ethers"; +import { type ContractFactory, type Signer, BaseContract } from "ethers"; // eslint-disable-next-line // @ts-ignore typedoc doesn't want to get types from toolbox import { ethers } from "hardhat"; @@ -9,17 +9,17 @@ import type { ConstantInitialVoiceCreditProxy, FreeForAllGatekeeper, MACI, - MessageProcessor, MockVerifier, PollFactory, + MessageProcessorFactory, + SubsidyFactory, + TallyFactory, PoseidonT3, PoseidonT4, PoseidonT5, PoseidonT6, SignUpToken, SignUpTokenGatekeeper, - Subsidy, - Tally, TopupCredit, Verifier, VkRegistry, @@ -216,7 +216,7 @@ export const deployContractWithLinkedLibraries = async ( * @param quiet - whether to suppress console output * @returns the deployed Poll Factory contract */ -export const deployPollFactory = async (signer: Signer, quiet = false): Promise => { +export const deployPollFactory = async (signer: Signer, quiet = false): Promise => { const poseidonContracts = await deployPoseidonContracts(signer, quiet); const [poseidonT3Contract, poseidonT4Contract, poseidonT5Contract, poseidonT6Contract] = await Promise.all([ poseidonContracts.PoseidonT3Contract.getAddress(), @@ -236,138 +236,19 @@ export const deployPollFactory = async (signer: Signer, quiet = false): Promise< return deployContractWithLinkedLibraries(contractFactory, "PollFactory", quiet); }; -/** - * Deploy a MessageProcessor contract - * @param verifierAddress - the address of the Verifier contract - * @param poseidonT3Address - the address of the PoseidonT3 contract - * @param poseidonT4Address - the address of the PoseidonT4 contract - * @param poseidonT5Address - the address of the PoseidonT5 contract - * @param poseidonT6Address - the address of the PoseidonT6 contract - * @param signer - the signer to use to deploy the contract - * @param quiet - whether to suppress console output - * @returns the deployed MessageProcessor contract - */ -export const deployMessageProcessor = async ( - verifierAddress: string, - vkRegistryAddress: string, - poseidonT3Address: string, - poseidonT4Address: string, - poseidonT5Address: string, - poseidonT6Address: string, - signer?: Signer, - quiet = false, -): Promise => { - // Link Poseidon contracts to MessageProcessor - const mpFactory = await linkPoseidonLibraries( - "MessageProcessor", - poseidonT3Address, - poseidonT4Address, - poseidonT5Address, - poseidonT6Address, - signer, - quiet, - ); - - return deployContractWithLinkedLibraries( - mpFactory, - "MessageProcessor", - quiet, - verifierAddress, - vkRegistryAddress, - ); -}; - -/** - * Deploy a Tally contract - * @param verifierAddress - the address of the Verifier contract - * @param poseidonT3Address - the address of the PoseidonT3 contract - * @param poseidonT4Address - the address of the PoseidonT4 contract - * @param poseidonT5Address - the address of the PoseidonT5 contract - * @param poseidonT6Address - the address of the PoseidonT6 contract - * @param signer - the signer to use to deploy the contract - * @param quiet - whether to suppress console output - * @returns the deployed Tally contract - */ -export const deployTally = async ( - verifierAddress: string, - vkRegistryAddress: string, - poseidonT3Address: string, - poseidonT4Address: string, - poseidonT5Address: string, - poseidonT6Address: string, - signer?: Signer, - quiet = false, -): Promise => { - // Link Poseidon contracts to Tally - const tallyFactory = await linkPoseidonLibraries( - "Tally", - poseidonT3Address, - poseidonT4Address, - poseidonT5Address, - poseidonT6Address, - signer, - quiet, - ); - - return deployContractWithLinkedLibraries(tallyFactory, "Tally", quiet, verifierAddress, vkRegistryAddress); -}; - -/** - * Depoloy a Subsidy contract - * @param verifierAddress - the address of the Verifier contract - * @param poseidonT3Address - the address of the PoseidonT3 contract - * @param poseidonT4Address - the address of the PoseidonT4 contract - * @param poseidonT5Address - the address of the PoseidonT5 contract - * @param poseidonT6Address - the address of the PoseidonT6 contract - * @param quiet - whether to suppress console output - * @returns the deployed Subsidy contract - */ -export const deploySubsidy = async ( - verifierAddress: string, - vkRegistryAddress: string, - poseidonT3Address: string, - poseidonT4Address: string, - poseidonT5Address: string, - poseidonT6Address: string, - signer?: Signer, - quiet = false, -): Promise => { - // Link Poseidon contracts to Subsidy - const subsidyFactory = await linkPoseidonLibraries( - "Subsidy", - poseidonT3Address, - poseidonT4Address, - poseidonT5Address, - poseidonT6Address, - signer, - quiet, - ); - - return deployContractWithLinkedLibraries( - subsidyFactory, - "Subsidy", - quiet, - verifierAddress, - vkRegistryAddress, - ); -}; - /** * Deploy a MACI contract * @param signUpTokenGatekeeperContractAddress - the address of the SignUpTokenGatekeeper contract * @param initialVoiceCreditBalanceAddress - the address of the ConstantInitialVoiceCreditProxy contract - * @param verifierContractAddress - the address of the Verifier contract - * @param vkRegistryContractAddress - the address of the VkRegistry contract * @param topupCreditContractAddress - the address of the TopupCredit contract - * @param stateTreeDepth - the depth of the state tree * @param signer - the signer to use to deploy the contract + * @param stateTreeDepth - the depth of the state tree * @param quiet - whether to suppress console output * @returns the deployed MACI contract */ export const deployMaci = async ( signUpTokenGatekeeperContractAddress: string, initialVoiceCreditBalanceAddress: string, - verifierContractAddress: string, topupCreditContractAddress: string, signer?: Signer, stateTreeDepth = 10, @@ -383,7 +264,7 @@ export const deployMaci = async ( PoseidonT6Contract.getAddress(), ]); - const contractsToLink = ["MACI", "PollFactory"]; + const contractsToLink = ["MACI", "PollFactory", "MessageProcessorFactory", "TallyFactory", "SubsidyFactory"]; // Link Poseidon contracts to MACI const linkedContractFactories = await Promise.all( @@ -400,19 +281,45 @@ export const deployMaci = async ( ), ); - const [maciContractFactory, pollFactoryContractFactory] = await Promise.all(linkedContractFactories); + const [maciContractFactory, pollFactoryContractFactory, messageProcessorFactory, tallyFactory, subsidyFactory] = + await Promise.all(linkedContractFactories); const pollFactoryContract = await deployContractWithLinkedLibraries( pollFactoryContractFactory, "PollFactory", quiet, ); + const messageProcessorFactoryContract = await deployContractWithLinkedLibraries( + messageProcessorFactory, + "MessageProcessorFactory", + quiet, + ); + const tallyFactoryContract = await deployContractWithLinkedLibraries( + tallyFactory, + "TallyFactory", + quiet, + ); + const subsidyFactoryContract = await deployContractWithLinkedLibraries( + subsidyFactory, + "SubsidyFactory", + quiet, + ); + + const [pollAddr, mpAddr, tallyAddr, subsidyAddr] = await Promise.all([ + pollFactoryContract.getAddress(), + messageProcessorFactoryContract.getAddress(), + tallyFactoryContract.getAddress(), + subsidyFactoryContract.getAddress(), + ]); const maciContract = await deployContractWithLinkedLibraries( maciContractFactory, "MACI", quiet, - await pollFactoryContract.getAddress(), + pollAddr, + mpAddr, + tallyAddr, + subsidyAddr, signUpTokenGatekeeperContractAddress, initialVoiceCreditBalanceAddress, topupCreditContractAddress, @@ -427,8 +334,6 @@ export const deployMaci = async ( await getDefaultSigner(), ) as AccQueueQuinaryMaci; - log(`Verifier contract address: ${verifierContractAddress}`, quiet); - return { maciContract, stateAqContract, diff --git a/contracts/ts/index.ts b/contracts/ts/index.ts index e98841016d..f87ca9f4a8 100644 --- a/contracts/ts/index.ts +++ b/contracts/ts/index.ts @@ -3,9 +3,6 @@ export { deployTopupCredit, deployVkRegistry, deployMaci, - deployMessageProcessor, - deployTally, - deploySubsidy, deployContract, deploySignupToken, deploySignupTokenGatekeeper, diff --git a/contracts/ts/types.ts b/contracts/ts/types.ts index 0e6af42c82..8c4ffc7a11 100644 --- a/contracts/ts/types.ts +++ b/contracts/ts/types.ts @@ -3,15 +3,12 @@ import type { ConstantInitialVoiceCreditProxy, FreeForAllGatekeeper, MACI, - MessageProcessor, MockVerifier, PollFactory, PoseidonT3, PoseidonT4, PoseidonT5, PoseidonT6, - Subsidy, - Tally, TopupCredit, VkRegistry, } from "../typechain-types"; @@ -77,10 +74,7 @@ export interface IDeployedTestContracts { maciContract: MACI; stateAqContract: AccQueueQuinaryMaci; vkRegistryContract: VkRegistry; - mpContract: MessageProcessor; - tallyContract: Tally; topupCreditContract: TopupCredit; - subsidyContract?: Subsidy; } /** diff --git a/integrationTests/ts/__tests__/integration.test.ts b/integrationTests/ts/__tests__/integration.test.ts index ee857bb4ca..e9d82feedf 100644 --- a/integrationTests/ts/__tests__/integration.test.ts +++ b/integrationTests/ts/__tests__/integration.test.ts @@ -100,6 +100,7 @@ describe("integration tests", function test() { MSG_TREE_DEPTH, VOTE_OPTION_TREE_DEPTH, coordinatorKeypair.pubKey.serialize(), + true, contracts.maciAddress, ); @@ -287,6 +288,7 @@ describe("integration tests", function test() { proveOnChain( pollId.toString(), path.resolve(__dirname, "../../../cli/proofs"), + subsidyEnabled, contracts.maciAddress, pollContracts.messageProcessor, pollContracts.tally, @@ -299,6 +301,7 @@ describe("integration tests", function test() { await expect( verify( pollId.toString(), + subsidyEnabled, path.resolve(__dirname, "../../../cli/tally.json"), tallyData, contracts.maciAddress, diff --git a/integrationTests/ts/__tests__/maci-keys.test.ts b/integrationTests/ts/__tests__/maci-keys.test.ts index e89295a5ea..33f50580fa 100644 --- a/integrationTests/ts/__tests__/maci-keys.test.ts +++ b/integrationTests/ts/__tests__/maci-keys.test.ts @@ -1,6 +1,6 @@ import { expect } from "chai"; import { BaseContract, Signer } from "ethers"; -import { MACI, Poll, getDefaultSigner, parseArtifact } from "maci-contracts"; +import { Poll, getDefaultSigner, parseArtifact } from "maci-contracts"; import { genPrivKey, genPubKey } from "maci-crypto"; import { Keypair, PrivKey, PubKey } from "maci-domainobjs"; @@ -67,8 +67,6 @@ describe("integration tests private/public/keypair", () => { }); describe("crypto/domainobjs/contracts", () => { - // deploy maci - let maciContract: MACI; let pollContract: Poll; let signer: Signer; @@ -76,10 +74,15 @@ describe("integration tests private/public/keypair", () => { before(async () => { signer = await getDefaultSigner(); - maciContract = await deployTestContracts(initialVoiceCredits, STATE_TREE_DEPTH, signer, true); + const { maci, verifier, vkRegistry } = await deployTestContracts( + initialVoiceCredits, + STATE_TREE_DEPTH, + signer, + true, + ); // deploy a poll - await maciContract.deployPoll( + await maci.deployPoll( BigInt(duration), { maxMessages, @@ -92,10 +95,13 @@ describe("integration tests private/public/keypair", () => { voteOptionTreeDepth: VOTE_OPTION_TREE_DEPTH, }, coordinatorKeypair.pubKey.asContractParam(), + verifier, + vkRegistry, + false, ); // we know it's the first poll so id is 0 - pollContract = new BaseContract(await maciContract.polls(0), parseArtifact("Poll")[0], signer) as Poll; + pollContract = new BaseContract(await maci.polls(0), parseArtifact("Poll")[0], signer) as Poll; }); it("should have the correct coordinator pub key set on chain", async () => { diff --git a/integrationTests/ts/__tests__/utils/interfaces.ts b/integrationTests/ts/__tests__/utils/interfaces.ts index b1d2285c38..ea1b4b1f62 100644 --- a/integrationTests/ts/__tests__/utils/interfaces.ts +++ b/integrationTests/ts/__tests__/utils/interfaces.ts @@ -1,3 +1,5 @@ +import { MACI, Verifier, VkRegistry } from "maci-contracts"; + /** * A util interface that represents a vote object */ @@ -52,3 +54,12 @@ export interface ITestSuite { votes?: IVote[][]; changeUsersKeys?: IChangeUsersKeys[][]; } + +/** + * A util interface that represents deployed test contracts + */ +export interface IDeployedTestContracts { + maci: MACI; + verifier: Verifier; + vkRegistry: VkRegistry; +} diff --git a/integrationTests/ts/__tests__/utils/utils.ts b/integrationTests/ts/__tests__/utils/utils.ts index 9d90e46b82..0775063090 100644 --- a/integrationTests/ts/__tests__/utils/utils.ts +++ b/integrationTests/ts/__tests__/utils/utils.ts @@ -3,12 +3,13 @@ import { expect } from "chai"; import { Signer } from "ethers"; import { FreeForAllGatekeeper, - MACI, deployConstantInitialVoiceCreditProxy, deployFreeForAllSignUpGatekeeper, deployMaci, deployMockVerifier, deployTopupCredit, + deployVkRegistry, + Verifier, } from "maci-contracts"; import { Keypair } from "maci-domainobjs"; @@ -17,7 +18,7 @@ import { arch } from "os"; import type { TallyData } from "maci-cli"; import { defaultVote } from "./constants"; -import { Subsidy, IVote, IBriber } from "./interfaces"; +import { Subsidy, IVote, IBriber, IDeployedTestContracts } from "./interfaces"; import { UserCommand } from "./user"; /** @@ -192,7 +193,7 @@ export const deployTestContracts = async ( signer?: Signer, quiet = false, gatekeeper: FreeForAllGatekeeper | undefined = undefined, -): Promise => { +): Promise => { const mockVerifierContract = await deployMockVerifier(signer, true); let gatekeeperContract = gatekeeper; @@ -207,28 +208,23 @@ export const deployTestContracts = async ( ); // VkRegistry + const vkRegistryContract = await deployVkRegistry(signer, true); const topupCreditContract = await deployTopupCredit(signer, true); - const [ - gatekeeperContractAddress, - mockVerifierContractAddress, - constantIntialVoiceCreditProxyContractAddress, - topupCreditContractAddress, - ] = await Promise.all([ - gatekeeperContract.getAddress(), - mockVerifierContract.getAddress(), - constantIntialVoiceCreditProxyContract.getAddress(), - topupCreditContract.getAddress(), - ]); + const [gatekeeperContractAddress, constantIntialVoiceCreditProxyContractAddress, topupCreditContractAddress] = + await Promise.all([ + gatekeeperContract.getAddress(), + constantIntialVoiceCreditProxyContract.getAddress(), + topupCreditContract.getAddress(), + ]); const { maciContract } = await deployMaci( gatekeeperContractAddress, constantIntialVoiceCreditProxyContractAddress, - mockVerifierContractAddress, topupCreditContractAddress, signer, stateTreeDepth, quiet, ); - return maciContract; + return { maci: maciContract, verifier: mockVerifierContract as Verifier, vkRegistry: vkRegistryContract }; };