diff --git a/contracts/contracts/DomainObjs.sol b/contracts/contracts/DomainObjs.sol deleted file mode 100644 index 8082f779f0..0000000000 --- a/contracts/contracts/DomainObjs.sol +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import { Hasher } from "./crypto/Hasher.sol"; - -contract IPubKey { - struct PubKey { - uint256 x; - uint256 y; - } -} - -contract IMessage { - uint8 constant MESSAGE_DATA_LENGTH = 10; - - struct Message { - uint256 msgType; // 1: vote message (size 10), 2: topup message (size 2) - uint256[MESSAGE_DATA_LENGTH] data; // data length is padded to size 10 - } -} - -contract DomainObjs is IMessage, Hasher, IPubKey { - struct StateLeaf { - PubKey pubKey; - uint256 voiceCreditBalance; - uint256 timestamp; - } - - function hashStateLeaf(StateLeaf memory _stateLeaf) public pure returns (uint256) { - uint256[4] memory plaintext; - plaintext[0] = _stateLeaf.pubKey.x; - plaintext[1] = _stateLeaf.pubKey.y; - plaintext[2] = _stateLeaf.voiceCreditBalance; - plaintext[3] = _stateLeaf.timestamp; - - return hash4(plaintext); - } -} diff --git a/contracts/contracts/HasherBenchmarks.sol b/contracts/contracts/HasherBenchmarks.sol deleted file mode 100644 index 0fd6d9543b..0000000000 --- a/contracts/contracts/HasherBenchmarks.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import { Hasher } from "./crypto/Hasher.sol"; - -contract HasherBenchmarks is Hasher { - function hash5Benchmark(uint256[5] memory array) public { - hash5(array); - } - - function hashLeftRightBenchmark(uint256 _left, uint256 _right) public { - hashLeftRight(_left, _right); - } -} diff --git a/contracts/contracts/IMACI.sol b/contracts/contracts/IMACI.sol deleted file mode 100644 index 82a9a62e27..0000000000 --- a/contracts/contracts/IMACI.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; -import { VkRegistry } from "./VkRegistry.sol"; -import { AccQueue } from "./trees/AccQueue.sol"; - -interface IMACI { - function stateTreeDepth() external view returns (uint8); - function vkRegistry() external view returns (VkRegistry); - function getStateAqRoot() external view returns (uint256); - function mergeStateAqSubRoots(uint256 _numSrQueueOps, uint256 _pollId) external; - function mergeStateAq(uint256 _pollId) external returns (uint256); - function numSignUps() external view returns (uint256); - function stateAq() external view returns (AccQueue); -} diff --git a/contracts/contracts/MACI.sol b/contracts/contracts/MACI.sol index fac220266b..92011ca4c9 100644 --- a/contracts/contracts/MACI.sol +++ b/contracts/contracts/MACI.sol @@ -1,60 +1,58 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.10; -import { Poll, PollFactory } from "./Poll.sol"; +import { Poll } from "./Poll.sol"; +import { PollFactory } from "./PollFactory.sol"; import { InitialVoiceCreditProxy } from "./initialVoiceCreditProxy/InitialVoiceCreditProxy.sol"; import { SignUpGatekeeper } from "./gatekeepers/SignUpGatekeeper.sol"; import { AccQueue, AccQueueQuinaryBlankSl } from "./trees/AccQueue.sol"; -import { IMACI } from "./IMACI.sol"; -import { Params } from "./Params.sol"; -import { DomainObjs } from "./DomainObjs.sol"; +import { IMACI } from "./interfaces/IMACI.sol"; +import { Params } from "./utilities/Params.sol"; +import { DomainObjs } from "./utilities/DomainObjs.sol"; import { VkRegistry } from "./VkRegistry.sol"; import { TopupCredit } from "./TopupCredit.sol"; import { SnarkCommon } from "./crypto/SnarkCommon.sol"; import { SnarkConstants } from "./crypto/SnarkConstants.sol"; - +import { Hasher } from "./crypto/Hasher.sol"; +import { Utilities } from "./utilities/Utilities.sol"; import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; -/* - * Minimum Anti-Collusion Infrastructure - * Version 1 - */ -contract MACI is IMACI, DomainObjs, Params, SnarkCommon, Ownable { - // The state tree depth is fixed. As such it should be as large as feasible - // so that there can be as many users as possible. i.e. 5 ** 10 = 9765625 - // this should also match the parameter of the circom circuits. +/// @title MACI - Minimum Anti-Collusion Infrastructure Version 1 +contract MACI is IMACI, DomainObjs, Params, Utilities, Ownable { + /// @notice The state tree depth is fixed. As such it should be as large as feasible + /// so that there can be as many users as possible. i.e. 5 ** 10 = 9765625 + /// this should also match the parameter of the circom circuits. uint8 public immutable stateTreeDepth; - // IMPORTANT: remember to change the ballot tree depth - // in contracts/ts/genEmptyBallotRootsContract.ts file - // if we change the state tree depth! - + /// @notice IMPORTANT: remember to change the ballot tree depth + /// in contracts/ts/genEmptyBallotRootsContract.ts file + /// if we change the state tree depth! uint8 internal constant STATE_TREE_SUBDEPTH = 2; uint8 internal constant STATE_TREE_ARITY = 5; uint8 internal constant MESSAGE_TREE_ARITY = 5; - //// The hash of a blank state leaf + /// @notice The hash of a blank state leaf uint256 internal constant BLANK_STATE_LEAF_HASH = uint256(6769006970205099520508948723718471724660867171122235270773600567925038008762); - // Each poll has an incrementing ID + /// @notice Each poll has an incrementing ID uint256 public nextPollId; - // A mapping of poll IDs to Poll contracts. + /// @notice A mapping of poll IDs to Poll contracts. mapping(uint256 => Poll) public polls; - // The number of signups + /// @notice The number of signups uint256 public override numSignUps; - // A mapping of block timestamps to the number of state leaves + /// @notice A mapping of block timestamps to the number of state leaves mapping(uint256 => uint256) public numStateLeaves; // The block timestamp at which the state queue subroots were last merged //uint256 public mergeSubRootsTimestamp; - // The verifying key registry. There may be multiple verifying keys stored - // on chain, and Poll contracts must select the correct VK based on the - // circuit's compile-time parameters, such as tree depths and batch sizes. + /// @notice The verifying key registry. There may be multiple verifying keys stored + /// on chain, and Poll contracts must select the correct VK based on the + /// circuit's compile-time parameters, such as tree depths and batch sizes. VkRegistry public override vkRegistry; // ERC20 contract that hold topup credits @@ -62,23 +60,23 @@ contract MACI is IMACI, DomainObjs, Params, SnarkCommon, Ownable { PollFactory public pollFactory; - // The state AccQueue. Represents a mapping between each user's public key - // and their voice credit balance. + /// @notice The state AccQueue. Represents a mapping between each user's public key + /// @notice and their voice credit balance. AccQueue public override stateAq; - // Whether the init() function has been successfully executed yet. + /// @notice Whether the init() function has been successfully executed yet. bool public isInitialised; - // Address of the SignUpGatekeeper, a contract which determines whether a - // user may sign up to vote + /// @notice Address of the SignUpGatekeeper, a contract which determines whether a + /// user may sign up to vote SignUpGatekeeper public signUpGatekeeper; - // The contract which provides the values of the initial voice credit - // balance per user + /// @notice The contract which provides the values of the initial voice credit + /// balance per user InitialVoiceCreditProxy public initialVoiceCreditProxy; - // When the contract was deployed. We assume that the signup period starts - // immediately upon deployment. + /// @notice When the contract was deployed. We assume that the signup period starts + /// immediately upon deployment. uint256 public signUpTimestamp; // Events @@ -92,17 +90,13 @@ contract MACI is IMACI, DomainObjs, Params, SnarkCommon, Ownable { event MergeStateAqSubRoots(uint256 _pollId, uint256 _numSrQueueOps); event MergeStateAq(uint256 _pollId); - /* - * Ensure certain functions only run after the contract has been initialized - */ + /// @notice Ensure certain functions only run after the contract has been initialized modifier afterInit() { if (!isInitialised) revert MaciNotInit(); _; } - /* - * Only allow a Poll contract to call the modified function. - */ + /// @notice Only allow a Poll contract to call the modified function. modifier onlyPoll(uint256 _pollId) { if (msg.sender != address(polls[_pollId])) revert CallerMustBePoll(msg.sender); _; @@ -140,12 +134,10 @@ contract MACI is IMACI, DomainObjs, Params, SnarkCommon, Ownable { if (hash2([uint256(1), uint256(1)]) == 0) revert PoseidonHashLibrariesNotLinked(); } - /* - * Initialise the various factory/helper contracts. This should only be run - * once and it must be run before deploying the first Poll. - * @param _vkRegistry The VkRegistry contract - * @param _topupCredit The topupCredit contract - */ + /// @notice Initialise the various factory/helper contracts. This should only be run + /// once and it must be run before deploying the first Poll. + /// @param _vkRegistry The VkRegistry contract + /// @param _topupCredit The topupCredit contract function init(VkRegistry _vkRegistry, TopupCredit _topupCredit) public onlyOwner { if (isInitialised) revert AlreadyInitialized(); @@ -162,20 +154,18 @@ contract MACI is IMACI, DomainObjs, Params, SnarkCommon, Ownable { emit Init(_vkRegistry, _topupCredit); } - /* - * Allows any eligible user sign up. The sign-up gatekeeper should prevent - * double sign-ups or ineligible users from doing so. This function will - * only succeed if the sign-up deadline has not passed. It also enqueues a - * fresh state leaf into the state AccQueue. - * @param _userPubKey The user's desired public key. - * @param _signUpGatekeeperData Data to pass to the sign-up gatekeeper's - * register() function. For instance, the POAPGatekeeper or - * SignUpTokenGatekeeper requires this value to be the ABI-encoded - * token ID. - * @param _initialVoiceCreditProxyData Data to pass to the - * InitialVoiceCreditProxy, which allows it to determine how many voice - * credits this user should have. - */ + /// @notice Allows any eligible user sign up. The sign-up gatekeeper should prevent + /// double sign-ups or ineligible users from doing so. This function will + /// only succeed if the sign-up deadline has not passed. It also enqueues a + /// fresh state leaf into the state AccQueue. + /// @param _pubKey The user's desired public key. + /// @param _signUpGatekeeperData Data to pass to the sign-up gatekeeper's + /// register() function. For instance, the POAPGatekeeper or + /// SignUpTokenGatekeeper requires this value to be the ABI-encoded + /// token ID. + /// @param _initialVoiceCreditProxyData Data to pass to the + /// InitialVoiceCreditProxy, which allows it to determine how many voice + /// credits this user should have. function signUp( PubKey memory _pubKey, bytes memory _signUpGatekeeperData, @@ -209,12 +199,10 @@ contract MACI is IMACI, DomainObjs, Params, SnarkCommon, Ownable { emit SignUp(stateIndex, _pubKey, voiceCreditBalance, timestamp); } - /* - * Deploy a new Poll contract. - * @param _duration How long should the Poll last for - * @param _treeDepths The depth of the Merkle trees - * @returns a new Poll contract address - */ + /// @notice Deploy a new Poll contract. + /// @param _duration How long should the Poll last for + /// @param _treeDepths The depth of the Merkle trees + /// @return a new Poll contract address function deployPoll( uint256 _duration, MaxValues memory _maxValues, @@ -259,22 +247,18 @@ contract MACI is IMACI, DomainObjs, Params, SnarkCommon, Ownable { return address(p); } - /** - * Allow Poll contracts to merge the state subroots - * @param _numSrQueueOps Number of operations - * @param _pollId The active Poll ID - */ + /// @notice Allow Poll contracts to merge the state subroots + /// @param _numSrQueueOps Number of operations + /// @param _pollId The active Poll ID function mergeStateAqSubRoots(uint256 _numSrQueueOps, uint256 _pollId) public override onlyPoll(_pollId) afterInit { stateAq.mergeSubRoots(_numSrQueueOps); emit MergeStateAqSubRoots(_pollId, _numSrQueueOps); } - /* - /* Allow Poll contracts to merge the state root - /* @param _pollId The active Poll ID - /* @returns uint256 The calculated Merkle root - */ + /// @notice Allow Poll contracts to merge the state root + /// @param _pollId The active Poll ID + /// @return uint256 The calculated Merkle root function mergeStateAq(uint256 _pollId) public override onlyPoll(_pollId) afterInit returns (uint256) { uint256 root = stateAq.merge(stateTreeDepth); @@ -283,19 +267,15 @@ contract MACI is IMACI, DomainObjs, Params, SnarkCommon, Ownable { return root; } - /* - * Return the main root of the StateAq contract - * @returns uint256 The Merkle root - */ + /// @notice Return the main root of the StateAq contract + /// @return uint256 The Merkle root function getStateAqRoot() public view override returns (uint256) { return stateAq.getMainRoot(stateTreeDepth); } - /* - * Get the Poll details - * @param _pollId The identifier of the Poll to retrieve - * @returns Poll The Poll data - */ + /// @notice Get the Poll details + /// @param _pollId The identifier of the Poll to retrieve + /// @return Poll The Poll data function getPoll(uint256 _pollId) public view returns (Poll) { if (_pollId >= nextPollId) revert PollDoesNotExist(_pollId); return polls[_pollId]; diff --git a/contracts/contracts/MessageProcessor.sol b/contracts/contracts/MessageProcessor.sol index c8431691ac..79ad2694b8 100644 --- a/contracts/contracts/MessageProcessor.sol +++ b/contracts/contracts/MessageProcessor.sol @@ -2,22 +2,21 @@ pragma solidity ^0.8.10; import { AccQueue } from "./trees/AccQueue.sol"; -import { IMACI } from "./IMACI.sol"; +import { IMACI } from "./interfaces/IMACI.sol"; import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; import { Poll } from "./Poll.sol"; import { SnarkCommon } from "./crypto/SnarkCommon.sol"; import { Hasher } from "./crypto/Hasher.sol"; -import { CommonUtilities } from "./utilities/Utility.sol"; +import { CommonUtilities } from "./utilities/Utilities.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 - */ +/// @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 { + /// @notice custom errors error NoMoreMessages(); error StateAqNotMerged(); error MessageAqNotMerged(); @@ -28,15 +27,15 @@ contract MessageProcessor is Ownable, SnarkCommon, CommonUtilities, Hasher { error CurrentMessageBatchIndexTooLarge(); error BatchEndIndexTooLarge(); - // Whether there are unprocessed messages left + /// @notice Whether there are unprocessed messages left bool public processingComplete; - // The number of batches processed + /// @notice The number of batches processed uint256 public numBatchesProcessed; - // The current message batch index. When the coordinator runs - // processMessages(), this action relates to messages - // currentMessageBatchIndex to currentMessageBatchIndex + messageBatchSize. + /// @notice The current message batch index. When the coordinator runs + /// processMessages(), this action relates to messages + /// currentMessageBatchIndex to currentMessageBatchIndex + messageBatchSize. uint256 public currentMessageBatchIndex; - // The commitment to the state and ballot roots + /// @notice The commitment to the state and ballot roots uint256 public sbCommitment; Verifier public verifier; @@ -45,13 +44,11 @@ contract MessageProcessor is Ownable, SnarkCommon, CommonUtilities, Hasher { verifier = _verifier; } - /** - * 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 - */ + /// @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); // There must be unprocessed messages @@ -128,6 +125,15 @@ 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) + /// @param _newSbCommitment The new sbCommitment after we update this message batch + /// @param _proof The zk-SNARK proof + /// @return true if the proof is valid function verifyProcessProof( Poll _poll, uint256 _currentMessageBatchIndex, @@ -166,21 +172,19 @@ contract MessageProcessor is Ownable, SnarkCommon, CommonUtilities, Hasher { return verifier.verify(_proof, vk, publicInputHash); } - /** - * @notice Returns the SHA256 hash of the packed values (see - * genProcessMessagesPackedVals), the hash of the coordinator's public key, - * the message root, and the commitment to the current state root and - * ballot root. By passing the SHA256 hash of these values to the circuit - * 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 higher. - * @param _poll: contract address - * @param _currentMessageBatchIndex: batch index of current message batch - * @param _numSignUps: number of users that signup - * @param _currentSbCommitment: current sbCommitment - * @param _newSbCommitment: new sbCommitment after we update this message batch - * @return returns the SHA256 hash of the packed values - */ + /// @notice Returns the SHA256 hash of the packed values (see + /// genProcessMessagesPackedVals), the hash of the coordinator's public key, + /// the message root, and the commitment to the current state root and + /// ballot root. By passing the SHA256 hash of these values to the circuit + /// 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 higher. + /// @param _poll The Poll ontract 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 balot root) + /// @param _newSbCommitment The new sbCommitment after we update this message batch + /// @return returns the SHA256 hash of the packed values function genProcessMessagesPublicInputHash( Poll _poll, uint256 _currentMessageBatchIndex, @@ -207,16 +211,14 @@ contract MessageProcessor is Ownable, SnarkCommon, CommonUtilities, Hasher { return inputHash; } - /** - * One of the inputs to the ProcessMessages circuit is a 250-bit - * representation of four 50-bit values. This function generates this - * 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 - */ + /// @notice One of the inputs to the ProcessMessages circuit is a 250-bit + /// representation of four 50-bit values. This function generates this + /// 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 function genProcessMessagesPackedVals( Poll _poll, uint256 _currentMessageBatchIndex, @@ -242,12 +244,10 @@ contract MessageProcessor is Ownable, SnarkCommon, CommonUtilities, Hasher { return result; } - /** - * @notice update message processing state variables - * @param _newSbCommitment: sbCommitment to be updated - * @param _currentMessageBatchIndex: currentMessageBatchIndex to be updated - * @param _processingComplete: update flag that indicate processing is finished or not - */ + /// @notice update message processing state variables + /// @param _newSbCommitment sbCommitment to be updated + /// @param _currentMessageBatchIndex currentMessageBatchIndex to be updated + /// @param _processingComplete update flag that indicate processing is finished or not function updateMessageProcessingData( uint256 _newSbCommitment, uint256 _currentMessageBatchIndex, diff --git a/contracts/contracts/Params.sol b/contracts/contracts/Params.sol deleted file mode 100644 index 4afabfa6fa..0000000000 --- a/contracts/contracts/Params.sol +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -contract Params { - // This structs help to reduce the number of parameters to the constructor - // and avoid a stack overflow error during compilation - struct TreeDepths { - uint8 intStateTreeDepth; - uint8 messageTreeSubDepth; - uint8 messageTreeDepth; - uint8 voteOptionTreeDepth; - } - - struct BatchSizes { - uint24 messageBatchSize; - uint24 tallyBatchSize; - uint24 subsidyBatchSize; - } - - struct MaxValues { - uint256 maxMessages; - uint256 maxVoteOptions; - } -} diff --git a/contracts/contracts/Poll.sol b/contracts/contracts/Poll.sol index 150f5888ac..0723f19e06 100644 --- a/contracts/contracts/Poll.sol +++ b/contracts/contracts/Poll.sol @@ -1,101 +1,23 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.10; -import { IMACI } from "./IMACI.sol"; -import { Params } from "./Params.sol"; +import { Params } from "./utilities/Params.sol"; import { SnarkCommon } from "./crypto/SnarkCommon.sol"; -import { DomainObjs, IPubKey, IMessage } from "./DomainObjs.sol"; +import { DomainObjs } from "./utilities/DomainObjs.sol"; import { AccQueue, AccQueueQuinaryMaci } from "./trees/AccQueue.sol"; 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 { VkRegistry } from "./VkRegistry.sol"; import { Verifier } from "./crypto/Verifier.sol"; import { EmptyBallotRoots } from "./trees/EmptyBallotRoots.sol"; import { TopupCredit } from "./TopupCredit.sol"; -import { Utilities } from "./utilities/Utility.sol"; +import { Utilities } from "./utilities/Utilities.sol"; import { MessageProcessor } from "./MessageProcessor.sol"; -contract PollDeploymentParams { - struct ExtContracts { - VkRegistry vkRegistry; - IMACI maci; - AccQueue messageAq; - TopupCredit topupCredit; - } -} - -/* - * 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, IPubKey, Ownable, PollDeploymentParams { - error InvalidMaxValues(); - - /* - * Deploy a new Poll contract and AccQueue contract for messages. - */ - function deploy( - uint256 _duration, - MaxValues memory _maxValues, - TreeDepths memory _treeDepths, - BatchSizes memory _batchSizes, - PubKey memory _coordinatorPubKey, - VkRegistry _vkRegistry, - IMACI _maci, - TopupCredit _topupCredit, - address _pollOwner - ) public onlyOwner returns (Poll) { - uint256 treeArity = 5; - - // Validate _maxValues - // NOTE: these checks may not be necessary. Removing them will save - // 0.28 Kb of bytecode. - - // 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 - // of the inputs (aka packedVal) - if ( - _maxValues.maxMessages > treeArity ** uint256(_treeDepths.messageTreeDepth) || - _maxValues.maxMessages < _batchSizes.messageBatchSize || - _maxValues.maxMessages % _batchSizes.messageBatchSize != 0 || - _maxValues.maxVoteOptions > treeArity ** uint256(_treeDepths.voteOptionTreeDepth) || - _maxValues.maxVoteOptions >= (2 ** 50) - ) { - revert InvalidMaxValues(); - } - - AccQueue messageAq = new AccQueueQuinaryMaci(_treeDepths.messageTreeSubDepth); - - ExtContracts memory extContracts; - - // TODO: remove _vkRegistry; only PollProcessorAndTallyer needs it - extContracts.vkRegistry = _vkRegistry; - extContracts.maci = _maci; - extContracts.messageAq = messageAq; - extContracts.topupCredit = _topupCredit; - - 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 - messageAq.transferOwnership(address(poll)); - - // init messageAq - poll.init(); - - // TODO: should this be _maci.owner() instead? - poll.transferOwnership(_pollOwner); - - return poll; - } -} - -/* - * Do not deploy this directly. Use PollFactory.deploy() which performs some - * checks on the Poll constructor arguments. - */ -contract Poll is Params, Utilities, SnarkCommon, Ownable, PollDeploymentParams, EmptyBallotRoots { +/// @title Poll +/// @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 { using SafeERC20 for ERC20; bool internal isInit = false; @@ -114,10 +36,6 @@ contract Poll is Params, Utilities, SnarkCommon, Ownable, PollDeploymentParams, // The duration of the polling period, in seconds uint256 internal duration; - function getDeployTimeAndDuration() public view returns (uint256, uint256) { - return (deployTime, duration); - } - // Whether the MACI contract's stateAq has been merged by this contract bool public stateAqMerged; @@ -159,10 +77,14 @@ contract Poll is Params, Utilities, SnarkCommon, Ownable, PollDeploymentParams, ExtContracts public extContracts; - /* - * Each MACI instance can have multiple Polls. - * When a Poll is deployed, its voting period starts immediately. - */ + /// @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 _treeDepths The depths of the merkle trees + /// @param _batchSizes The batch sizes for processing + /// @param _coordinatorPubKey The coordinator's public key + /// @param _extContracts The external contracts constructor( uint256 _duration, MaxValues memory _maxValues, @@ -184,23 +106,25 @@ contract Poll is Params, Utilities, SnarkCommon, Ownable, PollDeploymentParams, deployTime = block.timestamp; } - /* - * A modifier that causes the function to revert if the voting period is - * not over. - */ + /// @notice A modifier that causes the function to revert if the voting period is + /// not over. modifier isAfterVotingDeadline() { uint256 secondsPassed = block.timestamp - deployTime; if (secondsPassed <= duration) revert VotingPeriodNotOver(); _; } + /// @notice A modifier that causes the function to revert if the voting period is + /// over modifier isWithinVotingDeadline() { uint256 secondsPassed = block.timestamp - deployTime; if (secondsPassed >= duration) revert VotingPeriodOver(); _; } - // should be called immediately after Poll creation and messageAq ownership transferred + /// @notice The initialization function. + /// @dev Should be called immediately after Poll creation + /// and messageAq ownership transferred function init() public { if (isInit) revert PollAlreadyInit(); // set to true so it cannot be called again @@ -220,11 +144,9 @@ contract Poll is Params, Utilities, SnarkCommon, Ownable, PollDeploymentParams, emit PublishMessage(_message, _padKey); } - /* - * Allows to publish a Topup message - * @param stateIndex The index of user in the state queue - * @param amount The amount of credits to topup - */ + /// @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) public isWithinVotingDeadline { if (numMessages > maxValues.maxMessages) revert TooManyMessages(); @@ -242,14 +164,12 @@ contract Poll is Params, Utilities, SnarkCommon, Ownable, PollDeploymentParams, emit TopupMessage(_message); } - /* - * 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. - */ + /// @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(Message memory _message, PubKey memory _encPubKey) public isWithinVotingDeadline { if (numMessages == maxValues.maxMessages) revert TooManyMessages(); @@ -268,11 +188,9 @@ contract Poll is Params, Utilities, SnarkCommon, Ownable, PollDeploymentParams, emit PublishMessage(_message, _encPubKey); } - /* - * The first step of merging the MACI state AccQueue. This allows the - * ProcessMessages circuit to access the latest state tree and ballots via - * currentSbCommitment. - */ + /// @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. function mergeMaciStateAqSubRoots(uint256 _numSrQueueOps, uint256 _pollId) public onlyOwner isAfterVotingDeadline { // This function cannot be called after the stateAq was merged if (stateAqMerged) revert StateAqAlreadyMerged(); @@ -284,12 +202,10 @@ contract Poll is Params, Utilities, SnarkCommon, Ownable, PollDeploymentParams, emit MergeMaciStateAqSubRoots(_numSrQueueOps); } - /* - * 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 - */ + /// @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 function mergeMaciStateAq(uint256 _pollId) public onlyOwner isAfterVotingDeadline { // This function can only be called once per Poll after the voting // deadline @@ -311,21 +227,24 @@ contract Poll is Params, Utilities, SnarkCommon, Ownable, PollDeploymentParams, emit MergeMaciStateAq(mergedStateRoot); } - /* - * The first step in merging the message AccQueue so that the - * ProcessMessages circuit can access the message root. - */ + /// @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) public onlyOwner isAfterVotingDeadline { extContracts.messageAq.mergeSubRoots(_numSrQueueOps); emit MergeMessageAqSubRoots(_numSrQueueOps); } - /* - * The second step in merging the message AccQueue so that the - * ProcessMessages circuit can access the message root. - */ + /// @notice The second step in merging the message AccQueue so that the + /// ProcessMessages circuit can access the message root. 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 The deploy time and duration + function getDeployTimeAndDuration() public view returns (uint256, uint256) { + return (deployTime, duration); + } } diff --git a/contracts/contracts/PollFactory.sol b/contracts/contracts/PollFactory.sol new file mode 100644 index 0000000000..3341bf23f9 --- /dev/null +++ b/contracts/contracts/PollFactory.sol @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.10; + +import { IMACI } from "./interfaces/IMACI.sol"; +import { AccQueue, AccQueueQuinaryMaci } from "./trees/AccQueue.sol"; +import { TopupCredit } from "./TopupCredit.sol"; +import { Params } from "./utilities/Params.sol"; +import { DomainObjs } from "./utilities/DomainObjs.sol"; +import { VkRegistry } from "./VkRegistry.sol"; +import { Poll } from "./Poll.sol"; + +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; + +/// @title PollFactory +/// @author PSE +/// @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, Ownable { + // The number of children each node in the message tree has + uint256 public constant TREE_ARITY = 5; + // custom error + error InvalidMaxValues(); + + /// @notice Deploy a new Poll contract and AccQueue contract for messages. + function deploy( + uint256 _duration, + MaxValues memory _maxValues, + TreeDepths memory _treeDepths, + BatchSizes memory _batchSizes, + PubKey memory _coordinatorPubKey, + VkRegistry _vkRegistry, + IMACI _maci, + TopupCredit _topupCredit, + address _pollOwner + ) public onlyOwner returns (Poll) { + /// @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 + /// of the inputs (aka packedVal) + if ( + _maxValues.maxMessages > TREE_ARITY ** uint256(_treeDepths.messageTreeDepth) || + _maxValues.maxMessages < _batchSizes.messageBatchSize || + _maxValues.maxMessages % _batchSizes.messageBatchSize != 0 || + _maxValues.maxVoteOptions > TREE_ARITY ** uint256(_treeDepths.voteOptionTreeDepth) || + _maxValues.maxVoteOptions >= (2 ** 50) + ) { + revert InvalidMaxValues(); + } + + /// deploy a new AccQueue contract to store messages + AccQueue messageAq = new AccQueueQuinaryMaci(_treeDepths.messageTreeSubDepth); + + /// @notice the smart contracts that a Poll would interact with + ExtContracts memory extContracts = ExtContracts({ + vkRegistry: _vkRegistry, + maci: _maci, + messageAq: messageAq, + topupCredit: _topupCredit + }); + + // deploy the poll + 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 + messageAq.transferOwnership(address(poll)); + + // init Poll + poll.init(); + + // TODO: should this be _maci.owner() instead? + poll.transferOwnership(_pollOwner); + + return poll; + } +} diff --git a/contracts/contracts/SignUpToken.sol b/contracts/contracts/SignUpToken.sol index 6694015f8b..c4cf507df8 100644 --- a/contracts/contracts/SignUpToken.sol +++ b/contracts/contracts/SignUpToken.sol @@ -4,7 +4,11 @@ pragma solidity ^0.8.10; import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; +/// @title SignUpToken +/// @notice This contract is an ERC721 token contract which +/// can be used to allow users to sign up for a poll. contract SignUpToken is ERC721, Ownable { + /// @notice The constructor which calls the ERC721 constructor constructor() ERC721("SignUpToken", "SignUpToken") {} // Gives an ERC721 token to an address diff --git a/contracts/contracts/Subsidy.sol b/contracts/contracts/Subsidy.sol index 8c90d8e411..dbd737959d 100644 --- a/contracts/contracts/Subsidy.sol +++ b/contracts/contracts/Subsidy.sol @@ -1,13 +1,13 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.10; -import { IMACI } from "./IMACI.sol"; +import { IMACI } from "./interfaces/IMACI.sol"; import { MessageProcessor } from "./MessageProcessor.sol"; import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; import { Poll } from "./Poll.sol"; import { SnarkCommon } from "./crypto/SnarkCommon.sol"; import { Hasher } from "./crypto/Hasher.sol"; -import { CommonUtilities } from "./utilities/Utility.sol"; +import { CommonUtilities } from "./utilities/Utilities.sol"; import { Verifier } from "./crypto/Verifier.sol"; import { VkRegistry } from "./VkRegistry.sol"; @@ -94,15 +94,12 @@ contract Subsidy is Ownable, CommonUtilities, Hasher, SnarkCommon { increaseSubsidyIndex(subsidyBatchSize, numLeaves); } - /* - * @notice increase subsidy batch index (rbi, cbi) to next, - * it will try to cbi++ if the whole batch can fit into numLeaves - * otherwise it will increase row index: rbi++ - * @param batchSize: the size of 1 dimensional batch over the signup users, - * notice each batch for subsidy calculation is 2 dimenional: batchSize*batchSize - * @param numLeaves: total number of leaves in stateTree, i.e. number of signup users - * @return None - */ + /// @notice increase subsidy batch index (rbi, cbi) to next, + /// it will try to cbi++ if the whole batch can fit into numLeaves + /// otherwise it will increase row index: rbi++ + /// @param batchSize: the size of 1 dimensional batch over the signup users, + /// @notice each batch for subsidy calculation is 2 dimenional: batchSize*batchSize + /// @param numLeaves: total number of leaves in stateTree, i.e. number of signup users function increaseSubsidyIndex(uint256 batchSize, uint256 numLeaves) internal { if (cbi * batchSize + batchSize < numLeaves) { cbi++; diff --git a/contracts/contracts/Tally.sol b/contracts/contracts/Tally.sol index b69223dce9..6b3e015888 100644 --- a/contracts/contracts/Tally.sol +++ b/contracts/contracts/Tally.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.10; import { AccQueue } from "./trees/AccQueue.sol"; -import { IMACI } from "./IMACI.sol"; +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"; @@ -10,8 +10,9 @@ import { MessageProcessor } from "./MessageProcessor.sol"; import { SnarkCommon } from "./crypto/SnarkCommon.sol"; import { Verifier } from "./crypto/Verifier.sol"; import { VkRegistry } from "./VkRegistry.sol"; -import { CommonUtilities } from "./utilities/Utility.sol"; +import { CommonUtilities } from "./utilities/Utilities.sol"; +/// @title Tally contract Tally is Ownable, SnarkCommon, CommonUtilities, Hasher { // custom errors error ProcessingNotComplete(); @@ -23,18 +24,18 @@ contract Tally is Ownable, SnarkCommon, CommonUtilities, Hasher { uint8 private constant LEAVES_PER_NODE = 5; - // The commitment to the tally results. Its initial value is 0, but after - // the tally of each batch is proven on-chain via a zk-SNARK, it should be - // updated to: - // - // hash3( - // hashLeftRight(merkle root of current results, salt0) - // hashLeftRight(number of spent voice credits, salt1), - // hashLeftRight(merkle root of the no. of spent voice credits per vote option, salt2) - // ) - // - // Where each salt is unique and the merkle roots are of arrays of leaves - // TREE_ARITY ** voteOptionTreeDepth long. + /// @notice The commitment to the tally results. Its initial value is 0, but after + /// the tally of each batch is proven on-chain via a zk-SNARK, it should be + /// updated to: + /// + /// hash3( + /// hashLeftRight(merkle root of current results, salt0) + /// hashLeftRight(number of spent voice credits, salt1), + /// hashLeftRight(merkle root of the no. of spent voice credits per vote option, salt2) + /// ) + /// + /// Where each salt is unique and the merkle roots are of arrays of leaves + /// TREE_ARITY ** voteOptionTreeDepth long. uint256 public tallyCommitment; uint256 public tallyBatchNum; @@ -48,13 +49,11 @@ contract Tally is Ownable, SnarkCommon, CommonUtilities, Hasher { verifier = _verifier; } - /* - * @notice Pack the batch start index and number of signups into a 100-bit value. - * @param _numSignUps: number of signups - * @param _batchStartIndex: the start index of given batch - * @param _tallyBatchSize: size of batch - * @return an uint256 representing 3 inputs together - */ + /// @notice Pack the batch start index and number of signups into a 100-bit value. + /// @param _numSignUps: number of signups + /// @param _batchStartIndex: the start index of given batch + /// @param _tallyBatchSize: size of batch + /// @return an uint256 representing 3 inputs together function genTallyVotesPackedVals( uint256 _numSignUps, uint256 _batchStartIndex, @@ -69,14 +68,12 @@ contract Tally is Ownable, SnarkCommon, CommonUtilities, Hasher { return result; } - /* - * @notice generate hash of public inputs for tally circuit - * @param _numSignUps: number of signups - * @param _batchStartIndex: the start index of given batch - * @param _tallyBatchSize: size of batch - * @param _newTallyCommitment: the new tally commitment to be updated - * @return hash of public inputs - */ + /// @notice generate hash of public inputs for tally circuit + /// @param _numSignUps: number of signups + /// @param _batchStartIndex: the start index of given batch + /// @param _tallyBatchSize: size of batch + /// @param _newTallyCommitment: the new tally commitment to be updated + /// @return hash of public inputs function genTallyVotesPublicInputHash( uint256 _numSignUps, uint256 _batchStartIndex, @@ -131,16 +128,14 @@ contract Tally is Ownable, SnarkCommon, CommonUtilities, Hasher { tallyBatchNum++; } - /* - * @notice Verify the tally proof using the verifiying 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 - * @param _tallyBatchSize batch size for the tally - * @param _newTallyCommitment the tally commitment to be verified at a given batch index - * @return valid a boolean representing successful verification - */ + /// @notice Verify the tally proof using the verifiying 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 + /// @param _tallyBatchSize batch size for the tally + /// @param _newTallyCommitment the tally commitment to be verified at a given batch index + /// @return valid a boolean representing successful verification function verifyTallyProof( Poll _poll, uint256[8] memory _proof, diff --git a/contracts/contracts/VkRegistry.sol b/contracts/contracts/VkRegistry.sol index e5b276ab49..31931346fd 100644 --- a/contracts/contracts/VkRegistry.sol +++ b/contracts/contracts/VkRegistry.sol @@ -2,13 +2,14 @@ pragma solidity ^0.8.10; import { SnarkCommon } from "./crypto/SnarkCommon.sol"; + import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; -/* - * Stores verifying keys for the circuits. - * Each circuit has a signature which is its compile-time constants represented - * as a uint256. - */ +/// @title VkRegistry +/// @author PSE +/// @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 { mapping(uint256 => VerifyingKey) internal processVks; mapping(uint256 => bool) internal processVkSet; diff --git a/contracts/contracts/benchmarks/HasherBenchmarks.sol b/contracts/contracts/benchmarks/HasherBenchmarks.sol new file mode 100644 index 0000000000..310e9b254a --- /dev/null +++ b/contracts/contracts/benchmarks/HasherBenchmarks.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.10; + +import { Hasher } from "../crypto/Hasher.sol"; + +/// @title HasherBenchmarks +/// @notice A contract used to benchmark the poseidon hash function +contract HasherBenchmarks is Hasher { + /// @notice Benchmark the poseidon hash function with 5 inputs + /// @param array The array of inputs to hash + function hash5Benchmark(uint256[5] memory array) public returns (uint256) { + return hash5(array); + } + + /// @notice Benchmark the poseidon hash function with 2 inputs + /// @param _left The left input to hash + /// @param _right The right input to hash + function hashLeftRightBenchmark(uint256 _left, uint256 _right) public returns (uint256) { + return hashLeftRight(_left, _right); + } +} diff --git a/contracts/contracts/crypto/Hasher.sol b/contracts/contracts/crypto/Hasher.sol index 364c573847..9d96999437 100644 --- a/contracts/contracts/crypto/Hasher.sol +++ b/contracts/contracts/crypto/Hasher.sol @@ -3,51 +3,72 @@ pragma solidity ^0.8.10; import { SnarkConstants } from "./SnarkConstants.sol"; +/// @notice A library which provides functions for computing Pedersen hashes. library PoseidonT3 { function poseidon(uint256[2] memory input) public pure returns (uint256) {} } +/// @notice A library which provides functions for computing Pedersen hashes. library PoseidonT4 { function poseidon(uint256[3] memory input) public pure returns (uint256) {} } +/// @notice A library which provides functions for computing Pedersen hashes. library PoseidonT5 { function poseidon(uint256[4] memory input) public pure returns (uint256) {} } +/// @notice A library which provides functions for computing Pedersen hashes. library PoseidonT6 { function poseidon(uint256[5] memory input) public pure returns (uint256) {} } -/* - * A SHA256 hash function for any number of input elements, and Poseidon hash - * functions for 2, 3, 4, 5, and 12 input elements. - */ +/// @notice A SHA256 hash function for any number of input elements, and Poseidon hash +/// functions for 2, 3, 4, 5, and 12 input elements. contract Hasher is SnarkConstants { + /// @notice Computes the SHA256 hash of an array of uint256 elements. + /// @param array The array of uint256 elements. + /// @return The SHA256 hash of the array. function sha256Hash(uint256[] memory array) public pure returns (uint256) { return uint256(sha256(abi.encodePacked(array))) % SNARK_SCALAR_FIELD; } + /// @notice Computes the Poseidon hash of two uint256 elements. + /// @param array An array of two uint256 elements. + /// @return The Poseidon hash of the two elements. function hash2(uint256[2] memory array) public pure returns (uint256) { return PoseidonT3.poseidon(array); } + /// @notice Computes the Poseidon hash of three uint256 elements. + /// @param array An array of three uint256 elements. + /// @return The Poseidon hash of the three elements. function hash3(uint256[3] memory array) public pure returns (uint256) { return PoseidonT4.poseidon(array); } + /// @notice Computes the Poseidon hash of four uint256 elements. + /// @param array An array of four uint256 elements. + /// @return The Poseidon hash of the four elements. function hash4(uint256[4] memory array) public pure returns (uint256) { return PoseidonT5.poseidon(array); } + /// @notice Computes the Poseidon hash of five uint256 elements. + /// @param array An array of five uint256 elements. + /// @return The Poseidon hash of the five elements. function hash5(uint256[5] memory array) public pure returns (uint256) { return PoseidonT6.poseidon(array); } - function hashLeftRight(uint256 _left, uint256 _right) public pure returns (uint256) { + /// @notice Computes the Poseidon hash of two uint256 elements. + /// @param left the first element to hash. + /// @param right the second element to hash. + /// @return The Poseidon hash of the two elements. + function hashLeftRight(uint256 left, uint256 right) public pure returns (uint256) { uint256[2] memory input; - input[0] = _left; - input[1] = _right; + input[0] = left; + input[1] = right; return hash2(input); } } diff --git a/contracts/contracts/crypto/Pairing.sol b/contracts/contracts/crypto/Pairing.sol index 9baf01df4a..0a814ff38a 100644 --- a/contracts/contracts/crypto/Pairing.sol +++ b/contracts/contracts/crypto/Pairing.sol @@ -34,9 +34,7 @@ library Pairing { uint256[2] y; } - /* - * @return The negation of p, i.e. p.plus(p.negate()) should be zero. - */ + /// @notice The negation of p, i.e. p.plus(p.negate()) should be zero. function negate(G1Point memory p) internal pure returns (G1Point memory) { // The prime q in the base field F_q for G1 if (p.x == 0 && p.y == 0) { @@ -46,9 +44,7 @@ library Pairing { } } - /* - * @return The sum of two points of G1 - */ + /// @notice Returns the sum of two points of G1 function plus(G1Point memory p1, G1Point memory p2) internal view returns (G1Point memory r) { uint256[4] memory input; input[0] = p1.x; @@ -70,11 +66,9 @@ library Pairing { require(success, "pairing-add-failed"); } - /* - * @return The product of a point on G1 and a scalar, i.e. - * p == p.scalar_mul(1) and p.plus(p) == p.scalar_mul(2) for all - * points p. - */ + /// @notice Return te product of a point on G1 and a scalar, i.e. + /// p == p.scalar_mul(1) and p.plus(p) == p.scalar_mul(2) for all + /// points p. function scalar_mul(G1Point memory p, uint256 s) internal view returns (G1Point memory r) { uint256[3] memory input; input[0] = p.x; @@ -93,11 +87,10 @@ library Pairing { require(success, "pairing-mul-failed"); } - /* @return The result of computing the pairing check - * e(p1[0], p2[0]) * .... * e(p1[n], p2[n]) == 1 - * For example, - * pairing([P1(), P1().negate()], [P2(), P2()]) should return true. - */ + /// @return The result of computing the pairing check + /// e(p1[0], p2[0]) * .... * e(p1[n], p2[n]) == 1 + /// For example, + /// pairing([P1(), P1().negate()], [P2(), P2()]) should return true. function pairing( G1Point memory a1, G2Point memory a2, diff --git a/contracts/contracts/crypto/SnarkCommon.sol b/contracts/contracts/crypto/SnarkCommon.sol index 768366738c..e63bd8ec5f 100644 --- a/contracts/contracts/crypto/SnarkCommon.sol +++ b/contracts/contracts/crypto/SnarkCommon.sol @@ -2,7 +2,11 @@ pragma solidity ^0.8.10; import { Pairing } from "./Pairing.sol"; +/// @title SnarkCommon +/// @notice a Contract which holds a struct +/// representing a Groth16 verifying key contract SnarkCommon { + /// @notice a struct representing a Groth16 verifying key struct VerifyingKey { Pairing.G1Point alpha1; Pairing.G2Point beta2; diff --git a/contracts/contracts/crypto/SnarkConstants.sol b/contracts/contracts/crypto/SnarkConstants.sol index 7457f5918b..e263121fce 100644 --- a/contracts/contracts/crypto/SnarkConstants.sol +++ b/contracts/contracts/crypto/SnarkConstants.sol @@ -1,23 +1,28 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.10; +/// @title SnarkConstants +/// @notice This contract contains constants related to the SNARK +/// components of MACI. contract SnarkConstants { - // The scalar field + /// @notice The scalar field uint256 internal constant SNARK_SCALAR_FIELD = 21888242871839275222246405745257275088548364400416034343698204186575808495617; - // The public key here is the first Pedersen base - // point from iden3's circomlib implementation of the Pedersen hash. - // Since it is generated using a hash-to-curve function, we are - // confident that no-one knows the private key associated with this - // public key. See: - // https://github.com/iden3/circomlib/blob/d5ed1c3ce4ca137a6b3ca48bec4ac12c1b38957a/src/pedersen_printbases.js - // Its hash should equal - // 6769006970205099520508948723718471724660867171122235270773600567925038008762. + + /// @notice The public key here is the first Pedersen base + /// point from iden3's circomlib implementation of the Pedersen hash. + /// Since it is generated using a hash-to-curve function, we are + /// confident that no-one knows the private key associated with this + /// public key. See: + /// https://github.com/iden3/circomlib/blob/d5ed1c3ce4ca137a6b3ca48bec4ac12c1b38957a/src/pedersen_printbases.js + /// Its hash should equal + /// 6769006970205099520508948723718471724660867171122235270773600567925038008762. uint256 internal constant PAD_PUBKEY_X = 10457101036533406547632367118273992217979173478358440826365724437999023779287; uint256 internal constant PAD_PUBKEY_Y = 19824078218392094440610104313265183977899662750282163392862422243483260492317; - // The Keccack256 hash of 'Maci' + + /// @notice The Keccack256 hash of 'Maci' uint256 internal constant NOTHING_UP_MY_SLEEVE = 8370432830353022751713833565135785980866757267633941821328460903436894336785; } diff --git a/contracts/contracts/crypto/Verifier.sol b/contracts/contracts/crypto/Verifier.sol index 03b58394ef..44a91d2f37 100644 --- a/contracts/contracts/crypto/Verifier.sol +++ b/contracts/contracts/crypto/Verifier.sol @@ -30,12 +30,10 @@ contract Verifier is IVerifier, SnarkConstants { string constant ERROR_PROOF_Q = "VE1"; string constant ERROR_INPUT_VAL = "VE2"; - /* - * @returns 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. - */ + /// @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, VerifyingKey memory vk, uint256 input) public view override returns (bool) { Proof memory proof; proof.a = Pairing.G1Point(_proof[0], _proof[1]); diff --git a/contracts/contracts/gatekeepers/FreeForAllSignUpGatekeeper.sol b/contracts/contracts/gatekeepers/FreeForAllSignUpGatekeeper.sol index 55014804cf..02b33d4805 100644 --- a/contracts/contracts/gatekeepers/FreeForAllSignUpGatekeeper.sol +++ b/contracts/contracts/gatekeepers/FreeForAllSignUpGatekeeper.sol @@ -4,11 +4,15 @@ pragma solidity ^0.8.10; import { SignUpGatekeeper } from "./SignUpGatekeeper.sol"; import { MACI } from "../MACI.sol"; +/// @title FreeForAllGatekeeper +/// @notice A SignUpGatekeeper which allows anyone to sign up. contract FreeForAllGatekeeper is SignUpGatekeeper { + /// @notice setMaciInstance does nothing in this gatekeeper + /// @param _maci The MACI contract function setMaciInstance(MACI _maci) public override {} - /* - * Registers the user without any restrictions. - */ - function register(address, bytes memory) public override {} + /// @notice Registers the user without any restrictions. + /// @param _address The address of the user + /// @param _data memory additional data + function register(address _address, bytes memory _data) public override {} } diff --git a/contracts/contracts/gatekeepers/SignUpGatekeeper.sol b/contracts/contracts/gatekeepers/SignUpGatekeeper.sol index 54de8dac5c..97788711b3 100644 --- a/contracts/contracts/gatekeepers/SignUpGatekeeper.sol +++ b/contracts/contracts/gatekeepers/SignUpGatekeeper.sol @@ -3,8 +3,14 @@ pragma solidity ^0.8.10; import { MACI } from "../MACI.sol"; +/// @title SignUpGatekeeper +/// @notice A gatekeeper contract which allows users to sign up for a poll. abstract contract SignUpGatekeeper { + /// @notice Allows to set the MACI contract function setMaciInstance(MACI _maci) public virtual {} + /// @notice Registers the user + /// @param _user The address of the user + /// @param _data additional data function register(address _user, bytes memory _data) public virtual {} } diff --git a/contracts/contracts/gatekeepers/SignUpTokenGatekeeper.sol b/contracts/contracts/gatekeepers/SignUpTokenGatekeeper.sol index 7260b56b8a..f095fb5d74 100644 --- a/contracts/contracts/gatekeepers/SignUpTokenGatekeeper.sol +++ b/contracts/contracts/gatekeepers/SignUpTokenGatekeeper.sol @@ -8,35 +8,40 @@ import { SignUpToken } from "../SignUpToken.sol"; import { MACI } from "../MACI.sol"; +/// @title SignUpTokenGatekeeper +/// @notice This contract allows to gatekeep MACI signups +/// by requiring new voters to own a certain ERC721 token contract SignUpTokenGatekeeper is SignUpGatekeeper, Ownable { + /// @notice the reference to the SignUpToken contract SignUpToken public token; + /// @notice the reference to the MACI contract MACI public maci; - mapping(uint256 => bool) internal registeredTokenIds; + /// @notice a mapping of tokenIds to whether they have been used to sign up + mapping(uint256 => bool) public registeredTokenIds; + /// @notice custom errors error AlreadyRegistered(); error NotTokenOwner(); error OnlyMACI(); + /// @notice creates a new SignUpTokenGatekeeper + /// @param _token the address of the SignUpToken contract constructor(SignUpToken _token) Ownable() { token = _token; } - /* - * Adds an uninitialised MACI instance to allow for token singups - * @param _maci The MACI contract interface to be stored - */ + /// @notice Adds an uninitialised MACI instance to allow for token singups + /// @param _maci The MACI contract interface to be stored function setMaciInstance(MACI _maci) public override onlyOwner { maci = _maci; } - /* - * Registers the user if they own the token with the token ID encoded in - * _data. Throws if the user is does not own the token or if the token has - * already been used to sign up. - * @param _user The user's Ethereum address. - * @param _data The ABI-encoded tokenId as a uint256. - */ + /// @notice Registers the user if they own the token with the token ID encoded in + /// _data. Throws if the user is does not own the token or if the token has + /// already been used to sign up. + /// @param _user The user's Ethereum address. + /// @param _data The ABI-encoded tokenId as a uint256. function register(address _user, bytes memory _data) public override { if (address(maci) != msg.sender) revert OnlyMACI(); // Decode the given _data bytes into a uint256 which is the token ID diff --git a/contracts/contracts/initialVoiceCreditProxy/ConstantInitialVoiceCreditProxy.sol b/contracts/contracts/initialVoiceCreditProxy/ConstantInitialVoiceCreditProxy.sol index 81ce8e7fd7..851336085b 100644 --- a/contracts/contracts/initialVoiceCreditProxy/ConstantInitialVoiceCreditProxy.sol +++ b/contracts/contracts/initialVoiceCreditProxy/ConstantInitialVoiceCreditProxy.sol @@ -1,18 +1,25 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.10; -abstract contract InitialVoiceCreditProxy { - function getVoiceCredits(address _user, bytes memory _data) public view virtual returns (uint256) {} -} +import { InitialVoiceCreditProxy } from "./InitialVoiceCreditProxy.sol"; +/// @title ConstantInitialVoiceCreditProxy +/// @notice This contract allows to set a constant initial voice credit balance +/// for MACI's voters. contract ConstantInitialVoiceCreditProxy is InitialVoiceCreditProxy { + /// @notice the balance to be returned by getVoiceCredits uint256 internal balance; + /// @notice creates a new ConstantInitialVoiceCreditProxy constructor(uint256 _balance) { balance = _balance; } - function getVoiceCredits(address, bytes memory) public view override returns (uint256) { + /// @notice Returns the constant balance for any new MACI's voter + /// @param _address the address of the voter + /// @param _data additional data + /// @return the balance + function getVoiceCredits(address _address, bytes memory _data) public view override returns (uint256) { return balance; } } diff --git a/contracts/contracts/initialVoiceCreditProxy/InitialVoiceCreditProxy.sol b/contracts/contracts/initialVoiceCreditProxy/InitialVoiceCreditProxy.sol index 49d258f2fb..e68a98a1de 100644 --- a/contracts/contracts/initialVoiceCreditProxy/InitialVoiceCreditProxy.sol +++ b/contracts/contracts/initialVoiceCreditProxy/InitialVoiceCreditProxy.sol @@ -1,6 +1,14 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.10; +/// @title InitialVoiceCreditProxy +/// @notice This contract is the bast contract for +/// InitialVoiceCreditProxy contracts. It allows to set a custom initial voice +/// credit balance for MACI's voters. abstract contract InitialVoiceCreditProxy { + /// @notice Returns the initial voice credit balance for a new MACI's voter + /// @param _user the address of the voter + /// @param _data additional data + /// @return the balance function getVoiceCredits(address _user, bytes memory _data) public view virtual returns (uint256) {} } diff --git a/contracts/contracts/interfaces/IMACI.sol b/contracts/contracts/interfaces/IMACI.sol new file mode 100644 index 0000000000..b70a1fde70 --- /dev/null +++ b/contracts/contracts/interfaces/IMACI.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.10; + +import { VkRegistry } from "../VkRegistry.sol"; +import { AccQueue } from "../trees/AccQueue.sol"; + +/// @title IMACI 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 VkRegistry + /// @return The VkRegistry instance + function vkRegistry() external view returns (VkRegistry); + + /// @notice Get the root of the state accumulator queue + /// @return The root of the state accumulator queue + 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 + 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 + function mergeStateAq(uint256 _pollId) external returns (uint256); + + /// @notice Get the number of sign ups + /// @return The number of sign ups + function numSignUps() external view returns (uint256); + + /// @notice Get the state accumulator queue + /// @return The state accumulator queue + function stateAq() external view returns (AccQueue); +} diff --git a/contracts/contracts/trees/AccQueue.sol b/contracts/contracts/trees/AccQueue.sol index bdeaad54f1..8f4a3a9e76 100644 --- a/contracts/contracts/trees/AccQueue.sol +++ b/contracts/contracts/trees/AccQueue.sol @@ -10,24 +10,23 @@ import { MerkleZeros as MerkleQuinaryMaci } from "./zeros/MerkleQuinaryMaci.sol" import { MerkleZeros as MerkleQuinaryBlankSl } from "./zeros/MerkleQuinaryBlankSl.sol"; import { MerkleZeros as MerkleQuinaryMaciWithSha256 } from "./zeros/MerkleQuinaryMaciWithSha256.sol"; -/** - * This contract defines a Merkle tree where each leaf insertion only updates a - * subtree. To obtain the main tree root, the contract owner must merge the - * subtrees together. Merging subtrees requires at least 2 operations: - * mergeSubRoots(), and merge(). To get around the gas limit, - * the mergeSubRoots() can be performed in multiple transactions. - */ +/// @title AccQueue +/// @notice This contract defines a Merkle tree where each leaf insertion only updates a +/// subtree. To obtain the main tree root, the contract owner must merge the +/// subtrees together. Merging subtrees requires at least 2 operations: +/// mergeSubRoots(), and merge(). To get around the gas limit, +/// the mergeSubRoots() can be performed in multiple transactions. abstract contract AccQueue is Ownable, Hasher { // The maximum tree depth uint256 constant MAX_DEPTH = 32; - // A Queue is a 2D array of Merkle roots and indices which represents nodes - // in a Merkle tree while it is progressively updated. + /// @notice A Queue is a 2D array of Merkle roots and indices which represents nodes + /// in a Merkle tree while it is progressively updated. struct Queue { - // IMPORTANT: the following declares an array of b elements of type T: T[b] - // And the following declares an array of b elements of type T[a]: T[a][b] - // As such, the following declares an array of MAX_DEPTH+1 arrays of - // uint256[4] arrays, **not the other way round**: + /// @notice IMPORTANT: the following declares an array of b elements of type T: T[b] + /// And the following declares an array of b elements of type T[a]: T[a][b] + /// As such, the following declares an array of MAX_DEPTH+1 arrays of + /// uint256[4] arrays, **not the other way round**: uint256[4][MAX_DEPTH + 1] levels; uint256[MAX_DEPTH + 1] indices; } @@ -79,6 +78,7 @@ abstract contract AccQueue is Ownable, Hasher { // The number of leaves inserted across all subtrees so far uint256 public numLeaves; + /// @notice custom errors error SubDepthCannotBeZero(); error SubdepthTooLarge(uint256 _subDepth, uint256 max); error InvalidHashLength(); @@ -90,7 +90,11 @@ abstract contract AccQueue is Ownable, Hasher { error DepthTooSmall(uint256 _depth, uint256 min); error InvalidIndex(uint256 _index); + /// @notice Create a new AccQueue + /// @param _subDepth The depth of each subtree. + /// @param _hashLength The number of leaves per node (2 or 5). constructor(uint256 _subDepth, uint256 _hashLength) { + /// validation if (_subDepth == 0) revert SubDepthCannotBeZero(); if (_subDepth > MAX_DEPTH) revert SubdepthTooLarge(_subDepth, MAX_DEPTH); if (_hashLength != 2 && _hashLength != 5) revert InvalidHashLength(); @@ -101,32 +105,35 @@ abstract contract AccQueue is Ownable, Hasher { subTreeCapacity = _hashLength ** _subDepth; } - /** - * Hash the contents of the specified level and the specified leaf. - * This is a virtual function as the hash function which the overriding - * contract uses will be either hashLeftRight or hash5, which require - * different input array lengths. - * @param _level The level to hash. - * @param _leaf The leaf include with the level. - */ + /// @notice Hash the contents of the specified level and the specified leaf. + /// This is a virtual function as the hash function which the overriding + /// contract uses will be either hashLeftRight or hash5, which require + /// different input array lengths. + /// @param _level The level to hash. + /// @param _leaf The leaf include with the level. + /// @return The hash of the level and leaf. function hashLevel(uint256 _level, uint256 _leaf) internal virtual returns (uint256) {} + /// @notice Hash the contents of the specified level and the specified leaf. + /// This is a virtual function as the hash function which the overriding + /// contract uses will be either hashLeftRight or hash5, which require + /// different input array lengths. + /// @param _level The level to hash. + /// @param _leaf The leaf include with the level. + /// @return The hash of the level and leaf. function hashLevelLeaf(uint256 _level, uint256 _leaf) public view virtual returns (uint256) {} - /** - * Returns the zero leaf at a specified level. - * This is a virtual function as the hash function which the overriding - * contract uses will be either hashLeftRight or hash5, which will produce - * different zero values (e.g. hashLeftRight(0, 0) vs - * hash5([0, 0, 0, 0, 0]). Moreover, the zero value may be a - * nothing-up-my-sleeve value. - */ + /// @notice Returns the zero leaf at a specified level. + /// This is a virtual function as the hash function which the overriding + /// contract uses will be either hashLeftRight or hash5, which will produce + /// different zero values (e.g. hashLeftRight(0, 0) vs + /// hash5([0, 0, 0, 0, 0]). Moreover, the zero value may be a + /// nothing-up-my-sleeve value. + /// @param _level The level at which to return the zero leaf. function getZero(uint256 _level) internal virtual returns (uint256) {} - /** - * Add a leaf to the queue for the current subtree. - * @param _leaf The leaf to add. - */ + /// Add a leaf to the queue for the current subtree. + /// @param _leaf The leaf to add. function enqueue(uint256 _leaf) public onlyOwner returns (uint256) { uint256 leafIndex = numLeaves; // Recursively queue the leaf @@ -157,12 +164,10 @@ abstract contract AccQueue is Ownable, Hasher { return leafIndex; } - /** - * Updates the queue at a given level and hashes any subroots that need to - * be hashed. - * @param _leaf The leaf to add. - * @param _level The level at which to queue the leaf. - */ + /// @notice Updates the queue at a given level and hashes any subroots + /// that need to be hashed. + /// @param _leaf The leaf to add. + /// @param _level The level at which to queue the leaf. function _enqueue(uint256 _leaf, uint256 _level) internal { require(_level <= subDepth, "AccQueue: invalid level"); @@ -192,10 +197,8 @@ abstract contract AccQueue is Ownable, Hasher { } } - /** - * Fill any empty leaves of the current subtree with zeros and store the - * resulting subroot. - */ + /// @notice Fill any empty leaves of the current subtree with zeros and store the + /// resulting subroot. function fill() public onlyOwner { if (numLeaves % subTreeCapacity == 0) { // If the subtree is completely empty, then the subroot is a @@ -228,16 +231,12 @@ abstract contract AccQueue is Ownable, Hasher { subTreesMerged = false; } - /** - * A function that queues zeros to the specified level, hashes, - * the level, and enqueues the hash to the next level. - * @param _level The level at which to queue zeros. - */ + /// @notice A function that queues zeros to the specified level, hashes, + /// the level, and enqueues the hash to the next level. + /// @param _level The level at which to queue zeros. function _fill(uint256 _level) internal virtual {} - /** - * Insert a subtree. Used for batch enqueues. - */ + /// Insert a subtree. Used for batch enqueues. function insertSubTree(uint256 _subRoot) public onlyOwner { subRoots[currentSubtreeIndex] = _subRoot; @@ -253,10 +252,8 @@ abstract contract AccQueue is Ownable, Hasher { subTreesMerged = false; } - /* - * Calculate the lowest possible height of a tree with all the subroots - * merged together. - */ + /// @notice Calculate the lowest possible height of a tree with + /// all the subroots merged together. function calcMinHeight() public view returns (uint256) { uint256 depth = 1; while (true) { @@ -269,22 +266,20 @@ abstract contract AccQueue is Ownable, Hasher { return depth; } - /** - * Merge all subtrees to form the shortest possible tree. - * This function can be called either once to merge all subtrees in a - * single transaction, or multiple times to do the same in multiple - * transactions. If _numSrQueueOps is set to 0, this function will attempt - * to merge all subtrees in one go. If it is set to a number greater than - * 0, it will perform up to that number of queueSubRoot() operations. - * @param _numSrQueueOps The number of times this function will call - * queueSubRoot(), up to the maximum number of times - * is necessary. If it is set to 0, it will call - * queueSubRoot() as many times as is necessary. Set - * this to a low number and call this function - * multiple times if there are many subroots to - * merge, or a single transaction would run out of - * gas. - */ + /// @notice Merge all subtrees to form the shortest possible tree. + /// This function can be called either once to merge all subtrees in a + /// single transaction, or multiple times to do the same in multiple + /// transactions. If _numSrQueueOps is set to 0, this function will attempt + /// to merge all subtrees in one go. If it is set to a number greater than + /// 0, it will perform up to that number of queueSubRoot() operations. + /// @param _numSrQueueOps The number of times this function will call + /// queueSubRoot(), up to the maximum number of times + /// is necessary. If it is set to 0, it will call + /// queueSubRoot() as many times as is necessary. Set + /// this to a low number and call this function + /// multiple times if there are many subroots to + /// merge, or a single transaction would run out of + /// gas. function mergeSubRoots(uint256 _numSrQueueOps) public onlyOwner { // This function can only be called once unless a new subtree is created if (subTreesMerged) revert SubTreesAlreadyMerged(); @@ -340,12 +335,10 @@ abstract contract AccQueue is Ownable, Hasher { subTreesMerged = true; } - /* - * Queues a subroot into the subroot tree. - * @param _leaf The value to queue. - * @param _level The level at which to queue _leaf. - * @param _maxDepth The depth of the tree. - */ + /// @notice Queues a subroot into the subroot tree. + /// @param _leaf The value to queue. + /// @param _level The level at which to queue _leaf. + /// @param _maxDepth The depth of the tree. function queueSubRoot(uint256 _leaf, uint256 _level, uint256 _maxDepth) internal { if (_level > _maxDepth) { return; @@ -381,11 +374,9 @@ abstract contract AccQueue is Ownable, Hasher { } } - /** - * Merge all subtrees to form a main tree with a desired depth. - * @param _depth The depth of the main tree. It must fit all the leaves or - * this function will revert. - */ + /// @notice Merge all subtrees to form a main tree with a desired depth. + /// @param _depth The depth of the main tree. It must fit all the leaves or + /// this function will revert. function merge(uint256 _depth) public onlyOwner returns (uint256) { // The tree depth must be more than 0 if (_depth == 0) revert DepthCannotBeZero(); @@ -442,31 +433,25 @@ abstract contract AccQueue is Ownable, Hasher { } } - /* - * Returns the subroot at the specified index. Reverts if the index refers - * to a subtree which has not been filled yet. - * @param _index The subroot index. - */ + /// @notice Returns the subroot at the specified index. Reverts if the index refers + /// to a subtree which has not been filled yet. + /// @param _index The subroot index. function getSubRoot(uint256 _index) public view returns (uint256) { if (currentSubtreeIndex <= _index) revert InvalidIndex(_index); return subRoots[_index]; } - /* - * Returns the subroot tree (SRT) root. Its value must first be computed - * using mergeSubRoots. - */ + /// @notice Returns the subroot tree (SRT) root. Its value must first be computed + /// using mergeSubRoots. function getSmallSRTroot() public view returns (uint256) { if (!subTreesMerged) revert SubTreesNotMerged(); return smallSRTroot; } - /* - * Return the merged Merkle root of all the leaves at a desired depth. - * merge() or merged(_depth) must be called first. - * @param _depth The depth of the main tree. It must first be computed - * using mergeSubRoots() and merge(). - */ + /// @notice Return the merged Merkle root of all the leaves at a desired depth. + /// merge() or merged(_depth) must be called first. + /// @param _depth The depth of the main tree. It must first be computed + /// using mergeSubRoots() and merge(). function getMainRoot(uint256 _depth) public view returns (uint256) { if (hashLength ** _depth < numLeaves) revert DepthTooSmall(_depth, numLeaves); @@ -478,6 +463,13 @@ abstract contract AccQueue is Ownable, Hasher { } } +/// @title AccQueueBinary +/// @notice This contract defines a Merkle tree where each leaf insertion only updates a +/// subtree. To obtain the main tree root, the contract owner must merge the +/// subtrees together. Merging subtrees requires at least 2 operations: +/// mergeSubRoots(), and merge(). To get around the gas limit, +/// the mergeSubRoots() can be performed in multiple transactions. +/// @dev This contract is for a binary tree (2 leaves per node) abstract contract AccQueueBinary is AccQueue { constructor(uint256 _subDepth) AccQueue(_subDepth, 2) {} @@ -616,9 +608,18 @@ contract AccQueueQuinaryMaci is AccQueueQuinary, MerkleQuinaryMaci { } } +/// @title AccQueueQuinaryBlankSl +/// @notice This contract extends AccQueueQuinary and MerkleQuinaryBlankSl +/// @dev This contract is used for creating a +/// Merkle tree with quinary (5 leaves per node) structure contract AccQueueQuinaryBlankSl is AccQueueQuinary, MerkleQuinaryBlankSl { + /// @notice Constructor for creating AccQueueQuinaryBlankSl contract + /// @param _subDepth The depth of each subtree constructor(uint256 _subDepth) AccQueueQuinary(_subDepth) {} + /// @notice Returns the zero leaf at a specified level + /// @param _level The level at which to return the zero leaf + /// @return The zero leaf at the specified level function getZero(uint256 _level) internal view override returns (uint256) { return zeros[_level]; } diff --git a/contracts/contracts/utilities/DomainObjs.sol b/contracts/contracts/utilities/DomainObjs.sol new file mode 100644 index 0000000000..6c468582f0 --- /dev/null +++ b/contracts/contracts/utilities/DomainObjs.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.10; + +import { Hasher } from "../crypto/Hasher.sol"; + +/// @title DomainObjs +/// @author PSE +/// @notice An utility contract that holds +/// a number of domain objects and functions +contract DomainObjs { + /// @notice the length of a MACI message + uint8 constant MESSAGE_DATA_LENGTH = 10; + + /// @title Message + /// @notice this struct represents a MACI message + /// @dev msgType: 1 for vote message, 2 for topup message (size 2) + struct Message { + uint256 msgType; + uint256[MESSAGE_DATA_LENGTH] data; + } + + /// @title PubKey + /// @notice A MACI public key + struct PubKey { + uint256 x; + uint256 y; + } + + /// @title StateLeaf + /// @notice A MACI state leaf + /// @dev used to represent a user's state + /// in the state Merkle tree + struct StateLeaf { + PubKey pubKey; + uint256 voiceCreditBalance; + uint256 timestamp; + } +} diff --git a/contracts/contracts/utilities/Params.sol b/contracts/contracts/utilities/Params.sol new file mode 100644 index 0000000000..5333482c76 --- /dev/null +++ b/contracts/contracts/utilities/Params.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.10; + +import { IMACI } from "../interfaces/IMACI.sol"; +import { AccQueue } from "../trees/AccQueue.sol"; +import { TopupCredit } from "../TopupCredit.sol"; +import { VkRegistry } from "../VkRegistry.sol"; + +/// @title Params +/// @author PSE +/// @notice This contracts contains a number of structures +/// which are to be passed as parameters to Poll contracts. +/// This way we can reduce the number of parameters +/// and avoid a stack too deep error during compilation. +contract Params { + /// @notice A struct holding the depths of the merkle trees + struct TreeDepths { + uint8 intStateTreeDepth; + uint8 messageTreeSubDepth; + uint8 messageTreeDepth; + uint8 voteOptionTreeDepth; + } + + /// @notice A struct holding the batch sizes for processing + struct BatchSizes { + uint24 messageBatchSize; + uint24 tallyBatchSize; + uint24 subsidyBatchSize; + } + + /// @notice A struct holding the max values for the poll + struct MaxValues { + uint256 maxMessages; + uint256 maxVoteOptions; + } + + /// @notice A struct holding the external contracts + /// that are to be passed to a Poll contract on + /// deployment + struct ExtContracts { + VkRegistry vkRegistry; + IMACI maci; + AccQueue messageAq; + TopupCredit topupCredit; + } +} diff --git a/contracts/contracts/utilities/Utility.sol b/contracts/contracts/utilities/Utilities.sol similarity index 51% rename from contracts/contracts/utilities/Utility.sol rename to contracts/contracts/utilities/Utilities.sol index c7d0c065ea..a7e8189b0c 100644 --- a/contracts/contracts/utilities/Utility.sol +++ b/contracts/contracts/utilities/Utilities.sol @@ -1,14 +1,19 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.10; -import { DomainObjs, IPubKey, IMessage } from "../DomainObjs.sol"; +import { DomainObjs } from "./DomainObjs.sol"; import { Hasher } from "../crypto/Hasher.sol"; import { SnarkConstants } from "../crypto/SnarkConstants.sol"; 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 VOTING_PERIOD_NOT_PASSED(); - // common function for MessageProcessor, Tally and Subsidy + /// @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 @@ -19,9 +24,32 @@ contract CommonUtilities { } } -contract Utilities is SnarkConstants, Hasher, IPubKey, IMessage { +/// @title Utilities +/// @author PSE +/// @notice An utility contract that can be used to: +/// hash a state leaf +/// pad and hash a MACI message +/// hash a MACI message and an encryption public key +contract Utilities is SnarkConstants, DomainObjs, Hasher { + /// @notice An utility function used to hash a state leaf + /// @param _stateLeaf the state leaf to be hashed + /// @return the hash of the state leaf + function hashStateLeaf(StateLeaf memory _stateLeaf) public pure returns (uint256) { + uint256[4] memory plaintext; + plaintext[0] = _stateLeaf.pubKey.x; + plaintext[1] = _stateLeaf.pubKey.y; + plaintext[2] = _stateLeaf.voiceCreditBalance; + plaintext[3] = _stateLeaf.timestamp; + + return hash4(plaintext); + } + + /// @notice An utility function used to pad and hash a MACI message + /// @param dataToPad the data to be padded + /// @param msgType the type of the message + /// @return the padded message, the padding public key and the hash of the padded message function padAndHashMessage( - uint256[2] memory dataToPad, // we only need two for now + uint256[2] memory dataToPad, uint256 msgType ) public pure returns (Message memory, PubKey memory, uint256) { uint256[10] memory dat; @@ -38,6 +66,10 @@ contract Utilities is SnarkConstants, Hasher, IPubKey, IMessage { return (_message, _padKey, hashMessageAndEncPubKey(_message, _padKey)); } + /// @notice An utility function used to hash a MACI message and an encryption public key + /// @param _message the message to be hashed + /// @param _encPubKey the encryption public key to be hashed + /// @return the hash of the message and the encryption public key function hashMessageAndEncPubKey(Message memory _message, PubKey memory _encPubKey) public pure returns (uint256) { require(_message.data.length == 10, "Invalid message"); uint256[5] memory n; diff --git a/contracts/package.json b/contracts/package.json index c325901803..199785db30 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -14,7 +14,7 @@ "test": "hardhat test", "test-maci": "hardhat test ./tests/MACI.test.ts", "test-hasher": "hardhat test ./tests/Hasher.test.ts", - "test-domainObjs": "hardhat test ./tests/DomainObjs.test.ts", + "test-utilities": "hardhat test ./tests/Utilities.test.ts", "test-signupGatekeeper": "hardhat test ./tests/SignUpGatekeeper.test.ts", "test-verifier": "hardhat test ./tests/Verifier.test.ts", "test-accQueue": "hardhat test ./tests/AccQueue.test.ts", diff --git a/contracts/tests/HasherBenchmarks.test.ts b/contracts/tests/HasherBenchmarks.test.ts index c1059867f3..2fca338b83 100644 --- a/contracts/tests/HasherBenchmarks.test.ts +++ b/contracts/tests/HasherBenchmarks.test.ts @@ -9,7 +9,6 @@ require("module-alias/register"); describe("Hasher", () => { let hasherContract: HasherBenchmarks; - before(async () => { const { PoseidonT3Contract, PoseidonT4Contract, PoseidonT5Contract, PoseidonT6Contract } = await deployPoseidonContracts(true); diff --git a/contracts/tests/DomainObjs.test.ts b/contracts/tests/Utilities.test.ts similarity index 76% rename from contracts/tests/DomainObjs.test.ts rename to contracts/tests/Utilities.test.ts index 0a739a269f..4ef3dedec1 100644 --- a/contracts/tests/DomainObjs.test.ts +++ b/contracts/tests/Utilities.test.ts @@ -2,10 +2,10 @@ import { expect } from "chai"; import { StateLeaf, Keypair } from "maci-domainobjs"; import { deployPoseidonContracts, linkPoseidonLibraries } from "../ts/deploy"; -import { DomainObjs } from "../typechain-types"; +import { Utilities } from "../typechain-types"; describe("DomainObjs", () => { - let doContract: DomainObjs; + let utilitiesContract: Utilities; describe("Deployment", () => { before(async () => { @@ -25,8 +25,8 @@ describe("DomainObjs", () => { ]); // Link Poseidon contracts - const doContractFactory = await linkPoseidonLibraries( - "DomainObjs", + const utilitiesContractFactory = await linkPoseidonLibraries( + "Utilities", poseidonT3ContractAddress, poseidonT4ContractAddress, poseidonT5ContractAddress, @@ -34,15 +34,15 @@ describe("DomainObjs", () => { true, ); - doContract = (await doContractFactory.deploy()) as typeof doContract; - await doContract.deploymentTransaction()?.wait(); + utilitiesContract = (await utilitiesContractFactory.deploy()) as Utilities; + await utilitiesContract.deploymentTransaction()?.wait(); }); it("should correctly hash a StateLeaf", async () => { const keypair = new Keypair(); const voiceCreditBalance = BigInt(1234); const stateLeaf = new StateLeaf(keypair.pubKey, voiceCreditBalance, BigInt(456546345)); - const onChainHash = await doContract.hashStateLeaf(stateLeaf.asContractParam()); + const onChainHash = await utilitiesContract.hashStateLeaf(stateLeaf.asContractParam()); const expectedHash = stateLeaf.hash(); expect(onChainHash.toString()).to.eq(expectedHash.toString());