From 08075ea6c100f2668370c1c3bfa9f4b8ffba01a8 Mon Sep 17 00:00:00 2001 From: 0xmad <0xmad@users.noreply.github.com> Date: Tue, 20 Feb 2024 16:19:45 -0600 Subject: [PATCH] feat(cli): add get poll cli command - [x] Add get poll cli command - [x] Add indexed params for deploy poll event --- cli/testScript.sh | 2 + cli/tests/unit/poll.test.ts | 58 ++++++++++++++++++++++++++++ cli/ts/commands/index.ts | 1 + cli/ts/commands/poll.ts | 60 +++++++++++++++++++++++++++++ cli/ts/commands/signup.ts | 8 ++-- cli/ts/index.ts | 25 ++++++++++++ cli/ts/sdk/index.ts | 9 +++-- cli/ts/utils/index.ts | 3 ++ cli/ts/utils/interfaces.ts | 75 ++++++++++++++++++++++++++++++++++++ contracts/contracts/MACI.sol | 9 ++++- contracts/ts/genMaciState.ts | 5 ++- 11 files changed, 244 insertions(+), 11 deletions(-) create mode 100644 cli/tests/unit/poll.test.ts create mode 100644 cli/ts/commands/poll.ts diff --git a/cli/testScript.sh b/cli/testScript.sh index 9b13155a3f..3b18060993 100755 --- a/cli/testScript.sh +++ b/cli/testScript.sh @@ -14,6 +14,8 @@ node build/ts/index.js create -s 10 node build/ts/index.js deployPoll \ --pubkey macipk.ea638a3366ed91f2e955110888573861f7c0fc0bb5fb8b8dca9cd7a08d7d6b93 \ -t 30 -i 1 -m 2 -b 1 -v 2 -se false +node build/ts/index.js getPoll \ + --quiet false node build/ts/index.js signup \ --pubkey macipk.e743ffb5298ef0f5c1f63b6464a48fea19ea7ee5a885c67ae1b24a1d04f03f07 node build/ts/index.js isRegisteredUser \ diff --git a/cli/tests/unit/poll.test.ts b/cli/tests/unit/poll.test.ts new file mode 100644 index 0000000000..8a3997ce22 --- /dev/null +++ b/cli/tests/unit/poll.test.ts @@ -0,0 +1,58 @@ +import { expect } from "chai"; +import { getDefaultSigner } from "maci-contracts"; + +import type { Signer } from "ethers"; + +import { deploy, deployPoll, deployVkRegistryContract, setVerifyingKeys, getPoll } from "../../ts/commands"; +import { DeployedContracts, PollContracts } from "../../ts/utils"; +import { deployPollArgs, setVerifyingKeysArgs, deployArgs } from "../constants"; +import { cleanVanilla } from "../utils"; + +describe("poll", () => { + let maciAddresses: DeployedContracts; + let pollAddresses: PollContracts; + let signer: Signer; + + // before all tests we deploy the vk registry contract and set the verifying keys + before(async () => { + signer = await getDefaultSigner(); + + // we deploy the vk registry contract + await deployVkRegistryContract({ signer }); + // we set the verifying keys + await setVerifyingKeys({ ...setVerifyingKeysArgs, signer }); + }); + + describe("check deploy and get poll", () => { + after(() => { + cleanVanilla(); + }); + + before(async () => { + // deploy the smart contracts + maciAddresses = await deploy({ ...deployArgs, signer }); + // deploy a poll contract + pollAddresses = await deployPoll({ ...deployPollArgs, signer }); + }); + + it("should get current poll properly", async () => { + const pollData = await getPoll({ maciAddress: maciAddresses.maciAddress, signer }); + const samePollData = await getPoll({ maciAddress: maciAddresses.maciAddress, pollId: pollData.id, signer }); + + expect(pollData.address).to.eq(pollAddresses.poll); + expect(pollData).to.deep.eq(samePollData); + }); + + it("should throw error if current poll id is invalid", async () => { + await expect(getPoll({ maciAddress: maciAddresses.maciAddress, pollId: -1n, signer })).eventually.rejectedWith( + "Invalid poll id", + ); + }); + + it("should throw error if current poll is not deployed", async () => { + await expect(getPoll({ maciAddress: maciAddresses.maciAddress, pollId: 9000n, signer })).eventually.rejectedWith( + "MACI contract doesn't have any deployed poll", + ); + }); + }); +}); diff --git a/cli/ts/commands/index.ts b/cli/ts/commands/index.ts index 2a10da0e08..0258170a5f 100644 --- a/cli/ts/commands/index.ts +++ b/cli/ts/commands/index.ts @@ -1,6 +1,7 @@ export { airdrop } from "./airdrop"; export { deploy } from "./deploy"; export { deployPoll } from "./deployPoll"; +export { getPoll } from "./poll"; export { deployVkRegistryContract } from "./deployVkRegistry"; export { genKeyPair } from "./genKeyPair"; export { genMaciPubKey } from "./genPubKey"; diff --git a/cli/ts/commands/poll.ts b/cli/ts/commands/poll.ts new file mode 100644 index 0000000000..79183a72f4 --- /dev/null +++ b/cli/ts/commands/poll.ts @@ -0,0 +1,60 @@ +import { ZeroAddress } from "ethers"; +import { MACI__factory as MACIFactory, Poll__factory as PollFactory } from "maci-contracts"; + +import type { IGetPollArgs, IGetPollData } from "../utils/interfaces"; + +import { banner } from "../utils/banner"; +import { logError, logGreen, success } from "../utils/theme"; + +/** + * Get deployed poll from MACI contract + * @param {IGetPollArgs} args - The arguments for the get poll command + * @returns {IGetPollData} poll data + */ +export const getPoll = async ({ maciAddress, signer, pollId, quiet = true }: IGetPollArgs): Promise => { + banner(quiet); + + const maciContract = MACIFactory.connect(maciAddress, signer); + const id = + pollId === undefined ? await maciContract.nextPollId().then((nextPollId) => nextPollId - 1n) : BigInt(pollId); + + if (id < 0n) { + logError("Invalid poll id"); + } + + const pollAddress = await maciContract.polls(id); + + if (pollAddress === ZeroAddress) { + logError("MACI contract doesn't have any deployed poll"); + } + + const pollContract = PollFactory.connect(pollAddress, signer); + + const [[deployTime, duration], isStateAqMerged, numSignups] = await Promise.all([ + pollContract.getDeployTimeAndDuration(), + pollContract.stateAqMerged(), + pollContract.numSignups(), + ]); + + logGreen( + quiet, + success( + [ + `ID: ${id}`, + `Deploy time: ${new Date(Number(deployTime) * 1000).toString()}`, + `End time: ${new Date(Number(deployTime + duration) * 1000).toString()}`, + `Number of signups ${numSignups}`, + `State Aq merged: ${isStateAqMerged}`, + ].join("\n"), + ), + ); + + return { + id, + address: pollAddress, + deployTime, + duration, + numSignups, + isStateAqMerged, + }; +}; diff --git a/cli/ts/commands/signup.ts b/cli/ts/commands/signup.ts index 5c3b9c3764..8f51f728d2 100644 --- a/cli/ts/commands/signup.ts +++ b/cli/ts/commands/signup.ts @@ -1,7 +1,7 @@ import { MACI__factory as MACIFactory } from "maci-contracts/typechain-types"; import { PubKey } from "maci-domainobjs"; -import type { IRegisteredUserArgs, SignupArgs } from "../utils/interfaces"; +import type { IRegisteredUserArgs, ISignupData, SignupArgs } from "../utils/interfaces"; import type { ContractTransactionReceipt } from "ethers"; import { banner } from "../utils/banner"; @@ -11,8 +11,8 @@ import { info, logError, logGreen, logYellow, success } from "../utils/theme"; /** * Signup a user to the MACI contract - * @param SignupArgs - The arguments for the signup command - * @returns The state index of the user and transaction hash + * @param {SignupArgs} args - The arguments for the signup command + * @returns {ISignupData} The state index of the user and transaction hash */ export const signup = async ({ maciPubKey, @@ -21,7 +21,7 @@ export const signup = async ({ ivcpDataArg, signer, quiet = true, -}: SignupArgs): Promise<{ stateIndex: string; hash: string }> => { +}: SignupArgs): Promise => { banner(quiet); // validate user key diff --git a/cli/ts/index.ts b/cli/ts/index.ts index 69f1c76676..bdd2d888ca 100644 --- a/cli/ts/index.ts +++ b/cli/ts/index.ts @@ -16,6 +16,7 @@ import { deploy, showContracts, deployPoll, + getPoll, mergeMessages, publish, setVerifyingKeys, @@ -432,6 +433,29 @@ program program.error((error as Error).message, { exitCode: 1 }); } }); +program + .command("getPoll") + .description("Get deployed poll from MACI contract") + .option("-p, --poll ", "the poll id") + .option("-x, --maci-address ", "the MACI contract address") + .option("-q, --quiet ", "whether to print values to the console", (value) => value === "true", false) + .action(async (cmdObj) => { + try { + const signer = await getSigner(); + const network = await signer.provider?.getNetwork(); + + const maciContractAddress = cmdObj.maciAddress || readContractAddress("MACI", network?.name); + + await getPoll({ + pollId: cmdObj.poll, + maciAddress: maciContractAddress, + signer, + quiet: cmdObj.quiet, + }); + } catch (error) { + program.error((error as Error).message, { exitCode: 1 }); + } + }); program .command("topup") .description("Top up an account with voice credits") @@ -691,6 +715,7 @@ export { checkVerifyingKeys, deploy, deployPoll, + getPoll, deployVkRegistryContract, fundWallet, genLocalState, diff --git a/cli/ts/sdk/index.ts b/cli/ts/sdk/index.ts index bddbab007a..7d3a818b34 100644 --- a/cli/ts/sdk/index.ts +++ b/cli/ts/sdk/index.ts @@ -1,19 +1,22 @@ import { genKeyPair } from "../commands/genKeyPair"; import { genMaciPubKey } from "../commands/genPubKey"; +import { getPoll } from "../commands/poll"; import { publish } from "../commands/publish"; import { signup, isRegisteredUser } from "../commands/signup"; import { verify } from "../commands/verify"; -export { genKeyPair, genMaciPubKey, publish, signup, isRegisteredUser, verify }; +export { genKeyPair, genMaciPubKey, publish, signup, isRegisteredUser, verify, getPoll }; export type { Signer } from "ethers"; export type { - DeployedContracts, - PollContracts, TallyData, SubsidyData, PublishArgs, SignupArgs, + ISignupData, VerifyArgs, + IGetPollArgs, + IGetPollData, + IRegisteredUserArgs, } from "../utils"; diff --git a/cli/ts/utils/index.ts b/cli/ts/utils/index.ts index c861208864..6500faa4dd 100644 --- a/cli/ts/utils/index.ts +++ b/cli/ts/utils/index.ts @@ -27,6 +27,7 @@ export type { FundWalletArgs, TimeTravelArgs, SignupArgs, + ISignupData, SetVerifyingKeysArgs, MergeMessagesArgs, MergeSignupsArgs, @@ -40,6 +41,8 @@ export type { SubsidyData, IRegisteredUserArgs, IGenKeypairArgs, + IGetPollArgs, + IGetPollData, } from "./interfaces"; export { compareVks } from "./vks"; export { delay } from "./time"; diff --git a/cli/ts/utils/interfaces.ts b/cli/ts/utils/interfaces.ts index bf970912b9..9a4101d394 100644 --- a/cli/ts/utils/interfaces.ts +++ b/cli/ts/utils/interfaces.ts @@ -860,6 +860,21 @@ export interface SignupArgs { quiet?: boolean; } +/** + * Interface for the return data to the signup command + */ +export interface ISignupData { + /** + * The state index of the user + */ + stateIndex: string; + + /** + * The signup transaction hash + */ + hash: string; +} + /** * Interface for the arguments to the register check command */ @@ -885,6 +900,66 @@ export interface IRegisteredUserArgs { quiet?: boolean; } +/** + * Interface for the arguments to the get poll command + */ +export interface IGetPollArgs { + /** + * A signer object + */ + signer: Signer; + + /** + * The address of the MACI contract + */ + maciAddress: string; + + /** + * The poll id. If not specified, latest poll id will be used + */ + pollId?: BigNumberish; + + /** + * Whether to log the output + */ + quiet?: boolean; +} + +/** + * Interface for the return data to the get poll command + */ +export interface IGetPollData { + /** + * The poll id + */ + id: BigNumberish; + + /** + * The poll address + */ + address: string; + + /** + * The poll deployment time + */ + deployTime: BigNumberish; + + /** + * The poll duration + */ + duration: BigNumberish; + + /** + * The poll number of signups + */ + numSignups: BigNumberish; + + /** + * Whether the MACI contract's stateAq has been merged by this contract + */ + isStateAqMerged: boolean; +} + /** * Interface for the arguments to the topup command */ diff --git a/contracts/contracts/MACI.sol b/contracts/contracts/MACI.sol index e776468128..0ad24318eb 100644 --- a/contracts/contracts/MACI.sol +++ b/contracts/contracts/MACI.sol @@ -87,7 +87,12 @@ contract MACI is IMACI, Params, Utilities, Ownable { uint256 _voiceCreditBalance, uint256 _timestamp ); - event DeployPoll(uint256 _pollId, PubKey _pubKey, PollContracts pollAddr); + 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) { @@ -254,7 +259,7 @@ contract MACI is IMACI, Params, Utilities, Ownable { // store the addresses in a struct so they can be returned pollAddr = PollContracts({ poll: p, messageProcessor: mp, tally: tally, subsidy: subsidy }); - emit DeployPoll(pollId, _coordinatorPubKey, pollAddr); + emit DeployPoll(pollId, _coordinatorPubKey.x, _coordinatorPubKey.y, pollAddr); } /// @inheritdoc IMACI diff --git a/contracts/ts/genMaciState.ts b/contracts/ts/genMaciState.ts index 0c05daed55..371797ec28 100644 --- a/contracts/ts/genMaciState.ts +++ b/contracts/ts/genMaciState.ts @@ -117,7 +117,8 @@ export const genMaciStateFromContract = async ( const mutableLogs = { ...log, topics: [...log.topics] }; const event = maciIface.parseLog(mutableLogs) as unknown as { args: { - _pubKey: string[]; + _coordinatorPubKeyX: string; + _coordinatorPubKeyY: string; _pollId: bigint; pollAddr: { poll: string; @@ -127,7 +128,7 @@ export const genMaciStateFromContract = async ( }; }; - const pubKey = new PubKey(event.args._pubKey.map((x) => BigInt(x.toString())) as [bigint, bigint]); + const pubKey = new PubKey([BigInt(event.args._coordinatorPubKeyX), BigInt(event.args._coordinatorPubKeyY)]); const p = event.args._pollId; assert(p === index);