diff --git a/cli/ts/commands/deploy.ts b/cli/ts/commands/deploy.ts index e1a7c4c544..c782629b90 100644 --- a/cli/ts/commands/deploy.ts +++ b/cli/ts/commands/deploy.ts @@ -6,6 +6,7 @@ import { deployVerifier, deployMaci, deployTopupCredit, + getDefaultSigner, } from "maci-contracts"; import { logError, logGreen, success, DEFAULT_INITIAL_VOICE_CREDITS, DeployedContracts } from "../utils/"; @@ -32,11 +33,14 @@ export const deploy = async ( logError("Please provide either an initialVoiceCreditProxyAddress or initialVoiceCredits, not both"); } + const signer = await getDefaultSigner(); + // if we did not deploy it before, then deploy it now let initialVoiceCreditProxyContractAddress: string; if (!initialVoiceCreditsProxyAddress) { const contract = await deployConstantInitialVoiceCreditProxy( initialVoiceCredits ? initialVoiceCredits : DEFAULT_INITIAL_VOICE_CREDITS, + signer, true, ); @@ -46,15 +50,15 @@ export const deploy = async ( // check if we have a signupGatekeeper already deployed or passed as arg let signupGatekeeperContractAddress = readContractAddress("SignUpGatekeeper"); if (!signupGatekeeperContractAddress && !signupGatekeeperAddress) { - const contract = await deployFreeForAllSignUpGatekeeper(true); + const contract = await deployFreeForAllSignUpGatekeeper(signer, true); signupGatekeeperContractAddress = await contract.getAddress(); } // deploy a verifier contract - const verifierContract = await deployVerifier(true); + const verifierContract = await deployVerifier(signer, true); // topup credit - const topUpCredit = await deployTopupCredit(true); + const topUpCredit = await deployTopupCredit(signer, true); const [verifierContractAddress, topUpCreditAddress] = await Promise.all([ verifierContract.getAddress(), @@ -67,6 +71,7 @@ export const deploy = async ( initialVoiceCreditProxyContractAddress, verifierContractAddress, topUpCreditAddress, + signer, stateTreeDepth, true, ); diff --git a/cli/ts/commands/deployPoll.ts b/cli/ts/commands/deployPoll.ts index aab192fee1..23dedbab23 100644 --- a/cli/ts/commands/deployPoll.ts +++ b/cli/ts/commands/deployPoll.ts @@ -95,6 +95,7 @@ export const deployPoll = async ( poseidonT4, poseidonT5, poseidonT6, + signer, true, ); @@ -106,6 +107,7 @@ export const deployPoll = async ( poseidonT4, poseidonT5, poseidonT6, + signer, true, ); @@ -117,6 +119,7 @@ export const deployPoll = async ( poseidonT4, poseidonT5, poseidonT6, + signer, true, ); diff --git a/cli/ts/commands/deployVkRegistry.ts b/cli/ts/commands/deployVkRegistry.ts index 74b0cf2513..26b286b53b 100644 --- a/cli/ts/commands/deployVkRegistry.ts +++ b/cli/ts/commands/deployVkRegistry.ts @@ -1,4 +1,4 @@ -import { deployVkRegistry } from "maci-contracts"; +import { deployVkRegistry, getDefaultSigner } from "maci-contracts"; import { existsSync, renameSync } from "fs"; import { contractAddressesStore, oldContractAddressesStore } from "../utils/constants"; import { logGreen, success } from "../utils/theme"; @@ -18,7 +18,7 @@ export const deployVkRegistryContract = async (quiet = true): Promise => } // deploy and store the address - const vkRegistry = await deployVkRegistry(true); + const vkRegistry = await deployVkRegistry(await getDefaultSigner(), true); const vkRegistryAddress = await vkRegistry.getAddress(); storeContractAddress("VkRegistry", vkRegistryAddress); diff --git a/contracts/package.json b/contracts/package.json index 7a949c5b55..a65a4c1da4 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -5,7 +5,7 @@ "main": "build/ts/index.js", "scripts": { "watch": "tsc --watch", - "hardhat": "./scripts/runHardhat.sh", + "hardhat": "hardhat node --hostname 0.0.0.0 --port 8545", "compileSol": "./scripts/compileSol.sh $1", "moveIntegrationArtifacts": "cp -r artifacts/ ../integrationTests/artifacts", "prebuild": "npm run compileSol", @@ -22,7 +22,9 @@ "test-signupGatekeeper": "hardhat test ./tests/SignUpGatekeeper.test.ts", "test-verifier": "hardhat test ./tests/Verifier.test.ts", "test-accQueue": "hardhat test ./tests/AccQueue.test.ts", - "test-accQueueBenchmark": "hardhat test ./tests/AccQueueBenchmark.test.ts" + "test-accQueueBenchmark": "hardhat test ./tests/AccQueueBenchmark.test.ts", + "test-maciOverflow": "hardhat test ./tests/MaciOverflow.test.ts", + "test-hasherBenchmarks": "hardhat test ./tests/HasherBenchmarks.test.ts" }, "_moduleAliases": { "@maci-contracts": "." diff --git a/contracts/scripts/runHardhat.sh b/contracts/scripts/runHardhat.sh deleted file mode 100755 index 77dc415e44..0000000000 --- a/contracts/scripts/runHardhat.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/bash - -# This mnemonic seed is well-known to the public. If you transfer any ETH to -# addreses derived from it, expect it to be swept away. - -# Etherlime's ganache command works differently from ganache-cli. It -# concatenates `--count minus 10` new accounts generated from `--mnemonic`. The -# first 10 are predefined. -#npx etherlime ganache --mnemonic "candy maple cake sugar pudding cream honey rich smooth crumble sweet treat" --gasLimit=10000000 count=10 - -npx hardhat node --hostname 0.0.0.0 --port 8545 -#npx ganache-cli -a 10 -m='candy maple cake sugar pudding cream honey rich smooth crumble sweet treat' --gasLimit=8800000 --port 8545 - -# ETH accounts from the 'candy maple...' mnemonic -#0: 0x627306090abab3a6e1400e9345bc60c78a8bef57 -#1: 0xf17f52151ebef6c7334fad080c5704d77216b732 -#2: 0xc5fdf4076b8f3a5357c5e395ab970b5b54098fef -#3: 0x821aea9a577a9b44299b9c15c88cf3087f3b5544 -#4: 0x0d1d4e623d10f9fba5db95830f7d3839406c6af2 -#5: 0x2932b7a2355d6fecc4b5c0b6bd44cc31df247a2e -#6: 0x2191ef87e392377ec08e7c08eb105ef5448eced5 -#7: 0x0f4f2ac550a1b4e2280d04c21cea7ebd822934b5 -#8: 0x6330a553fc93768f612722bb8c2ec78ac90b3bbc -#9: 0x5aeda56215b167893e80b4fe645ba6d5bab767de - -# Private keys -#0 0xc87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3 - -# ethnode (https://github.com/vrde/ethnode) seems faster than ganache, but it -# doesn't relay revert messages - -#npx ethnode --allocate=0x627306090abab3a6e1400e9345bc60c78a8bef57,0xf17f52151ebef6c7334fad080c5704d77216b732,0xc5fdf4076b8f3a5357c5e395ab970b5b54098fef,0x821aea9a577a9b44299b9c15c88cf3087f3b5544,0x0d1d4e623d10f9fba5db95830f7d3839406c6af2 diff --git a/contracts/scripts/runTestsInCi.sh b/contracts/scripts/runTestsInCi.sh deleted file mode 100755 index b0aecdacbc..0000000000 --- a/contracts/scripts/runTestsInCi.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash -xe - -cd "$(dirname "$0")" -cd .. - -npm run hardhat & -sleep 3 && -npm run test \ No newline at end of file diff --git a/contracts/ts/EmptyBallotRoots.sol.template b/contracts/templates/EmptyBallotRoots.sol.template similarity index 100% rename from contracts/ts/EmptyBallotRoots.sol.template rename to contracts/templates/EmptyBallotRoots.sol.template diff --git a/contracts/ts/MerkleZeros.sol.template b/contracts/templates/MerkleZeros.sol.template similarity index 100% rename from contracts/ts/MerkleZeros.sol.template rename to contracts/templates/MerkleZeros.sol.template diff --git a/contracts/tests/AccQueueBenchmark.test.ts b/contracts/tests/AccQueueBenchmark.test.ts index 24b59f22ff..67cdd5a3f0 100644 --- a/contracts/tests/AccQueueBenchmark.test.ts +++ b/contracts/tests/AccQueueBenchmark.test.ts @@ -2,15 +2,14 @@ import { expect } from "chai"; import { AccQueue, NOTHING_UP_MY_SLEEVE } from "maci-crypto"; import { deployPoseidonContracts, linkPoseidonLibraries } from "../ts/deploy"; +import { getDefaultSigner } from "../ts/utils"; import { AccQueue as AccQueueContract } from "../typechain-types"; -require("module-alias/register"); - let aqContract: AccQueueContract; const deploy = async (contractName: string, SUB_DEPTH: number, HASH_LENGTH: number, ZERO: bigint) => { const { PoseidonT3Contract, PoseidonT4Contract, PoseidonT5Contract, PoseidonT6Contract } = - await deployPoseidonContracts(true); + await deployPoseidonContracts(await getDefaultSigner(), true); const [poseidonT3ContractAddress, poseidonT4ContractAddress, poseidonT5ContractAddress, poseidonT6ContractAddress] = await Promise.all([ PoseidonT3Contract.getAddress(), @@ -26,6 +25,7 @@ const deploy = async (contractName: string, SUB_DEPTH: number, HASH_LENGTH: numb poseidonT4ContractAddress, poseidonT5ContractAddress, poseidonT6ContractAddress, + await getDefaultSigner(), true, ); diff --git a/contracts/tests/Hasher.test.ts b/contracts/tests/Hasher.test.ts index b16a48dbad..90373e64db 100644 --- a/contracts/tests/Hasher.test.ts +++ b/contracts/tests/Hasher.test.ts @@ -3,6 +3,7 @@ import { BigNumberish } from "ethers"; import { sha256Hash, hashLeftRight, hash3, hash4, hash5, genRandomSalt } from "maci-crypto"; import { deployPoseidonContracts, linkPoseidonLibraries } from "../ts/deploy"; +import { getDefaultSigner } from "../ts/utils"; import { Hasher } from "../typechain-types"; describe("Hasher", () => { @@ -10,7 +11,7 @@ describe("Hasher", () => { before(async () => { const { PoseidonT3Contract, PoseidonT4Contract, PoseidonT5Contract, PoseidonT6Contract } = - await deployPoseidonContracts(true); + await deployPoseidonContracts(await getDefaultSigner(), true); const [poseidonT3ContractAddress, poseidonT4ContractAddress, poseidonT5ContractAddress, poseidonT6ContractAddress] = await Promise.all([ PoseidonT3Contract.getAddress(), @@ -25,6 +26,7 @@ describe("Hasher", () => { poseidonT4ContractAddress, poseidonT5ContractAddress, poseidonT6ContractAddress, + await getDefaultSigner(), true, ); diff --git a/contracts/tests/HasherBenchmarks.test.ts b/contracts/tests/HasherBenchmarks.test.ts index 2fca338b83..4d8ac2e395 100644 --- a/contracts/tests/HasherBenchmarks.test.ts +++ b/contracts/tests/HasherBenchmarks.test.ts @@ -3,6 +3,7 @@ import { BigNumberish } from "ethers"; import { genRandomSalt } from "maci-crypto"; import { deployPoseidonContracts, linkPoseidonLibraries } from "../ts/deploy"; +import { getDefaultSigner } from "../ts/utils"; import { HasherBenchmarks } from "../typechain-types"; require("module-alias/register"); @@ -11,7 +12,7 @@ describe("Hasher", () => { let hasherContract: HasherBenchmarks; before(async () => { const { PoseidonT3Contract, PoseidonT4Contract, PoseidonT5Contract, PoseidonT6Contract } = - await deployPoseidonContracts(true); + await deployPoseidonContracts(await getDefaultSigner(), true); const [poseidonT3ContractAddress, poseidonT4ContractAddress, poseidonT5ContractAddress, poseidonT6ContractAddress] = await Promise.all([ PoseidonT3Contract.getAddress(), @@ -26,6 +27,7 @@ describe("Hasher", () => { poseidonT4ContractAddress, poseidonT5ContractAddress, poseidonT6ContractAddress, + await getDefaultSigner(), true, ); diff --git a/contracts/tests/MACI.test.ts b/contracts/tests/MACI.test.ts index facc63a046..0f30f4a28f 100644 --- a/contracts/tests/MACI.test.ts +++ b/contracts/tests/MACI.test.ts @@ -8,8 +8,8 @@ import { VerifyingKey, Keypair, PubKey, Message } from "maci-domainobjs"; import type { IVerifyingKeyStruct } from "../ts/types"; -import { parseArtifact, getDefaultSigner } from "../ts/deploy"; -import { deployTestContracts } from "../ts/utils"; +import { parseArtifact } from "../ts/abi"; +import { getDefaultSigner } from "../ts/utils"; import { AccQueueQuinaryMaci, MACI, VkRegistry, Poll as PollContract } from "../typechain-types"; import { @@ -22,7 +22,7 @@ import { testTallyVk, treeDepths, } from "./constants"; -import { compareVks, timeTravel } from "./utils"; +import { compareVks, timeTravel, deployTestContracts } from "./utils"; describe("MACI", () => { let maciContract: MACI; @@ -42,7 +42,7 @@ describe("MACI", () => { describe("Deployment", () => { before(async () => { signer = await getDefaultSigner(); - const r = await deployTestContracts(initialVoiceCreditBalance, STATE_TREE_DEPTH, true); + const r = await deployTestContracts(initialVoiceCreditBalance, STATE_TREE_DEPTH, signer, true); maciContract = r.maciContract; stateAqContract = r.stateAqContract; @@ -108,7 +108,8 @@ describe("MACI", () => { it("should not allow to sign up more than the supported amount of users (5 ** stateTreeDepth)", async () => { const stateTreeDepthTest = 1; const maxUsers = 5 ** stateTreeDepthTest; - const maci = (await deployTestContracts(initialVoiceCreditBalance, stateTreeDepthTest, true)).maciContract; + const maci = (await deployTestContracts(initialVoiceCreditBalance, stateTreeDepthTest, signer, true)) + .maciContract; const keypair = new Keypair(); for (let i = 0; i < maxUsers; i += 1) { // eslint-disable-next-line no-await-in-loop diff --git a/contracts/tests/MaciOverflow.test.ts b/contracts/tests/MaciOverflow.test.ts index 3a9ccb6a17..9db5eecfe3 100644 --- a/contracts/tests/MaciOverflow.test.ts +++ b/contracts/tests/MaciOverflow.test.ts @@ -3,10 +3,11 @@ import { AbiCoder, BigNumberish } from "ethers"; import { MaxValues, STATE_TREE_DEPTH, TreeDepths } from "maci-core"; import { Keypair } from "maci-domainobjs"; -import { deployTestContracts } from "../ts/utils"; +import { getDefaultSigner } from "../ts/utils"; import { MACI } from "../typechain-types"; import { duration, initialVoiceCreditBalance, maxValues, treeDepths } from "./constants"; +import { deployTestContracts } from "./utils"; describe("Overflow testing", () => { let maciContract: MACI; @@ -15,7 +16,7 @@ describe("Overflow testing", () => { const users = [new Keypair(), new Keypair(), new Keypair()]; beforeEach(async () => { - const r = await deployTestContracts(initialVoiceCreditBalance, STATE_TREE_DEPTH, true); + const r = await deployTestContracts(initialVoiceCreditBalance, STATE_TREE_DEPTH, await getDefaultSigner(), true); maciContract = r.maciContract; }); diff --git a/contracts/tests/MessageProcessor.test.ts b/contracts/tests/MessageProcessor.test.ts index 086cd34ff7..0b248bc4b0 100644 --- a/contracts/tests/MessageProcessor.test.ts +++ b/contracts/tests/MessageProcessor.test.ts @@ -6,7 +6,9 @@ import { MaciState, Poll, STATE_TREE_DEPTH, packProcessMessageSmallVals } from " import { NOTHING_UP_MY_SLEEVE } from "maci-crypto"; import { Keypair, Message, PubKey } from "maci-domainobjs"; -import { IVerifyingKeyStruct, deployTestContracts, getDefaultSigner, parseArtifact } from "../ts"; +import { parseArtifact } from "../ts/abi"; +import { IVerifyingKeyStruct } from "../ts/types"; +import { getDefaultSigner } from "../ts/utils"; import { MACI, MessageProcessor, Poll as PollContract } from "../typechain-types"; import { @@ -18,7 +20,7 @@ import { testTallyVk, treeDepths, } from "./constants"; -import { timeTravel } from "./utils"; +import { timeTravel, deployTestContracts } from "./utils"; describe("MessageProcessor", () => { // contracts @@ -40,8 +42,9 @@ describe("MessageProcessor", () => { const users = [new Keypair(), new Keypair()]; before(async () => { + signer = await getDefaultSigner(); // deploy test contracts - const r = await deployTestContracts(initialVoiceCreditBalance, STATE_TREE_DEPTH, true); + const r = await deployTestContracts(initialVoiceCreditBalance, STATE_TREE_DEPTH, signer, true); maciContract = r.maciContract; signer = await getDefaultSigner(); mpContract = r.mpContract; diff --git a/contracts/tests/Poll.test.ts b/contracts/tests/Poll.test.ts index 13b71ccada..18530097f9 100644 --- a/contracts/tests/Poll.test.ts +++ b/contracts/tests/Poll.test.ts @@ -6,7 +6,8 @@ import { MaciState } from "maci-core"; import { NOTHING_UP_MY_SLEEVE } from "maci-crypto"; import { Keypair, Message, PCommand, PubKey } from "maci-domainobjs"; -import { deployTestContracts, getDefaultSigner, parseArtifact } from "../ts"; +import { parseArtifact } from "../ts/abi"; +import { getDefaultSigner } from "../ts/utils"; import { AccQueue, MACI, Poll as PollContract } from "../typechain-types"; import { @@ -18,7 +19,7 @@ import { messageBatchSize, treeDepths, } from "./constants"; -import { timeTravel } from "./utils"; +import { timeTravel, deployTestContracts } from "./utils"; describe("Poll", () => { let maciContract: MACI; @@ -34,7 +35,7 @@ describe("Poll", () => { before(async () => { signer = await getDefaultSigner(); - const r = await deployTestContracts(initialVoiceCreditBalance, STATE_TREE_DEPTH, true); + const r = await deployTestContracts(initialVoiceCreditBalance, STATE_TREE_DEPTH, signer, true); maciContract = r.maciContract; // deploy on chain poll @@ -108,7 +109,7 @@ describe("Poll", () => { maciState.polls[pollId].publishMessage(message, keypair.pubKey); }); - it("shold not publish a message after the voting period", async () => { + it("should not publish a message after the voting period", async () => { const dd = await pollContract.getDeployTimeAndDuration(); await timeTravel(signer.provider as unknown as EthereumProvider, Number(dd[0]) + 1); diff --git a/contracts/tests/SignUpGatekeeper.test.ts b/contracts/tests/SignUpGatekeeper.test.ts index d40625bbbd..9ca7679f73 100644 --- a/contracts/tests/SignUpGatekeeper.test.ts +++ b/contracts/tests/SignUpGatekeeper.test.ts @@ -1,27 +1,25 @@ import { expect } from "chai"; -import { AbiCoder, BigNumberish } from "ethers"; +import { AbiCoder, BigNumberish, Signer } from "ethers"; +import { STATE_TREE_DEPTH } from "maci-core"; import { Keypair } from "maci-domainobjs"; -import { - getDefaultSigner, - deploySignupToken, - deploySignupTokenGatekeeper, - deployFreeForAllSignUpGatekeeper, -} from "../ts/deploy"; -import { deployTestContracts } from "../ts/utils"; +import { deploySignupToken, deploySignupTokenGatekeeper, deployFreeForAllSignUpGatekeeper } from "../ts/deploy"; +import { getDefaultSigner } from "../ts/utils"; import { FreeForAllGatekeeper, MACI, SignUpToken, SignUpTokenGatekeeper } from "../typechain-types"; -const initialVoiceCreditBalance = 100; -const STATE_TREE_DEPTH = 10; +import { initialVoiceCreditBalance } from "./constants"; +import { deployTestContracts } from "./utils"; describe("SignUpGatekeeper", () => { let signUpToken: SignUpToken; let freeForAllContract: FreeForAllGatekeeper; let signUpTokenGatekeeperContract: SignUpTokenGatekeeper; + let signer: Signer; before(async () => { - freeForAllContract = await deployFreeForAllSignUpGatekeeper(true); - signUpToken = await deploySignupToken(true); + signer = await getDefaultSigner(); + freeForAllContract = await deployFreeForAllSignUpGatekeeper(signer, true); + signUpToken = await deploySignupToken(signer, true); signUpTokenGatekeeperContract = await deploySignupTokenGatekeeper(await signUpToken.getAddress()); }); @@ -41,13 +39,14 @@ describe("SignUpGatekeeper", () => { let maciContract: MACI; beforeEach(async () => { - freeForAllContract = await deployFreeForAllSignUpGatekeeper(true); - signUpToken = await deploySignupToken(true); - signUpTokenGatekeeperContract = await deploySignupTokenGatekeeper(await signUpToken.getAddress(), true); + freeForAllContract = await deployFreeForAllSignUpGatekeeper(signer, true); + signUpToken = await deploySignupToken(signer, true); + signUpTokenGatekeeperContract = await deploySignupTokenGatekeeper(await signUpToken.getAddress(), signer, true); const r = await deployTestContracts( initialVoiceCreditBalance, STATE_TREE_DEPTH, + signer, true, signUpTokenGatekeeperContract, ); @@ -65,7 +64,6 @@ describe("SignUpGatekeeper", () => { it("Reverts if address provided is not a MACI instance", async () => { const user = new Keypair(); - const signer = await getDefaultSigner(); const tx = await signUpToken.giveToken(await signer.getAddress(), 0); await tx.wait(); diff --git a/contracts/tests/Tally.test.ts b/contracts/tests/Tally.test.ts index d4b4611f8a..3b65ed9110 100644 --- a/contracts/tests/Tally.test.ts +++ b/contracts/tests/Tally.test.ts @@ -6,7 +6,9 @@ import { MaciState, Poll, packTallyVotesSmallVals } from "maci-core"; import { NOTHING_UP_MY_SLEEVE } from "maci-crypto"; import { Keypair, Message, PubKey } from "maci-domainobjs"; -import { IVerifyingKeyStruct, deployTestContracts, getDefaultSigner, parseArtifact } from "../ts"; +import { parseArtifact } from "../ts/abi"; +import { IVerifyingKeyStruct } from "../ts/types"; +import { getDefaultSigner } from "../ts/utils"; import { Tally, MACI, Poll as PollContract, MessageProcessor } from "../typechain-types"; import { @@ -19,7 +21,7 @@ import { testTallyVk, treeDepths, } from "./constants"; -import { timeTravel } from "./utils"; +import { timeTravel, deployTestContracts } from "./utils"; describe("TallyVotes", () => { let signer: Signer; @@ -42,7 +44,7 @@ describe("TallyVotes", () => { before(async () => { signer = await getDefaultSigner(); - const r = await deployTestContracts(100, STATE_TREE_DEPTH, true); + const r = await deployTestContracts(100, STATE_TREE_DEPTH, signer, true); maciContract = r.maciContract; mpContract = r.mpContract; tallyContract = r.tallyContract; @@ -178,7 +180,7 @@ describe("TallyVotes", () => { expect(tallyGeneratedInputs.newTallyCommitment).to.eq(onChainNewTallyCommitment.toString()); }); - it("tallyVotes() shuold revert when votes have already been tallied", async () => { + it("tallyVotes() should revert when votes have already been tallied", async () => { await expect( tallyContract.tallyVotes( await pollContract.getAddress(), diff --git a/contracts/tests/Utilities.test.ts b/contracts/tests/Utilities.test.ts index 4ef3dedec1..ff91e2f87f 100644 --- a/contracts/tests/Utilities.test.ts +++ b/contracts/tests/Utilities.test.ts @@ -2,15 +2,16 @@ import { expect } from "chai"; import { StateLeaf, Keypair } from "maci-domainobjs"; import { deployPoseidonContracts, linkPoseidonLibraries } from "../ts/deploy"; +import { getDefaultSigner } from "../ts/utils"; import { Utilities } from "../typechain-types"; -describe("DomainObjs", () => { +describe("Utilities", () => { let utilitiesContract: Utilities; describe("Deployment", () => { before(async () => { const { PoseidonT3Contract, PoseidonT4Contract, PoseidonT5Contract, PoseidonT6Contract } = - await deployPoseidonContracts(true); + await deployPoseidonContracts(await getDefaultSigner(), true); const [ poseidonT3ContractAddress, @@ -31,6 +32,7 @@ describe("DomainObjs", () => { poseidonT4ContractAddress, poseidonT5ContractAddress, poseidonT6ContractAddress, + await getDefaultSigner(), true, ); diff --git a/contracts/tests/Verifier.test.ts b/contracts/tests/Verifier.test.ts index e3d064238a..73044c62cc 100644 --- a/contracts/tests/Verifier.test.ts +++ b/contracts/tests/Verifier.test.ts @@ -6,6 +6,7 @@ import type { IVerifyingKeyStruct } from "../ts/types"; import type { BigNumberish } from "ethers"; import { deployVerifier } from "../ts/deploy"; +import { getDefaultSigner } from "../ts/utils"; import { Verifier } from "../typechain-types"; describe("DomainObjs", () => { @@ -78,7 +79,7 @@ describe("DomainObjs", () => { describe("Deployment", () => { before(async () => { - verifierContract = await deployVerifier(true); + verifierContract = await deployVerifier(await getDefaultSigner(), true); }); it("should correctly verify a proof", async () => { diff --git a/contracts/tests/utils.ts b/contracts/tests/utils.ts index eb98b21b91..be77a4ccbe 100644 --- a/contracts/tests/utils.ts +++ b/contracts/tests/utils.ts @@ -1,13 +1,26 @@ /* eslint-disable import/no-extraneous-dependencies */ import { expect } from "chai"; -import { BaseContract } from "ethers"; +import { BaseContract, Signer } from "ethers"; import { IncrementalQuinTree, AccQueue, calcDepthFromNumLeaves, hash2, hash5 } from "maci-crypto"; import { VerifyingKey } from "maci-domainobjs"; import type { EthereumProvider } from "hardhat/types"; -import { deployPoseidonContracts, linkPoseidonLibraries } from "../ts/deploy"; -import { AccQueue as AccQueueContract } from "../typechain-types"; +import { getDefaultSigner } from "../ts"; +import { + deployConstantInitialVoiceCreditProxy, + deployFreeForAllSignUpGatekeeper, + deployMaci, + deployMessageProcessor, + deployMockVerifier, + deployPoseidonContracts, + deployTally, + deployTopupCredit, + deployVkRegistry, + linkPoseidonLibraries, +} from "../ts/deploy"; +import { IDeployedTestContracts } from "../ts/types"; +import { AccQueue as AccQueueContract, FreeForAllGatekeeper } from "../typechain-types"; export const insertSubTreeGasLimit = { gasLimit: 300000 }; export const enqueueGasLimit = { gasLimit: 500000 }; @@ -65,7 +78,7 @@ export const deployTestAccQueues = async ( ZERO: bigint, ): Promise<{ aq: AccQueue; aqContract: BaseContract }> => { const { PoseidonT3Contract, PoseidonT4Contract, PoseidonT5Contract, PoseidonT6Contract } = - await deployPoseidonContracts(true); + await deployPoseidonContracts(await getDefaultSigner(), true); const [poseidonT3ContractAddress, poseidonT4ContractAddress, poseidonT5ContractAddress, poseidonT6ContractAddress] = await Promise.all([ @@ -81,6 +94,7 @@ export const deployTestAccQueues = async ( poseidonT4ContractAddress, poseidonT5ContractAddress, poseidonT6ContractAddress, + await getDefaultSigner(), true, ); @@ -110,18 +124,18 @@ export const testEmptySubtree = async (aq: AccQueue, aqContract: AccQueueContrac /** * Insert one leaf and compute the subroot * @param aq - the AccQueue class instance - * @param contract - the AccQueue contract + * @param aqContract - the AccQueue contract */ -export const testIncompleteSubtree = async (aq: AccQueue, contract: AccQueueContract): Promise => { +export const testIncompleteSubtree = async (aq: AccQueue, aqContract: AccQueueContract): Promise => { const leaf = BigInt(1); aq.enqueue(leaf); - await contract.enqueue(leaf.toString(), enqueueGasLimit).then((tx) => tx.wait()); + await aqContract.enqueue(leaf.toString(), enqueueGasLimit).then((tx) => tx.wait()); aq.fill(); - await contract.fill(fillGasLimit).then((tx) => tx.wait()); + await aqContract.fill(fillGasLimit).then((tx) => tx.wait()); - const subRoot = await contract.getSubRoot(1); + const subRoot = await aqContract.getSubRoot(1); expect(subRoot.toString()).to.equal(aq.getSubRoot(1).toString()); }; @@ -155,24 +169,24 @@ export const testFillForAllIncompletes = async ( /** * Test whether the AccQueue is empty upon deployment - * @param contract - the AccQueue contract + * @param aqContract - the AccQueue contract */ -export const testEmptyUponDeployment = async (contract: AccQueueContract): Promise => { - const numLeaves = await contract.numLeaves(); +export const testEmptyUponDeployment = async (aqContract: AccQueueContract): Promise => { + const numLeaves = await aqContract.numLeaves(); expect(numLeaves.toString()).to.equal("0"); - await expect(contract.getSubRoot(0)).to.be.revertedWithCustomError(contract, "InvalidIndex"); + await expect(aqContract.getSubRoot(0)).to.be.revertedWithCustomError(aqContract, "InvalidIndex"); }; /** * Enqueue leaves and check their subroots - * @param contract - the AccQueue contract + * @param aqContract - the AccQueue contract * @param HASH_LENGTH - the number of leaves in each subtree * @param SUB_DEPTH - the depth of the subtrees * @param ZERO - the zero value to be used as leaf */ export const testEnqueue = async ( - contract: AccQueueContract, + aqContract: AccQueueContract, HASH_LENGTH: number, SUB_DEPTH: number, ZERO: bigint, @@ -187,13 +201,13 @@ export const testEnqueue = async ( tree0.insert(leaf); // eslint-disable-next-line no-await-in-loop - await contract.enqueue(leaf.toString(), enqueueGasLimit).then((tx) => tx.wait()); + await aqContract.enqueue(leaf.toString(), enqueueGasLimit).then((tx) => tx.wait()); } - let numLeaves = await contract.numLeaves(); + let numLeaves = await aqContract.numLeaves(); expect(numLeaves.toString()).to.eq(subtreeCapacity.toString()); - const r = await contract.getSubRoot(0); + const r = await aqContract.getSubRoot(0); expect(r.toString()).to.eq(tree0.root.toString()); const tree1 = new IncrementalQuinTree(SUB_DEPTH, ZERO, HASH_LENGTH, hashFunc); @@ -204,25 +218,25 @@ export const testEnqueue = async ( tree1.insert(leaf); // eslint-disable-next-line no-await-in-loop - await contract.enqueue(leaf.toString(), enqueueGasLimit).then((tx) => tx.wait()); + await aqContract.enqueue(leaf.toString(), enqueueGasLimit).then((tx) => tx.wait()); } - numLeaves = await contract.numLeaves(); + numLeaves = await aqContract.numLeaves(); expect(numLeaves.toString()).to.eq((subtreeCapacity * 2).toString()); - const subroot1 = await contract.getSubRoot(1); + const subroot1 = await aqContract.getSubRoot(1); expect(subroot1.toString()).to.eq(tree1.root.toString()); }; /** * Insert subtrees directly * @param aq - the AccQueue class instance - * @param contract - the AccQueue contract + * @param aqContract - the AccQueue contract * @param NUM_SUBTREES - the number of subtrees to insert */ export const testInsertSubTrees = async ( aq: AccQueue, - contract: AccQueueContract, + aqContract: AccQueueContract, NUM_SUBTREES: number, MAIN_DEPTH: number, ): Promise => { @@ -236,7 +250,7 @@ export const testInsertSubTrees = async ( // insert the subtree root aq.insertSubTree(subTree.root); // eslint-disable-next-line no-await-in-loop - await contract.insertSubTree(subTree.root.toString(), insertSubTreeGasLimit).then((tx) => tx.wait()); + await aqContract.insertSubTree(subTree.root.toString(), insertSubTreeGasLimit).then((tx) => tx.wait()); } let correctRoot: string; @@ -255,21 +269,21 @@ export const testInsertSubTrees = async ( // Check whether mergeSubRoots() works aq.mergeSubRoots(0); - await contract.mergeSubRoots(0, { gasLimit: 8000000 }).then((tx) => tx.wait()); + await aqContract.mergeSubRoots(0, { gasLimit: 8000000 }).then((tx) => tx.wait()); const expectedSmallSRTroot = aq.smallSRTroot.toString(); expect(correctRoot).to.eq(expectedSmallSRTroot); - const contractSmallSRTroot = await contract.getSmallSRTroot(); + const contractSmallSRTroot = await aqContract.getSmallSRTroot(); expect(expectedSmallSRTroot.toString()).to.eq(contractSmallSRTroot.toString()); // Check whether merge() works aq.merge(MAIN_DEPTH); - await contract.merge(MAIN_DEPTH, { gasLimit: 8000000 }).then((tx) => tx.wait()); + await aqContract.merge(MAIN_DEPTH, { gasLimit: 8000000 }).then((tx) => tx.wait()); const expectedMainRoot = aq.mainRoots[MAIN_DEPTH]; - const contractMainRoot = await contract.getMainRoot(MAIN_DEPTH); + const contractMainRoot = await aqContract.getMainRoot(MAIN_DEPTH); expect(expectedMainRoot.toString()).to.eq(contractMainRoot.toString()); }; @@ -277,9 +291,9 @@ export const testInsertSubTrees = async ( /** * The order of leaves when using enqueue() and insertSubTree() should be correct. * @param aq - the AccQueue class instance - * @param contract - the AccQueue contract + * @param aqContract - the AccQueue contract */ -export const testEnqueueAndInsertSubTree = async (aq: AccQueue, contract: AccQueueContract): Promise => { +export const testEnqueueAndInsertSubTree = async (aq: AccQueue, aqContract: AccQueueContract): Promise => { const z = aq.zeros[0]; const n = BigInt(1); @@ -304,31 +318,31 @@ export const testEnqueueAndInsertSubTree = async (aq: AccQueue, contract: AccQue const expectedRoot = tree.root.toString(); aq.enqueue(n); - await contract.enqueue(n.toString(), enqueueGasLimit).then((tx) => tx.wait()); + await aqContract.enqueue(n.toString(), enqueueGasLimit).then((tx) => tx.wait()); aq.insertSubTree(subTree.root); - await contract.insertSubTree(subTree.root.toString(), insertSubTreeGasLimit).then((tx) => tx.wait()); + await aqContract.insertSubTree(subTree.root.toString(), insertSubTreeGasLimit).then((tx) => tx.wait()); aq.fill(); - await contract.fill(fillGasLimit).then((tx) => tx.wait()); + await aqContract.fill(fillGasLimit).then((tx) => tx.wait()); aq.mergeSubRoots(0); - await contract.mergeSubRoots(0, { gasLimit: 8000000 }).then((tx) => tx.wait()); + await aqContract.mergeSubRoots(0, { gasLimit: 8000000 }).then((tx) => tx.wait()); expect(expectedRoot).to.eq(aq.smallSRTroot.toString()); - const contractSmallSRTroot = await contract.getSmallSRTroot(); + const contractSmallSRTroot = await aqContract.getSmallSRTroot(); expect(expectedRoot).to.eq(contractSmallSRTroot.toString()); }; /** * Insert a number of subtrees and merge them all into a main tree * @param aq - the AccQueue class instance - * @param contract - the AccQueue contract + * @param aqContract - the AccQueue contract */ export const testMerge = async ( aq: AccQueue, - contract: AccQueueContract, + aqContract: AccQueueContract, NUM_SUBTREES: number, MAIN_DEPTH: number, ): Promise => { @@ -340,9 +354,9 @@ export const testMerge = async ( aq.enqueue(leaf); aq.fill(); // eslint-disable-next-line no-await-in-loop - await contract.enqueue(leaf.toString(), enqueueGasLimit).then((tx) => tx.wait()); + await aqContract.enqueue(leaf.toString(), enqueueGasLimit).then((tx) => tx.wait()); // eslint-disable-next-line no-await-in-loop - await contract.fill(fillGasLimit).then((tx) => tx.wait()); + await aqContract.fill(fillGasLimit).then((tx) => tx.wait()); leaves.push(leaf); @@ -359,16 +373,16 @@ export const testMerge = async ( }); // minHeight should be the small SRT height - const minHeight = await contract.calcMinHeight(); + const minHeight = await aqContract.calcMinHeight(); const c = calcDepthFromNumLeaves(aq.hashLength, NUM_SUBTREES); expect(minHeight.toString()).to.eq(c.toString()); // Check whether mergeSubRoots() works aq.mergeSubRoots(0); - await (await contract.mergeSubRoots(0, { gasLimit: 8000000 })).wait(); + await (await aqContract.mergeSubRoots(0, { gasLimit: 8000000 })).wait(); const expectedSmallSRTroot = aq.smallSRTroot.toString(); - const contractSmallSRTroot = (await contract.getSmallSRTroot()).toString(); + const contractSmallSRTroot = (await aqContract.getSmallSRTroot()).toString(); expect(expectedSmallSRTroot).to.eq(contractSmallSRTroot); @@ -401,35 +415,35 @@ export const testMerge = async ( expect(expectedMainRoot).to.eq(directlyMergedRoot); // Check whether on-chain merge() works - await (await contract.merge(MAIN_DEPTH, { gasLimit: 8000000 })).wait(); - const contractMainRoot = (await contract.getMainRoot(MAIN_DEPTH)).toString(); + await (await aqContract.merge(MAIN_DEPTH, { gasLimit: 8000000 })).wait(); + const contractMainRoot = (await aqContract.getMainRoot(MAIN_DEPTH)).toString(); expect(expectedMainRoot).to.eq(contractMainRoot); }; /** * Enqueue, merge, enqueue, and merge again * @param aq - the AccQueue class instance - * @param contract - the AccQueue contract + * @param aqContract - the AccQueue contract */ -export const testMergeAgain = async (aq: AccQueue, contract: AccQueueContract, MAIN_DEPTH: number): Promise => { +export const testMergeAgain = async (aq: AccQueue, aqContract: AccQueueContract, MAIN_DEPTH: number): Promise => { const tree = new IncrementalQuinTree(MAIN_DEPTH, aq.zeros[0], aq.hashLength, aq.hashFunc); const leaf = BigInt(123); // Enqueue aq.enqueue(leaf); - await contract.enqueue(leaf.toString()).then((tx) => tx.wait()); + await aqContract.enqueue(leaf.toString()).then((tx) => tx.wait()); tree.insert(leaf); // Merge aq.mergeDirect(MAIN_DEPTH); - await contract.mergeSubRoots(0, { gasLimit: 8000000 }).then((tx) => tx.wait()); - await contract.merge(MAIN_DEPTH, { gasLimit: 8000000 }).then((tx) => tx.wait()); + await aqContract.mergeSubRoots(0, { gasLimit: 8000000 }).then((tx) => tx.wait()); + await aqContract.merge(MAIN_DEPTH, { gasLimit: 8000000 }).then((tx) => tx.wait()); for (let i = 1; i < aq.hashLength ** aq.subDepth; i += 1) { tree.insert(aq.zeros[0]); } - const mainRoot = (await contract.getMainRoot(MAIN_DEPTH)).toString(); + const mainRoot = (await aqContract.getMainRoot(MAIN_DEPTH)).toString(); const expectedMainRoot = aq.mainRoots[MAIN_DEPTH].toString(); expect(expectedMainRoot).to.eq(mainRoot); expect(expectedMainRoot).to.eq(tree.root.toString()); @@ -438,22 +452,110 @@ export const testMergeAgain = async (aq: AccQueue, contract: AccQueueContract, M // Enqueue aq.enqueue(leaf2); - await contract.enqueue(leaf2.toString()).then((tx) => tx.wait()); + await aqContract.enqueue(leaf2.toString()).then((tx) => tx.wait()); tree.insert(leaf2); // Merge aq.mergeDirect(MAIN_DEPTH); - await contract.mergeSubRoots(0, { gasLimit: 8000000 }).then((tx) => tx.wait()); - await contract.merge(MAIN_DEPTH, { gasLimit: 8000000 }).then((tx) => tx.wait()); + await aqContract.mergeSubRoots(0, { gasLimit: 8000000 }).then((tx) => tx.wait()); + await aqContract.merge(MAIN_DEPTH, { gasLimit: 8000000 }).then((tx) => tx.wait()); for (let i = 1; i < aq.hashLength ** aq.subDepth; i += 1) { tree.insert(aq.zeros[0]); } - const mainRoot2 = (await contract.getMainRoot(MAIN_DEPTH)).toString(); + const mainRoot2 = (await aqContract.getMainRoot(MAIN_DEPTH)).toString(); const expectedMainRoot2 = aq.mainRoots[MAIN_DEPTH].toString(); expect(expectedMainRoot2).to.eq(tree.root.toString()); expect(expectedMainRoot2).not.to.eq(expectedMainRoot); expect(expectedMainRoot2).to.eq(mainRoot2); }; + +/** + * Deploy a set of smart contracts that can be used for testing. + * @param initialVoiceCreditBalance - the initial voice credit balance for each user + * @param stateTreeDepth - the depth of the state tree + * @param signer - the signer to use + * @param quiet - whether to suppress console output + * @param gatekeeper - the gatekeeper contract to use + * @returns the deployed contracts + */ +export const deployTestContracts = async ( + initialVoiceCreditBalance: number, + stateTreeDepth: number, + signer?: Signer, + quiet = false, + gatekeeper: FreeForAllGatekeeper | undefined = undefined, +): Promise => { + const mockVerifierContract = await deployMockVerifier(signer, true); + + let gatekeeperContract = gatekeeper; + if (!gatekeeperContract) { + gatekeeperContract = await deployFreeForAllSignUpGatekeeper(signer, true); + } + + const constantIntialVoiceCreditProxyContract = await deployConstantInitialVoiceCreditProxy( + initialVoiceCreditBalance, + signer, + true, + ); + + // VkRegistry + const vkRegistryContract = await deployVkRegistry(signer, true); + const topupCreditContract = await deployTopupCredit(signer, true); + const [ + gatekeeperContractAddress, + mockVerifierContractAddress, + constantIntialVoiceCreditProxyContractAddress, + vkRegistryContractAddress, + topupCreditContractAddress, + ] = await Promise.all([ + gatekeeperContract.getAddress(), + mockVerifierContract.getAddress(), + constantIntialVoiceCreditProxyContract.getAddress(), + vkRegistryContract.getAddress(), + topupCreditContract.getAddress(), + ]); + + const { maciContract, stateAqContract, poseidonAddrs } = await deployMaci( + gatekeeperContractAddress, + constantIntialVoiceCreditProxyContractAddress, + mockVerifierContractAddress, + topupCreditContractAddress, + signer, + stateTreeDepth, + quiet, + ); + const mpContract = await deployMessageProcessor( + mockVerifierContractAddress, + vkRegistryContractAddress, + poseidonAddrs[0], + poseidonAddrs[1], + poseidonAddrs[2], + poseidonAddrs[3], + signer, + true, + ); + const tallyContract = await deployTally( + mockVerifierContractAddress, + vkRegistryContractAddress, + poseidonAddrs[0], + poseidonAddrs[1], + poseidonAddrs[2], + poseidonAddrs[3], + signer, + true, + ); + + return { + mockVerifierContract, + gatekeeperContract, + constantIntialVoiceCreditProxyContract, + maciContract, + stateAqContract, + vkRegistryContract, + mpContract, + tallyContract, + }; +}; diff --git a/contracts/ts/abi.ts b/contracts/ts/abi.ts new file mode 100644 index 0000000000..52805211bc --- /dev/null +++ b/contracts/ts/abi.ts @@ -0,0 +1,48 @@ +import fs from "fs"; +import path from "path"; + +import { abiDir } from "./constants"; +import { TAbi } from "./types"; + +/** + * Parse a contract artifact and return its ABI and bytecode. + * @param filename - the name of the contract + * @returns the ABI and bytecode of the contract + */ +export const parseArtifact = (filename: string): [TAbi, string] => { + let filePath = "contracts/"; + if (filename.includes("Gatekeeper")) { + filePath += "gatekeepers/"; + filePath += `${filename}.sol`; + } + + if (filename.includes("VoiceCredit")) { + filePath += "initialVoiceCreditProxy/"; + filePath += `${filename}.sol`; + } + + if (filename.includes("Verifier")) { + filePath += "crypto/Verifier.sol/"; + } + + if (filename.includes("AccQueue")) { + filePath += "trees/AccQueue.sol/"; + } + + if (filename.includes("Poll") || filename.includes("MessageAq")) { + filePath += "Poll.sol"; + } + + if (!filePath.includes(".sol")) { + filePath += `${filename}.sol`; + } + + const contractArtifact = JSON.parse( + fs.readFileSync(path.resolve(abiDir, filePath, `${filename}.json`)).toString(), + ) as { + abi: TAbi; + bytecode: string; + }; + + return [contractArtifact.abi, contractArtifact.bytecode]; +}; diff --git a/contracts/ts/buildPoseidon.ts b/contracts/ts/buildPoseidon.ts index 054a70b8a0..7b71d1205c 100644 --- a/contracts/ts/buildPoseidon.ts +++ b/contracts/ts/buildPoseidon.ts @@ -7,10 +7,10 @@ const buildPoseidon = async (numInputs: number) => { await (hre as ExtendedHre).overwriteArtifact(`PoseidonT${numInputs + 1}`, poseidonContract.createCode(numInputs)); }; -const buildPoseidonT3 = (): Promise => buildPoseidon(2); -const buildPoseidonT4 = (): Promise => buildPoseidon(3); -const buildPoseidonT5 = (): Promise => buildPoseidon(4); -const buildPoseidonT6 = (): Promise => buildPoseidon(5); +export const buildPoseidonT3 = (): Promise => buildPoseidon(2); +export const buildPoseidonT4 = (): Promise => buildPoseidon(3); +export const buildPoseidonT5 = (): Promise => buildPoseidon(4); +export const buildPoseidonT6 = (): Promise => buildPoseidon(5); if (require.main === module) { buildPoseidonT3(); @@ -18,5 +18,3 @@ if (require.main === module) { buildPoseidonT5(); buildPoseidonT6(); } - -export { buildPoseidonT3, buildPoseidonT4, buildPoseidonT5, buildPoseidonT6 }; diff --git a/contracts/ts/constants.ts b/contracts/ts/constants.ts new file mode 100644 index 0000000000..4512842596 --- /dev/null +++ b/contracts/ts/constants.ts @@ -0,0 +1,6 @@ +import path from "path"; + +// The directory where the contract artifacts are stored. +export const abiDir = path.resolve(__dirname, "..", "artifacts"); +// The directory where the contract source files are stored. +export const solDir = path.resolve(__dirname, "..", "contracts"); diff --git a/contracts/ts/deploy.ts b/contracts/ts/deploy.ts index 7a64b5561e..7951868e58 100644 --- a/contracts/ts/deploy.ts +++ b/contracts/ts/deploy.ts @@ -1,21 +1,8 @@ -import { - type Contract, - type ContractFactory, - type Signer, - type FeeData, - BaseContract, - JsonRpcProvider, - Interface, -} from "ethers"; +import { type Contract, type ContractFactory, BaseContract, Signer } from "ethers"; // eslint-disable-next-line // @ts-ignore typedoc doesn't want to get types from toolbox import { ethers } from "hardhat"; -import { readFileSync, writeFileSync } from "fs"; -import path from "path"; - -import type { Fragment, JsonFragment } from "@ethersproject/abi"; - import { AccQueueQuinaryMaci, ConstantInitialVoiceCreditProxy, @@ -23,7 +10,6 @@ import { MACI, MessageProcessor, MockVerifier, - Ownable, PollFactory, PoseidonT3, PoseidonT4, @@ -38,106 +24,33 @@ import { VkRegistry, } from "../typechain-types"; -const abiDir = path.resolve(__dirname, "..", "artifacts"); -const solDir = path.resolve(__dirname, "..", "contracts"); - -type TAbi = string | readonly (string | Fragment | JsonFragment)[]; - -const getDefaultSigner = async (): Promise => { - const signers = await ethers.getSigners(); - return signers[0]; -}; - -const parseArtifact = (filename: string): [TAbi, string] => { - let filePath = "contracts/"; - if (filename.includes("Gatekeeper")) { - filePath += "gatekeepers/"; - filePath += `${filename}.sol`; - } - - if (filename.includes("VoiceCredit")) { - filePath += "initialVoiceCreditProxy/"; - filePath += `${filename}.sol`; - } - - if (filename.includes("Verifier")) { - filePath += "crypto/Verifier.sol/"; - } - - if (filename.includes("AccQueue")) { - filePath += "trees/AccQueue.sol/"; - } - - if (filename.includes("Poll") || filename.includes("MessageAq")) { - filePath += "Poll.sol"; - } - - if (!filePath.includes(".sol")) { - filePath += `${filename}.sol`; - } - - const contractArtifact = JSON.parse(readFileSync(path.resolve(abiDir, filePath, `${filename}.json`)).toString()) as { - abi: TAbi; - bytecode: string; - }; - - return [contractArtifact.abi, contractArtifact.bytecode]; -}; - -const getInitialVoiceCreditProxyAbi = (): TAbi => { - const [abi] = parseArtifact("InitialVoiceCreditProxy.abi"); - return abi; -}; - -export class JSONRPCDeployer { - provider: JsonRpcProvider; - - signer: Signer; - - options?: Record; - - constructor(privateKey: string, providerUrl: string, options?: Record) { - this.provider = new JsonRpcProvider(providerUrl); - this.signer = new ethers.Wallet(privateKey, this.provider); - this.options = options; - } - - async deploy(abi: TAbi, bytecode: string, ...args: unknown[]): Promise { - const contractInterface = new Interface(abi); - const factory = new ethers.ContractFactory(contractInterface, bytecode, this.signer); - const contract = await factory.deploy(...args); - - return contract as Contract; - } -} - -const genJsonRpcDeployer = (privateKey: string, url: string): JSONRPCDeployer => new JSONRPCDeployer(privateKey, url); - -const log = (msg: string, quiet: boolean) => { - if (!quiet) { - // eslint-disable-next-line no-console - console.log(msg); - } -}; - -const getFeeData = async (): Promise => { - const signer = await getDefaultSigner(); - return signer.provider?.getFeeData(); -}; - -const linkPoseidonLibraries = async ( +import { parseArtifact } from "./abi"; +import { IDeployedMaci, IDeployedPoseidonContracts } from "./types"; +import { getDefaultSigner, getFeeData, log } from "./utils"; + +/** + * Link Poseidon libraries to a Smart Contract + * @param solFileToLink - the name of the contract to link the libraries to + * @param poseidonT3Address - the address of the PoseidonT3 contract + * @param poseidonT4Address - the address of the PoseidonT4 contract + * @param poseidonT5Address - the address of the PoseidonT5 contract + * @param poseidonT6Address - the address of the PoseidonT6 contract + * @param signer - the signer to use to deploy the contract + * @param quiet - whether to suppress console output + * @returns a contract factory with the libraries linked + */ +export const linkPoseidonLibraries = async ( solFileToLink: string, poseidonT3Address: string, poseidonT4Address: string, poseidonT5Address: string, poseidonT6Address: string, + signer?: Signer, quiet = false, ): Promise => { - const signer = await getDefaultSigner(); - log(`Linking Poseidon libraries to ${solFileToLink}`, quiet); const contractFactory = await ethers.getContractFactory(solFileToLink, { - signer, + signer: signer || (await getDefaultSigner()), libraries: { PoseidonT3: poseidonT3Address, PoseidonT4: poseidonT4Address, @@ -149,15 +62,21 @@ const linkPoseidonLibraries = async ( return contractFactory; }; -// Deploy a contract given a name and args -const deployContract = async ( +/** + * Deploy a Smart Contract given a name and some arguments + * @param contractName - the name of the contract + * @param signer - the signer to use to deploy the contract + * @param quiet - whether to suppress console output + * @param args - the constructor arguments of the contract + */ +export const deployContract = async ( contractName: string, + signer?: Signer, quiet = false, ...args: unknown[] ): Promise => { log(`Deploying ${contractName}`, quiet); - const signer = await getDefaultSigner(); - const contractFactory = await ethers.getContractFactory(contractName, signer); + const contractFactory = await ethers.getContractFactory(contractName, signer || (await getDefaultSigner())); const contract = await contractFactory.deploy(...args, { maxFeePerGas: await getFeeData().then((res) => res?.maxFeePerGas), }); @@ -166,43 +85,99 @@ const deployContract = async ( return contract as unknown as T; }; -const deployTopupCredit = async (quiet = false): Promise => - deployContract("TopupCredit", quiet); - -const deployVkRegistry = async (quiet = false): Promise => deployContract("VkRegistry", quiet); - -const deployMockVerifier = async (quiet = false): Promise => - deployContract("MockVerifier", quiet); - -const deployVerifier = async (quiet = false): Promise => deployContract("Verifier", quiet); - -const deployConstantInitialVoiceCreditProxy = async ( +/** + * Deploy a TopupCredit contract + * @param signer - the signer to use to deploy the contract + * @param quiet - whether to suppress console output + * @returns the deployed TopupCredit contract + */ +export const deployTopupCredit = async (signer?: Signer, quiet = false): Promise => + deployContract("TopupCredit", signer, quiet); + +/** + * Deploy a VkRegistry contract + * @param signer - the signer to use to deploy the contract + * @param quiet - whether to suppress console output + * @returns the deployed VkRegistry contract + */ +export const deployVkRegistry = async (signer?: Signer, quiet = false): Promise => + deployContract("VkRegistry", signer, quiet); + +/** + * Deploy a MockVerifier contract (testing only) + * @param signer - the signer to use to deploy the contract + * @param quiet - whether to suppress console output + * @returns the deployed MockVerifier contract + */ +export const deployMockVerifier = async (signer?: Signer, quiet = false): Promise => + deployContract("MockVerifier", signer, quiet); + +/** + * Deploy a Verifier contract + * @param signer - the signer to use to deploy the contract + * @param quiet - whether to suppress console output + * @returns the deployed Verifier contract + */ +export const deployVerifier = async (signer?: Signer, quiet = false): Promise => + deployContract("Verifier", signer, quiet); + +/** + * Deploy a constant initial voice credit proxy contract + * @param signer - the signer to use to deploy the contract + * @param amount - the amount of initial voice credit to give to each user + * @param quiet - whether to suppress console output + * @returns the deployed ConstantInitialVoiceCreditProxy contract + */ +export const deployConstantInitialVoiceCreditProxy = async ( amount: number, + signer?: Signer, quiet = false, ): Promise => - deployContract("ConstantInitialVoiceCreditProxy", quiet, amount.toString()); - -const deploySignupToken = async (quiet = false): Promise => - deployContract("SignUpToken", quiet); - -const deploySignupTokenGatekeeper = async (signUpTokenAddress: string, quiet = false): Promise => - deployContract("SignUpTokenGatekeeper", quiet, signUpTokenAddress); - -const deployFreeForAllSignUpGatekeeper = async (quiet = false): Promise => - deployContract("FreeForAllGatekeeper", quiet); - -interface IDeployedPoseidonContracts { - PoseidonT3Contract: PoseidonT3; - PoseidonT4Contract: PoseidonT4; - PoseidonT5Contract: PoseidonT5; - PoseidonT6Contract: PoseidonT6; -} - -const deployPoseidonContracts = async (quiet = false): Promise => { - const PoseidonT3Contract = await deployContract("PoseidonT3", quiet); - const PoseidonT4Contract = await deployContract("PoseidonT4", quiet); - const PoseidonT5Contract = await deployContract("PoseidonT5", quiet); - const PoseidonT6Contract = await deployContract("PoseidonT6", quiet); + deployContract("ConstantInitialVoiceCreditProxy", signer, quiet, amount.toString()); + +/** + * Deploy a SignUpToken contract + * @param signer - the signer to use to deploy the contract + * @param quiet - whether to suppress console output + * @returns the deployed SignUpToken contract + */ +export const deploySignupToken = async (signer?: Signer, quiet = false): Promise => + deployContract("SignUpToken", signer, quiet); + +/** + * Deploy a SignUpTokenGatekeeper contract + * @param signUpTokenAddress - the address of the SignUpToken contract + * @param signer - the signer to use to deploy the contract + * @param quiet - whether to suppress console output + * @returns a SignUpTokenGatekeeper contract + */ +export const deploySignupTokenGatekeeper = async ( + signUpTokenAddress: string, + signer?: Signer, + quiet = false, +): Promise => + deployContract("SignUpTokenGatekeeper", signer, quiet, signUpTokenAddress); + +/** + * Deploy a FreeForAllGatekeeper contract + * @param signer - the signer to use to deploy the contract + * @param quiet - whether to suppress console output + * @returns the deployed FreeForAllGatekeeper contract + */ +export const deployFreeForAllSignUpGatekeeper = async (signer?: Signer, quiet = false): Promise => + deployContract("FreeForAllGatekeeper", signer, quiet); + +/** + * Deploy Poseidon contracts + * @param signer - the signer to use to deploy the contracts + * @param quiet - whether to suppress console output + * @returns the deployed Poseidon contracts + */ +export const deployPoseidonContracts = async (signer?: Signer, quiet = false): Promise => { + const PoseidonT3Contract = await deployContract("PoseidonT3", signer, quiet); + const PoseidonT4Contract = await deployContract("PoseidonT4", signer, quiet); + const PoseidonT5Contract = await deployContract("PoseidonT5", signer, quiet); + const PoseidonT6Contract = await deployContract("PoseidonT6", signer, quiet); return { PoseidonT3Contract, @@ -212,10 +187,24 @@ const deployPoseidonContracts = async (quiet = false): Promise => deployContract("PollFactory", quiet); - -// deploy a contract with linked libraries -const deployContractWithLinkedLibraries = async ( +/** + * Deploy a PollFactory contract + * @param signer - the signer to use to deploy the contract + * @param quiet - whether to suppress console output + * @returns the deployed PollFactory contract + */ +export const deployPollFactory = async (signer?: Signer, quiet = false): Promise => + deployContract("PollFactory", signer, quiet); + +/** + * Deploy a contract with linked libraries + * @param contractFactory - the contract factory to use + * @param name - the name of the contract + * @param quiet - whether to suppress console output + * @param args - the constructor arguments of the contract + * @returns the deployed contract instance + */ +export const deployContractWithLinkedLibraries = async ( contractFactory: ContractFactory, name: string, quiet = false, @@ -230,22 +219,25 @@ const deployContractWithLinkedLibraries = async ( return contract as T; }; -const transferOwnership = async (contract: T, newOwner: string, quiet = false): Promise => { - log(`Transferring ownership of ${await contract.getAddress()} to ${newOwner}`, quiet); - const tx = await contract.transferOwnership(newOwner, { - maxFeePerGas: await getFeeData().then((res) => res?.maxFeePerGas), - }); - - await tx.wait(); -}; - -const deployMessageProcessor = async ( +/** + * Deploy a MessageProcessor contract + * @param verifierAddress - the address of the Verifier contract + * @param poseidonT3Address - the address of the PoseidonT3 contract + * @param poseidonT4Address - the address of the PoseidonT4 contract + * @param poseidonT5Address - the address of the PoseidonT5 contract + * @param poseidonT6Address - the address of the PoseidonT6 contract + * @param signer - the signer to use to deploy the contract + * @param quiet - whether to suppress console output + * @returns the deployed MessageProcessor contract + */ +export const deployMessageProcessor = async ( verifierAddress: string, vkRegistryAddress: string, poseidonT3Address: string, poseidonT4Address: string, poseidonT5Address: string, poseidonT6Address: string, + signer?: Signer, quiet = false, ): Promise => { // Link Poseidon contracts to MessageProcessor @@ -255,6 +247,7 @@ const deployMessageProcessor = async ( poseidonT4Address, poseidonT5Address, poseidonT6Address, + signer, quiet, ); @@ -267,13 +260,25 @@ const deployMessageProcessor = async ( ); }; -const deployTally = async ( +/** + * Deploy a Tally contract + * @param verifierAddress - the address of the Verifier contract + * @param poseidonT3Address - the address of the PoseidonT3 contract + * @param poseidonT4Address - the address of the PoseidonT4 contract + * @param poseidonT5Address - the address of the PoseidonT5 contract + * @param poseidonT6Address - the address of the PoseidonT6 contract + * @param signer - the signer to use to deploy the contract + * @param quiet - whether to suppress console output + * @returns the deployed Tally contract + */ +export const deployTally = async ( verifierAddress: string, vkRegistryAddress: string, poseidonT3Address: string, poseidonT4Address: string, poseidonT5Address: string, poseidonT6Address: string, + signer?: Signer, quiet = false, ): Promise => { // Link Poseidon contracts to Tally @@ -283,19 +288,31 @@ const deployTally = async ( poseidonT4Address, poseidonT5Address, poseidonT6Address, + signer, quiet, ); return deployContractWithLinkedLibraries(tallyFactory, "Tally", quiet, verifierAddress, vkRegistryAddress); }; -const deploySubsidy = async ( +/** + * Depoloy a Subsidy contract + * @param verifierAddress - the address of the Verifier contract + * @param poseidonT3Address - the address of the PoseidonT3 contract + * @param poseidonT4Address - the address of the PoseidonT4 contract + * @param poseidonT5Address - the address of the PoseidonT5 contract + * @param poseidonT6Address - the address of the PoseidonT6 contract + * @param quiet - whether to suppress console output + * @returns the deployed Subsidy contract + */ +export const deploySubsidy = async ( verifierAddress: string, vkRegistryAddress: string, poseidonT3Address: string, poseidonT4Address: string, poseidonT5Address: string, poseidonT6Address: string, + signer?: Signer, quiet = false, ): Promise => { // Link Poseidon contracts to Subsidy @@ -305,6 +322,7 @@ const deploySubsidy = async ( poseidonT4Address, poseidonT5Address, poseidonT6Address, + signer, quiet, ); @@ -317,23 +335,29 @@ const deploySubsidy = async ( ); }; -interface IDeployedMaci { - maciContract: MACI; - stateAqContract: AccQueueQuinaryMaci; - pollFactoryContract: PollFactory; - poseidonAddrs: string[]; -} - -const deployMaci = async ( +/** + * Deploy a MACI contract + * @param signUpTokenGatekeeperContractAddress - the address of the SignUpTokenGatekeeper contract + * @param initialVoiceCreditBalanceAddress - the address of the ConstantInitialVoiceCreditProxy contract + * @param verifierContractAddress - the address of the Verifier contract + * @param vkRegistryContractAddress - the address of the VkRegistry contract + * @param topupCreditContractAddress - the address of the TopupCredit contract + * @param stateTreeDepth - the depth of the state tree + * @param signer - the signer to use to deploy the contract + * @param quiet - whether to suppress console output + * @returns the deployed MACI contract + */ +export const deployMaci = async ( signUpTokenGatekeeperContractAddress: string, initialVoiceCreditBalanceAddress: string, verifierContractAddress: string, topupCreditContractAddress: string, + signer?: Signer, stateTreeDepth = 10, quiet = false, ): Promise => { const { PoseidonT3Contract, PoseidonT4Contract, PoseidonT5Contract, PoseidonT6Contract } = - await deployPoseidonContracts(quiet); + await deployPoseidonContracts(signer, quiet); const poseidonAddrs = await Promise.all([ PoseidonT3Contract.getAddress(), @@ -353,6 +377,7 @@ const deployMaci = async ( poseidonAddrs[1], poseidonAddrs[2], poseidonAddrs[3], + signer, quiet, ), ), @@ -394,54 +419,3 @@ const deployMaci = async ( poseidonAddrs, }; }; - -const writeContractAddresses = ( - maciContractAddress: string, - vkRegistryContractAddress: string, - stateAqContractAddress: string, - signUpTokenAddress: string, - outputAddressFile: string, -): void => { - const addresses = { - MaciContract: maciContractAddress, - VkRegistry: vkRegistryContractAddress, - StateAqContract: stateAqContractAddress, - SignUpToken: signUpTokenAddress, - }; - - const addressJsonPath = path.resolve(__dirname, "..", outputAddressFile); - writeFileSync(addressJsonPath, JSON.stringify(addresses)); - - // eslint-disable-next-line no-console - console.log(addresses); -}; - -export { - type IDeployedPoseidonContracts, - type IDeployedMaci, - deployContract, - deployContractWithLinkedLibraries, - deployTopupCredit, - deployVkRegistry, - deployMaci, - deployMessageProcessor, - deployTally, - deploySubsidy, - deploySignupToken, - deploySignupTokenGatekeeper, - deployConstantInitialVoiceCreditProxy, - deployFreeForAllSignUpGatekeeper, - deployMockVerifier, - deployVerifier, - deployPollFactory, - genJsonRpcDeployer, - getInitialVoiceCreditProxyAbi, - transferOwnership, - abiDir, - solDir, - parseArtifact, - linkPoseidonLibraries, - deployPoseidonContracts, - getDefaultSigner, - writeContractAddresses, -}; diff --git a/contracts/ts/deployer.ts b/contracts/ts/deployer.ts new file mode 100644 index 0000000000..a5e5707680 --- /dev/null +++ b/contracts/ts/deployer.ts @@ -0,0 +1,46 @@ +import { Contract, ContractFactory, Interface, JsonRpcProvider, Signer, Wallet } from "ethers"; + +import { TAbi } from "./types"; + +/** + * A class that can deploy smart contracts using a JSON-RPC provider. + */ +export class JSONRPCDeployer { + provider: JsonRpcProvider; + + signer: Signer; + + /** + * Generate a new JSONRPCDeployer instance. + * @param privateKey - the private key of the deployer + * @param providerUrl - the URL of the JSON-RPC provider + */ + constructor(privateKey: string, providerUrl: string) { + this.provider = new JsonRpcProvider(providerUrl); + this.signer = new Wallet(privateKey, this.provider); + } + + /** + * Deploy a new smart contract using the deployer's signer. + * @param abi - the ABI of the contract + * @param bytecode - the bytecode of the contract + * @param args - the constructor arguments of the contract + * @returns a Contract object + */ + async deploy(abi: TAbi, bytecode: string, ...args: unknown[]): Promise { + const contractInterface = new Interface(abi); + const factory = new ContractFactory(contractInterface, bytecode, this.signer); + const contract = await factory.deploy(...args); + + return contract as Contract; + } +} + +/** + * Generate a new JSONRPCDeployer instance. + * @param privateKey - the private key of the deployer + * @param url - the URL of the JSON-RPC provider + * @returns the deployer instance + */ +export const genJsonRpcDeployer = (privateKey: string, url: string): JSONRPCDeployer => + new JSONRPCDeployer(privateKey, url); diff --git a/contracts/ts/genEmptyBallotRootsContract.ts b/contracts/ts/genEmptyBallotRootsContract.ts index 2c835930a7..c723c04505 100644 --- a/contracts/ts/genEmptyBallotRootsContract.ts +++ b/contracts/ts/genEmptyBallotRootsContract.ts @@ -5,7 +5,9 @@ import fs from "fs"; import path from "path"; const genEmptyBallotRootsContract = (): string => { - const template = fs.readFileSync(path.resolve(__dirname, "EmptyBallotRoots.sol.template")).toString(); + const template = fs + .readFileSync(path.resolve(__dirname, "..", "templates", "EmptyBallotRoots.sol.template")) + .toString(); // This hard-coded value should be consistent with the value of `stateTreeDepth` of MACI.sol const stateTreeDepth = process.argv[2] ? Number.parseInt(process.argv[2], 10) : 10; diff --git a/contracts/ts/genMaciState.ts b/contracts/ts/genMaciState.ts index 14947c0184..8610145707 100644 --- a/contracts/ts/genMaciState.ts +++ b/contracts/ts/genMaciState.ts @@ -7,29 +7,23 @@ import assert from "assert"; import { MACI, Poll } from "../typechain-types"; -import { parseArtifact } from "./deploy"; -import { sleep } from "./utils"; - -interface Action { - type: string; - data: Partial<{ - pubKey: PubKey; - encPubKey: PubKey; - message: Message; - voiceCreditBalance: number; - timestamp: number; - stateIndex: number; - numSrQueueOps: number; - pollId: number; - pollAddr: string; - stateRoot: bigint; - messageRoot: bigint; - }>; - blockNumber: number; - transactionIndex: number; -} - -const genMaciStateFromContract = async ( +import { parseArtifact } from "./abi"; +import { Action } from "./types"; +import { sleep, sortActions } from "./utils"; + +/** + * Generate a MaciState object from the events of a MACI and Poll smart contracts + * @param provider - the ethereum provider + * @param address - the address of the MACI contract + * @param coordinatorKeypair - the keypair of the coordinator + * @param pollId - the id of the poll for which we are fetching events + * @param fromBlock - the block number from which to start fetching events + * @param blocksPerRequest - the number of blocks to fetch in each request + * @param endBlock - the block number at which to stop fetching events + * @param sleepAmount - the amount of time to sleep between each request + * @returns an instance of MaciState + */ +export const genMaciStateFromContract = async ( provider: Provider, address: string, coordinatorKeypair: Keypair, @@ -39,7 +33,7 @@ const genMaciStateFromContract = async ( endBlock: number | undefined = undefined, sleepAmount: number | undefined = undefined, ): Promise => { - // Verify and sort pollIds + // ensure the pollId is valid assert(pollId >= 0); const [pollContractAbi] = parseArtifact("Poll"); @@ -55,12 +49,13 @@ const genMaciStateFromContract = async ( // we need to pass the stateTreeDepth const maciState = new MaciState(Number(stateTreeDepth)); - + // ensure it is set correctly assert(stateTreeDepth === BigInt(maciState.stateTreeDepth)); let signUpLogs: Log[] = []; let deployPollLogs: Log[] = []; + // if no last block is set then we fetch until the current block number const lastBlock = endBlock || (await provider.getBlockNumber()); // Fetch event logs in batches @@ -289,7 +284,6 @@ const genMaciStateFromContract = async ( actions = sortActions(actions); // Reconstruct MaciState in order - actions.forEach((action) => { switch (true) { case action.type === "SignUp": { @@ -328,6 +322,7 @@ const genMaciStateFromContract = async ( break; } + // ensure that the message root is correct (i.e. all messages have been published offchain) case action.type === "MergeMessageAq": { assert(maciState.polls[pollId]?.messageTree.root.toString() === action.data.messageRoot?.toString()); break; @@ -345,35 +340,11 @@ const genMaciStateFromContract = async ( assert(Number(numSignUpsAndMessages[1]) === poll.messages.length); poll.numSignUps = Number(numSignUpsAndMessages[0]); + + // we need to ensure that the stateRoot is correct + assert(maciState.stateTree.root.toString() === (await maciContract.getStateAqRoot()).toString()); + maciState.polls[pollId] = poll; return maciState; }; - -/* - * The comparision function for Actions based on block number and transaction - * index. - */ -function sortActions(actions: Action[]): Action[] { - return actions.slice().sort((a, b) => { - if (a.blockNumber > b.blockNumber) { - return 1; - } - - if (a.blockNumber < b.blockNumber) { - return -1; - } - - if (a.transactionIndex > b.transactionIndex) { - return 1; - } - - if (a.transactionIndex < b.transactionIndex) { - return -1; - } - - return 0; - }); -} - -export { genMaciStateFromContract }; diff --git a/contracts/ts/genZerosContract.ts b/contracts/ts/genZerosContract.ts index 17a4c33f6c..953be5da75 100644 --- a/contracts/ts/genZerosContract.ts +++ b/contracts/ts/genZerosContract.ts @@ -15,7 +15,7 @@ const genZerosContract = ( ): string => { assert(hashLength === 2 || hashLength === 5); - const template = fs.readFileSync(path.resolve(__dirname, "MerkleZeros.sol.template")).toString(); + const template = fs.readFileSync(path.resolve(__dirname, "..", "templates", "MerkleZeros.sol.template")).toString(); const zeros: bigint[] = [zeroVal]; for (let i = 1; i < numZeros; i += 1) { diff --git a/contracts/ts/index.ts b/contracts/ts/index.ts index 97e1518540..8f83d11f2c 100644 --- a/contracts/ts/index.ts +++ b/contracts/ts/index.ts @@ -1,5 +1,4 @@ -import { - genJsonRpcDeployer, +export { deployMockVerifier, deployTopupCredit, deployVkRegistry, @@ -13,45 +12,15 @@ import { deployConstantInitialVoiceCreditProxy, deployFreeForAllSignUpGatekeeper, deployPollFactory, - getInitialVoiceCreditProxyAbi, - abiDir, - parseArtifact, - solDir, linkPoseidonLibraries, deployPoseidonContracts, deployVerifier, - getDefaultSigner, } from "./deploy"; -import { genMaciStateFromContract } from "./genMaciState"; -import { formatProofForVerifierContract, deployTestContracts } from "./utils"; - -export { - abiDir, - solDir, - parseArtifact, - genJsonRpcDeployer, - deployTopupCredit, - deployVkRegistry, - deployMaci, - deployMessageProcessor, - deployTally, - deploySubsidy, - deployContract, - deployMockVerifier, - deploySignupToken, - deploySignupTokenGatekeeper, - deployFreeForAllSignUpGatekeeper, - deployConstantInitialVoiceCreditProxy, - deployPollFactory, - deployTestContracts, - getInitialVoiceCreditProxyAbi, - formatProofForVerifierContract, - linkPoseidonLibraries, - deployPoseidonContracts, - deployVerifier, - getDefaultSigner, - genMaciStateFromContract, -}; +export { genJsonRpcDeployer } from "./deployer"; +export { genMaciStateFromContract } from "./genMaciState"; +export { formatProofForVerifierContract, getDefaultSigner, getSigners } from "./utils"; +export { abiDir, solDir } from "./constants"; +export { parseArtifact } from "./abi"; export type { IVerifyingKeyStruct } from "./types"; export * from "../typechain-types"; diff --git a/contracts/ts/types.ts b/contracts/ts/types.ts index fa99003fd2..5199358ea0 100644 --- a/contracts/ts/types.ts +++ b/contracts/ts/types.ts @@ -1,3 +1,5 @@ +import { Message, PubKey } from "maci-domainobjs"; + import type { AccQueueQuinaryMaci, ConstantInitialVoiceCreditProxy, @@ -5,11 +7,22 @@ import type { MACI, MessageProcessor, MockVerifier, + PollFactory, + PoseidonT3, + PoseidonT4, + PoseidonT5, + PoseidonT6, Tally, VkRegistry, } from "../typechain-types"; -import type { BigNumberish } from "ethers"; +import type { BigNumberish, Fragment, JsonFragment } from "ethers"; + +// a type representing the ABI of a contract +export type TAbi = string | readonly (string | Fragment | JsonFragment)[]; +/** + * The data structure of the verifying key of the SNARK circuit. + */ export interface IVerifyingKeyStruct { alpha1: { x: BigNumberish; @@ -33,12 +46,18 @@ export interface IVerifyingKeyStruct { }[]; } +/** + * The data structure representing a SNARK proof. + */ export interface SnarkProof { pi_a: bigint[]; pi_b: bigint[][]; pi_c: bigint[]; } +/** + * An interface holding all of the smart contracts part of MACI. + */ export interface IDeployedTestContracts { mockVerifierContract: MockVerifier; gatekeeperContract: FreeForAllGatekeeper; @@ -49,3 +68,47 @@ export interface IDeployedTestContracts { mpContract: MessageProcessor; tallyContract: Tally; } + +/** + * An interface that representes an action that should + * be applied to a MaciState and its Polls within the + * genMaciState function. + */ +export interface Action { + type: string; + data: Partial<{ + pubKey: PubKey; + encPubKey: PubKey; + message: Message; + voiceCreditBalance: number; + timestamp: number; + stateIndex: number; + numSrQueueOps: number; + pollId: number; + pollAddr: string; + stateRoot: bigint; + messageRoot: bigint; + }>; + blockNumber: number; + transactionIndex: number; +} + +/** + * An interface that represents the deployed Poseidon contracts. + */ +export interface IDeployedPoseidonContracts { + PoseidonT3Contract: PoseidonT3; + PoseidonT4Contract: PoseidonT4; + PoseidonT5Contract: PoseidonT5; + PoseidonT6Contract: PoseidonT6; +} + +/** + * An interface that represents the deployed MACI contracts. + */ +export interface IDeployedMaci { + maciContract: MACI; + stateAqContract: AccQueueQuinaryMaci; + pollFactoryContract: PollFactory; + poseidonAddrs: string[]; +} diff --git a/contracts/ts/utils.ts b/contracts/ts/utils.ts index a73174023c..0b52ae1716 100644 --- a/contracts/ts/utils.ts +++ b/contracts/ts/utils.ts @@ -1,19 +1,19 @@ -import type { IDeployedTestContracts, SnarkProof } from "./types"; - -import { FreeForAllGatekeeper } from "../typechain-types"; - -import { - deployVkRegistry, - deployTopupCredit, - deployMaci, - deployMessageProcessor, - deployTally, - deployMockVerifier, - deployFreeForAllSignUpGatekeeper, - deployConstantInitialVoiceCreditProxy, -} from "./deploy"; - -const formatProofForVerifierContract = (proof: SnarkProof): string[] => +import { FeeData, Signer } from "ethers"; +// eslint-disable-next-line +// @ts-ignore typedoc doesn't want to get types from toolbox +import { ethers } from "hardhat"; + +import type { Action, SnarkProof } from "./types"; + +import { Ownable } from "../typechain-types"; + +/** + * Format a SnarkProof type to an array of strings + * which can be passed to the Groth16 verifier contract. + * @param proof the SnarkProof to format + * @returns an array of strings + */ +export const formatProofForVerifierContract = (proof: SnarkProof): string[] => [ proof.pi_a[0], proof.pi_a[1], @@ -27,88 +27,97 @@ const formatProofForVerifierContract = (proof: SnarkProof): string[] => proof.pi_c[1], ].map((x) => x.toString()); -const deployTestContracts = async ( - initialVoiceCreditBalance: number, - stateTreeDepth: number, - quiet = false, - gatekeeper: FreeForAllGatekeeper | undefined = undefined, -): Promise => { - const mockVerifierContract = await deployMockVerifier(true); - - let gatekeeperContract = gatekeeper; - if (!gatekeeperContract) { - gatekeeperContract = await deployFreeForAllSignUpGatekeeper(true); - } - - const constantIntialVoiceCreditProxyContract = await deployConstantInitialVoiceCreditProxy( - initialVoiceCreditBalance, - true, - ); - - // VkRegistry - const vkRegistryContract = await deployVkRegistry(true); - const topupCreditContract = await deployTopupCredit(true); - const [ - gatekeeperContractAddress, - mockVerifierContractAddress, - constantIntialVoiceCreditProxyContractAddress, - vkRegistryContractAddress, - topupCreditContractAddress, - ] = await Promise.all([ - gatekeeperContract.getAddress(), - mockVerifierContract.getAddress(), - constantIntialVoiceCreditProxyContract.getAddress(), - vkRegistryContract.getAddress(), - topupCreditContract.getAddress(), - ]); - - const { maciContract, stateAqContract, poseidonAddrs } = await deployMaci( - gatekeeperContractAddress, - constantIntialVoiceCreditProxyContractAddress, - mockVerifierContractAddress, - topupCreditContractAddress, - stateTreeDepth, - quiet, - ); - const mpContract = await deployMessageProcessor( - mockVerifierContractAddress, - vkRegistryContractAddress, - poseidonAddrs[0], - poseidonAddrs[1], - poseidonAddrs[2], - poseidonAddrs[3], - true, - ); - const tallyContract = await deployTally( - mockVerifierContractAddress, - vkRegistryContractAddress, - poseidonAddrs[0], - poseidonAddrs[1], - poseidonAddrs[2], - poseidonAddrs[3], - true, - ); - - return { - mockVerifierContract, - gatekeeperContract, - constantIntialVoiceCreditProxyContract, - maciContract, - stateAqContract, - vkRegistryContract, - mpContract, - tallyContract, - }; -}; - /** * Pause the thread for n milliseconds * @param ms - the amount of time to sleep in milliseconds */ -const sleep = async (ms: number): Promise => { +export const sleep = async (ms: number): Promise => { await new Promise((resolve) => { setTimeout(resolve, ms); }); }; -export { type IDeployedTestContracts, deployTestContracts, formatProofForVerifierContract, sleep }; +/** + * The comparision function for Actions based on block number and transaction + * index. + * @param actions - the array of actions to sort + * @returns the sorted array of actions + */ +export function sortActions(actions: Action[]): Action[] { + return actions.slice().sort((a, b) => { + if (a.blockNumber > b.blockNumber) { + return 1; + } + + if (a.blockNumber < b.blockNumber) { + return -1; + } + + if (a.transactionIndex > b.transactionIndex) { + return 1; + } + + if (a.transactionIndex < b.transactionIndex) { + return -1; + } + + return 0; + }); +} + +/** + * Print to the console + * @param msg - the message to print + * @param quiet - whether to suppress console output + */ +export const log = (msg: string, quiet: boolean): void => { + if (!quiet) { + // eslint-disable-next-line no-console + console.log(msg); + } +}; + +/** + * Get the default signer from the hardhat node + * @returns the default signer + */ +export const getDefaultSigner = async (): Promise => { + const signers = await ethers.getSigners(); + return signers[0]; +}; + +/** + * Get all of the available signers from the hardhat node + * @dev to be used while testing + * @returns the signers + */ +export const getSigners = async (): Promise => ethers.getSigners(); + +/** + * Get the current fee data from the blockchain node. + * This is needed to ensure transaction go through in busy times + * @returns - the fee data + */ +export const getFeeData = async (): Promise => { + const signer = await getDefaultSigner(); + return signer.provider?.getFeeData(); +}; + +/** + * Transfer ownership of a contract (using Ownable from OpenZeppelin) + * @param contract - the contract to transfer ownership of + * @param newOwner - the address of the new owner + * @param quiet - whether to suppress console output + */ +export const transferOwnership = async ( + contract: T, + newOwner: string, + quiet = false, +): Promise => { + log(`Transferring ownership of ${await contract.getAddress()} to ${newOwner}`, quiet); + const tx = await contract.transferOwnership(newOwner, { + maxFeePerGas: await getFeeData().then((res) => res?.maxFeePerGas), + }); + + await tx.wait(); +};