diff --git a/contracts/contracts/Subsidy.sol b/contracts/contracts/Subsidy.sol index 4e7af76ba1..885c966407 100644 --- a/contracts/contracts/Subsidy.sol +++ b/contracts/contracts/Subsidy.sol @@ -7,6 +7,7 @@ import { Ownable } from "@openzeppelin/contracts/access/Ownable.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 { IVerifier } from "./interfaces/IVerifier.sol"; import { IVkRegistry } from "./interfaces/IVkRegistry.sol"; @@ -14,7 +15,7 @@ import { IVkRegistry } from "./interfaces/IVkRegistry.sol"; /// @notice This contract is used to verify that the subsidy calculations /// are correct. It is also used to update the subsidy commitment if the /// proof is valid. -contract Subsidy is Ownable, Hasher, SnarkCommon { +contract Subsidy is Ownable, CommonUtilities, Hasher, SnarkCommon { uint256 public rbi; // row batch index uint256 public cbi; // column batch index // The final commitment to the state and ballot roots diff --git a/contracts/contracts/SubsidyFactory.sol b/contracts/contracts/SubsidyFactory.sol index a62d5ba1b6..b5aed58405 100644 --- a/contracts/contracts/SubsidyFactory.sol +++ b/contracts/contracts/SubsidyFactory.sol @@ -11,6 +11,7 @@ contract SubsidyFactory { /// @param _vkRegistry VkRegistry contract /// @param _poll Poll contract /// @param _messageProcessor MessageProcessor contract + /// @param _owner Owner of the Subsidy contract /// @return subsidyAddr The deployed Subsidy contract function deploy( address _verifier, @@ -19,7 +20,7 @@ contract SubsidyFactory { address _messageProcessor, address _owner ) public returns (address subsidyAddr) { - /// @notice deploy Tally for this Poll + /// @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 2753253e82..cf24c00621 100644 --- a/contracts/contracts/Tally.sol +++ b/contracts/contracts/Tally.sol @@ -10,11 +10,12 @@ import { IMessageProcessor } from "./interfaces/IMessageProcessor.sol"; import { SnarkCommon } from "./crypto/SnarkCommon.sol"; import { IVerifier } from "./interfaces/IVerifier.sol"; import { IVkRegistry } from "./interfaces/IVkRegistry.sol"; +import { CommonUtilities } from "./utilities/CommonUtilities.sol"; /// @title Tally /// @notice The Tally contract is used during votes tallying /// and by users to verify the tally results. -contract Tally is Ownable, SnarkCommon, Hasher { +contract Tally is Ownable, SnarkCommon, CommonUtilities, Hasher { // custom errors error ProcessingNotComplete(); error InvalidTallyVotesProof(); diff --git a/contracts/contracts/utilities/CommonUtilities.sol b/contracts/contracts/utilities/CommonUtilities.sol new file mode 100644 index 0000000000..4268366417 --- /dev/null +++ b/contracts/contracts/utilities/CommonUtilities.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.10; + +import { Poll } from "../Poll.sol"; + +/// @title CommonUtilities +/// @notice A contract that holds common utilities +/// which are to be used by multiple contracts +/// namely Subsidy, Tally and MessageProcessor +contract CommonUtilities { + error VotingPeriodNotPassed(); + + /// @notice common function for MessageProcessor, Tally and Subsidy + /// @param _poll the poll to be checked + function _votingPeriodOver(Poll _poll) internal view { + (uint256 deployTime, uint256 duration) = _poll.getDeployTimeAndDuration(); + // Require that the voting period is over + uint256 secondsPassed = block.timestamp - deployTime; + if (secondsPassed <= duration) { + revert VotingPeriodNotPassed(); + } + } +} diff --git a/contracts/tests/Poll.test.ts b/contracts/tests/Poll.test.ts index e704450b19..a07406f204 100644 --- a/contracts/tests/Poll.test.ts +++ b/contracts/tests/Poll.test.ts @@ -41,12 +41,14 @@ describe("Poll", () => { signer = await getDefaultSigner(); const r = await deployTestContracts(initialVoiceCreditBalance, STATE_TREE_DEPTH, signer, true); maciContract = r.maciContract; + verifierContract = r.mockVerifierContract; + vkRegistryContract = r.vkRegistryContract; // deploy on chain poll const tx = await maciContract.deployPoll( - duration, - maxValues, - treeDepths, + duration, + maxValues, + treeDepths, coordinator.pubKey.asContractParam(), verifierContract, vkRegistryContract, diff --git a/contracts/tests/Subsidy.test.ts b/contracts/tests/Subsidy.test.ts index 34f8b1daf4..ba08beb3ea 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; + 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,22 @@ 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 }; + 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 }; }; - pollId = event.args._pollId; + 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 }; + }; + 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 +111,8 @@ describe("Subsidy", () => { generatedInputs = poll.processMessages(pollId) as typeof generatedInputs; // set the verification keys on the vk smart contract - const vkContract = r.vkRegistryContract; - await vkContract.setVerifyingKeys( + vkRegistryContract = r.vkRegistryContract; + await vkRegistryContract.setVerifyingKeys( STATE_TREE_DEPTH, treeDepths.intStateTreeDepth, treeDepths.messageTreeDepth, @@ -101,7 +122,7 @@ describe("Subsidy", () => { testTallyVk.asContractParam() as IVerifyingKeyStruct, { gasLimit: 1000000 }, ); - await vkContract.setSubsidyKeys( + await vkRegistryContract.setSubsidyKeys( STATE_TREE_DEPTH, treeDepths.intStateTreeDepth, treeDepths.voteOptionTreeDepth, @@ -111,14 +132,10 @@ 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, "VOTING_PERIOD_NOT_PASSED"); + await expect(subsidyContract.updateSubsidy(0, [0, 0, 0, 0, 0, 0, 0, 0])).to.be.revertedWithCustomError( + subsidyContract, + "ProcessingNotComplete", + ); }); it("genSubsidyPackedVals() should generate the correct value", async () => { @@ -131,17 +148,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 +173,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 +189,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 2cb7269778..b659a53b17 100644 --- a/contracts/tests/Tally.test.ts +++ b/contracts/tests/Tally.test.ts @@ -67,7 +67,7 @@ describe("TallyVotes", () => { gasLimit: 8000000, }, ); - let receipt = await tx.wait(); + const receipt = await tx.wait(); const block = await signer.provider!.getBlock(receipt!.blockHash); const deployTime = block!.timestamp; @@ -123,7 +123,7 @@ describe("TallyVotes", () => { it("should not be possible to tally votes before the poll has ended", async () => { await expect(tallyContract.tallyVotes(0, [0, 0, 0, 0, 0, 0, 0, 0])).to.be.revertedWithCustomError( tallyContract, - "VOTING_PERIOD_NOT_PASSED", + "ProcessingNotComplete", ); });