From 5011207b9dac4a5df94492e25ea5120476c37756 Mon Sep 17 00:00:00 2001 From: 0xmad <0xmad@users.noreply.github.com> Date: Tue, 20 Feb 2024 09:50:42 -0600 Subject: [PATCH] feat(cli): improve cli integration ux - [x] Return transaction hash from signup command - [x] Add indexed params for signup event - [x] Add command to check if user is registered and get state index --- cli/testScript.sh | 3 ++ cli/tests/e2e/e2e.test.ts | 18 +++++++- cli/tests/e2e/keyChange.test.ts | 12 ++++-- cli/ts/commands/index.ts | 2 +- cli/ts/commands/signup.ts | 43 ++++++++++++++++--- cli/ts/index.ts | 27 ++++++++++++ cli/ts/sdk/index.ts | 6 ++- cli/ts/utils/index.ts | 2 + cli/ts/utils/interfaces.ts | 25 +++++++++++ contracts/contracts/MACI.sol | 10 ++++- contracts/deploy-config-example.json | 2 +- contracts/ts/genMaciState.ts | 10 ++++- .../ts/__tests__/integration.test.ts | 2 +- website/versioned_docs/version-v1.x/cli.md | 1 + 14 files changed, 144 insertions(+), 19 deletions(-) diff --git a/cli/testScript.sh b/cli/testScript.sh index ec770a8990..9b13155a3f 100755 --- a/cli/testScript.sh +++ b/cli/testScript.sh @@ -16,6 +16,9 @@ node build/ts/index.js deployPoll \ -t 30 -i 1 -m 2 -b 1 -v 2 -se false node build/ts/index.js signup \ --pubkey macipk.e743ffb5298ef0f5c1f63b6464a48fea19ea7ee5a885c67ae1b24a1d04f03f07 +node build/ts/index.js isRegisteredUser \ + --pubkey macipk.e743ffb5298ef0f5c1f63b6464a48fea19ea7ee5a885c67ae1b24a1d04f03f07 \ + --quiet false node build/ts/index.js publish \ --pubkey macipk.e743ffb5298ef0f5c1f63b6464a48fea19ea7ee5a885c67ae1b24a1d04f03f07 \ --privkey macisk.0ab0281365e01cff60afc62310daec765e590487bf989a7c4986ebc3fd49895e \ diff --git a/cli/tests/e2e/e2e.test.ts b/cli/tests/e2e/e2e.test.ts index dae2712f6f..1b724b962b 100644 --- a/cli/tests/e2e/e2e.test.ts +++ b/cli/tests/e2e/e2e.test.ts @@ -1,3 +1,4 @@ +import { expect } from "chai"; import { getDefaultSigner } from "maci-contracts"; import { genRandomSalt } from "maci-crypto"; import { Keypair } from "maci-domainobjs"; @@ -23,6 +24,7 @@ import { timeTravel, topup, verify, + isRegisteredUser, } from "../../ts/commands"; import { DeployedContracts, GenProofsArgs, PollContracts } from "../../ts/utils"; import { @@ -618,7 +620,7 @@ describe("e2e tests", function test() { maciAddresses = await deploy({ ...deployArgs, signer }); }); - it("should run the first poll", async () => { + it.only("should run the first poll", async () => { // deploy a poll contract pollAddresses = await deployPoll({ ...deployPollArgs, signer }); @@ -627,6 +629,16 @@ describe("e2e tests", function test() { for (let i = 0; i < users.length; i += 1) { // eslint-disable-next-line no-await-in-loop await signup({ maciAddress: maciAddresses.maciAddress, maciPubKey: users[i].pubKey.serialize(), signer }); + + // eslint-disable-next-line no-await-in-loop + const { isRegistered, stateIndex } = await isRegisteredUser({ + maciAddress: maciAddresses.maciAddress, + maciPubKey: users[i].pubKey.serialize(), + signer, + }); + + expect(isRegistered).to.eq(true); + expect(stateIndex).to.not.eq(undefined); } // publish @@ -868,7 +880,9 @@ describe("e2e tests", function test() { it("should signup one user", async () => { stateIndex = BigInt( - await signup({ maciAddress: maciAddresses.maciAddress, maciPubKey: user.pubKey.serialize(), signer }), + await signup({ maciAddress: maciAddresses.maciAddress, maciPubKey: user.pubKey.serialize(), signer }).then( + (result) => result.stateIndex, + ), ); }); diff --git a/cli/tests/e2e/keyChange.test.ts b/cli/tests/e2e/keyChange.test.ts index 420cf06e11..795993d8d5 100644 --- a/cli/tests/e2e/keyChange.test.ts +++ b/cli/tests/e2e/keyChange.test.ts @@ -104,7 +104,9 @@ describe("keyChange tests", function test() { // deploy a poll contract await deployPoll({ ...deployPollArgs, signer }); stateIndex = BigInt( - await signup({ maciAddress: maciAddresses.maciAddress, maciPubKey: keypair1.pubKey.serialize(), signer }), + await signup({ maciAddress: maciAddresses.maciAddress, maciPubKey: keypair1.pubKey.serialize(), signer }).then( + (result) => result.stateIndex, + ), ); await publish({ pubkey: keypair1.pubKey.serialize(), @@ -172,7 +174,9 @@ describe("keyChange tests", function test() { // deploy a poll contract await deployPoll({ ...deployPollArgs, signer }); stateIndex = BigInt( - await signup({ maciAddress: maciAddresses.maciAddress, maciPubKey: keypair1.pubKey.serialize(), signer }), + await signup({ maciAddress: maciAddresses.maciAddress, maciPubKey: keypair1.pubKey.serialize(), signer }).then( + (result) => result.stateIndex, + ), ); await publish({ pubkey: keypair1.pubKey.serialize(), @@ -240,7 +244,9 @@ describe("keyChange tests", function test() { // deploy a poll contract await deployPoll({ ...deployPollArgs, signer }); stateIndex = BigInt( - await signup({ maciAddress: maciAddresses.maciAddress, maciPubKey: keypair1.pubKey.serialize(), signer }), + await signup({ maciAddress: maciAddresses.maciAddress, maciPubKey: keypair1.pubKey.serialize(), signer }).then( + (result) => result.stateIndex, + ), ); await publish({ pubkey: keypair1.pubKey.serialize(), diff --git a/cli/ts/commands/index.ts b/cli/ts/commands/index.ts index a8ff190e71..2a10da0e08 100644 --- a/cli/ts/commands/index.ts +++ b/cli/ts/commands/index.ts @@ -10,7 +10,7 @@ export { publish } from "./publish"; export { setVerifyingKeys } from "./setVerifyingKeys"; export { showContracts } from "./showContracts"; export { timeTravel } from "./timeTravel"; -export { signup } from "./signup"; +export { signup, isRegisteredUser } from "./signup"; export { topup } from "./topup"; export { verify } from "./verify"; export { genProofs } from "./genProofs"; diff --git a/cli/ts/commands/signup.ts b/cli/ts/commands/signup.ts index d6328fb55a..5c3b9c3764 100644 --- a/cli/ts/commands/signup.ts +++ b/cli/ts/commands/signup.ts @@ -1,7 +1,8 @@ import { MACI__factory as MACIFactory } from "maci-contracts/typechain-types"; import { PubKey } from "maci-domainobjs"; -import type { SignupArgs } from "../utils/interfaces"; +import type { IRegisteredUserArgs, SignupArgs } from "../utils/interfaces"; +import type { ContractTransactionReceipt } from "ethers"; import { banner } from "../utils/banner"; import { contractExists } from "../utils/contracts"; @@ -11,7 +12,7 @@ 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 + * @returns The state index of the user and transaction hash */ export const signup = async ({ maciPubKey, @@ -20,7 +21,7 @@ export const signup = async ({ ivcpDataArg, signer, quiet = true, -}: SignupArgs): Promise => { +}: SignupArgs): Promise<{ stateIndex: string; hash: string }> => { banner(quiet); // validate user key @@ -51,10 +52,12 @@ export const signup = async ({ const maciContract = MACIFactory.connect(maciAddress, signer); let stateIndex = ""; + let receipt: ContractTransactionReceipt | null = null; + try { // sign up to the MACI contract const tx = await maciContract.signUp(userMaciPubKey.asContractParam(), sgData, ivcpData); - const receipt = await tx.wait(); + receipt = await tx.wait(); logYellow(quiet, info(`Transaction hash: ${tx.hash}`)); @@ -77,5 +80,35 @@ export const signup = async ({ logError((error as Error).message); } - return stateIndex ? stateIndex.toString() : ""; + return { + stateIndex: stateIndex ? stateIndex.toString() : "", + hash: receipt!.hash, + }; +}; + +/** + * Checks if user is registered with public key + * @param IRegisteredArgs - The arguments for the register check command + * @returns user registered or not and state index + */ +export const isRegisteredUser = async ({ + maciAddress, + maciPubKey, + signer, + quiet = true, +}: IRegisteredUserArgs): Promise<{ isRegistered: boolean; stateIndex?: string }> => { + banner(quiet); + + const maciContract = MACIFactory.connect(maciAddress, signer); + const publicKey = PubKey.deserialize(maciPubKey).asContractParam(); + + const events = await maciContract.queryFilter(maciContract.filters.SignUp(undefined, publicKey.x, publicKey.y)); + const stateIndex = events[0]?.args.toString() as string | undefined; + + logGreen(quiet, success(`State index: ${stateIndex?.toString()}, registered: ${stateIndex !== undefined}`)); + + return { + isRegistered: stateIndex !== undefined, + stateIndex, + }; }; diff --git a/cli/ts/index.ts b/cli/ts/index.ts index d6525a3919..69f1c76676 100644 --- a/cli/ts/index.ts +++ b/cli/ts/index.ts @@ -22,6 +22,7 @@ import { mergeSignups, timeTravel, signup, + isRegisteredUser, topup, verify, genProofs, @@ -408,6 +409,29 @@ program program.error((error as Error).message, { exitCode: 1 }); } }); +program + .command("isRegisteredUser") + .description("Checks if user is registered with public key") + .requiredOption("-p, --pubkey ", "the MACI public key") + .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 isRegisteredUser({ + maciPubKey: cmdObj.pubkey, + 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") @@ -679,6 +703,7 @@ export { proveOnChain, setVerifyingKeys, signup, + isRegisteredUser, timeTravel, topup, verify, @@ -701,4 +726,6 @@ export type { ProveOnChainArgs, DeployArgs, SubsidyData, + IRegisteredUserArgs, + IGenKeypairArgs, } from "./utils"; diff --git a/cli/ts/sdk/index.ts b/cli/ts/sdk/index.ts index de93442cbe..bddbab007a 100644 --- a/cli/ts/sdk/index.ts +++ b/cli/ts/sdk/index.ts @@ -1,10 +1,12 @@ import { genKeyPair } from "../commands/genKeyPair"; import { genMaciPubKey } from "../commands/genPubKey"; import { publish } from "../commands/publish"; -import { signup } from "../commands/signup"; +import { signup, isRegisteredUser } from "../commands/signup"; import { verify } from "../commands/verify"; -export { genKeyPair, genMaciPubKey, publish, signup, verify }; +export { genKeyPair, genMaciPubKey, publish, signup, isRegisteredUser, verify }; + +export type { Signer } from "ethers"; export type { DeployedContracts, diff --git a/cli/ts/utils/index.ts b/cli/ts/utils/index.ts index 42023cc3a1..c861208864 100644 --- a/cli/ts/utils/index.ts +++ b/cli/ts/utils/index.ts @@ -38,6 +38,8 @@ export type { TopupArgs, VerifyArgs, SubsidyData, + IRegisteredUserArgs, + IGenKeypairArgs, } 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 b7ddfe0fdb..bf970912b9 100644 --- a/cli/ts/utils/interfaces.ts +++ b/cli/ts/utils/interfaces.ts @@ -860,6 +860,31 @@ export interface SignupArgs { quiet?: boolean; } +/** + * Interface for the arguments to the register check command + */ +export interface IRegisteredUserArgs { + /** + * A signer object + */ + signer: Signer; + + /** + * The public key of the user + */ + maciPubKey: string; + + /** + * The address of the MACI contract + */ + maciAddress: string; + + /** + * Whether to log the output + */ + quiet?: boolean; +} + /** * Interface for the arguments to the topup command */ diff --git a/contracts/contracts/MACI.sol b/contracts/contracts/MACI.sol index c00223c5a1..c4b022bda9 100644 --- a/contracts/contracts/MACI.sol +++ b/contracts/contracts/MACI.sol @@ -77,7 +77,13 @@ contract MACI is IMACI, Params, Utilities, Ownable { } // Events - event SignUp(uint256 _stateIndex, PubKey _userPubKey, uint256 _voiceCreditBalance, uint256 _timestamp); + event SignUp( + uint256 _stateIndex, + uint256 indexed _userPubKeyX, + uint256 indexed _userPubKeyY, + uint256 _voiceCreditBalance, + uint256 _timestamp + ); event DeployPoll(uint256 _pollId, PubKey _pubKey, PollContracts pollAddr); /// @notice Only allow a Poll contract to call the modified function. @@ -179,7 +185,7 @@ contract MACI is IMACI, Params, Utilities, Ownable { uint256 stateLeaf = hashStateLeaf(StateLeaf(_pubKey, voiceCreditBalance, timestamp)); uint256 stateIndex = stateAq.enqueue(stateLeaf); - emit SignUp(stateIndex, _pubKey, voiceCreditBalance, timestamp); + emit SignUp(stateIndex, _pubKey.x, _pubKey.y, voiceCreditBalance, timestamp); } /// @notice Deploy a new Poll contract. diff --git a/contracts/deploy-config-example.json b/contracts/deploy-config-example.json index 46b15a34a0..ba6378e3ad 100644 --- a/contracts/deploy-config-example.json +++ b/contracts/deploy-config-example.json @@ -10,7 +10,7 @@ "EASGatekeeper": { "deploy": true, "easAddress": "0xC2679fBD37d54388Ce493F1DB75320D236e1815e", - "schema": "0xfdcfdad2dbe7489e0ce56b260348b7f14e8365a8a325aef9834818c00d46b31b", + "schema": "0xe2636f31239f7948afdd9a9c477048b7fc2a089c347af60e3aa1251e5bf63e5c", "attester": "attester-address" }, "MACI": { diff --git a/contracts/ts/genMaciState.ts b/contracts/ts/genMaciState.ts index 803c60b52f..0c05daed55 100644 --- a/contracts/ts/genMaciState.ts +++ b/contracts/ts/genMaciState.ts @@ -86,7 +86,13 @@ export const genMaciStateFromContract = async ( assert(!!log); const mutableLog = { ...log, topics: [...log.topics] }; const event = maciIface.parseLog(mutableLog) as unknown as { - args: { _stateIndex: number; _userPubKey: string[]; _voiceCreditBalance: number; _timestamp: number }; + args: { + _stateIndex: number; + _userPubKeyX: string; + _userPubKeyY: string; + _voiceCreditBalance: number; + _timestamp: number; + }; }; actions.push({ @@ -95,7 +101,7 @@ export const genMaciStateFromContract = async ( transactionIndex: log.transactionIndex, data: { stateIndex: Number(event.args._stateIndex), - pubKey: new PubKey(event.args._userPubKey.map((x) => BigInt(x)) as [bigint, bigint]), + pubKey: new PubKey([BigInt(event.args._userPubKeyX), BigInt(event.args._userPubKeyY)]), voiceCreditBalance: Number(event.args._voiceCreditBalance), timestamp: Number(event.args._timestamp), }, diff --git a/integrationTests/ts/__tests__/integration.test.ts b/integrationTests/ts/__tests__/integration.test.ts index d9edf664a6..bc4bc5c855 100644 --- a/integrationTests/ts/__tests__/integration.test.ts +++ b/integrationTests/ts/__tests__/integration.test.ts @@ -191,7 +191,7 @@ describe("Integration tests", function test() { sgDataArg: SG_DATA, ivcpDataArg: ivcpData, signer, - }), + }).then((result) => result.stateIndex), ); // signup on local maci state diff --git a/website/versioned_docs/version-v1.x/cli.md b/website/versioned_docs/version-v1.x/cli.md index d1bb0e3e87..64196b60bb 100644 --- a/website/versioned_docs/version-v1.x/cli.md +++ b/website/versioned_docs/version-v1.x/cli.md @@ -43,6 +43,7 @@ pnpm run hardhat | `mergeSignups` | Merge the signups accumulator queue | `-q, --quiet`: Whether to print values to the console
`-x, --maci-contract-address `: The MACI contract address
`-o, --poll-id `: The poll id
`-n, --num-queue-ops `: The number of queue operations | | `timeTravel` | Fast-forward the time (only works for local hardhat testing) | `-s, --seconds `: The number of seconds to fast-forward
`-q, --quiet`: Whether to print values to the console | | `signup` | Sign up to a MACI contract | `-p, --pubkey `: The MACI public key
`-x, --maci-address `: The MACI contract address
`-s, --sg-data `: The signup gateway data
`-i, --ivcp-data `: The initial voice credit proxy data
`-q, --quiet`: Whether to print values to the console | +| `isRegisteredUser` | Checks if user is registered with public key | `-p, --pubkey `: The MACI public key
`-x, --maci-address `: The MACI contract address
`-q, --quiet`: Whether to print values to the console | | `topup` | Top up an account with voice credits | `-a, --amount `: The amount of topup
`-x, --maci-address `: The MACI contract address
`-i, --state-index `: State leaf index
`-o, --poll-id `: Poll id
`-q, --quiet`: Whether to print values to the console | | `fundWallet` | Fund a wallet with Ether | `-a, --amount `: The amount of Ether
`-w, --address
`: The address to fund
`-q, --quiet`: Whether to print values to the console | | `verify` | Verify the results of a poll and optionally the subsidy results on-chain | `-o, --poll-id `: The poll id
`-t, --tally-file `: The tally file with results, per vote option spent credits, spent voice credits total
`-s, --subsidy-file `: The subsidy file
`-x, --contract `: The MACI contract address
`-tc, --tally-contract `: The tally contract address
`-sc, --subsidy-contract `: The subsidy contract address
`-q, --quiet`: Whether to print values to the console |