Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: general cleanup, variables/events/functions renaming #1426

Merged
merged 1 commit into from
May 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cli/ts/commands/genLocalState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export const genLocalState = async ({
.queryFilter(pollContract.filters.MergeMessageAq(messageRoot), fromBlock)
.then((events) => events[events.length - 1]?.blockNumber),
pollContract
.queryFilter(pollContract.filters.MergeMaciStateAq(stateRoot, numSignups), fromBlock)
.queryFilter(pollContract.filters.MergeMaciState(stateRoot, numSignups), fromBlock)
.then((events) => events[events.length - 1]?.blockNumber),
]).then((blocks) => Math.max(...blocks));

Expand Down
4 changes: 2 additions & 2 deletions cli/ts/commands/genProofs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ export const genProofs = async ({
const messageAqContract = AccQueueFactory.connect(messageAqContractAddr, signer);

// Check that the state and message trees have been merged
if (!(await pollContract.stateAqMerged())) {
if (!(await pollContract.stateMerged())) {
logError("The state tree has not been merged yet. Please use the mergeSignups subcommmand to do so.");
}

Expand Down Expand Up @@ -201,7 +201,7 @@ export const genProofs = async ({
.queryFilter(pollContract.filters.MergeMessageAq(messageRoot), fromBlock)
.then((events) => events[events.length - 1]?.blockNumber),
pollContract
.queryFilter(pollContract.filters.MergeMaciStateAq(stateRoot, numSignups), fromBlock)
.queryFilter(pollContract.filters.MergeMaciState(stateRoot, numSignups), fromBlock)
.then((events) => events[events.length - 1]?.blockNumber),
]).then((blocks) => Math.max(...blocks));

Expand Down
8 changes: 0 additions & 8 deletions cli/ts/commands/mergeMessages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,6 @@ export const mergeMessages = async ({

const accQueueContract = AccQueueFactory.connect(messageAqContractAddr, signer);

// we need to ensure that the signer is the owner of the poll contract
// this is because only the owner can merge the message AQ
const pollOwner = await pollContract.owner();
const signerAddress = await signer.getAddress();
if (pollOwner.toLowerCase() !== signerAddress.toLowerCase()) {
logError("The signer is not the owner of this Poll contract");
}

// check if it's time to merge the message AQ
const dd = await pollContract.getDeployTimeAndDuration();
const deadline = Number(dd[0]) + Number(dd[1]);
Expand Down
6 changes: 3 additions & 3 deletions cli/ts/commands/mergeSignups.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,10 @@ export const mergeSignups = async ({ pollId, maciAddress, signer, quiet = true }
logError("Voting period is not over");
}

if (!(await pollContract.stateAqMerged())) {
if (!(await pollContract.stateMerged())) {
// go and merge the state tree
logYellow(quiet, info("Merging subroots to a main state root..."));
const tx = await pollContract.mergeMaciStateAq();
logYellow(quiet, info("Calculating root and storing on Poll..."));
const tx = await pollContract.mergeMaciState();
const receipt = await tx.wait();

if (receipt?.status !== 1) {
Expand Down
2 changes: 1 addition & 1 deletion cli/ts/commands/poll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export const getPoll = async ({

const [[deployTime, duration], isStateAqMerged] = await Promise.all([
pollContract.getDeployTimeAndDuration(),
pollContract.stateAqMerged(),
pollContract.stateMerged(),
]);

const numSignups = await (isStateAqMerged ? pollContract.numSignups() : maciContract.numSignUps());
Expand Down
38 changes: 10 additions & 28 deletions contracts/contracts/MACI.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,242 +12,224 @@
import { Utilities } from "./utilities/Utilities.sol";
import { DomainObjs } from "./utilities/DomainObjs.sol";
import { CurveBabyJubJub } from "./crypto/BabyJubJub.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { InternalLazyIMT, LazyIMTData } from "./trees/LazyIMT.sol";

/// @title MACI - Minimum Anti-Collusion Infrastructure Version 1
/// @notice A contract which allows users to sign up, and deploy new polls
contract MACI is IMACI, DomainObjs, Params, Utilities, Ownable(msg.sender) {
contract MACI is IMACI, DomainObjs, Params, Utilities {
/// @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
/// so that there can be as many users as possible. i.e. 2 ** 23 = 8388608
/// this should also match the parameter of the circom circuits.
uint8 public immutable stateTreeDepth;

/// @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 public immutable stateTreeDepth;

uint8 internal constant TREE_ARITY = 2;
uint8 internal constant MESSAGE_TREE_ARITY = 5;

/// @notice The hash of a blank state leaf
uint256 internal constant BLANK_STATE_LEAF_HASH =
uint256(6769006970205099520508948723718471724660867171122235270773600567925038008762);

/// @notice Each poll has an incrementing ID
uint256 public nextPollId;

/// @notice A mapping of poll IDs to Poll contracts.
mapping(uint256 => address) public polls;

/// @notice ERC20 contract that hold topup credits
TopupCredit public immutable topupCredit;

/// @notice Factory contract that deploy a Poll contract
IPollFactory public immutable pollFactory;

/// @notice Factory contract that deploy a MessageProcessor contract
IMessageProcessorFactory public immutable messageProcessorFactory;

/// @notice Factory contract that deploy a Tally contract
ITallyFactory public immutable tallyFactory;

/// @notice The state tree. Represents a mapping between each user's public key
/// and their voice credit balance.
LazyIMTData public lazyIMTData;

/// @notice Address of the SignUpGatekeeper, a contract which determines whether a
/// user may sign up to vote
SignUpGatekeeper public immutable signUpGatekeeper;

/// @notice The contract which provides the values of the initial voice credit
/// balance per user
InitialVoiceCreditProxy public immutable initialVoiceCreditProxy;

/// @notice A struct holding the addresses of poll, mp and tally
struct PollContracts {
address poll;
address messageProcessor;
address tally;
}

// Events
event SignUp(
uint256 _stateIndex,
uint256 indexed _userPubKeyX,
uint256 indexed _userPubKeyY,
uint256 _voiceCreditBalance,
uint256 _timestamp
);
event DeployPoll(
uint256 _pollId,
uint256 indexed _coordinatorPubKeyX,
uint256 indexed _coordinatorPubKeyY,
PollContracts pollAddr
);

/// @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);
_;
}

/// @notice custom errors
error CallerMustBePoll(address _caller);
error PoseidonHashLibrariesNotLinked();
error TooManySignups();
error InvalidPubKey();
error PreviousPollNotCompleted(uint256 pollId);
error PollDoesNotExist(uint256 pollId);
error SignupTemporaryBlocked();

/// @notice Create a new instance of the MACI contract.
/// @param _pollFactory The PollFactory contract
/// @param _messageProcessorFactory The MessageProcessorFactory contract
/// @param _tallyFactory The TallyFactory contract
/// @param _signUpGatekeeper The SignUpGatekeeper contract
/// @param _initialVoiceCreditProxy The InitialVoiceCreditProxy contract
/// @param _topupCredit The TopupCredit contract
/// @param _stateTreeDepth The depth of the state tree
constructor(
IPollFactory _pollFactory,
IMessageProcessorFactory _messageProcessorFactory,
ITallyFactory _tallyFactory,
SignUpGatekeeper _signUpGatekeeper,
InitialVoiceCreditProxy _initialVoiceCreditProxy,
TopupCredit _topupCredit,
uint8 _stateTreeDepth
) payable {
// initialize and insert the blank leaf
InternalLazyIMT._init(lazyIMTData, _stateTreeDepth);
InternalLazyIMT._insert(lazyIMTData, BLANK_STATE_LEAF_HASH);

pollFactory = _pollFactory;
messageProcessorFactory = _messageProcessorFactory;
tallyFactory = _tallyFactory;
topupCredit = _topupCredit;
signUpGatekeeper = _signUpGatekeeper;
initialVoiceCreditProxy = _initialVoiceCreditProxy;
stateTreeDepth = _stateTreeDepth;

// Verify linked poseidon libraries
if (hash2([uint256(1), uint256(1)]) == 0) revert PoseidonHashLibrariesNotLinked();
}

/// @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,
bytes memory _initialVoiceCreditProxyData
) public virtual {
// ensure we do not have more signups than what the circuits support
if (lazyIMTData.numberOfLeaves >= uint256(TREE_ARITY) ** uint256(stateTreeDepth)) revert TooManySignups();

// ensure that the public key is on the baby jubjub curve
if (!CurveBabyJubJub.isOnCurve(_pubKey.x, _pubKey.y)) {
revert InvalidPubKey();
}

// Register the user via the sign-up gatekeeper. This function should
// throw if the user has already registered or if ineligible to do so.
signUpGatekeeper.register(msg.sender, _signUpGatekeeperData);

// Get the user's voice credit balance.
uint256 voiceCreditBalance = initialVoiceCreditProxy.getVoiceCredits(msg.sender, _initialVoiceCreditProxyData);

uint256 timestamp = block.timestamp;

// Create a state leaf and insert it into the tree.
uint256 stateLeaf = hashStateLeaf(StateLeaf(_pubKey, voiceCreditBalance, timestamp));
InternalLazyIMT._insert(lazyIMTData, stateLeaf);

emit SignUp(lazyIMTData.numberOfLeaves - 1, _pubKey.x, _pubKey.y, voiceCreditBalance, timestamp);
}

/// @notice Deploy a new Poll contract.
/// @param _duration How long should the Poll last for
/// @param _treeDepths The depth of the Merkle trees
/// @param _coordinatorPubKey The coordinator's public key
/// @param _verifier The Verifier Contract
/// @param _vkRegistry The VkRegistry Contract
/// @param _mode Voting mode
/// @return pollAddr a new Poll contract address
function deployPoll(
uint256 _duration,
TreeDepths memory _treeDepths,
PubKey memory _coordinatorPubKey,
address _verifier,
address _vkRegistry,
Mode _mode
) public virtual onlyOwner returns (PollContracts memory pollAddr) {
) public virtual returns (PollContracts memory pollAddr) {
kittybest marked this conversation as resolved.
Show resolved Hide resolved
// cache the poll to a local variable so we can increment it
uint256 pollId = nextPollId;

// Increment the poll ID for the next poll
// 2 ** 256 polls available
unchecked {
nextPollId++;
}

// check coordinator key is a valid point on the curve
if (!CurveBabyJubJub.isOnCurve(_coordinatorPubKey.x, _coordinatorPubKey.y)) {
revert InvalidPubKey();
}

MaxValues memory maxValues = MaxValues({
maxMessages: uint256(MESSAGE_TREE_ARITY) ** _treeDepths.messageTreeDepth,
maxVoteOptions: uint256(MESSAGE_TREE_ARITY) ** _treeDepths.voteOptionTreeDepth
});

address _owner = owner();
// the owner of the message processor and tally contract will be the msg.sender
address _msgSender = msg.sender;

address p = pollFactory.deploy(
_duration,
maxValues,
_treeDepths,
_coordinatorPubKey,
address(this),
topupCredit,
_owner
);
address p = pollFactory.deploy(_duration, maxValues, _treeDepths, _coordinatorPubKey, address(this), topupCredit);

address mp = messageProcessorFactory.deploy(_verifier, _vkRegistry, p, _owner, _mode);
address tally = tallyFactory.deploy(_verifier, _vkRegistry, p, mp, _owner, _mode);
address mp = messageProcessorFactory.deploy(_verifier, _vkRegistry, p, _msgSender, _mode);
address tally = tallyFactory.deploy(_verifier, _vkRegistry, p, mp, _msgSender, _mode);

polls[pollId] = p;

// store the addresses in a struct so they can be returned
pollAddr = PollContracts({ poll: p, messageProcessor: mp, tally: tally });

emit DeployPoll(pollId, _coordinatorPubKey.x, _coordinatorPubKey.y, pollAddr);
}

/// @inheritdoc IMACI
function getStateTreeRoot() public view returns (uint256 root) {
root = InternalLazyIMT._root(lazyIMTData);
}

/// @notice Get the Poll details
/// @param _pollId The identifier of the Poll to retrieve
/// @return poll The Poll contract object
function getPoll(uint256 _pollId) public view returns (address poll) {
if (_pollId >= nextPollId) revert PollDoesNotExist(_pollId);
poll = polls[_pollId];
}

/// @inheritdoc IMACI
function numSignUps() public view returns (uint256 signUps) {
signUps = lazyIMTData.numberOfLeaves;
}
}

Check warning

Code scanning / Slither

Contracts that lock Ether Medium

Contract locking ether found:
Contract MACI has payable functions:
- MACI.constructor(IPollFactory,IMessageProcessorFactory,ITallyFactory,SignUpGatekeeper,InitialVoiceCreditProxy,uint8,uint256[5])
But does not have a function to withdraw the ether
18 changes: 12 additions & 6 deletions contracts/contracts/MessageProcessor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,282 +17,288 @@
/// @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(msg.sender), SnarkCommon, Hasher, CommonUtilities, IMessageProcessor, DomainObjs {
contract MessageProcessor is Ownable, SnarkCommon, Hasher, CommonUtilities, IMessageProcessor, DomainObjs {
/// @notice custom errors
error NoMoreMessages();
error StateAqNotMerged();
error StateNotMerged();
error MessageAqNotMerged();
error InvalidProcessMessageProof();
error VkNotSet();
error MaxVoteOptionsTooLarge();
error NumSignUpsTooLarge();
error CurrentMessageBatchIndexTooLarge();
error BatchEndIndexTooLarge();

// the number of children per node in the merkle trees
uint256 internal constant TREE_ARITY = 5;

/// @inheritdoc IMessageProcessor
bool public processingComplete;

/// @notice The number of batches processed
uint256 public numBatchesProcessed;

/// @notice The current message batch index. When the coordinator runs
/// processMessages(), this action relates to messages
/// currentMessageBatchIndex to currentMessageBatchIndex + messageBatchSize.
uint256 public currentMessageBatchIndex;

/// @inheritdoc IMessageProcessor
uint256 public sbCommitment;

IPoll public immutable poll;
IVerifier public immutable verifier;
IVkRegistry public immutable vkRegistry;
Mode public immutable mode;

/// @notice Create a new instance
/// @param _verifier The Verifier contract address
/// @param _vkRegistry The VkRegistry contract address
/// @param _poll The Poll contract address
/// @param _mpOwner The owner of the MessageProcessor contract
/// @param _mode Voting mode
constructor(address _verifier, address _vkRegistry, address _poll, Mode _mode) payable {
constructor(
address _verifier,
address _vkRegistry,
address _poll,
address _mpOwner,
Mode _mode
) payable Ownable(_mpOwner) {
verifier = IVerifier(_verifier);
vkRegistry = IVkRegistry(_vkRegistry);
poll = IPoll(_poll);
mode = _mode;
}

/// @notice Update the Poll's currentSbCommitment if the proof is valid.
/// @param _newSbCommitment The new state root and ballot root commitment
/// after all messages are processed
/// @param _proof The zk-SNARK proof
function processMessages(uint256 _newSbCommitment, uint256[8] memory _proof) external onlyOwner {
// ensure the voting period is over
_votingPeriodOver(poll);

// There must be unprocessed messages
if (processingComplete) {
revert NoMoreMessages();
}

// The state AccQueue must be merged
if (!poll.stateAqMerged()) {
revert StateAqNotMerged();
if (!poll.stateMerged()) {
revert StateNotMerged();
}

// Retrieve stored vals
(, uint8 messageTreeSubDepth, uint8 messageTreeDepth, uint8 voteOptionTreeDepth) = poll.treeDepths();
// calculate the message batch size from the message tree subdepth
uint256 messageBatchSize = TREE_ARITY ** messageTreeSubDepth;

(, AccQueue messageAq, ) = poll.extContracts();

// Require that the message queue has been merged
uint256 messageRoot = messageAq.getMainRoot(messageTreeDepth);
if (messageRoot == 0) {
revert MessageAqNotMerged();
}

// Copy the state and ballot commitment and set the batch index if this
// is the first batch to process
if (numBatchesProcessed == 0) {
uint256 currentSbCommitment = poll.currentSbCommitment();
sbCommitment = currentSbCommitment;
(, uint256 numMessages) = poll.numSignUpsAndMessages();
uint256 r = numMessages % messageBatchSize;

currentMessageBatchIndex = numMessages;

if (currentMessageBatchIndex > 0) {
if (r == 0) {
currentMessageBatchIndex -= messageBatchSize;
} else {
currentMessageBatchIndex -= r;
}
}
}

if (
!verifyProcessProof(
currentMessageBatchIndex,
messageRoot,
sbCommitment,
_newSbCommitment,
messageTreeSubDepth,
messageTreeDepth,
voteOptionTreeDepth,
_proof
)
) {
revert InvalidProcessMessageProof();
}

{
(, uint256 numMessages) = poll.numSignUpsAndMessages();
// Decrease the message batch start index to ensure that each
// message batch is processed in order
if (currentMessageBatchIndex > 0) {
currentMessageBatchIndex -= messageBatchSize;
}

updateMessageProcessingData(
_newSbCommitment,
currentMessageBatchIndex,
numMessages <= messageBatchSize * (numBatchesProcessed + 1)
);
}
}

/// @notice Verify the proof for processMessage
/// @dev used to update the sbCommitment
/// @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 _messageTreeSubDepth The message tree subdepth
/// @param _messageTreeDepth The message tree depth
/// @param _voteOptionTreeDepth The vote option tree depth
/// @param _proof The zk-SNARK proof
/// @return isValid Whether the proof is valid
function verifyProcessProof(
uint256 _currentMessageBatchIndex,
uint256 _messageRoot,
uint256 _currentSbCommitment,
uint256 _newSbCommitment,
uint8 _messageTreeSubDepth,
uint8 _messageTreeDepth,
uint8 _voteOptionTreeDepth,
uint256[8] memory _proof
) internal view returns (bool isValid) {
// get the tree depths
// get the message batch size from the message tree subdepth
// get the number of signups
(uint256 numSignUps, uint256 numMessages) = poll.numSignUpsAndMessages();
(IMACI maci, , ) = poll.extContracts();

// Calculate the public input hash (a SHA256 hash of several values)
uint256 publicInputHash = genProcessMessagesPublicInputHash(
_currentMessageBatchIndex,
_messageRoot,
numSignUps,
numMessages,
_currentSbCommitment,
_newSbCommitment,
_messageTreeSubDepth,
_voteOptionTreeDepth
);

// Get the verifying key from the VkRegistry
VerifyingKey memory vk = vkRegistry.getProcessVk(
maci.stateTreeDepth(),
_messageTreeDepth,
_voteOptionTreeDepth,
TREE_ARITY ** _messageTreeSubDepth,
mode
);

isValid = 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 longer.
/// @param _currentMessageBatchIndex The batch index of current message batch
/// @param _numSignUps The number of users that signup
/// @param _numMessages The number of messages
/// @param _currentSbCommitment The current sbCommitment (state and ballot root)
/// @param _newSbCommitment The new sbCommitment after we update this message batch
/// @param _messageTreeSubDepth The message tree subdepth
/// @return inputHash Returns the SHA256 hash of the packed values
function genProcessMessagesPublicInputHash(
uint256 _currentMessageBatchIndex,
uint256 _messageRoot,
uint256 _numSignUps,
uint256 _numMessages,
uint256 _currentSbCommitment,
uint256 _newSbCommitment,
uint8 _messageTreeSubDepth,
uint8 _voteOptionTreeDepth
) public view returns (uint256 inputHash) {
uint256 coordinatorPubKeyHash = poll.coordinatorPubKeyHash();

uint8 actualStateTreeDepth = poll.actualStateTreeDepth();

// pack the values
uint256 packedVals = genProcessMessagesPackedVals(
_currentMessageBatchIndex,
_numSignUps,
_numMessages,
_messageTreeSubDepth,
_voteOptionTreeDepth
);

(uint256 deployTime, uint256 duration) = poll.getDeployTimeAndDuration();

// generate the circuit only public input
uint256[] memory input = new uint256[](7);
input[0] = packedVals;
input[1] = coordinatorPubKeyHash;
input[2] = _messageRoot;
input[3] = _currentSbCommitment;
input[4] = _newSbCommitment;
input[5] = deployTime + duration;
input[6] = actualStateTreeDepth;
inputHash = sha256Hash(input);
}

/// @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 _currentMessageBatchIndex batch index of current message batch
/// @param _numSignUps number of users that signup
/// @param _numMessages number of messages
/// @param _messageTreeSubDepth message tree subdepth
/// @param _voteOptionTreeDepth vote option tree depth
/// @return result The packed value
function genProcessMessagesPackedVals(
uint256 _currentMessageBatchIndex,
uint256 _numSignUps,
uint256 _numMessages,
uint8 _messageTreeSubDepth,
uint8 _voteOptionTreeDepth
) public pure returns (uint256 result) {
uint256 maxVoteOptions = TREE_ARITY ** _voteOptionTreeDepth;

// calculate the message batch size from the message tree subdepth
uint256 messageBatchSize = TREE_ARITY ** _messageTreeSubDepth;
uint256 batchEndIndex = _currentMessageBatchIndex + messageBatchSize;
if (batchEndIndex > _numMessages) {
batchEndIndex = _numMessages;
}

if (maxVoteOptions >= 2 ** 50) revert MaxVoteOptionsTooLarge();
if (_numSignUps >= 2 ** 50) revert NumSignUpsTooLarge();
if (_currentMessageBatchIndex >= 2 ** 50) revert CurrentMessageBatchIndexTooLarge();
if (batchEndIndex >= 2 ** 50) revert BatchEndIndexTooLarge();

result = maxVoteOptions + (_numSignUps << 50) + (_currentMessageBatchIndex << 100) + (batchEndIndex << 150);
}

/// @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,
bool _processingComplete
) internal {
sbCommitment = _newSbCommitment;
processingComplete = _processingComplete;
currentMessageBatchIndex = _currentMessageBatchIndex;
numBatchesProcessed++;
}
}

Check warning

Code scanning / Slither

Contracts that lock Ether Medium

Contract locking ether found:
Contract MessageProcessor has payable functions:
- MessageProcessor.constructor(address,address,address,address,DomainObjs.Mode)
But does not have a function to withdraw the ether
3 changes: 1 addition & 2 deletions contracts/contracts/MessageProcessorFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ contract MessageProcessorFactory is Params, DomainObjs, IMessageProcessorFactory
Mode _mode
) public returns (address messageProcessorAddr) {
// deploy MessageProcessor for this Poll
MessageProcessor messageProcessor = new MessageProcessor(_verifier, _vkRegistry, _poll, _mode);
messageProcessor.transferOwnership(_owner);
MessageProcessor messageProcessor = new MessageProcessor(_verifier, _vkRegistry, _poll, _owner, _mode);
messageProcessorAddr = address(messageProcessor);
}
}
28 changes: 13 additions & 15 deletions contracts/contracts/Poll.sol
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import { Params } from "./utilities/Params.sol";
import { SnarkCommon } from "./crypto/SnarkCommon.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 { Params } from "./utilities/Params.sol";
import { SnarkCommon } from "./crypto/SnarkCommon.sol";
import { EmptyBallotRoots } from "./trees/EmptyBallotRoots.sol";
import { IPoll } from "./interfaces/IPoll.sol";
import { Utilities } from "./utilities/Utilities.sol";
Expand All @@ -16,263 +16,261 @@
/// which can be either votes, key change messages or topup messages.
/// @dev Do not deploy this directly. Use PollFactory.deploy() which performs some
/// checks on the Poll constructor arguments.
contract Poll is Params, Utilities, SnarkCommon, Ownable(msg.sender), EmptyBallotRoots, IPoll {
contract Poll is Params, Utilities, SnarkCommon, EmptyBallotRoots, IPoll {
using SafeERC20 for ERC20;

/// @notice Whether the Poll has been initialized
bool internal isInit;

/// @notice The coordinator's public key
PubKey public coordinatorPubKey;

/// @notice Hash of the coordinator's public key
uint256 public immutable coordinatorPubKeyHash;

/// @notice the state root of the state merkle tree
uint256 public mergedStateRoot;

// The timestamp of the block at which the Poll was deployed
uint256 internal immutable deployTime;

// The duration of the polling period, in seconds
uint256 internal immutable duration;

/// @notice Whether the MACI contract's stateAq has been merged by this contract
bool public stateAqMerged;
bool public stateMerged;

/// @notice Get the commitment to the state leaves and the ballots. This is
/// hash3(stateRoot, ballotRoot, salt).
/// Its initial value should be
/// hash(maciStateRootSnapshot, emptyBallotRoot, 0)
/// Each successful invocation of processMessages() should use a different
/// salt to update this value, so that an external observer cannot tell in
/// the case that none of the messages are valid.
uint256 public currentSbCommitment;

/// @notice The number of messages that have been published
uint256 public numMessages;

/// @notice The number of signups that have been processed
/// before the Poll ended (stateAq merged)
uint256 public numSignups;

/// @notice The actual depth of the state tree
/// to be used as public input for the circuit
uint8 public actualStateTreeDepth;

/// @notice Max values for the poll
MaxValues public maxValues;

/// @notice Depths of the merkle trees
TreeDepths public treeDepths;

/// @notice The contracts used by the Poll
ExtContracts public extContracts;

error VotingPeriodOver();
error VotingPeriodNotOver();
error PollAlreadyInit();
error TooManyMessages();
error InvalidPubKey();
error StateAqAlreadyMerged();
error StateAqSubtreesNeedMerge();
error StateAlreadyMerged();
error InvalidBatchLength();

event PublishMessage(Message _message, PubKey _encPubKey);
event TopupMessage(Message _message);
event MergeMaciStateAqSubRoots(uint256 indexed _numSrQueueOps);
event MergeMaciStateAq(uint256 indexed _stateRoot, uint256 indexed _numSignups);
event MergeMaciState(uint256 indexed _stateRoot, uint256 indexed _numSignups);
kittybest marked this conversation as resolved.
Show resolved Hide resolved
event MergeMessageAqSubRoots(uint256 indexed _numSrQueueOps);
event MergeMessageAq(uint256 indexed _messageRoot);

/// @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 messages and vote options
/// @param _treeDepths The depths of the merkle trees
/// @param _coordinatorPubKey The coordinator's public key
/// @param _extContracts The external contracts
constructor(
uint256 _duration,
MaxValues memory _maxValues,
TreeDepths memory _treeDepths,
PubKey memory _coordinatorPubKey,
ExtContracts memory _extContracts
) payable {
// check that the coordinator public key is valid
if (!CurveBabyJubJub.isOnCurve(_coordinatorPubKey.x, _coordinatorPubKey.y)) {
revert InvalidPubKey();
}

// store the pub key as object then calculate the hash
coordinatorPubKey = _coordinatorPubKey;
// we hash it ourselves to ensure we store the correct value
coordinatorPubKeyHash = hashLeftRight(_coordinatorPubKey.x, _coordinatorPubKey.y);
// store the external contracts to interact with
extContracts = _extContracts;
// store duration of the poll
duration = _duration;
// store max values
maxValues = _maxValues;
// store tree depth
treeDepths = _treeDepths;
// Record the current timestamp
deployTime = block.timestamp;
}

/// @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();
_;
}

/// @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
isInit = true;

unchecked {
numMessages++;
}

// init messageAq here by inserting placeholderLeaf
uint256[2] memory dat;
dat[0] = NOTHING_UP_MY_SLEEVE;
dat[1] = 0;

(Message memory _message, PubKey memory _padKey, uint256 placeholderLeaf) = padAndHashMessage(dat, 1);
extContracts.messageAq.enqueue(placeholderLeaf);

emit PublishMessage(_message, _padKey);
}

/// @inheritdoc IPoll
function topup(uint256 stateIndex, uint256 amount) public virtual isWithinVotingDeadline {
// we check that we do not exceed the max number of messages
if (numMessages >= maxValues.maxMessages) revert TooManyMessages();

// cannot realistically overflow
unchecked {
numMessages++;
}

/// @notice topupCredit is a trusted token contract which reverts if the transfer fails
extContracts.topupCredit.transferFrom(msg.sender, address(this), amount);

uint256[2] memory dat;
dat[0] = stateIndex;
dat[1] = amount;

(Message memory _message, , uint256 messageLeaf) = padAndHashMessage(dat, 2);

extContracts.messageAq.enqueue(messageLeaf);

emit TopupMessage(_message);
}

/// @inheritdoc IPoll
function publishMessage(Message memory _message, PubKey calldata _encPubKey) public virtual isWithinVotingDeadline {
// we check that we do not exceed the max number of messages
if (numMessages >= maxValues.maxMessages) revert TooManyMessages();

// check if the public key is on the curve
if (!CurveBabyJubJub.isOnCurve(_encPubKey.x, _encPubKey.y)) {
revert InvalidPubKey();
}

// cannot realistically overflow
unchecked {
numMessages++;
}

// we enforce that msgType here is 1 so we don't need checks
// at the circuit level
_message.msgType = 1;

uint256 messageLeaf = hashMessageAndEncPubKey(_message, _encPubKey);
extContracts.messageAq.enqueue(messageLeaf);

emit PublishMessage(_message, _encPubKey);
}

/// @notice submit a message batch
/// @dev Can only be submitted before the voting deadline
/// @param _messages the messages
/// @param _encPubKeys the encrypted public keys
function publishMessageBatch(Message[] calldata _messages, PubKey[] calldata _encPubKeys) external {
if (_messages.length != _encPubKeys.length) {
revert InvalidBatchLength();
}

uint256 len = _messages.length;
for (uint256 i = 0; i < len; ) {
// an event will be published by this function already
publishMessage(_messages[i], _encPubKeys[i]);

unchecked {
i++;
}
}
}

/// @inheritdoc IPoll
function mergeMaciStateAq() public onlyOwner isAfterVotingDeadline {
function mergeMaciState() public isAfterVotingDeadline {
// This function can only be called once per Poll after the voting
// deadline
if (stateAqMerged) revert StateAqAlreadyMerged();
if (stateMerged) revert StateAlreadyMerged();

// set merged to true so it cannot be called again
stateAqMerged = true;
stateMerged = true;

mergedStateRoot = extContracts.maci.getStateTreeRoot();

// Set currentSbCommitment
uint256[3] memory sb;
sb[0] = mergedStateRoot;
sb[1] = emptyBallotRoots[treeDepths.voteOptionTreeDepth - 1];
sb[2] = uint256(0);

currentSbCommitment = hash3(sb);

// get number of signups and cache in a var for later use
uint256 _numSignups = extContracts.maci.numSignUps();
numSignups = _numSignups;

// dynamically determine the actual depth of the state tree
uint8 depth = 1;
while (uint40(2) ** uint40(depth) < _numSignups) {
depth++;
}

actualStateTreeDepth = depth;

emit MergeMaciStateAq(mergedStateRoot, numSignups);
emit MergeMaciState(mergedStateRoot, numSignups);
}

/// @inheritdoc IPoll
function mergeMessageAqSubRoots(uint256 _numSrQueueOps) public onlyOwner isAfterVotingDeadline {
function mergeMessageAqSubRoots(uint256 _numSrQueueOps) public isAfterVotingDeadline {
Dismissed Show dismissed Hide dismissed
ctrlc03 marked this conversation as resolved.
Show resolved Hide resolved
extContracts.messageAq.mergeSubRoots(_numSrQueueOps);
emit MergeMessageAqSubRoots(_numSrQueueOps);
}

Check notice

Code scanning / Slither

Reentrancy vulnerabilities Low


/// @inheritdoc IPoll
function mergeMessageAq() public onlyOwner isAfterVotingDeadline {
function mergeMessageAq() public isAfterVotingDeadline {
uint256 root = extContracts.messageAq.merge(treeDepths.messageTreeDepth);
emit MergeMessageAq(root);
}

Check notice

Code scanning / Slither

Reentrancy vulnerabilities Low

Reentrancy in Poll.mergeMessageAq():
External calls:
- root = extContracts.messageAq.merge(treeDepths.messageTreeDepth)
Event emitted after the call(s):
- MergeMessageAq(root)

/// @inheritdoc IPoll
function getDeployTimeAndDuration() public view returns (uint256 pollDeployTime, uint256 pollDuration) {
Expand Down
5 changes: 1 addition & 4 deletions contracts/contracts/PollFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,7 @@ contract PollFactory is Params, DomainObjs, IPollFactory {
TreeDepths calldata _treeDepths,
PubKey calldata _coordinatorPubKey,
address _maci,
TopupCredit _topupCredit,
address _pollOwner
TopupCredit _topupCredit
Dismissed Show dismissed Hide dismissed
) public virtual returns (address pollAddr) {
/// @notice Validate _maxValues
/// maxVoteOptions must be less than 2 ** 50 due to circuit limitations;
Expand Down Expand Up @@ -62,8 +61,6 @@ contract PollFactory is Params, DomainObjs, IPollFactory {
// init Poll
poll.init();

poll.transferOwnership(_pollOwner);

pollAddr = address(poll);
}
}
13 changes: 11 additions & 2 deletions contracts/contracts/Tally.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,363 +15,372 @@
/// @title Tally
/// @notice The Tally contract is used during votes tallying
/// and by users to verify the tally results.
contract Tally is Ownable(msg.sender), SnarkCommon, CommonUtilities, Hasher, DomainObjs {
contract Tally is Ownable, SnarkCommon, CommonUtilities, Hasher, DomainObjs {
uint256 internal constant TREE_ARITY = 2;
uint256 internal constant VOTE_OPTION_TREE_ARITY = 5;

/// @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:
///
/// QV:
/// 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)
/// )
///
/// Non-QV:
/// hash2(
/// hashLeftRight(merkle root of current results, salt0)
/// hashLeftRight(number of spent voice credits, salt1),
/// )
///
/// Where each salt is unique and the merkle roots are of arrays of leaves
/// TREE_ARITY ** voteOptionTreeDepth long.
uint256 public tallyCommitment;

uint256 public tallyBatchNum;

// The final commitment to the state and ballot roots
uint256 public sbCommitment;

IVerifier public immutable verifier;
IVkRegistry public immutable vkRegistry;
IPoll public immutable poll;
IMessageProcessor public immutable messageProcessor;
Mode public immutable mode;

/// @notice custom errors
error ProcessingNotComplete();
error InvalidTallyVotesProof();
error AllBallotsTallied();
error NumSignUpsTooLarge();
error BatchStartIndexTooLarge();
error TallyBatchSizeTooLarge();
error NotSupported();

/// @notice Create a new Tally contract
/// @param _verifier The Verifier contract
/// @param _vkRegistry The VkRegistry contract
/// @param _poll The Poll contract
/// @param _mp The MessageProcessor contract
constructor(address _verifier, address _vkRegistry, address _poll, address _mp, Mode _mode) payable {
/// @param _tallyOwner The owner of the Tally contract
/// @param _mode The mode of the poll
constructor(
address _verifier,
address _vkRegistry,
address _poll,
address _mp,
address _tallyOwner,
Mode _mode
) payable Ownable(_tallyOwner) {
verifier = IVerifier(_verifier);
vkRegistry = IVkRegistry(_vkRegistry);
poll = IPoll(_poll);
messageProcessor = IMessageProcessor(_mp);
mode = _mode;
}

/// @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 result an uint256 representing the 3 inputs packed together
function genTallyVotesPackedVals(
uint256 _numSignUps,
uint256 _batchStartIndex,
uint256 _tallyBatchSize
) public pure returns (uint256 result) {
if (_numSignUps >= 2 ** 50) revert NumSignUpsTooLarge();
if (_batchStartIndex >= 2 ** 50) revert BatchStartIndexTooLarge();
if (_tallyBatchSize >= 2 ** 50) revert TallyBatchSizeTooLarge();

result = (_batchStartIndex / _tallyBatchSize) + (_numSignUps << uint256(50));
}

/// @notice Check if all ballots are tallied
/// @return tallied whether all ballots are tallied
function isTallied() public view returns (bool tallied) {
(uint8 intStateTreeDepth, , , ) = poll.treeDepths();
(uint256 numSignUps, ) = poll.numSignUpsAndMessages();

// Require that there are untallied ballots left
tallied = tallyBatchNum * (TREE_ARITY ** intStateTreeDepth) >= numSignUps;
}

/// @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 inputHash hash of public inputs
function genTallyVotesPublicInputHash(
uint256 _numSignUps,
uint256 _batchStartIndex,
uint256 _tallyBatchSize,
uint256 _newTallyCommitment
) public view returns (uint256 inputHash) {
uint256 packedVals = genTallyVotesPackedVals(_numSignUps, _batchStartIndex, _tallyBatchSize);
uint256[] memory input = new uint256[](4);
input[0] = packedVals;
input[1] = sbCommitment;
input[2] = tallyCommitment;
input[3] = _newTallyCommitment;
inputHash = sha256Hash(input);
}

/// @notice Update the state and ballot root commitment
function updateSbCommitment() public onlyOwner {
// Require that all messages have been processed
if (!messageProcessor.processingComplete()) {
revert ProcessingNotComplete();
}

if (sbCommitment == 0) {
sbCommitment = messageProcessor.sbCommitment();
}
}

/// @notice Verify the result of a tally batch
/// @param _newTallyCommitment the new tally commitment to be verified
/// @param _proof the proof generated after tallying this batch
function tallyVotes(uint256 _newTallyCommitment, uint256[8] calldata _proof) public onlyOwner {
_votingPeriodOver(poll);
updateSbCommitment();

// get the batch size and start index
(uint8 intStateTreeDepth, , , ) = poll.treeDepths();
uint256 tallyBatchSize = TREE_ARITY ** intStateTreeDepth;
uint256 batchStartIndex = tallyBatchNum * tallyBatchSize;

// save some gas because we won't overflow uint256
unchecked {
tallyBatchNum++;
}

(uint256 numSignUps, ) = poll.numSignUpsAndMessages();

// Require that there are untallied ballots left
if (batchStartIndex >= numSignUps) {
revert AllBallotsTallied();
}

bool isValid = verifyTallyProof(_proof, numSignUps, batchStartIndex, tallyBatchSize, _newTallyCommitment);

if (!isValid) {
revert InvalidTallyVotesProof();
}

// Update the tally commitment and the tally batch num
tallyCommitment = _newTallyCommitment;
}

/// @notice Verify the tally proof using the verifying key
/// @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 isValid whether the proof is valid
function verifyTallyProof(
uint256[8] calldata _proof,
uint256 _numSignUps,
uint256 _batchStartIndex,
uint256 _tallyBatchSize,
uint256 _newTallyCommitment
) public view returns (bool isValid) {
(uint8 intStateTreeDepth, , , uint8 voteOptionTreeDepth) = poll.treeDepths();

(IMACI maci, , ) = poll.extContracts();

// Get the verifying key
VerifyingKey memory vk = vkRegistry.getTallyVk(maci.stateTreeDepth(), intStateTreeDepth, voteOptionTreeDepth, mode);

// Get the public inputs
uint256 publicInputHash = genTallyVotesPublicInputHash(
_numSignUps,
_batchStartIndex,
_tallyBatchSize,
_newTallyCommitment
);

// Verify the proof
isValid = verifier.verify(_proof, vk, publicInputHash);
}

/// @notice Compute the merkle root from the path elements
/// and a leaf
/// @param _depth the depth of the merkle tree
/// @param _index the index of the leaf
/// @param _leaf the leaf
/// @param _pathElements the path elements to reconstruct the merkle root
/// @return current The merkle root
function computeMerkleRootFromPath(
uint8 _depth,
uint256 _index,
uint256 _leaf,
uint256[][] calldata _pathElements
) internal pure returns (uint256 current) {
uint256 pos = _index % VOTE_OPTION_TREE_ARITY;
current = _leaf;
uint8 k;

uint256[VOTE_OPTION_TREE_ARITY] memory level;

for (uint8 i = 0; i < _depth; ++i) {
for (uint8 j = 0; j < VOTE_OPTION_TREE_ARITY; ++j) {
if (j == pos) {
level[j] = current;
} else {
if (j > pos) {
k = j - 1;
} else {
k = j;
}
level[j] = _pathElements[i][k];
}
}

_index /= VOTE_OPTION_TREE_ARITY;
pos = _index % VOTE_OPTION_TREE_ARITY;
current = hash5(level);
}
}

/// @notice Verify the number of spent voice credits from the tally.json
/// @param _totalSpent spent field retrieved in the totalSpentVoiceCredits object
/// @param _totalSpentSalt the corresponding salt in the totalSpentVoiceCredit object
/// @param _resultCommitment hashLeftRight(merkle root of the results.tally, results.salt) in tally.json file
/// @param _perVOSpentVoiceCreditsHash only for QV - hashLeftRight(merkle root of the no spent voice credits, salt)
/// @return isValid Whether the provided values are valid
function verifySpentVoiceCredits(
uint256 _totalSpent,
uint256 _totalSpentSalt,
uint256 _resultCommitment,
uint256 _perVOSpentVoiceCreditsHash
) public view returns (bool isValid) {
uint256[3] memory tally;
tally[0] = _resultCommitment;
tally[1] = hashLeftRight(_totalSpent, _totalSpentSalt);
tally[2] = _perVOSpentVoiceCreditsHash;

if (mode == Mode.QV) {
isValid = verifyQvSpentVoiceCredits(_totalSpent, _totalSpentSalt, _resultCommitment, _perVOSpentVoiceCreditsHash);
} else if (mode == Mode.NON_QV) {
isValid = verifyNonQvSpentVoiceCredits(_totalSpent, _totalSpentSalt, _resultCommitment);
}
}

/// @notice Verify the number of spent voice credits for QV from the tally.json
/// @param _totalSpent spent field retrieved in the totalSpentVoiceCredits object
/// @param _totalSpentSalt the corresponding salt in the totalSpentVoiceCredit object
/// @param _resultCommitment hashLeftRight(merkle root of the results.tally, results.salt) in tally.json file
/// @param _perVOSpentVoiceCreditsHash hashLeftRight(merkle root of the no spent voice credits per vote option, salt)
/// @return isValid Whether the provided values are valid
function verifyQvSpentVoiceCredits(
uint256 _totalSpent,
uint256 _totalSpentSalt,
uint256 _resultCommitment,
uint256 _perVOSpentVoiceCreditsHash
) internal view returns (bool isValid) {
uint256[3] memory tally;
tally[0] = _resultCommitment;
tally[1] = hashLeftRight(_totalSpent, _totalSpentSalt);
tally[2] = _perVOSpentVoiceCreditsHash;

isValid = hash3(tally) == tallyCommitment;
}

/// @notice Verify the number of spent voice credits for Non-QV from the tally.json
/// @param _totalSpent spent field retrieved in the totalSpentVoiceCredits object
/// @param _totalSpentSalt the corresponding salt in the totalSpentVoiceCredit object
/// @param _resultCommitment hashLeftRight(merkle root of the results.tally, results.salt) in tally.json file
/// @return isValid Whether the provided values are valid
function verifyNonQvSpentVoiceCredits(
uint256 _totalSpent,
uint256 _totalSpentSalt,
uint256 _resultCommitment
) internal view returns (bool isValid) {
uint256[2] memory tally;
tally[0] = _resultCommitment;
tally[1] = hashLeftRight(_totalSpent, _totalSpentSalt);

isValid = hash2(tally) == tallyCommitment;
}

/// @notice Verify the number of spent voice credits per vote option from the tally.json
/// @param _voteOptionIndex the index of the vote option where credits were spent
/// @param _spent the spent voice credits for a given vote option index
/// @param _spentProof proof generated for the perVOSpentVoiceCredits
/// @param _spentSalt the corresponding salt given in the tally perVOSpentVoiceCredits object
/// @param _voteOptionTreeDepth depth of the vote option tree
/// @param _spentVoiceCreditsHash hashLeftRight(number of spent voice credits, spent salt)
/// @param _resultCommitment hashLeftRight(merkle root of the results.tally, results.salt)
// in the tally.json file
/// @return isValid Whether the provided proof is valid
function verifyPerVOSpentVoiceCredits(
uint256 _voteOptionIndex,
uint256 _spent,
uint256[][] calldata _spentProof,
uint256 _spentSalt,
uint8 _voteOptionTreeDepth,
uint256 _spentVoiceCreditsHash,
uint256 _resultCommitment
) public view returns (bool isValid) {
if (mode != Mode.QV) {
revert NotSupported();
}

uint256 computedRoot = computeMerkleRootFromPath(_voteOptionTreeDepth, _voteOptionIndex, _spent, _spentProof);

uint256[3] memory tally;
tally[0] = _resultCommitment;
tally[1] = _spentVoiceCreditsHash;
tally[2] = hashLeftRight(computedRoot, _spentSalt);

isValid = hash3(tally) == tallyCommitment;
}

/// @notice Verify the result generated from the tally.json
/// @param _voteOptionIndex the index of the vote option to verify the correctness of the tally
/// @param _tallyResult Flattened array of the tally
/// @param _tallyResultProof Corresponding proof of the tally result
/// @param _tallyResultSalt the respective salt in the results object in the tally.json
/// @param _voteOptionTreeDepth depth of the vote option tree
/// @param _spentVoiceCreditsHash hashLeftRight(number of spent voice credits, spent salt)
/// @param _perVOSpentVoiceCreditsHash hashLeftRight(merkle root of the no spent voice
/// credits per vote option, perVOSpentVoiceCredits salt)
/// @return isValid Whether the provided proof is valid
function verifyTallyResult(
uint256 _voteOptionIndex,
uint256 _tallyResult,
uint256[][] calldata _tallyResultProof,
uint256 _tallyResultSalt,
uint8 _voteOptionTreeDepth,
uint256 _spentVoiceCreditsHash,
uint256 _perVOSpentVoiceCreditsHash
) public view returns (bool isValid) {
uint256 computedRoot = computeMerkleRootFromPath(
_voteOptionTreeDepth,
_voteOptionIndex,
_tallyResult,
_tallyResultProof
);

if (mode == Mode.QV) {
uint256[3] memory tally;
tally[0] = hashLeftRight(computedRoot, _tallyResultSalt);
tally[1] = _spentVoiceCreditsHash;
tally[2] = _perVOSpentVoiceCreditsHash;

isValid = hash3(tally) == tallyCommitment;
} else if (mode == Mode.NON_QV) {
uint256[2] memory tally;
tally[0] = hashLeftRight(computedRoot, _tallyResultSalt);
tally[1] = _spentVoiceCreditsHash;

isValid = hash2(tally) == tallyCommitment;
}
}
}

Check warning

Code scanning / Slither

Contracts that lock Ether Medium

Contract locking ether found:
Contract Tally has payable functions:
- Tally.constructor(address,address,address,address,address,DomainObjs.Mode)
But does not have a function to withdraw the ether
3 changes: 1 addition & 2 deletions contracts/contracts/TallyFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ contract TallyFactory is ITallyFactory, DomainObjs {
Mode _mode
) public virtual returns (address tallyAddr) {
// deploy Tally for this Poll
Tally tally = new Tally(_verifier, _vkRegistry, _poll, _messageProcessor, _mode);
tally.transferOwnership(_owner);
Tally tally = new Tally(_verifier, _vkRegistry, _poll, _messageProcessor, _owner, _mode);
tallyAddr = address(tally);
}
}
4 changes: 2 additions & 2 deletions contracts/contracts/interfaces/IPoll.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ interface IPoll {
/// @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.
function mergeMaciStateAq() external;
function mergeMaciState() external;

/// @notice The first step in merging the message AccQueue so that the
/// ProcessMessages circuit can access the message root.
Expand All @@ -48,7 +48,7 @@ interface IPoll {

/// @notice Get the result of whether the MACI contract's stateAq has been merged by this contract
/// @return Whether the MACI contract's stateAq has been merged by this contract
function stateAqMerged() external view returns (bool);
function stateMerged() external view returns (bool);

/// @notice Get the depths of the merkle trees
/// @return intStateTreeDepth The depth of the state tree
Expand Down
4 changes: 1 addition & 3 deletions contracts/contracts/interfaces/IPollFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,13 @@ interface IPollFactory {
/// @param _coordinatorPubKey The coordinator's public key
/// @param _maci The MACI contract interface reference
/// @param _topupCredit The TopupCredit contract
/// @param _pollOwner The owner of the poll
/// @return The deployed Poll contract
function deploy(
uint256 _duration,
Params.MaxValues memory _maxValues,
Params.TreeDepths memory _treeDepths,
DomainObjs.PubKey memory _coordinatorPubKey,
address _maci,
TopupCredit _topupCredit,
address _pollOwner
TopupCredit _topupCredit
) external returns (address);
}
2 changes: 1 addition & 1 deletion contracts/tasks/helpers/ProofGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ export class ProofGenerator {
.queryFilter(pollContract.filters.MergeMessageAq(messageRoot), fromBlock)
.then((events) => events[events.length - 1]?.blockNumber),
pollContract
.queryFilter(pollContract.filters.MergeMaciStateAq(stateRoot, numSignups), fromBlock)
.queryFilter(pollContract.filters.MergeMaciState(stateRoot, numSignups), fromBlock)
.then((events) => events[events.length - 1]?.blockNumber),
]).then((blocks) => Math.max(...blocks));

Expand Down
Loading
Loading