Skip to content

Commit

Permalink
chore(contracts): add merge signups and messages task
Browse files Browse the repository at this point in the history
- [x] Add TreeMerger class to simplify merging
- [x] Move contract types to types file
  • Loading branch information
0xmad authored and ctrlc03 committed Feb 7, 2024
1 parent cbbe5b9 commit 5ee0237
Show file tree
Hide file tree
Showing 8 changed files with 554 additions and 48 deletions.
1 change: 1 addition & 0 deletions contracts/hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import "./tasks/deploy";
import { EChainId, ESupportedChains, NETWORKS_DEFAULT_GAS, getNetworkRpcUrls } from "./tasks/helpers/constants";
import "./tasks/runner/deployFull";
import "./tasks/runner/deployPoll";
import "./tasks/runner/merge";
import "./tasks/runner/verifyFull";

dotenv.config();
Expand Down
3 changes: 3 additions & 0 deletions contracts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,13 @@
"deploy": "hardhat deploy-full",
"deploy-poll": "hardhat deploy-poll",
"verify": "hardhat verify-full",
"merge": "hardhat merge",
"deploy:localhost": "pnpm run deploy",
"deploy:sepolia": "pnpm run deploy --network sepolia",
"deploy-poll:localhost": "pnpm run deploy-poll",
"deploy-poll:sepolia": "pnpm run deploy-poll --network sepolia",
"merge:localhost": "pnpm run merge",
"merge:sepolia": "pnpm run merge --network sepolia",
"verify:sepolia": "pnpm run verify --network sepolia"
},
"dependencies": {
Expand Down
15 changes: 1 addition & 14 deletions contracts/tasks/deploy/maci/11-vkRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,10 @@ import { extractVk } from "maci-circuits";
import { VerifyingKey } from "maci-domainobjs";

import type { IVerifyingKeyStruct } from "../../../ts";
import type { BaseContract, BaseContractMethod, BigNumberish, TransactionResponse } from "ethers";

import { ContractStorage } from "../../helpers/ContractStorage";
import { Deployment } from "../../helpers/Deployment";
import { EContracts, IDeployParams } from "../../helpers/types";

// Add type manually because typechain is not available during compilation
interface VkRegistry extends BaseContract {
setVerifyingKeys: BaseContractMethod<
[BigNumberish, BigNumberish, BigNumberish, BigNumberish, BigNumberish, IVerifyingKeyStruct, IVerifyingKeyStruct],
TransactionResponse
>;
setSubsidyKeys: BaseContractMethod<
[BigNumberish, BigNumberish, BigNumberish, IVerifyingKeyStruct],
TransactionResponse
>;
}
import { EContracts, type IDeployParams, type VkRegistry } from "../../helpers/types";

const deployment = Deployment.getInstance();
const storage = ContractStorage.getInstance();
Expand Down
36 changes: 3 additions & 33 deletions contracts/tasks/deploy/poll/01-poll.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,15 @@
/* eslint-disable no-console */
import { BaseContract, BaseContractMethod, BigNumberish, TransactionResponse } from "ethers";
import { IG1ContractParams, PubKey } from "maci-domainobjs";
import { BaseContract } from "ethers";
import { PubKey } from "maci-domainobjs";

import { parseArtifact } from "../../../ts/abi";
import { ContractStorage } from "../../helpers/ContractStorage";
import { Deployment } from "../../helpers/Deployment";
import { EContracts } from "../../helpers/types";
import { EContracts, type MACI, type Poll, type StateAq, type TDeployPollParams } from "../../helpers/types";

const deployment = Deployment.getInstance();
const storage = ContractStorage.getInstance();

// Add type manually because typechain is not available during compilation
type TDeployPollParams = [
BigNumberish,
{
intStateTreeDepth: BigNumberish;
messageTreeSubDepth: BigNumberish;
messageTreeDepth: BigNumberish;
voteOptionTreeDepth: BigNumberish;
},
IG1ContractParams,
string,
string,
boolean,
];

interface MACI extends BaseContract {
nextPollId: () => Promise<bigint>;
stateAq: () => Promise<string>;
deployPoll: BaseContractMethod<TDeployPollParams, TransactionResponse & [string]>;
}

interface StateAq extends BaseContract {
treeMerged: () => Promise<boolean>;
}

interface Poll extends BaseContract {
maxValues: () => Promise<[BigNumberish, BigNumberish]>;
extContracts: () => Promise<[string, string, string]>;
}

/**
* Deploy step registration and task itself
*/
Expand Down
213 changes: 213 additions & 0 deletions contracts/tasks/helpers/TreeMerger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
/* eslint-disable no-console */
import { type BigNumberish } from "ethers";

import type { ITreeMergeParams, MACI, Poll, StateAq } from "./types";
import type { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers";

/**
* @notice Tree merger keeps merging simple for hardhat task.
* This class is using for merging signups and messages.
*/
export class TreeMerger {
/**
* MACI contract
*/
private maciContract: MACI;

/**
* User signups AccQueue contract
*/
private signupAccQueueContract: StateAq;

/**
* User messages AccQueue contract
*/
private messageAccQueueContract: StateAq;

/**
* Poll contract
*/
private pollContract: Poll;

/**
* Ethers signer
*/
private deployer: HardhatEthersSigner;

/**
* Initialize class properties
*
* @param {ITreeMergeParams} params - contracts and signer
*/
constructor({
deployer,
signupAccQueueContract,
messageAccQueueContract,
pollContract,
maciContract,
}: ITreeMergeParams) {
this.maciContract = maciContract;
this.pollContract = pollContract;
this.signupAccQueueContract = signupAccQueueContract;
this.messageAccQueueContract = messageAccQueueContract;
this.deployer = deployer;
}

/**
* Check if signer is an owner. Otherwise, throw an error.
*/
async checkPollOwner(): Promise<void> {
const pollOwner = await this.pollContract.owner();
const deployerAddress = await this.deployer.getAddress();

if (pollOwner.toLowerCase() !== deployerAddress.toLowerCase()) {
throw new Error("The signer is not the owner of this Poll contract");
}
}

/**
* Check if voting period is over. Otherwise, throw an error.
*/
async checkPollDuration(): Promise<void> {
// check if it's time to merge the message AQ
const [deployTime, duration] = await this.pollContract.getDeployTimeAndDuration();
const deadline = Number(deployTime) + Number(duration);

const blockNum = await this.deployer.provider.getBlockNumber();
const block = await this.deployer.provider.getBlock(blockNum);
const now = Number(block?.timestamp);

if (now < deadline) {
throw new Error("Voting period is not over");
}
}

/**
* Merge user signup subtrees
*
* @param pollId - poll id
* @param queueOps - the number of queue operations to perform
*/
async mergeSignupSubtrees(pollId: BigNumberish, queueOps: number): Promise<void> {
let subTreesMerged = false;

// infinite loop to merge the sub trees
while (!subTreesMerged) {
// eslint-disable-next-line no-await-in-loop
subTreesMerged = await this.signupAccQueueContract.subTreesMerged();

if (subTreesMerged) {
console.log("All state subtrees have been merged.");
} else {
// eslint-disable-next-line no-await-in-loop
await this.signupAccQueueContract.getSrIndices().then((indices) => {
console.log(`Merging state subroots ${indices[0] + 1n} / ${indices[1] + 1n}`);
});

// first merge the subroots
// eslint-disable-next-line no-await-in-loop
const receipt = await this.pollContract
.mergeMaciStateAqSubRoots(queueOps, pollId.toString())
.then((tx) => tx.wait());

if (receipt?.status !== 1) {
throw new Error("Error merging state subroots");
}

console.log(`Transaction hash: ${receipt.hash}`);
console.log(`Executed mergeMaciStateAqSubRoots(); gas used: ${receipt.gasUsed.toString()}`);
}
}
}

/**
* Merge user signup MACI state
*
* @param pollId - poll id
*/
async mergeSignups(pollId: BigNumberish): Promise<void> {
// check if the state AQ has been fully merged
const stateTreeDepth = Number(await this.maciContract.stateTreeDepth());
const mainRoot = await this.signupAccQueueContract.getMainRoot(stateTreeDepth.toString());

if (mainRoot.toString() === "0" || BigInt(pollId) > 0n) {
// go and merge the state tree
console.log("Merging subroots to a main state root...");
const receipt = await this.pollContract.mergeMaciStateAq(pollId.toString()).then((tx) => tx.wait());

if (receipt?.status !== 1) {
throw new Error("Error merging signup state subroots");
}

console.log(`Transaction hash: ${receipt.hash}`);
console.log(`Executed mergeStateAq(); gas used: ${receipt.gasUsed.toString()}`);
} else {
console.log("The signup state tree has already been merged.");
}
}

/**
* Merge message subtrees
*
* @param queueOps - the number of queue operations to perform
*/
async mergeMessageSubtrees(queueOps: number): Promise<void> {
let subTreesMerged = false;

// infinite loop to merge the sub trees
while (!subTreesMerged) {
// eslint-disable-next-line no-await-in-loop
subTreesMerged = await this.messageAccQueueContract.subTreesMerged();

if (subTreesMerged) {
console.log("All message subtrees have been merged.");
} else {
// eslint-disable-next-line no-await-in-loop
await this.messageAccQueueContract.getSrIndices().then((indices) => {
console.log(`Merging message subroots ${indices[0] + 1n} / ${indices[1] + 1n}`);
});

// eslint-disable-next-line no-await-in-loop
const tx = await this.pollContract.mergeMessageAqSubRoots(queueOps);
// eslint-disable-next-line no-await-in-loop
const receipt = await tx.wait();

if (receipt?.status !== 1) {
throw new Error("Merge message subroots transaction failed");
}

console.log(`Executed mergeMessageAqSubRoots(); gas used: ${receipt.gasUsed.toString()}`);

console.log(`Transaction hash: ${receipt.hash}`);
}
}
}

/**
* Merge message queue
*/
async mergeMessages(): Promise<void> {
// check if the message AQ has been fully merged
const messageTreeDepth = await this.pollContract.treeDepths().then((depths) => Number(depths[2]));

// check if the main root was not already computed
const mainRoot = await this.messageAccQueueContract.getMainRoot(messageTreeDepth.toString());
if (mainRoot.toString() === "0") {
// go and merge the message tree

console.log("Merging subroots to a main message root...");
const tx = await this.pollContract.mergeMessageAq();
const receipt = await tx.wait();

if (receipt?.status !== 1) {
throw new Error("Merge messages transaction failed");
}

console.log(`Executed mergeMessageAq(); gas used: ${receipt.gasUsed.toString()}`);
console.log(`Transaction hash: ${receipt.hash}`);
console.log("The message tree has been merged.");
} else {
console.log("The message tree has already been merged.");
}
}
}
Loading

0 comments on commit 5ee0237

Please sign in to comment.