diff --git a/contracts/.env.example b/contracts/.env.example index 558b8773bc..beb0e8ceac 100644 --- a/contracts/.env.example +++ b/contracts/.env.example @@ -2,3 +2,4 @@ MNEMONIC= ETHERSCAN_API_KEY= INFURA_KEY= OP_RPC_URL= +GAS_PRICE= diff --git a/contracts/deploy-config-example.json b/contracts/deploy-config-example.json index beec8861e9..d73c5b4cbc 100644 --- a/contracts/deploy-config-example.json +++ b/contracts/deploy-config-example.json @@ -16,6 +16,20 @@ "MACI": { "stateTreeDepth": 10, "gatekeeper": "EASGatekeeper" + }, + "VkRegistry": { + "stateTreeDepth": 10, + "intStateTreeDepth": 1, + "messageTreeDepth": 2, + "voteOptionTreeDepth": 2, + "messageBatchDepth": 1, + "processMessagesZkey": "../cli/zkeys/ProcessMessages_10-2-1-2_test/ProcessMessages_10-2-1-2_test.0.zkey", + "tallyVotesZkey": "../cli/zkeys/TallyVotes_10-1-2_test/TallyVotes_10-1-2_test.0.zkey" + }, + "Poll": { + "pollDuration": 30, + "coordinatorPubkey": "macipk.ea638a3366ed91f2e955110888573861f7c0fc0bb5fb8b8dca9cd7a08d7d6b93", + "subsidyEnabled": false } } } diff --git a/contracts/hardhat.config.ts b/contracts/hardhat.config.ts index 9adcc8a197..d7c824c663 100644 --- a/contracts/hardhat.config.ts +++ b/contracts/hardhat.config.ts @@ -11,6 +11,7 @@ import type { HardhatUserConfig } from "hardhat/config"; import "./tasks/deploy"; import { EChainId, ESupportedChains, NETWORKS_DEFAULT_GAS, getNetworkRpcUrls } from "./tasks/helpers/constants"; import "./tasks/runner/deployFull"; +import "./tasks/runner/deployPoll"; import "./tasks/runner/verifyFull"; dotenv.config(); @@ -24,7 +25,7 @@ const getCommonNetworkConfig = (networkName: ESupportedChains, chainId: number, url: NETWORKS_RPC_URL[networkName], blockGasLimit: DEFAULT_BLOCK_GAS_LIMIT, gasMultiplier: DEFAULT_GAS_MUL, - gasPrice: NETWORKS_DEFAULT_GAS[networkName], + gasPrice: process.env.GAS_PRICE ? Number(process.env.GAS_PRICE) : NETWORKS_DEFAULT_GAS[networkName], saveDeployments: true, chainId, accounts: { @@ -45,9 +46,14 @@ const config: HardhatUserConfig = { }, }, }, + defaultNetwork: "localhost", networks: { sepolia: getCommonNetworkConfig(ESupportedChains.Sepolia, EChainId.Sepolia), coverage: getCommonNetworkConfig(ESupportedChains.Coverage, EChainId.Coverage, TEST_MNEMONIC), + localhost: { + url: "http://localhost:8545", + loggingEnabled: false, + }, hardhat: { blockGasLimit: DEFAULT_BLOCK_GAS_LIMIT, gasMultiplier: DEFAULT_GAS_MUL, diff --git a/contracts/package.json b/contracts/package.json index ca30405edd..89d3ead9c1 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -36,9 +36,12 @@ "test:subsidy": "hardhat test ./tests/Subsidy.test.ts", "test:eas_gatekeeper": "hardhat test ./tests/EASGatekeeper.test.ts", "deploy": "hardhat deploy-full", + "deploy-poll": "hardhat deploy-poll", "verify": "hardhat verify-full", - "deploy:hardhat": "pnpm run deploy", + "deploy:localhost": "pnpm run deploy", "deploy:sepolia": "pnpm run deploy --network sepolia", + "deploy-poll:localhost": "pnpm run deploy-poll", + "deploy-poll:sepolia": "pnpm run deploy-poll --network sepolia", "verify:sepolia": "pnpm run verify --network sepolia" }, "dependencies": { diff --git a/contracts/tasks/deploy/index.ts b/contracts/tasks/deploy/index.ts index 251930a18d..71be91f7c5 100644 --- a/contracts/tasks/deploy/index.ts +++ b/contracts/tasks/deploy/index.ts @@ -4,7 +4,7 @@ import path from "path"; /** * The same as individual imports but doesn't require to add new import line everytime */ -["maci"].forEach((folder) => { +["maci", "poll"].forEach((folder) => { const tasksPath = path.resolve(__dirname, folder); if (fs.existsSync(tasksPath)) { diff --git a/contracts/tasks/deploy/maci/01-constantInitialVoiceCreditProxy.ts b/contracts/tasks/deploy/maci/01-constantInitialVoiceCreditProxy.ts index 4b10b9eb9f..9566f1b3ea 100644 --- a/contracts/tasks/deploy/maci/01-constantInitialVoiceCreditProxy.ts +++ b/contracts/tasks/deploy/maci/01-constantInitialVoiceCreditProxy.ts @@ -12,7 +12,7 @@ const storage = ContractStorage.getInstance(); */ deployment .deployTask("full:deploy-constant-initial-voice-credit-proxy", "Deploy constant initial voice credit proxy") - .setAction(async ({ incremental }: IDeployParams & { amount: number }, hre) => { + .setAction(async ({ incremental }: IDeployParams, hre) => { deployment.setHre(hre); const deployer = await deployment.getDeployer(); diff --git a/contracts/tasks/deploy/maci/11-vkRegistry.ts b/contracts/tasks/deploy/maci/11-vkRegistry.ts new file mode 100644 index 0000000000..24c492f4f1 --- /dev/null +++ b/contracts/tasks/deploy/maci/11-vkRegistry.ts @@ -0,0 +1,90 @@ +import { extractVk } from "maci-circuits"; +import { VerifyingKey } from "maci-domainobjs"; + +import type { IVerifyingKeyStruct } from "../../../ts"; +import type { BaseContract, BaseContractMethod, BigNumberish, TransactionResponse } from "ethers"; + +import { ContractStorage } from "../../helpers/ContractStorage"; +import { Deployment } from "../../helpers/Deployment"; +import { EContracts, IDeployParams } from "../../helpers/types"; + +// Add type manually because typechain is not available during compilation +interface VkRegistry extends BaseContract { + setVerifyingKeys: BaseContractMethod< + [BigNumberish, BigNumberish, BigNumberish, BigNumberish, BigNumberish, IVerifyingKeyStruct, IVerifyingKeyStruct], + TransactionResponse + >; + setSubsidyKeys: BaseContractMethod< + [BigNumberish, BigNumberish, BigNumberish, IVerifyingKeyStruct], + TransactionResponse + >; +} + +const deployment = Deployment.getInstance(); +const storage = ContractStorage.getInstance(); + +/** + * Deploy step registration and task itself + */ +deployment + .deployTask("full:deploy-vk-registry", "Deploy Vk Registry and set keys") + .setAction(async ({ incremental }: IDeployParams, hre) => { + deployment.setHre(hre); + const deployer = await deployment.getDeployer(); + + const vkRegistryContractAddress = storage.getAddress(EContracts.VkRegistry, hre.network.name); + + if (incremental && vkRegistryContractAddress) { + return; + } + + const stateTreeDepth = deployment.getDeployConfigField(EContracts.VkRegistry, "stateTreeDepth"); + const intStateTreeDepth = deployment.getDeployConfigField(EContracts.VkRegistry, "intStateTreeDepth"); + const messageTreeDepth = deployment.getDeployConfigField(EContracts.VkRegistry, "messageTreeDepth"); + const messageBatchDepth = deployment.getDeployConfigField(EContracts.VkRegistry, "messageBatchDepth"); + const voteOptionTreeDepth = deployment.getDeployConfigField(EContracts.VkRegistry, "voteOptionTreeDepth"); + const processMessagesZkeyPath = deployment.getDeployConfigField( + EContracts.VkRegistry, + "processMessagesZkey", + ); + const tallyVotesZkeyPath = deployment.getDeployConfigField(EContracts.VkRegistry, "tallyVotesZkey"); + const subsidyZkeyPath = deployment.getDeployConfigField(EContracts.VkRegistry, "subsidyZkey"); + + const [processVk, tallyVk, subsidyVk] = await Promise.all([ + extractVk(processMessagesZkeyPath), + extractVk(tallyVotesZkeyPath), + subsidyZkeyPath && extractVk(subsidyZkeyPath), + ]).then((vks) => vks.map((vk) => (vk ? VerifyingKey.fromObj(vk) : null))); + + const vkRegistryContract = await deployment.deployContract(EContracts.VkRegistry, deployer); + + await vkRegistryContract + .setVerifyingKeys( + stateTreeDepth, + intStateTreeDepth, + messageTreeDepth, + voteOptionTreeDepth, + 5 ** messageBatchDepth, + processVk!.asContractParam() as IVerifyingKeyStruct, + tallyVk!.asContractParam() as IVerifyingKeyStruct, + ) + .then((tx) => tx.wait()); + + if (subsidyVk) { + await vkRegistryContract + .setSubsidyKeys( + stateTreeDepth, + intStateTreeDepth, + voteOptionTreeDepth, + subsidyVk.asContractParam() as IVerifyingKeyStruct, + ) + .then((tx) => tx.wait()); + } + + await storage.register({ + id: EContracts.VkRegistry, + contract: vkRegistryContract, + args: [], + network: hre.network.name, + }); + }); diff --git a/contracts/tasks/deploy/poll/01-poll.ts b/contracts/tasks/deploy/poll/01-poll.ts new file mode 100644 index 0000000000..e9b7ee35ea --- /dev/null +++ b/contracts/tasks/deploy/poll/01-poll.ts @@ -0,0 +1,133 @@ +/* eslint-disable no-console */ +import { BaseContract, BaseContractMethod, BigNumberish, TransactionResponse } from "ethers"; +import { IG1ContractParams, PubKey } from "maci-domainobjs"; + +import { parseArtifact } from "../../../ts/abi"; +import { ContractStorage } from "../../helpers/ContractStorage"; +import { Deployment } from "../../helpers/Deployment"; +import { EContracts } from "../../helpers/types"; + +const deployment = Deployment.getInstance(); +const storage = ContractStorage.getInstance(); + +// Add type manually because typechain is not available during compilation +type TDeployPollParams = [ + BigNumberish, + { + intStateTreeDepth: BigNumberish; + messageTreeSubDepth: BigNumberish; + messageTreeDepth: BigNumberish; + voteOptionTreeDepth: BigNumberish; + }, + IG1ContractParams, + string, + string, + boolean, +]; + +interface MACI extends BaseContract { + nextPollId: () => Promise; + stateAq: () => Promise; + deployPoll: BaseContractMethod; +} + +interface StateAq extends BaseContract { + treeMerged: () => Promise; +} + +interface Poll extends BaseContract { + maxValues: () => Promise<[BigNumberish, BigNumberish]>; + extContracts: () => Promise<[string, string, string]>; +} + +/** + * Deploy step registration and task itself + */ +deployment.deployTask("poll:deploy-poll", "Deploy poll").setAction(async (_, hre) => { + deployment.setHre(hre); + const deployer = await deployment.getDeployer(); + + const maciContractAddress = storage.getAddress(EContracts.MACI, hre.network.name); + const verifierContractAddress = storage.getAddress(EContracts.Verifier, hre.network.name); + const vkRegistryContractAddress = storage.getAddress(EContracts.VkRegistry, hre.network.name); + + if (!maciContractAddress) { + throw new Error("Need to deploy MACI contract first"); + } + + if (!verifierContractAddress) { + throw new Error("Need to deploy Verifier contract first"); + } + + if (!vkRegistryContractAddress) { + throw new Error("Need to deploy VkRegistry contract first"); + } + + const [maciAbi] = parseArtifact("MACI"); + const maciContract = new BaseContract(maciContractAddress, maciAbi, deployer) as MACI; + const pollId = await maciContract.nextPollId(); + const stateAqContractAddress = await maciContract.stateAq(); + + const [stateAqAbi] = parseArtifact("AccQueue"); + const stateAq = new BaseContract(stateAqContractAddress, stateAqAbi, deployer) as StateAq; + const isTreeMerged = await stateAq.treeMerged(); + + if (pollId > 0n && !isTreeMerged) { + console.log("Previous poll is not completed"); + return; + } + + const coordinatorPubkey = deployment.getDeployConfigField(EContracts.Poll, "coordinatorPubkey"); + const pollDuration = deployment.getDeployConfigField(EContracts.Poll, "pollDuration"); + const intStateTreeDepth = deployment.getDeployConfigField(EContracts.VkRegistry, "intStateTreeDepth"); + const messageTreeSubDepth = deployment.getDeployConfigField(EContracts.VkRegistry, "messageBatchDepth"); + const messageTreeDepth = deployment.getDeployConfigField(EContracts.VkRegistry, "messageTreeDepth"); + const voteOptionTreeDepth = deployment.getDeployConfigField(EContracts.VkRegistry, "voteOptionTreeDepth"); + const subsidyEnabled = deployment.getDeployConfigField(EContracts.Poll, "subsidyEnabled") ?? false; + const unserializedKey = PubKey.deserialize(coordinatorPubkey); + + const deployPollParams: TDeployPollParams = [ + pollDuration, + { + intStateTreeDepth, + messageTreeSubDepth, + messageTreeDepth, + voteOptionTreeDepth, + }, + unserializedKey.asContractParam(), + verifierContractAddress, + vkRegistryContractAddress, + subsidyEnabled, + ]; + + const [pollAddress] = await maciContract.deployPoll.staticCall(...deployPollParams); + const tx = await maciContract.deployPoll(...deployPollParams); + const receipt = await tx.wait(); + + if (receipt?.status !== 1) { + throw new Error("Deploy poll transaction is failed"); + } + + const [pollAbi] = parseArtifact("Poll"); + const pollContract = new BaseContract(pollAddress, pollAbi, deployer) as Poll; + const [maxValues, extContracts] = await Promise.all([pollContract.maxValues(), pollContract.extContracts()]); + + await storage.register({ + id: EContracts.Poll, + key: pollId, + contract: pollContract, + args: [ + pollDuration, + maxValues.map((value) => value.toString()), + { + intStateTreeDepth, + messageTreeSubDepth, + messageTreeDepth, + voteOptionTreeDepth, + }, + unserializedKey.asContractParam(), + extContracts, + ], + network: hre.network.name, + }); +}); diff --git a/contracts/tasks/helpers/ContractStorage.ts b/contracts/tasks/helpers/ContractStorage.ts index b2365bac59..eea9fb9dc7 100644 --- a/contracts/tasks/helpers/ContractStorage.ts +++ b/contracts/tasks/helpers/ContractStorage.ts @@ -13,7 +13,7 @@ import type { EContracts, IRegisterContract, IStorageInstanceEntry, IStorageName type TStorage = Record< string, Partial<{ - named: Record; + named: Record>; instance: Record; verified: Record; }> @@ -59,7 +59,7 @@ export class ContractStorage { * * @param {IRegisterContract} args - register arguments */ - async register({ id, contract, network, args }: IRegisterContract): Promise { + async register({ id, key, contract, network, args }: IRegisterContract): Promise { const contractAddress = await contract.getAddress(); const deploymentTx = contract.deploymentTransaction(); @@ -90,10 +90,12 @@ export class ContractStorage { this.db.set(`${network}.instance.${contractAddress}`, logEntry).write(); - const namedEntry = this.db.get(`${network}.named.${id}`).value() as IStorageNamedEntry | undefined; + const namedEntry = this.db.get(`${network}.named.${id}${key !== undefined ? `.poll-${key}` : ""}`).value() as + | IStorageNamedEntry + | undefined; const count = namedEntry?.count ?? 0; this.db - .set(`${network}.named.${id}`, { + .set(`${network}.named.${id}${key !== undefined ? `.poll-${key}` : ""}`, { address: contractAddress, count: count + 1, }) @@ -142,8 +144,8 @@ export class ContractStorage { * @param network - selected network * @returns contract address */ - getAddress(id: EContracts, network: string): string | undefined { - const collection = this.db.get(`${network}.named.${id}`); + getAddress(id: EContracts, network: string, key?: string): string | undefined { + const collection = this.db.get(`${network}.named.${id}${key !== undefined ? `.poll-${key}` : ""}`); const namedEntry = collection.value() as IStorageNamedEntry | undefined; return namedEntry?.address; @@ -180,7 +182,7 @@ export class ContractStorage { const entryMap = new Map(); const { named, instance } = this.db.get(network).value(); - const namedEntries = Object.entries(named || {}); + const namedEntries = Object.entries>(named || {}); const instanceEntries = Object.entries(instance || {}); let multiCount = 0; @@ -190,12 +192,26 @@ export class ContractStorage { return; } - if (value.count > 1) { - console.log(`\t${key}: N=${value.count}`); - multiCount += 1; + if (typeof value.count === "number" && typeof value.address === "string") { + if (value.count > 1) { + console.log(`\t${key}: N=${value.count}`); + multiCount += 1; + } else { + console.log(`\t${key}: ${value.address}`); + entryMap.set(key, value.address); + } } else { - console.log(`\t${key}: ${value.address}`); - entryMap.set(key, value.address); + const entries = Object.entries(value as Record); + + entries.forEach(([id, nested]) => { + if (nested.count > 1) { + console.log(`\t${key}-${id}: N=${nested.count}`); + multiCount += 1; + } else { + console.log(`\t${key}-${id}: ${nested.address}`); + entryMap.set(id, nested.address); + } + }); } }); diff --git a/contracts/tasks/helpers/Deployment.ts b/contracts/tasks/helpers/Deployment.ts index fe5950a155..8f3410bb7d 100644 --- a/contracts/tasks/helpers/Deployment.ts +++ b/contracts/tasks/helpers/Deployment.ts @@ -1,13 +1,18 @@ +/* eslint-disable no-console */ /* eslint-disable import/no-extraneous-dependencies */ import { BaseContract, ContractFactory, Signer } from "ethers"; import { task } from "hardhat/config"; import low from "lowdb"; import FileSync from "lowdb/adapters/FileSync"; +import { exit } from "process"; + import type { EContracts, IDeployParams, IDeployStep, IDeployStepCatalog } from "./types"; import type { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"; import type { ConfigurableTaskDefinition, HardhatRuntimeEnvironment, TaskArguments } from "hardhat/types"; +import { ContractStorage } from "./ContractStorage"; + /** * Internal deploy config structure type. */ @@ -37,13 +42,22 @@ export class Deployment { */ private config: low.LowdbSync; + /** + * Contract storage + */ + private storage: ContractStorage; + /** * Initialize class properties only once */ private constructor(hre?: HardhatRuntimeEnvironment) { - this.stepCatalog = new Map([["full", []]]); + this.stepCatalog = new Map([ + ["full", []], + ["poll", []], + ]); this.hre = hre; this.config = low(new FileSync("./deploy-config.json")); + this.storage = ContractStorage.getInstance(); } /** @@ -59,6 +73,125 @@ export class Deployment { return Deployment.INSTANCE; } + /** + * Start deploy with console log information + * + * @param catalog - deploy steps catalog + * @param {IDeployParams} params - deploy params + * @returns deploy steps for selected catalog + */ + async start(catolog: string, { incremental, verify }: IDeployParams): Promise { + const deployer = await this.getDeployer(); + const deployerAddress = await deployer.getAddress(); + const startBalance = await deployer.provider.getBalance(deployer); + + console.log("Deployer address:", deployerAddress); + console.log("Deployer start balance: ", Number(startBalance / 10n ** 12n) / 1e6); + + if (incremental) { + console.log("======================================================================"); + console.log("======================================================================"); + console.log("==================== ATTENTION! INCREMENTAL MODE ==============="); + console.log("======================================================================"); + console.log("=========== Delete 'deployed-contracts.json' to start a new =========="); + console.log("======================================================================"); + console.log("======================================================================"); + } else { + this.storage.cleanup(this.hre!.network.name); + } + + console.log("Deployment started\n"); + + return this.getDeploySteps(catolog, { + incremental, + verify, + }); + } + + /** + * Run deploy steps + * + * @param steps - deploy steps + * @param skip - skip steps with less or equal index + */ + async runSteps(steps: IDeployStep[], skip: number): Promise { + // eslint-disable-next-line no-restricted-syntax + for (const step of steps) { + const stepId = `0${step.id}`; + console.log("\n======================================================================"); + console.log(stepId.slice(stepId.length - 2), step.name); + console.log("======================================================================\n"); + + if (step.id <= skip) { + console.log(`STEP ${step.id} WAS SKIPPED`); + } else { + // eslint-disable-next-line no-await-in-loop + await this.hre!.run(step.taskName, step.args); + } + } + } + + /** + * Print deployment results and check warnings + * + * @param strict - fail on warnings is enabled + * @throws error if strict is enabled and warning is found + */ + async checkResults(strict?: boolean): Promise { + const deployer = await this.getDeployer(); + const deployerAddress = await deployer.getAddress(); + const [entryMap, instanceCount, multiCount] = this.storage.printContracts(deployerAddress, this.hre!.network.name); + let hasWarn = false; + + if (multiCount > 0) { + console.warn("WARNING: multi-deployed contract(s) detected"); + hasWarn = true; + } else if (entryMap.size !== instanceCount) { + console.warn("WARNING: unknown contract(s) detected"); + hasWarn = true; + } + + entryMap.forEach((_, key) => { + if (key.startsWith("Mock")) { + console.warn("WARNING: mock contract detected:", key); + hasWarn = true; + } + }); + + if (hasWarn && strict) { + throw new Error("Warnings are present"); + } + } + + /** + * Finish deployment with console log information + * + * @param startBalance - start deployer balance + * @param success - success or not + */ + async finish(startBalance: bigint, success: boolean): Promise { + const deployer = await this.getDeployer(); + const { gasPrice } = this.hre!.network.config; + const endBalance = await deployer.provider.getBalance(deployer); + + console.log("======================================================================"); + console.log("Deployer end balance: ", Number(endBalance / 10n ** 12n) / 1e6); + console.log("Deploy expenses: ", Number((startBalance - endBalance) / 10n ** 12n) / 1e6); + + if (gasPrice !== "auto") { + console.log("Deploy gas: ", Number(startBalance - endBalance) / gasPrice, "@", gasPrice / 1e9, " gwei"); + } + + console.log("======================================================================"); + + if (!success) { + console.log("\nDeployment has failed"); + exit(1); + } + + console.log("\nDeployment has finished"); + } + /** * Get deployer (first signer) from hardhat runtime environment * @@ -143,7 +276,7 @@ export class Deployment { * @param {IDeployParams} params - deploy params * @returns {Promise} deploy steps */ - getDeploySteps = async (deployType: string, params: IDeployParams): Promise => { + private async getDeploySteps(deployType: string, params: IDeployParams): Promise { const stepList = this.stepCatalog.get(deployType); if (!stepList) { @@ -158,7 +291,7 @@ export class Deployment { args: args as unknown, })), ); - }; + } /** * Deploy contract and return it diff --git a/contracts/tasks/helpers/types.ts b/contracts/tasks/helpers/types.ts index 0be0994819..dac0fb4cc7 100644 --- a/contracts/tasks/helpers/types.ts +++ b/contracts/tasks/helpers/types.ts @@ -1,4 +1,4 @@ -import type { BaseContract } from "ethers"; +import type { BaseContract, BigNumberish } from "ethers"; import type { Libraries, TaskArguments } from "hardhat/types"; /** @@ -131,6 +131,11 @@ export interface IRegisterContract { * Contract deployment arguments */ args?: unknown[]; + + /** + * Group key for same contracts + */ + key?: BigNumberish; } /** @@ -152,6 +157,8 @@ export enum EContracts { PoseidonT4 = "PoseidonT4", PoseidonT5 = "PoseidonT5", PoseidonT6 = "PoseidonT6", + VkRegistry = "VkRegistry", + Poll = "Poll", } /** diff --git a/contracts/tasks/runner/deployFull.ts b/contracts/tasks/runner/deployFull.ts index e0e8be1caf..30b8d42af9 100644 --- a/contracts/tasks/runner/deployFull.ts +++ b/contracts/tasks/runner/deployFull.ts @@ -1,16 +1,13 @@ /* eslint-disable no-console */ import { task, types } from "hardhat/config"; -import { exit } from "process"; - import type { IDeployParams } from "../helpers/types"; -import { ContractStorage } from "../helpers/ContractStorage"; import { Deployment } from "../helpers/Deployment"; /** * Main deployment task which runs deploy steps in the same order that `Deployment#deployTask` is called. - * Note: you probably need to use indicies for deployment step files to support the correct order. + * Note: you probably need to use indices for deployment step files to support the correct order. * Make sure you have deploy-config.json (see deploy-config-example.json). */ task("deploy-full", "Deploy environment") @@ -18,103 +15,28 @@ task("deploy-full", "Deploy environment") .addFlag("strict", "Fail on warnings") .addFlag("verify", "Verify contracts at Etherscan") .addOptionalParam("skip", "Skip steps with less or equal index", 0, types.int) - .addOptionalParam("amount", "The amount of initial voice credit to give to each user", undefined, types.int) - .addOptionalParam("stateTreeDepth", "The depth of the state tree", 10, types.int) .setAction(async ({ incremental, strict, verify, skip = 0 }: IDeployParams, hre) => { const deployment = Deployment.getInstance(hre); - const storage = ContractStorage.getInstance(); deployment.setHre(hre); + const deployer = await deployment.getDeployer(); const startBalance = await deployer.provider.getBalance(deployer); - const deployerAddress = await deployer.getAddress(); - let success = false; try { - console.log("Deployer address:", deployerAddress); - console.log("Deployer start balance: ", startBalance / 10n ** 18n); - - if (incremental) { - console.log("======================================================================"); - console.log("======================================================================"); - console.log("==================== ATTENTION! INCREMENTAL MODE ==============="); - console.log("======================================================================"); - console.log("=========== Delete 'deployed-contracts.json' to start a new =========="); - console.log("======================================================================"); - console.log("======================================================================"); - } else { - storage.cleanup(hre.network.name); - } - - console.log("Deployment started\n"); - const steps = await deployment.getDeploySteps("full", { - incremental, - verify, - }); - - // eslint-disable-next-line no-restricted-syntax - for (const step of steps) { - const stepId = `0${step.id}`; - console.log("\n======================================================================"); - console.log(stepId.slice(stepId.length - 2), step.name); - console.log("======================================================================\n"); - - if (step.id <= skip) { - console.log(`STEP ${step.id} WAS SKIPPED`); - } else { - // eslint-disable-next-line no-await-in-loop - await hre.run(step.taskName, step.args); - } - } - - const [entryMap, instanceCount, multiCount] = storage.printContracts(deployerAddress, hre.network.name); - - let hasWarn = false; + const steps = await deployment.start("full", { incremental, verify }); - if (multiCount > 0) { - console.warn("WARNING: multi-deployed contract(s) detected"); - hasWarn = true; - } else if (entryMap.size !== instanceCount) { - console.warn("WARNING: unknown contract(s) detected"); - hasWarn = true; - } + await deployment.runSteps(steps, skip); - entryMap.forEach((_, key) => { - if (key.startsWith("Mock")) { - console.warn("WARNING: mock contract detected:", key); - hasWarn = true; - } - }); - - if (hasWarn && strict) { - throw new Error("Warnings are present"); - } + await deployment.checkResults(strict); success = true; } catch (err) { console.error("\n=========================================================\nERROR:", err, "\n"); } - const { gasPrice } = hre.network.config; - const endBalance = await deployer.provider.getBalance(deployer); - - console.log("======================================================================"); - console.log("Deployer end balance: ", Number(endBalance / 10n ** 12n) / 1e6); - console.log("Deploy expenses: ", Number((startBalance - endBalance) / 10n ** 12n) / 1e6); - - if (gasPrice !== "auto") { - console.log("Deploy gas: ", Number(startBalance - endBalance) / gasPrice, "@", gasPrice / 1e9, " gwei"); - } - - console.log("======================================================================"); - - if (!success) { - console.log("\nDeployment has failed"); - exit(1); - } - - console.log("\nDeployment has finished"); + await deployment.finish(startBalance, success); if (verify) { console.log("Verify all contracts"); diff --git a/contracts/tasks/runner/deployPoll.ts b/contracts/tasks/runner/deployPoll.ts new file mode 100644 index 0000000000..56cb7de35a --- /dev/null +++ b/contracts/tasks/runner/deployPoll.ts @@ -0,0 +1,45 @@ +/* eslint-disable no-console */ +import { task, types } from "hardhat/config"; + +import type { IDeployParams } from "../helpers/types"; + +import { Deployment } from "../helpers/Deployment"; + +/** + * Poll deployment task which runs deploy steps in the same order that `Deployment#deployTask` is called. + * Note: you probably need to use indices for deployment step files to support the correct order. + * Make sure you run `deploy-full` task before running `deploy-poll` and have deploy-config.json (see deploy-config-example.json). + */ +task("deploy-poll", "Deploy poll") + .addFlag("incremental", "Incremental deployment") + .addFlag("strict", "Fail on warnings") + .addFlag("verify", "Verify contracts at Etherscan") + .addOptionalParam("skip", "Skip steps with less or equal index", 0, types.int) + .setAction(async ({ strict, verify, skip = 0 }: IDeployParams, hre) => { + const deployment = Deployment.getInstance(hre); + + deployment.setHre(hre); + + const deployer = await deployment.getDeployer(); + const startBalance = await deployer.provider.getBalance(deployer); + let success = false; + + try { + const steps = await deployment.start("poll", { incremental: true, verify }); + + await deployment.runSteps(steps, skip); + + await deployment.checkResults(strict); + + success = true; + } catch (err) { + console.error("\n=========================================================\nERROR:", err, "\n"); + } + + await deployment.finish(startBalance, success); + + if (verify) { + console.log("Verify all contracts"); + await hre.run("verify-full"); + } + }); diff --git a/contracts/ts/abi.ts b/contracts/ts/abi.ts index 00af45519f..75e8911157 100644 --- a/contracts/ts/abi.ts +++ b/contracts/ts/abi.ts @@ -1,10 +1,13 @@ import fs from "fs"; import path from "path"; -import type { TAbi } from "./types"; +import type { Fragment, JsonFragment } from "ethers"; import { abiDir } from "./constants"; +// a type representing the ABI of a contract +export type TAbi = string | readonly (string | Fragment | JsonFragment)[]; + /** * Parse a contract artifact and return its ABI and bytecode. * @param filename - the name of the contract diff --git a/contracts/ts/deployer.ts b/contracts/ts/deployer.ts index 74f3d868ad..f1fa93069d 100644 --- a/contracts/ts/deployer.ts +++ b/contracts/ts/deployer.ts @@ -1,6 +1,6 @@ import { type Contract, type Signer, ContractFactory, Interface, JsonRpcProvider, Wallet } from "ethers"; -import type { TAbi } from "./types"; +import type { TAbi } from "./abi"; /** * A class that can deploy smart contracts using a JSON-RPC provider. diff --git a/contracts/ts/types.ts b/contracts/ts/types.ts index b9a2013095..0567811800 100644 --- a/contracts/ts/types.ts +++ b/contracts/ts/types.ts @@ -12,12 +12,9 @@ import type { TopupCredit, VkRegistry, } from "../typechain-types"; -import type { BigNumberish, Fragment, JsonFragment, Signer } from "ethers"; +import type { BigNumberish, Signer } from "ethers"; import type { Message, PubKey } from "maci-domainobjs"; -// 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. */