Skip to content

Commit

Permalink
feat(contracts): allow for dynamic emptyBallotRoots (#1695)
Browse files Browse the repository at this point in the history
Pass emptyBallotRoots as argument to the maci contract and then the specific ballot root to each
deployed poll based on the vote option tree depth. This simplifies the process and does not require
setting a .env variable before deploying contracts.
  • Loading branch information
ctrlc03 committed Jul 25, 2024
1 parent 6122dff commit e7aa4dd
Show file tree
Hide file tree
Showing 16 changed files with 75 additions and 98 deletions.
3 changes: 0 additions & 3 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@ on:
branches: [dev]
pull_request:

env:
STATE_TREE_DEPTH: ${{ vars.STATE_TREE_DEPTH }}

concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
Expand Down
3 changes: 0 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@ on:
push:
tags: ["*"]

env:
STATE_TREE_DEPTH: ${{ vars.STATE_TREE_DEPTH }}

jobs:
draft-release:
runs-on: ubuntu-22.04
Expand Down
30 changes: 21 additions & 9 deletions contracts/contracts/MACI.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ contract MACI is IMACI, DomainObjs, Params, Utilities {
uint256 internal constant BLANK_STATE_LEAF_HASH =
uint256(6769006970205099520508948723718471724660867171122235270773600567925038008762);

/// @notice The roots of the empty ballot trees
uint256[5] public emptyBallotRoots;

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

Expand Down Expand Up @@ -96,13 +99,15 @@ contract MACI is IMACI, DomainObjs, Params, Utilities {
/// @param _signUpGatekeeper The SignUpGatekeeper contract
/// @param _initialVoiceCreditProxy The InitialVoiceCreditProxy contract
/// @param _stateTreeDepth The depth of the state tree
/// @param _emptyBallotRoots The roots of the empty ballot trees
constructor(
IPollFactory _pollFactory,
IMessageProcessorFactory _messageProcessorFactory,
ITallyFactory _tallyFactory,
SignUpGatekeeper _signUpGatekeeper,
InitialVoiceCreditProxy _initialVoiceCreditProxy,
uint8 _stateTreeDepth
uint8 _stateTreeDepth,
uint256[5] memory _emptyBallotRoots
) payable {
// initialize and insert the blank leaf
InternalLazyIMT._init(lazyIMTData, _stateTreeDepth);
Expand All @@ -115,6 +120,7 @@ contract MACI is IMACI, DomainObjs, Params, Utilities {
initialVoiceCreditProxy = _initialVoiceCreditProxy;
stateTreeDepth = _stateTreeDepth;
maxSignups = uint256(TREE_ARITY) ** uint256(_stateTreeDepth);
emptyBallotRoots = _emptyBallotRoots;

// Verify linked poseidon libraries
if (hash2([uint256(1), uint256(1)]) == 0) revert PoseidonHashLibrariesNotLinked();
Expand Down Expand Up @@ -191,18 +197,24 @@ contract MACI is IMACI, DomainObjs, Params, Utilities {
revert InvalidPubKey();
}

uint256 voteOptionTreeDepth = _treeDepths.voteOptionTreeDepth;

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

// 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));

address mp = messageProcessorFactory.deploy(_verifier, _vkRegistry, p, _msgSender, _mode);
address tally = tallyFactory.deploy(_verifier, _vkRegistry, p, mp, _msgSender, _mode);
address p = pollFactory.deploy(
_duration,
maxValues,
_treeDepths,
_coordinatorPubKey,
address(this),
emptyBallotRoots[voteOptionTreeDepth - 1]
);

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

polls[pollId] = p;

Expand Down
13 changes: 9 additions & 4 deletions contracts/contracts/Poll.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ pragma solidity ^0.8.20;

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";
import { CurveBabyJubJub } from "./crypto/BabyJubJub.sol";
Expand All @@ -13,7 +12,7 @@ import { CurveBabyJubJub } from "./crypto/BabyJubJub.sol";
/// which can be either votes or key change 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, EmptyBallotRoots, IPoll {
contract Poll is Params, Utilities, SnarkCommon, IPoll {
/// @notice Whether the Poll has been initialized
bool internal isInit;

Expand All @@ -32,6 +31,9 @@ contract Poll is Params, Utilities, SnarkCommon, EmptyBallotRoots, IPoll {
// The duration of the polling period, in seconds
uint256 internal immutable duration;

/// @notice The root of the empty ballot tree at a given voteOptionTree depth
uint256 public immutable emptyBallotRoot;

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

Expand Down Expand Up @@ -89,7 +91,8 @@ contract Poll is Params, Utilities, SnarkCommon, EmptyBallotRoots, IPoll {
MaxValues memory _maxValues,
TreeDepths memory _treeDepths,
PubKey memory _coordinatorPubKey,
ExtContracts memory _extContracts
ExtContracts memory _extContracts,
uint256 _emptyBallotRoot
) payable {
// check that the coordinator public key is valid
if (!CurveBabyJubJub.isOnCurve(_coordinatorPubKey.x, _coordinatorPubKey.y)) {
Expand All @@ -110,6 +113,8 @@ contract Poll is Params, Utilities, SnarkCommon, EmptyBallotRoots, IPoll {
treeDepths = _treeDepths;
// Record the current timestamp
deployTime = block.timestamp;
// store the empty ballot root
emptyBallotRoot = _emptyBallotRoot;
}

/// @notice A modifier that causes the function to revert if the voting period is
Expand Down Expand Up @@ -207,7 +212,7 @@ contract Poll is Params, Utilities, SnarkCommon, EmptyBallotRoots, IPoll {
// Set currentSbCommitment
uint256[3] memory sb;
sb[0] = _mergedStateRoot;
sb[1] = emptyBallotRoots[treeDepths.voteOptionTreeDepth - 1];
sb[1] = emptyBallotRoot;
sb[2] = uint256(0);

currentSbCommitment = hash3(sb);
Expand Down
5 changes: 3 additions & 2 deletions contracts/contracts/PollFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ contract PollFactory is Params, DomainObjs, IPollFactory {
MaxValues calldata _maxValues,
TreeDepths calldata _treeDepths,
PubKey calldata _coordinatorPubKey,
address _maci
address _maci,
uint256 _emptyBallotRoot
) public virtual returns (address pollAddr) {
/// @notice Validate _maxValues
/// maxVoteOptions must be less than 2 ** 50 due to circuit limitations;
Expand All @@ -43,7 +44,7 @@ contract PollFactory is Params, DomainObjs, IPollFactory {
ExtContracts memory extContracts = ExtContracts({ maci: IMACI(_maci), messageAq: messageAq });

// deploy the poll
Poll poll = new Poll(_duration, _maxValues, _treeDepths, _coordinatorPubKey, extContracts);
Poll poll = new Poll(_duration, _maxValues, _treeDepths, _coordinatorPubKey, extContracts, _emptyBallotRoot);

// Make the Poll contract own the messageAq contract, so only it can
// run enqueue/merge
Expand Down
4 changes: 3 additions & 1 deletion contracts/contracts/interfaces/IPollFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@ interface IPollFactory {
/// @param _treeDepths The depths of the merkle trees
/// @param _coordinatorPubKey The coordinator's public key
/// @param _maci The MACI contract interface reference
/// @param _emptyBallotRoot The root of the empty ballot tree
/// @return The deployed Poll contract
function deploy(
uint256 _duration,
Params.MaxValues memory _maxValues,
Params.TreeDepths memory _treeDepths,
DomainObjs.PubKey memory _coordinatorPubKey,
address _maci
address _maci,
uint256 _emptyBallotRoot
) external returns (address);
}
1 change: 0 additions & 1 deletion contracts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,6 @@
"@nomicfoundation/hardhat-ethers": "^3.0.6",
"@nomicfoundation/hardhat-toolbox": "^5.0.0",
"@openzeppelin/contracts": "^5.0.2",
"@zk-kit/imt.sol": "2.0.0-beta.12",
"circomlibjs": "^0.1.7",
"ethers": "^6.13.1",
"hardhat": "^2.22.6",
Expand Down
5 changes: 0 additions & 5 deletions contracts/scripts/compileSol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import fs from "fs";
import path from "path";

import { buildPoseidonT3, buildPoseidonT4, buildPoseidonT5, buildPoseidonT6 } from "../ts/buildPoseidon";
import { genEmptyBallotRootsContract } from "../ts/genEmptyBallotRootsContract";
import { genZerosContract } from "../ts/genZerosContract";

const PATHS = [
Expand Down Expand Up @@ -75,10 +74,6 @@ async function main(): Promise<void> {
),
);

await genEmptyBallotRootsContract().then((text) =>
fs.promises.writeFile(path.resolve(__dirname, "..", "contracts/trees/EmptyBallotRoots.sol"), `${text}\n`),
);

await hre.run("compile");

await Promise.all([buildPoseidonT3(), buildPoseidonT4(), buildPoseidonT5(), buildPoseidonT6()]);
Expand Down
5 changes: 5 additions & 0 deletions contracts/tasks/deploy/maci/08-maci.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type {
SemaphoreGatekeeper,
} from "../../../typechain-types";

import { genEmptyBallotRoots } from "../../../ts/genEmptyBallotRoots";
import { ContractStorage } from "../../helpers/ContractStorage";
import { Deployment } from "../../helpers/Deployment";
import { EContracts, IDeployParams } from "../../helpers/types";
Expand Down Expand Up @@ -62,6 +63,8 @@ deployment.deployTask("full:deploy-maci", "Deploy MACI contract").then((task) =>
const stateTreeDepth =
deployment.getDeployConfigField<number | null>(EContracts.MACI, "stateTreeDepth") ?? DEFAULT_STATE_TREE_DEPTH;

const emptyBallotRoots = genEmptyBallotRoots(stateTreeDepth);

const maciContract = await deployment.deployContractWithLinkedLibraries<MACI>(
{ contractFactory: maciContractFactory },
pollFactoryContractAddress,
Expand All @@ -70,6 +73,7 @@ deployment.deployTask("full:deploy-maci", "Deploy MACI contract").then((task) =>
gatekeeperContractAddress,
constantInitialVoiceCreditProxyContractAddress,
stateTreeDepth,
emptyBallotRoots,
);

if (gatekeeper === EContracts.EASGatekeeper) {
Expand Down Expand Up @@ -115,6 +119,7 @@ deployment.deployTask("full:deploy-maci", "Deploy MACI contract").then((task) =>
gatekeeperContractAddress,
constantInitialVoiceCreditProxyContractAddress,
stateTreeDepth,
emptyBallotRoots,
],
network: hre.network.name,
});
Expand Down
26 changes: 0 additions & 26 deletions contracts/templates/EmptyBallotRoots.sol.template

This file was deleted.

9 changes: 7 additions & 2 deletions contracts/tests/PollFactory.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,20 @@ import { expect } from "chai";
import { BaseContract, Signer, ZeroAddress } from "ethers";
import { Keypair } from "maci-domainobjs";

import { deployPollFactory, getDefaultSigner } from "../ts";
import { deployPollFactory, genEmptyBallotRoots, getDefaultSigner } from "../ts";
import { PollFactory } from "../typechain-types";

import { maxValues, treeDepths } from "./constants";
import { maxValues, STATE_TREE_DEPTH, treeDepths } from "./constants";

describe("pollFactory", () => {
let pollFactory: PollFactory;
let signer: Signer;

const { pubKey: coordinatorPubKey } = new Keypair();

const emptyBallotRoots = genEmptyBallotRoots(STATE_TREE_DEPTH);
const emptyBallotRoot = emptyBallotRoots[treeDepths.voteOptionTreeDepth];

before(async () => {
signer = await getDefaultSigner();
pollFactory = (await deployPollFactory(signer, true)) as BaseContract as PollFactory;
Expand All @@ -26,6 +29,7 @@ describe("pollFactory", () => {
treeDepths,
coordinatorPubKey.asContractParam(),
ZeroAddress,
emptyBallotRoot,
);
const receipt = await tx.wait();
expect(receipt?.status).to.eq(1);
Expand All @@ -42,6 +46,7 @@ describe("pollFactory", () => {
treeDepths,
coordinatorPubKey.asContractParam(),
ZeroAddress,
emptyBallotRoot,
),
).to.be.revertedWithCustomError(pollFactory, "InvalidMaxValues");
});
Expand Down
4 changes: 4 additions & 0 deletions contracts/ts/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
SemaphoreGatekeeper,
} from "../typechain-types";

import { genEmptyBallotRoots } from "./genEmptyBallotRoots";
import { log } from "./utils";

/**
Expand Down Expand Up @@ -271,6 +272,8 @@ export const deployMaci = async ({
stateTreeDepth = 10,
quiet = true,
}: IDeployMaciArgs): Promise<IDeployedMaci> => {
const emptyBallotRoots = genEmptyBallotRoots(stateTreeDepth);

const { PoseidonT3Contract, PoseidonT4Contract, PoseidonT5Contract, PoseidonT6Contract } =
await deployPoseidonContracts(signer, poseidonAddresses, quiet);

Expand Down Expand Up @@ -328,6 +331,7 @@ export const deployMaci = async ({
signUpTokenGatekeeperContractAddress,
initialVoiceCreditBalanceAddress,
stateTreeDepth,
emptyBallotRoots,
);

return {
Expand Down
22 changes: 22 additions & 0 deletions contracts/ts/genEmptyBallotRoots.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { STATE_TREE_ARITY } from "maci-core";
import { IncrementalQuinTree, hash2 } from "maci-crypto";
import { Ballot } from "maci-domainobjs";

/**
* Generate empty ballot roots for a given state tree depth
* @param stateTreeDepth The depth of the state tree
* @returns The empty ballot roots
*/
export const genEmptyBallotRoots = (stateTreeDepth: number): bigint[] => {
const roots: bigint[] = [];

for (let i = 0; i < 5; i += 1) {
const ballot = new Ballot(0, i + 1);
// The empty Ballot tree root
const ballotTree = new IncrementalQuinTree(stateTreeDepth, ballot.hash(), STATE_TREE_ARITY, hash2);

roots.push(ballotTree.root);
}

return roots;
};
27 changes: 0 additions & 27 deletions contracts/ts/genEmptyBallotRootsContract.ts

This file was deleted.

1 change: 1 addition & 0 deletions contracts/ts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export {
deployVerifier,
} from "./deploy";
export { genMaciStateFromContract } from "./genMaciState";
export { genEmptyBallotRoots } from "./genEmptyBallotRoots";
export { formatProofForVerifierContract, getDefaultSigner, getDefaultNetwork, getSigners } from "./utils";
export { EMode } from "./constants";
export { Deployment } from "../tasks/helpers/Deployment";
Expand Down
Loading

0 comments on commit e7aa4dd

Please sign in to comment.