From 2bf1d1c3ceac67731a420b6d25cf58974769cf60 Mon Sep 17 00:00:00 2001 From: yuetloo Date: Mon, 11 Mar 2024 00:10:24 -0400 Subject: [PATCH 01/17] refactor deployment scripts to allow increment deployment and manually manage nonce --- README.md | 16 +- contracts/.gitignore | 2 + contracts/README.md | 28 +- contracts/deploy-config-example.json | 58 ++ contracts/sh/deployLocal.sh | 37 +- contracts/sh/runScriptTests.sh | 63 +-- contracts/tasks/configure/setCoordinator.ts | 36 -- .../tasks/configure/setFundingRoundFactory.ts | 47 -- .../tasks/configure/setMaciParameters.ts | 37 -- contracts/tasks/configure/setPollFactory.ts | 40 -- .../tasks/configure/setRecipientRegistry.ts | 82 --- contracts/tasks/configure/setToken.ts | 52 -- contracts/tasks/configure/setUserRegistry.ts | 87 --- contracts/tasks/deploy/newClrFund.ts | 341 ------------ contracts/tasks/deploy/newDeployer.ts | 77 --- contracts/tasks/deploy/newRound.ts | 102 ---- contracts/tasks/deploy/newSponsor.ts | 21 - contracts/tasks/deployRecipientRegistry.ts | 76 --- contracts/tasks/helpers/ContractStorage.ts | 327 +++++++++++ contracts/tasks/helpers/ContractVerifier.ts | 60 +++ contracts/tasks/helpers/Subtask.ts | 509 ++++++++++++++++++ contracts/tasks/helpers/types.ts | 229 ++++++++ contracts/tasks/index.ts | 59 +- contracts/tasks/maciNewKey.ts | 15 - .../{testutils => runners}/addRecipients.ts | 12 +- .../tasks/{clrCancel.ts => runners/cancel.ts} | 6 +- .../tasks/{clrClaim.ts => runners/claim.ts} | 23 +- contracts/tasks/runners/contribute.ts | 200 +++++++ .../exportRound.ts} | 14 +- .../{clrFinalize.ts => runners/finalize.ts} | 31 +- .../findStorageSlot.ts} | 7 +- .../loadMerkleUsers.ts} | 12 +- .../loadSimpleUsers.ts} | 9 +- contracts/tasks/{ => runners}/maciPubkey.ts | 0 .../mergeAllocation.ts} | 0 contracts/tasks/runners/newClrFund.ts | 68 +++ contracts/tasks/runners/newMaciKey.ts | 16 + contracts/tasks/runners/newRound.ts | 84 +++ contracts/tasks/runners/setCoordinator.ts | 41 ++ contracts/tasks/runners/setMaciParameters.ts | 52 ++ .../tasks/runners/setRecipientRegistry.ts | 61 +++ .../{configure => runners}/setStorageRoot.ts | 2 +- contracts/tasks/runners/setToken.ts | 56 ++ contracts/tasks/runners/setUserRegistry.ts | 70 +++ .../tasks/{clrTally.ts => runners/tally.ts} | 77 ++- .../{testutils => runners}/timeTravel.ts | 4 +- contracts/tasks/runners/verifyAll.ts | 98 ++++ contracts/tasks/runners/verifyTallyFile.ts | 37 ++ .../tasks/subtasks/clrfund/01-poseidon.ts | 88 +++ .../tasks/subtasks/clrfund/02-vkRegistry.ts | 55 ++ .../tasks/subtasks/clrfund/03-verifier.ts | 36 ++ .../clrfund/04-messageProcessorFactory.ts | 44 ++ .../tasks/subtasks/clrfund/05-tallyFactory.ts | 41 ++ .../tasks/subtasks/clrfund/06-pollFactory.ts | 41 ++ .../tasks/subtasks/clrfund/07-maciFactory.ts | 74 +++ .../clrfund/08-fundingRoundFactory.ts | 40 ++ .../tasks/subtasks/clrfund/09-clrfund.ts | 62 +++ .../subtasks/coordinator/01-coordinator.ts | 77 +++ .../subtasks/deployer/01-clrfundDeployer.ts | 66 +++ contracts/tasks/subtasks/index.ts | 29 + .../tasks/subtasks/maci/01-setMaciParams.ts | 67 +++ .../recipient/01-simpleRecipientRegistry.ts | 60 +++ .../02-optimisticRecipientRegistry.ts | 102 ++++ .../recipient/99-setRecipientRegistry.ts | 64 +++ .../round/01-deploy-brightid-user-registry.ts | 44 ++ .../tasks/subtasks/round/02-deploy-round.ts | 217 ++++++++ .../subtasks/token/01-anyOldERC20Token.ts | 55 ++ contracts/tasks/subtasks/token/02-setToken.ts | 52 ++ .../subtasks/user/01-simpleUserRegistry.ts | 52 ++ .../subtasks/user/02-semaphoreUserRegistry.ts | 55 ++ .../tasks/subtasks/user/03-brightidSponsor.ts | 51 ++ .../subtasks/user/04-brightidUserRegistry.ts | 73 +++ .../tasks/subtasks/user/99-setUserRegistry.ts | 63 +++ contracts/tasks/testutils/addContributors.ts | 40 -- contracts/tasks/testutils/contribute.ts | 88 --- contracts/tasks/testutils/vote.ts | 113 ---- contracts/tasks/verify/verifyAll.ts | 290 ---------- contracts/tasks/verify/verifyDeployer.ts | 35 -- contracts/tasks/verify/verifyMaci.ts | 35 -- contracts/tasks/verify/verifyMaciFactory.ts | 36 -- contracts/tasks/verify/verifyPoll.ts | 59 -- .../tasks/verify/verifyRecipientRegistry.ts | 55 -- contracts/tasks/verify/verifyRound.ts | 29 - contracts/tasks/verify/verifyTallyFile.ts | 43 -- contracts/tasks/verify/verifyUserRegistry.ts | 66 --- contracts/utils/maci.ts | 5 - contracts/utils/types.ts | 7 +- docs/deployment.md | 68 ++- docs/tally-verify.md | 17 +- 89 files changed, 3818 insertions(+), 2157 deletions(-) create mode 100644 contracts/deploy-config-example.json delete mode 100644 contracts/tasks/configure/setCoordinator.ts delete mode 100644 contracts/tasks/configure/setFundingRoundFactory.ts delete mode 100644 contracts/tasks/configure/setMaciParameters.ts delete mode 100644 contracts/tasks/configure/setPollFactory.ts delete mode 100644 contracts/tasks/configure/setRecipientRegistry.ts delete mode 100644 contracts/tasks/configure/setToken.ts delete mode 100644 contracts/tasks/configure/setUserRegistry.ts delete mode 100644 contracts/tasks/deploy/newClrFund.ts delete mode 100644 contracts/tasks/deploy/newDeployer.ts delete mode 100644 contracts/tasks/deploy/newRound.ts delete mode 100644 contracts/tasks/deploy/newSponsor.ts delete mode 100644 contracts/tasks/deployRecipientRegistry.ts create mode 100644 contracts/tasks/helpers/ContractStorage.ts create mode 100644 contracts/tasks/helpers/ContractVerifier.ts create mode 100644 contracts/tasks/helpers/Subtask.ts create mode 100644 contracts/tasks/helpers/types.ts delete mode 100644 contracts/tasks/maciNewKey.ts rename contracts/tasks/{testutils => runners}/addRecipients.ts (84%) rename contracts/tasks/{clrCancel.ts => runners/cancel.ts} (78%) rename contracts/tasks/{clrClaim.ts => runners/claim.ts} (75%) create mode 100644 contracts/tasks/runners/contribute.ts rename contracts/tasks/{clrExportRound.ts => runners/exportRound.ts} (96%) rename contracts/tasks/{clrFinalize.ts => runners/finalize.ts} (72%) rename contracts/tasks/{clrFindStorageSlot.ts => runners/findStorageSlot.ts} (89%) rename contracts/tasks/{clrLoadMerkleUsers.ts => runners/loadMerkleUsers.ts} (91%) rename contracts/tasks/{clrLoadSimpleUsers.ts => runners/loadSimpleUsers.ts} (90%) rename contracts/tasks/{ => runners}/maciPubkey.ts (100%) rename contracts/tasks/{clrMergeAllocation.ts => runners/mergeAllocation.ts} (100%) create mode 100644 contracts/tasks/runners/newClrFund.ts create mode 100644 contracts/tasks/runners/newMaciKey.ts create mode 100644 contracts/tasks/runners/newRound.ts create mode 100644 contracts/tasks/runners/setCoordinator.ts create mode 100644 contracts/tasks/runners/setMaciParameters.ts create mode 100644 contracts/tasks/runners/setRecipientRegistry.ts rename contracts/tasks/{configure => runners}/setStorageRoot.ts (98%) create mode 100644 contracts/tasks/runners/setToken.ts create mode 100644 contracts/tasks/runners/setUserRegistry.ts rename contracts/tasks/{clrTally.ts => runners/tally.ts} (80%) rename contracts/tasks/{testutils => runners}/timeTravel.ts (71%) create mode 100644 contracts/tasks/runners/verifyAll.ts create mode 100644 contracts/tasks/runners/verifyTallyFile.ts create mode 100644 contracts/tasks/subtasks/clrfund/01-poseidon.ts create mode 100644 contracts/tasks/subtasks/clrfund/02-vkRegistry.ts create mode 100644 contracts/tasks/subtasks/clrfund/03-verifier.ts create mode 100644 contracts/tasks/subtasks/clrfund/04-messageProcessorFactory.ts create mode 100644 contracts/tasks/subtasks/clrfund/05-tallyFactory.ts create mode 100644 contracts/tasks/subtasks/clrfund/06-pollFactory.ts create mode 100644 contracts/tasks/subtasks/clrfund/07-maciFactory.ts create mode 100644 contracts/tasks/subtasks/clrfund/08-fundingRoundFactory.ts create mode 100644 contracts/tasks/subtasks/clrfund/09-clrfund.ts create mode 100644 contracts/tasks/subtasks/coordinator/01-coordinator.ts create mode 100644 contracts/tasks/subtasks/deployer/01-clrfundDeployer.ts create mode 100644 contracts/tasks/subtasks/index.ts create mode 100644 contracts/tasks/subtasks/maci/01-setMaciParams.ts create mode 100644 contracts/tasks/subtasks/recipient/01-simpleRecipientRegistry.ts create mode 100644 contracts/tasks/subtasks/recipient/02-optimisticRecipientRegistry.ts create mode 100644 contracts/tasks/subtasks/recipient/99-setRecipientRegistry.ts create mode 100644 contracts/tasks/subtasks/round/01-deploy-brightid-user-registry.ts create mode 100644 contracts/tasks/subtasks/round/02-deploy-round.ts create mode 100644 contracts/tasks/subtasks/token/01-anyOldERC20Token.ts create mode 100644 contracts/tasks/subtasks/token/02-setToken.ts create mode 100644 contracts/tasks/subtasks/user/01-simpleUserRegistry.ts create mode 100644 contracts/tasks/subtasks/user/02-semaphoreUserRegistry.ts create mode 100644 contracts/tasks/subtasks/user/03-brightidSponsor.ts create mode 100644 contracts/tasks/subtasks/user/04-brightidUserRegistry.ts create mode 100644 contracts/tasks/subtasks/user/99-setUserRegistry.ts delete mode 100644 contracts/tasks/testutils/addContributors.ts delete mode 100644 contracts/tasks/testutils/contribute.ts delete mode 100644 contracts/tasks/testutils/vote.ts delete mode 100644 contracts/tasks/verify/verifyAll.ts delete mode 100644 contracts/tasks/verify/verifyDeployer.ts delete mode 100644 contracts/tasks/verify/verifyMaci.ts delete mode 100644 contracts/tasks/verify/verifyMaciFactory.ts delete mode 100644 contracts/tasks/verify/verifyPoll.ts delete mode 100644 contracts/tasks/verify/verifyRecipientRegistry.ts delete mode 100644 contracts/tasks/verify/verifyRound.ts delete mode 100644 contracts/tasks/verify/verifyTallyFile.ts delete mode 100644 contracts/tasks/verify/verifyUserRegistry.ts diff --git a/README.md b/README.md index 33b291a72..b2bf0f7c7 100644 --- a/README.md +++ b/README.md @@ -78,10 +78,24 @@ yarn && yarn build husky - Git hooks installed ``` +### Copy configuration for contract deployment + +```sh +# adjust the configuration for localhost if necessary +cp contracts/deploy-config-example.json contracts/deploy-config.json +``` + +### Generate coordinator MACI key +```sh +yarn hardhat new-maci-key +``` + ### Copy env for contracts ```sh -cp contracts/.env.example contracts/.env # adjust if necessary +# update COORDINATOR_MACISK with the MACI key from previous step +# adjust other configuration if necessary +cp contracts/.env.example contracts/.env ``` ### Copy env for the webapp diff --git a/contracts/.gitignore b/contracts/.gitignore index 2094497b2..f78e8d418 100644 --- a/contracts/.gitignore +++ b/contracts/.gitignore @@ -12,3 +12,5 @@ proof_output typechain-types params local-state.json +deploy-config.json +deployed-contracts.json diff --git a/contracts/README.md b/contracts/README.md index a9e30112d..20608ad58 100644 --- a/contracts/README.md +++ b/contracts/README.md @@ -18,6 +18,26 @@ yarn e2e ``` ## Scripts +### Generate coordinator key + +``` +yarn hardhat new-maci-key +``` + +### Copy env for contracts + +```sh +# update COORDINATOR_MACISK with the MACI key from previous step +# adjust other configuration if necessary +cp contracts/.env.example contracts/.env +``` + +### Copy configuration for contract deployment + +```sh +# adjust the configuration for localhost if necessary +cp contracts/deploy-config-example.json contracts/deploy-config.json +``` ### Deploy the ClrFund contract @@ -50,12 +70,6 @@ The test includes setting coordinator keys, contribute funds, vote, tally, final The following command will verify all clr.fund contracts. It will log a warning if contract already verified or missing. ``` -yarn hardhat verify-all --clrfund --network +yarn hardhat verify-all --network ``` -### Generate coordinator key -If you want to genereate a single key to coordinate multiple rounds. - -``` -yarn ts-node tasks/maciNewKey.ts -``` diff --git a/contracts/deploy-config-example.json b/contracts/deploy-config-example.json new file mode 100644 index 000000000..783782855 --- /dev/null +++ b/contracts/deploy-config-example.json @@ -0,0 +1,58 @@ +{ + "localhost": { + "VkRegistry": { + "circuit": "micro", + "paramsDirectory": "./params" + }, + "ClrFund": { + "template": false, + "coordinator": "", + "token": "", + "userRegistry": "SimpleUserRegistry", + "recipientRegistry": "SimpleRecipientRegistry" + }, + "FundingRound": { + "duration": 900 + }, + "AnyOldERC20Token": { + "initialSupply": "10000000000000000000000" + }, + "OptimisticRecipientRegistry": { + "challengePeriodSeconds": 9007199254740990, + "deposit": "0.001" + }, + "BrightIdUserRegistry": { + "deploy": false, + "context": "clrfund-arbitrum-goerli", + "verifier": "0xdbf0b2ee9887fe11934789644096028ed3febe9c" + } + }, + "optimism-sepolia": { + "VkRegistry": { + "circuit": "micro", + "paramsDirectory": "./params" + }, + "ClrFund": { + "template": false, + "coordinator": "", + "token": "0x65bc8dd04808d99cf8aa6749f128d55c2051edde", + "userRegistry": "SemaphoreUserRegistry", + "recipientRegistry": "OptimisticRecipientRegistry" + }, + "FundingRound": { + "duration": 900 + }, + "AnyOldERC20Token": { + "initialSupply": "10000000000000000000000" + }, + "OptimisticRecipientRegistry": { + "challengePeriodSeconds": 9007199254740990, + "deposit": "0.001" + }, + "BrightIdUserRegistry": { + "context": "clrfund-arbitrum-goerli", + "verifier": "0xdbf0b2ee9887fe11934789644096028ed3febe9c", + "sponsor": "0xC7c81634Dac2de4E7f2Ba407B638ff003ce4534C" + } + } +} diff --git a/contracts/sh/deployLocal.sh b/contracts/sh/deployLocal.sh index 7ce2efcf1..d0c1aac73 100755 --- a/contracts/sh/deployLocal.sh +++ b/contracts/sh/deployLocal.sh @@ -8,45 +8,22 @@ set -e # Local settings export CLRFUND_ROOT=$(cd $(dirname $0) && cd ../.. && pwd) export CONTRACTS_DIRECTORY=${CLRFUND_ROOT}/contracts -export CIRCUIT=micro export NETWORK=localhost -export CIRCUIT_DIRECTORY=${CONTRACTS_DIRECTORY}/params -export HARDHAT_NETWORK=localhost -export STATE_FILE=${CONTRACTS_DIRECTORY}/local-state.json + +# the sample config has default configuration for localhost deployment +cp deploy-config-example.json deploy-config.json # download the circuit params if not exists -if ! [ -f "${CIRCUIT_DIRECTORY}/ProcessMessages_6-9-2-3/ProcessMessages_6-9-2-3.zkey" ]; then +if ! [ -f "${CONTRACTS_DIRECTORY}/params/ProcessMessages_6-9-2-3/ProcessMessages_6-9-2-3.zkey" ]; then ${CLRFUND_ROOT}/.github/scripts/download-6-9-2-3.sh fi -# 20 mins -ROUND_DURATION=1800 - -# A helper to extract field value from the JSON state file -# The pattern "field": "value" must be on 1 line -# Usage: extract 'clrfund' -function extract() { - val=$(cat "${STATE_FILE}" | grep -o "${1}\": *\"[^\"]*" | grep -o "[^\"]*$") - echo ${val} -} - # create a new maci key for the coordinator -MACI_KEYPAIR=$(yarn ts-node tasks/maciNewKey.ts) +MACI_KEYPAIR=$(yarn hardhat new-maci-key) export COORDINATOR_MACISK=$(echo "${MACI_KEYPAIR}" | grep -o "macisk.*$") # create a new instance of ClrFund -yarn hardhat new-clrfund \ - --circuit "${CIRCUIT}" \ - --directory "${CIRCUIT_DIRECTORY}" \ - --user-registry-type simple \ - --recipient-registry-type simple \ - --state-file ${STATE_FILE} \ - --network ${HARDHAT_NETWORK} +yarn hardhat new-clrfund --network ${NETWORK} # deploy a new funding round -CLRFUND=$(extract 'clrfund') -yarn hardhat new-round \ - --clrfund "${CLRFUND}" \ - --duration "${ROUND_DURATION}" \ - --state-file ${STATE_FILE} \ - --network ${HARDHAT_NETWORK} +yarn hardhat new-round --network ${NETWORK} diff --git a/contracts/sh/runScriptTests.sh b/contracts/sh/runScriptTests.sh index 00a7ba7f7..bff6b4e07 100755 --- a/contracts/sh/runScriptTests.sh +++ b/contracts/sh/runScriptTests.sh @@ -8,77 +8,42 @@ set -e # Test settings NOW=$(date +%s) export OUTPUT_DIR="./proof_output/${NOW}" -export CIRCUIT=micro -export CIRCUIT_DIRECTORY=${CIRCUIT_DIRECTORY:-"./params"} -export STATE_FILE=${OUTPUT_DIR}/state.json export TALLY_FILE=${OUTPUT_DIR}/tally.json export HARDHAT_NETWORK=localhost export RAPID_SNARK=${RAPID_SNARK:-~/rapidsnark/package/bin/prover} -# 20 mins -ROUND_DURATION=1800 - mkdir -p ${OUTPUT_DIR} -# A helper to extract field value from the JSON state file -# The pattern "field": "value" must be on 1 line -# Usage: extract 'clrfund' -function extract() { - val=$(cat "${STATE_FILE}" | grep -o "${1}\": *\"[^\"]*" | grep -o "[^\"]*$") - echo ${val} -} +# create a deploy config using the example version +cp deploy-config-example.json deploy-config.json # create a new maci key for the coordinator -MACI_KEYPAIR=$(yarn ts-node tasks/maciNewKey.ts) +MACI_KEYPAIR=$(yarn hardhat new-maci-key) export COORDINATOR_MACISK=$(echo "${MACI_KEYPAIR}" | grep -o "macisk.*$") # create a new instance of ClrFund -yarn hardhat new-clrfund \ - --circuit "${CIRCUIT}" \ - --directory "${CIRCUIT_DIRECTORY}" \ - --user-registry-type simple \ - --recipient-registry-type simple \ - --state-file ${STATE_FILE} \ - --network ${HARDHAT_NETWORK} +yarn hardhat new-clrfund --network ${HARDHAT_NETWORK} -# # deploy a new funding round -CLRFUND=$(extract 'clrfund') -yarn hardhat new-round \ - --clrfund "${CLRFUND}" \ - --duration "${ROUND_DURATION}" \ - --state-file ${STATE_FILE} \ - --network ${HARDHAT_NETWORK} - -yarn hardhat test-add-contributors --clrfund "${CLRFUND}" --network ${HARDHAT_NETWORK} -yarn hardhat test-add-recipients --clrfund "${CLRFUND}" --network ${HARDHAT_NETWORK} +# deploy a new funding round +yarn hardhat new-round --network ${HARDHAT_NETWORK} -yarn hardhat test-contribute --state-file ${STATE_FILE} --network ${HARDHAT_NETWORK} -yarn hardhat test-vote --state-file ${STATE_FILE} --network ${HARDHAT_NETWORK} +yarn hardhat add-recipients --network ${HARDHAT_NETWORK} +yarn hardhat contribute --network ${HARDHAT_NETWORK} -yarn hardhat test-time-travel --seconds ${ROUND_DURATION} --network ${HARDHAT_NETWORK} +ROUND_DURATION=$(node -e 'const config=JSON.parse(fs.readFileSync(`./deploy-config.json`).toString()); console.log(config.localhost?.FundingRound?.duration || 1000)') +yarn hardhat time-travel --seconds ${ROUND_DURATION} --network ${HARDHAT_NETWORK} # run the tally script -MACI_TRANSACTION_HASH=$(extract 'maciTxHash') NODE_OPTIONS="--max-old-space-size=4096" -yarn hardhat clr-tally \ - --clrfund ${CLRFUND} \ - --circuit-directory ${CIRCUIT_DIRECTORY} \ - --circuit "${CIRCUIT}" \ +yarn hardhat tally \ --rapidsnark ${RAPID_SNARK} \ --batch-size 8 \ --output-dir ${OUTPUT_DIR} \ - --maci-tx-hash "${MACI_TRANSACTION_HASH}" \ --network "${HARDHAT_NETWORK}" # finalize the round -yarn hardhat clr-finalize --clrfund "${CLRFUND}" --tally-file ${TALLY_FILE} --network ${HARDHAT_NETWORK} +yarn hardhat finalize --tally-file ${TALLY_FILE} --network ${HARDHAT_NETWORK} # claim funds -FUNDING_ROUND=$(extract 'fundingRound') -yarn hardhat clr-claim --funding-round "${FUNDING_ROUND}" --tally-file ${TALLY_FILE} \ - --recipient 1 \ - --network ${HARDHAT_NETWORK} - -yarn hardhat clr-claim --funding-round "${FUNDING_ROUND}" --tally-file ${TALLY_FILE} \ - --recipient 2 \ - --network ${HARDHAT_NETWORK} +yarn hardhat claim --recipient 1 --tally-file ${TALLY_FILE} --network ${HARDHAT_NETWORK} +yarn hardhat claim --recipient 2 --tally-file ${TALLY_FILE} --network ${HARDHAT_NETWORK} diff --git a/contracts/tasks/configure/setCoordinator.ts b/contracts/tasks/configure/setCoordinator.ts deleted file mode 100644 index 1b0587e1b..000000000 --- a/contracts/tasks/configure/setCoordinator.ts +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Set the coordinator in clrfund - * Usage: - * hardhat set-coordinator \ - * --clrfund \ - * --coordinator \ - * --pubkey \ - * --network - */ -import { BaseContract } from 'ethers' -import { task } from 'hardhat/config' -import { ClrFund } from '../../typechain-types' -import { PubKey } from '@clrfund/common' - -task('set-coordinator', 'Set the Clrfund coordinator') - .addParam('clrfund', 'The ClrFund contract address') - .addParam('coordinator', 'The coordinator wallet address') - .addParam('pubkey', 'The coordinator MACI public key') - .setAction(async ({ clrfund, coordinator, pubkey }, { ethers }) => { - const clrfundContract = (await ethers.getContractAt( - 'ClrFund', - clrfund - )) as BaseContract as ClrFund - - const coordinatorPubkey = PubKey.deserialize(pubkey) - const tx = await clrfundContract.setCoordinator( - coordinator, - coordinatorPubkey.asContractParam() - ) - const receipt = await tx.wait() - if (receipt?.status !== 1) { - throw new Error('Failed to set coordinator') - } - - console.log('Set coordinator at tx', tx.hash) - }) diff --git a/contracts/tasks/configure/setFundingRoundFactory.ts b/contracts/tasks/configure/setFundingRoundFactory.ts deleted file mode 100644 index a51bb12ef..000000000 --- a/contracts/tasks/configure/setFundingRoundFactory.ts +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Set the funding round factory in clrfund - * Usage: - * hardhat set-round-factory \ - * --clrfund \ - * [--round-factory ] \ - * --network - */ -import { BaseContract } from 'ethers' -import { task } from 'hardhat/config' -import { ClrFund } from '../../typechain-types' -import { deployContract } from '../../utils/deployment' -import { EContracts } from '../../utils/types' - -task( - 'set-round-factory', - 'Set (create if non-existent) the funding round factory address in the ClrFund contract' -) - .addParam('clrfund', 'The ClrFund contract address') - .addOptionalParam( - 'roundFactory', - 'The funding round factory contract address' - ) - .setAction(async ({ clrfund, roundFactory }, { ethers }) => { - const clrfundContract = (await ethers.getContractAt( - 'ClrFund', - clrfund - )) as BaseContract as ClrFund - - let roundFactoryAddress = roundFactory - if (!roundFactoryAddress) { - const roundFactoryContract = await deployContract({ - name: EContracts.FundingRoundFactory, - ethers, - }) - roundFactoryAddress = roundFactoryContract.target - console.log('Deployed funding round factory at', roundFactoryAddress) - } - - const tx = await clrfundContract.setFundingRoundFactory(roundFactoryAddress) - const receipt = await tx.wait() - if (receipt?.status !== 1) { - throw new Error('Failed to set funding round factory') - } - - console.log('Set funding round factory at tx', tx.hash) - }) diff --git a/contracts/tasks/configure/setMaciParameters.ts b/contracts/tasks/configure/setMaciParameters.ts deleted file mode 100644 index 58561e877..000000000 --- a/contracts/tasks/configure/setMaciParameters.ts +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Set the zkeys parameters in MACI factory - * Sample usage: - * - * yarn hardhat set-maci-parameters \ - * --circuit \ - * --maci-factory \ - * --network - * - * See utils/circuits.ts for the circuit type value - */ - -import { task } from 'hardhat/config' -import { DEFAULT_CIRCUIT } from '../../utils/circuits' -import { MaciParameters } from '../../utils/maciParameters' -import { EContracts } from '../../utils/types' - -task('set-maci-parameters', 'Set the token in ClrFund') - .addParam('maciFactory', 'The MACIFactory contract address') - .addParam('circuit', 'The circuit type', DEFAULT_CIRCUIT) - .addParam('directory', 'The zkeys directory') - .setAction(async ({ maciFactory, circuit, directory }, { ethers }) => { - const factory = await ethers.getContractAt( - EContracts.MACIFactory, - maciFactory - ) - - const maciParameters = await MaciParameters.fromConfig(circuit, directory) - const setMaciTx = await factory.setMaciParameters( - ...maciParameters.asContractParam() - ) - console.log('Set MACI parameters at ', setMaciTx.hash) - await setMaciTx.wait() - - const newParameters = await MaciParameters.fromContract(factory) - console.log(newParameters) - }) diff --git a/contracts/tasks/configure/setPollFactory.ts b/contracts/tasks/configure/setPollFactory.ts deleted file mode 100644 index 5f86df209..000000000 --- a/contracts/tasks/configure/setPollFactory.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Set the Poll factory in the MACI factory - * Usage: - * hardhat set-poll-factory \ - * --maci-factory \ - * [--poll-factory ] \ - * --network - */ -import { task } from 'hardhat/config' -import { deployPollFactory } from '../../utils/deployment' -import { EContracts } from '../../utils/types' - -task( - 'set-poll-factory', - 'Set (create if non-existent) the Poll factory address in the MACI factory' -) - .addParam('maciFactory', 'The MACI factory contract address') - .addOptionalParam('pollFactory', 'The poll factory contract address') - .setAction(async ({ maciFactory, pollFactory }, { ethers, config }) => { - const maciFactoryContract = await ethers.getContractAt( - EContracts.MACIFactory, - maciFactory - ) - - let pollFactoryAddress = pollFactory - if (!pollFactoryAddress) { - const [signer] = await ethers.getSigners() - const pollFactoryContract = await deployPollFactory({ - signer, - ethers, - artifactsPath: config.paths.artifacts, - }) - pollFactoryAddress = await pollFactoryContract.getAddress() - } - - const tx = await maciFactoryContract.setPollFactory(pollFactoryAddress) - await tx.wait() - - console.log('Set poll factory at tx', tx.hash) - }) diff --git a/contracts/tasks/configure/setRecipientRegistry.ts b/contracts/tasks/configure/setRecipientRegistry.ts deleted file mode 100644 index e1946841e..000000000 --- a/contracts/tasks/configure/setRecipientRegistry.ts +++ /dev/null @@ -1,82 +0,0 @@ -/** - * Set the recipient registry in the ClrFund contract. - * - * Sample usage: - * - * yarn hardhat set-recipient-registry --clrfund --type optimistic --network - * - * Valid recipient registry types are simple, brightid, merkle, storage - * - */ - -import { task } from 'hardhat/config' -import { parseUnits } from 'ethers' -import { - deployRecipientRegistry, - challengePeriodSeconds, -} from '../../utils/deployment' -import { EContracts } from '../../utils/types' - -task('set-recipient-registry', 'Set the recipient registry in ClrFund') - .addParam('clrfund', 'The ClrFund contract address') - .addOptionalParam( - 'type', - 'The recipient registry type, e.g simple, optimistic' - ) - .addOptionalParam('registry', 'The recipient registry to set to') - .addOptionalParam( - 'deposit', - 'The base deposit for the optimistic registry', - '0.001' - ) - .addOptionalParam( - 'challengePeriod', - 'The challenge period in seconds', - challengePeriodSeconds - ) - .setAction( - async ( - { clrfund, type, registry, deposit, challengePeriod }, - { ethers } - ) => { - const clrfundContract = await ethers.getContractAt( - EContracts.ClrFund, - clrfund - ) - - const recipientRegistryType = type || '' - let recipientRegistryAddress = registry - if (!recipientRegistryAddress) { - const [signer] = await ethers.getSigners() - console.log(`Deploying recipient registry by: ${signer.address}`) - - const token = await clrfundContract.nativeToken() - const tokenContract = await ethers.getContractAt( - EContracts.ERC20, - token - ) - const decimals = await tokenContract.decimals() - const depositInUnits = parseUnits(deposit, decimals) - - const controller = await clrfundContract.getAddress() - const registry = await deployRecipientRegistry({ - type: recipientRegistryType, - controller, - deposit: depositInUnits, - challengePeriod: BigInt(challengePeriod), - ethers, - }) - recipientRegistryAddress = await registry.getAddress() - } - - const tx = await clrfundContract.setRecipientRegistry( - recipientRegistryAddress - ) - await tx.wait() - - console.log( - `Recipient registry (${recipientRegistryType}): ${recipientRegistryAddress}` - ) - console.log(`Recipient registry set at tx: ${tx.hash}`) - } - ) diff --git a/contracts/tasks/configure/setToken.ts b/contracts/tasks/configure/setToken.ts deleted file mode 100644 index 35adb334d..000000000 --- a/contracts/tasks/configure/setToken.ts +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Set the native token in the ClrFund contract - * Sample usage: - * - * yarn hardhat set-token --token --clrfund --network - */ - -import { task } from 'hardhat/config' -import { Contract } from 'ethers' -import { deployContract } from '../../utils/deployment' -import { UNIT } from '../../utils/constants' -import { EContracts } from '../../utils/types' - -/** - * Set the token address in the ClrFund contract - * - * @param clrfundContract ClrFund contract - * @param tokenAddress The token address to set in ClrFund - */ -async function setToken(clrfundContract: Contract, tokenAddress: string) { - const tx = await clrfundContract.setToken(tokenAddress) - await tx.wait() - - console.log(`Token set at tx: ${tx.hash}`) -} - -task('set-token', 'Set the token in ClrFund') - .addParam('clrfund', 'The ClrFund contract address') - .addOptionalParam('token', 'The token address') - .addOptionalParam('tokenAmount', 'Initial token amount', '1000') - .setAction(async ({ clrfund, token, tokenAmount }, { ethers }) => { - const [signer] = await ethers.getSigners() - const clrfundContract = await ethers.getContractAt( - EContracts.ClrFund, - clrfund, - signer - ) - console.log('Setting token by', signer.address) - - let tokenAddress: string = token || '' - if (!tokenAddress) { - const initialTokenSupply = BigInt(tokenAmount) * UNIT - const tokenContract = await deployContract({ - name: EContracts.AnyOldERC20Token, - contractArgs: [initialTokenSupply], - ethers, - }) - tokenAddress = await tokenContract.getAddress() - console.log('New token address', tokenAddress) - } - await setToken(clrfundContract, tokenAddress) - }) diff --git a/contracts/tasks/configure/setUserRegistry.ts b/contracts/tasks/configure/setUserRegistry.ts deleted file mode 100644 index bc26aa634..000000000 --- a/contracts/tasks/configure/setUserRegistry.ts +++ /dev/null @@ -1,87 +0,0 @@ -/** - * Set the user registry in the ClrFund contract. - * - * Sample usage: - * - * yarn hardhat set-user-registry --network \ - * --clrfund \ - * [--type ] \ - * [--registry ] \ - * [--context ] \ - * [--verifier ] \ - * [--sponsor ] - * - * Valid user registry types are simple, brightid, merkle, storage - * - * Verifier is the brightid node verifier address. - * Clrfund's brightId node is in the ethSigningAddress field from https://brightid.clr.fund - * - * Context is the bright app id - * The context value can be found here: https://apps.brightid.org/#nodes - */ - -import { task } from 'hardhat/config' -import { BrightIdParams, deployUserRegistry } from '../../utils/deployment' - -task('set-user-registry', 'Set the user registry in ClrFund') - .addParam('clrfund', 'The ClrFund contract address') - .addOptionalParam( - 'type', - 'The user registry type, e.g brightid, simple, merkle, snapshot' - ) - .addOptionalParam('registry', 'The user registry contract address') - .addOptionalParam('context', 'The BrightId context') - .addOptionalParam('verifier', 'The BrightId verifier address') - .addOptionalParam('sponsor', 'The BrightId sponsor contract address') - .setAction( - async ( - { clrfund, type, registry, context, verifier, sponsor }, - { ethers } - ) => { - const clrfundContract = await ethers.getContractAt('ClrFund', clrfund) - - let brightIdParams: BrightIdParams | undefined = undefined - - if (type === 'brightid') { - if (!context) { - throw Error('BrightId context is required') - } - - if (!verifier) { - throw Error('BrightId node verifier address is required') - } - - if (!sponsor) { - throw Error('BrightId sponsor contract address is required') - } - - brightIdParams = { - context, - verifierAddress: verifier, - sponsor, - } - } - - const userRegistryType = type || '' - let userRegistryAddress = registry - if (!registry) { - const [signer] = await ethers.getSigners() - console.log(`Deploying a user registry by: ${signer.address}`) - - const registry = await deployUserRegistry({ - userRegistryType, - ethers, - brightidContext: brightIdParams?.context, - brightidSponsor: brightIdParams?.sponsor, - brightidVerifier: brightIdParams?.verifierAddress, - }) - userRegistryAddress = await registry.getAddress() - } - - const tx = await clrfundContract.setUserRegistry(userRegistryAddress) - await tx.wait() - - console.log(`${userRegistryType} User registry: ${userRegistryAddress}`) - console.log(`User registry set at tx ${tx.hash}`) - } - ) diff --git a/contracts/tasks/deploy/newClrFund.ts b/contracts/tasks/deploy/newClrFund.ts deleted file mode 100644 index 3f2b26c29..000000000 --- a/contracts/tasks/deploy/newClrFund.ts +++ /dev/null @@ -1,341 +0,0 @@ -/** - * Create a new instance of the ClrFund contract. - * - * If the coordinator ETH address is not provided, use the signer address - * If the COORDINATOR_MACISK environment varibale not set in the .env file, - * this script will create a new random MACI key - * - * Sample usage: - * - * yarn hardhat new-clrfund \ - * --directory \ - * --token \ - * --coordinator \ - * --user-registry-type \ - * --recipient-registry-type \ - * --network - * - * - * If user registry address and recipient registry address are not provided, - * the registry types become mandatory as well as the other parameters needed - * to deploy the registries - * - * If token is not provided, a new ERC20 token will be created - */ -import { parseUnits, Signer } from 'ethers' -import { getEventArg } from '../../utils/contracts' -import { newMaciPrivateKey } from '../../utils/maci' -import { MaciParameters } from '../../utils/maciParameters' -import { - challengePeriodSeconds, - deployContract, - deployUserRegistry, - deployRecipientRegistry, - setCoordinator, - deployMaciFactory, - deployPoseidonLibraries, -} from '../../utils/deployment' -import { JSONFile } from '../../utils/JSONFile' -import dotenv from 'dotenv' -import { UNIT, BRIGHTID_VERIFIER_ADDR } from '../../utils/constants' -import { DEFAULT_CIRCUIT } from '../../utils/circuits' -import { task, types } from 'hardhat/config' -import { EContracts } from '../../utils/types' -import { HardhatEthersHelpers } from '@nomicfoundation/hardhat-ethers/types' -import { HardhatRuntimeEnvironment } from 'hardhat/types' - -dotenv.config() - -const DEFAULT_DEPOSIT_AMOUNT = '0.001' - -type Args = { - deployer: string - circuit: string - directory: string - coordinator?: string - token?: string - initialTokenSupply: number - userRegistryAddress?: string - userRegistryType?: string - brightidContext?: string - brightidSponsor?: string - brightidVerifier?: string - recipientRegistryAddress?: string - recipientRegistryType?: string - deposit?: string - challengePeriod?: number - stateFile?: string -} - -task('new-clrfund', 'Deploy a new ClrFund instance') - .addOptionalParam('deployer', 'The ClrFund deployer contract address') - .addOptionalParam('circuit', 'The circuit type', DEFAULT_CIRCUIT) - .addOptionalParam('directory', 'The circuit directory') - .addOptionalParam('coordinator', 'The coordinator ETH address') - .addOptionalParam('token', 'The native token address') - .addOptionalParam( - 'initialTokenSupply', - 'Initial token amount for new token', - 1000, - types.int - ) - .addOptionalParam('userRegistryAddress', 'The user registry address') - .addOptionalParam( - 'userRegistryType', - 'The user registry type: simple, semaphore, brightid, merkle, storage', - 'simple' - ) - .addOptionalParam('brightidContext', 'The brightid context') - .addOptionalParam( - 'brightidVerifier', - 'The brightid verifier address', - BRIGHTID_VERIFIER_ADDR - ) - .addOptionalParam('brightidSponsor', 'The brightid sponsor contract address') - .addOptionalParam( - 'recipientRegistryAddress', - 'The recipient registry address' - ) - .addOptionalParam( - 'recipientRegistryType', - 'The recipient registry type: simpleoptimistic', - 'optimistic' - ) - .addOptionalParam( - 'deposit', - 'The optimistic recipient registry deposit', - DEFAULT_DEPOSIT_AMOUNT - ) - .addOptionalParam( - 'challengePeriod', - 'The optimistic recipient registry challenge period in seconds', - challengePeriodSeconds - ) - .addOptionalParam( - 'stateFile', - 'File to store the ClrFundDeployer address for e2e testing' - ) - .setAction(async (args, hre) => { - await main(args, hre) - }) - -/** - * Deploy ClrFund as a standalone instance - * @param artifactsPath The hardhat artifacts path - * @param circuit The circuit type - * @param directory The directory containing the zkeys files - * @returns ClrFund contract address - */ -async function deployStandaloneClrFund({ - artifactsPath, - circuit, - directory, - signer, - ethers, -}: { - artifactsPath: string - circuit: string - directory: string - signer: Signer - ethers: HardhatEthersHelpers -}): Promise { - const libraries = await deployPoseidonLibraries({ - artifactsPath, - signer, - ethers, - }) - console.log('Deployed Poseidons', libraries) - - const maciParameters = await MaciParameters.fromConfig(circuit, directory) - const quiet = false - const maciFactory = await deployMaciFactory({ - libraries, - ethers, - maciParameters, - quiet, - }) - - const fundingRoundFactory = await deployContract({ - name: 'FundingRoundFactory', - ethers, - }) - - const clrfund = await deployContract({ - name: 'ClrFund', - ethers, - signer, - }) - const clrfundAddress = await clrfund.getAddress() - console.log('Deployed ClrFund at', clrfundAddress) - - const initTx = await clrfund.init(maciFactory, fundingRoundFactory) - await initTx.wait() - - return clrfundAddress -} - -/** - * Deploy the ClrFund contract using the deployer contract - * @param deployer ClrFund deployer contract - * @returns ClrFund contract address - */ -async function deployClrFundFromDeployer( - deployer: string, - ethers: HardhatEthersHelpers -): Promise { - const clrfundDeployer = await ethers.getContractAt( - EContracts.ClrFundDeployer, - deployer - ) - console.log(EContracts.ClrFundDeployer, clrfundDeployer.target) - - const tx = await clrfundDeployer.deployClrFund() - const receipt = await tx.wait() - - let clrfund: string - try { - clrfund = await getEventArg(tx, clrfundDeployer, 'NewInstance', 'clrfund') - console.log('ClrFund: ', clrfund) - } catch (e) { - console.log('receipt', receipt) - throw new Error( - 'Unable to get clrfund address after deployment. ' + (e as Error).message - ) - } - - return clrfund -} - -async function main(args: Args, hre: HardhatRuntimeEnvironment) { - const ethers = hre.ethers - const { deployer, coordinator, stateFile, circuit, directory } = args - const [signer] = await ethers.getSigners() - - console.log('Network: ', hre.network.name) - console.log(`Deploying from address: ${signer.address}`) - console.log('args', args) - - if (!directory && !deployer) { - throw new Error(`'--directory' is required`) - } - - if (!args.userRegistryAddress && !args.userRegistryType) { - throw new Error( - `Either '--user-registry-address' or '--user-registry-type' is required` - ) - } - - if (!args.recipientRegistryAddress && !args.recipientRegistryType) { - throw new Error( - `Either '--recipient-registry-address' or '--recipient-registry-type' is required` - ) - } - - const userRegistryType = args.userRegistryType || '' - const recipientRegistryType = args.recipientRegistryType || '' - - // If the maci secret key is not set in the env. variable, create a new key - const coordinatorMacisk = - process.env.COORDINATOR_MACISK ?? newMaciPrivateKey() - - const clrfund = deployer - ? await deployClrFundFromDeployer(deployer, ethers) - : await deployStandaloneClrFund({ - signer, - circuit, - directory, - artifactsPath: hre.config.paths.artifacts, - ethers, - }) - - const clrfundContract = await ethers.getContractAt( - EContracts.ClrFund, - clrfund, - signer - ) - - // set coordinator, use the coordinator address if available, - // otherwise use the signer address - const coordinatorAddress = coordinator ?? signer.address - const setCoordinatorTx = await setCoordinator({ - clrfundContract, - coordinatorAddress, - coordinatorMacisk, - }) - await setCoordinatorTx.wait() - console.log('Set coordinator address', coordinatorAddress) - - // set token - let tokenAddress = args.token - if (!tokenAddress) { - const initialTokenSupply = UNIT * BigInt(args.initialTokenSupply) - const tokenContract = await deployContract({ - name: EContracts.AnyOldERC20Token, - contractArgs: [initialTokenSupply], - ethers, - signer, - }) - tokenAddress = await tokenContract.getAddress() - } - const setTokenTx = await clrfundContract.setToken(tokenAddress) - await setTokenTx.wait() - console.log('Set token address', tokenAddress) - - // set user registry - let userRegistryAddress = args.userRegistryAddress - if (!userRegistryAddress) { - if (!args.userRegistryType) { - throw new Error('Missing --user-registry-type') - } - const userRegistryContract = await deployUserRegistry({ - ethers, - signer, - userRegistryType: userRegistryType, - brightidContext: args.brightidContext, - brightidVerifier: args.brightidVerifier, - brightidSponsor: args.brightidSponsor, - }) - userRegistryAddress = await userRegistryContract.getAddress() - } - const setUserRegistryTx = - await clrfundContract.setUserRegistry(userRegistryAddress) - await setUserRegistryTx.wait() - console.log(`Set ${userRegistryType} user registry: ${userRegistryAddress}`) - - // set recipient registry - let recipientRegistryAddress = args.recipientRegistryAddress - if (!recipientRegistryAddress) { - const deposit = parseDeposit(args.deposit || '0') - const recipientRegistryContract = await deployRecipientRegistry({ - ethers, - signer, - type: recipientRegistryType, - challengePeriod: BigInt(args.challengePeriod || 0), - deposit, - controller: clrfund, - }) - recipientRegistryAddress = await recipientRegistryContract.getAddress() - } - - const setRecipientRegistryTx = await clrfundContract.setRecipientRegistry( - recipientRegistryAddress - ) - await setRecipientRegistryTx.wait() - - console.log( - `Set ${args.recipientRegistryType} recipient registry: ${recipientRegistryAddress}` - ) - - if (stateFile) { - // save the test data for running the tally script later - JSONFile.update(stateFile, { clrfund, coordinatorMacisk }) - } -} - -function parseDeposit(deposit: string): bigint { - try { - return parseUnits(deposit) - } catch (e) { - throw new Error(`Error parsing deposit ${(e as Error).message}`) - } -} diff --git a/contracts/tasks/deploy/newDeployer.ts b/contracts/tasks/deploy/newDeployer.ts deleted file mode 100644 index ebdbfeeab..000000000 --- a/contracts/tasks/deploy/newDeployer.ts +++ /dev/null @@ -1,77 +0,0 @@ -/** - * Create a new instance of the ClrFundDeployer - * - * Sample usage: - * - * yarn hardhat new-deployer --directory ~/params --network - * - */ -import { - deployContract, - deployPoseidonLibraries, - deployMaciFactory, -} from '../../utils/deployment' -import { DEFAULT_CIRCUIT } from '../../utils/circuits' -import { JSONFile } from '../../utils/JSONFile' -import { MaciParameters } from '../../utils/maciParameters' -import { task } from 'hardhat/config' -import { EContracts } from '../../utils/types' - -task('new-deployer', 'Deploy a new ClrFund deployer') - .addParam('directory', 'The circuit directory') - .addOptionalParam('circuit', 'The circuit type', DEFAULT_CIRCUIT) - .addOptionalParam( - 'stateFile', - 'File to store the ClrFundDeployer address for e2e testing' - ) - .setAction( - async ({ circuit, stateFile, directory }, { ethers, network, config }) => { - const [signer] = await ethers.getSigners() - console.log('Circuit: ', circuit) - console.log('Network: ', network.name) - console.log(`Deploying from address: ${signer.address}`) - - const libraries = await deployPoseidonLibraries({ - artifactsPath: config.paths.artifacts, - signer, - ethers, - }) - console.log('Deployed Poseidons', libraries) - - const maciParameters = await MaciParameters.fromConfig(circuit, directory) - const maciFactory = await deployMaciFactory({ - libraries, - ethers, - maciParameters, - }) - console.log('Deployed MaciFactory at', maciFactory.target) - - const clrfundTemplate = await deployContract({ - name: EContracts.ClrFund, - ethers, - }) - console.log('Deployed ClrFund Template at', clrfundTemplate.target) - - const fundingRoundFactory = await deployContract({ - name: EContracts.FundingRoundFactory, - ethers, - }) - console.log('Deployed FundingRoundFactory at', fundingRoundFactory.target) - - const clrfundDeployer = await deployContract({ - name: EContracts.ClrFundDeployer, - ethers, - contractArgs: [ - clrfundTemplate.target, - maciFactory.target, - fundingRoundFactory.target, - ], - }) - console.log('Deployed ClrfundDeployer at', clrfundDeployer.target) - - if (stateFile) { - const clrfundDeployerAddress = await clrfundDeployer.getAddress() - JSONFile.update(stateFile, { deployer: clrfundDeployerAddress }) - } - } - ) diff --git a/contracts/tasks/deploy/newRound.ts b/contracts/tasks/deploy/newRound.ts deleted file mode 100644 index 977ee08dc..000000000 --- a/contracts/tasks/deploy/newRound.ts +++ /dev/null @@ -1,102 +0,0 @@ -/** - * Create a new instance of the ClrFundDeployer - * - * Sample usage: - * - * yarn hardhat new-round \ - * --clrfund \ - * --duration \ - * --network - * - */ -import { JSONFile } from '../../utils/JSONFile' -import { deployUserRegistry } from '../../utils/deployment' -import { ZERO_ADDRESS } from '../../utils/constants' -import { task, types } from 'hardhat/config' -import { EContracts } from '../../utils/types' - -task('new-round', 'Deploy a new funding round contract') - .addParam('clrfund', 'ClrFund contract address') - .addParam( - 'duration', - 'The funding round duration in seconds', - undefined, - types.int - ) - .addOptionalParam( - 'newBrightid', - 'Create a new BrightId user registry', - false, - types.boolean - ) - .addOptionalParam('context', 'The brightid context for the new user registry') - .addOptionalParam( - 'verifier', - 'The brightid verifier address for the new user registry' - ) - .addOptionalParam( - 'sponsor', - 'The brightid sponsor contract address for the new user registry' - ) - .addOptionalParam( - 'stateFile', - 'File to store the ClrFundDeployer address for e2e testing' - ) - .setAction( - async ( - { clrfund, duration, newBrightid, context, verifier, sponsor, stateFile }, - { ethers } - ) => { - const [signer] = await ethers.getSigners() - console.log(`Deploying from address: ${signer.address}`) - - const clrfundContract = await ethers.getContractAt('ClrFund', clrfund) - - // check if the current round is finalized before starting a new round to avoid revert - const currentRoundAddress = await clrfundContract.getCurrentRound() - if (currentRoundAddress !== ZERO_ADDRESS) { - const currentRound = await ethers.getContractAt( - EContracts.FundingRound, - currentRoundAddress - ) - const isFinalized = await currentRound.isFinalized() - if (!isFinalized) { - throw new Error( - 'Cannot start a new round as the current round is not finalized' - ) - } - } - - if (newBrightid) { - const userRegistryType = 'brightid' - const userRegistryContract = await deployUserRegistry({ - ethers, - signer, - userRegistryType, - brightidContext: context, - brightidVerifier: verifier, - brightidSponsor: sponsor, - }) - - const setUserRegistryTx = await clrfundContract.setUserRegistry( - userRegistryContract.target - ) - - await setUserRegistryTx.wait() - console.log( - `New ${userRegistryType} user registry: ${userRegistryContract.target}` - ) - } - - const tx = await clrfundContract.deployNewRound(duration) - await tx.wait() - const fundingRound = await clrfundContract.getCurrentRound() - console.log('New funding round address: ', fundingRound) - - if (stateFile) { - const pollId = 0 - const state = { fundingRound, pollId, maciTxHash: tx.hash } - JSONFile.update(stateFile, state) - } - } - ) diff --git a/contracts/tasks/deploy/newSponsor.ts b/contracts/tasks/deploy/newSponsor.ts deleted file mode 100644 index 42b3ef3c4..000000000 --- a/contracts/tasks/deploy/newSponsor.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Deploy an instance of the BrightID sponsor contract - * - * Sample usage: - * - * yarn hardhat new-sponsor --network - */ -import { task } from 'hardhat/config' -import { EContracts } from '../../utils/types' - -task('new-sponsor', 'Deploy the BrightID sponsor contract').setAction( - async (_, { ethers }) => { - const SponsorContract = await ethers.getContractFactory( - EContracts.BrightIdSponsor - ) - const sponsor = await SponsorContract.deploy() - // wait for the contract to be deployed to the network - await sponsor.waitForDeployment() - console.log('Deployed the sponsor contract at', sponsor.target) - } -) diff --git a/contracts/tasks/deployRecipientRegistry.ts b/contracts/tasks/deployRecipientRegistry.ts deleted file mode 100644 index 0a8662585..000000000 --- a/contracts/tasks/deployRecipientRegistry.ts +++ /dev/null @@ -1,76 +0,0 @@ -/** - * Deploy a new recipient registry - * - * Sample usage: - * - * yarn hardhat deploy-recipient-registry \ - * --network arbitrum-goerli \ - * --factory 0x85802c7871e7e778Ec376F097b56BD7299250b8D \ - * --type optimistic - */ -import { task, types } from 'hardhat/config' -import { Contract } from 'ethers' - -// Number.MAX_SAFE_INTEGER - 1 -const challengePeriodSeconds = 9007199254740990 - -task('deploy-recipient-registry', 'Deploy a new recipient registry') - .addParam('factory', 'The funding round factory contract address') - .addParam( - 'type', - 'The recipient registry type, e.g. simple, optimistic', - 'optimistic' - ) - .addOptionalParam( - 'deposit', - 'The base deposit for optimistic recipient registry', - '0.001' - ) - .addOptionalParam( - 'challengePeriod', - 'The challenge period in seconds for optimistic recipient registry', - challengePeriodSeconds, - types.int - ) - .setAction( - async ({ factory, type, deposit, challengePeriod }, { ethers }) => { - const [deployer] = await ethers.getSigners() - const fundingRoundFactory = await ethers.getContractAt( - 'FundingRoundFactory', - factory - ) - - let recipientRegistry: Contract - if (type === 'simple') { - const SimpleRecipientRegistry = await ethers.getContractFactory( - 'SimpleRecipientRegistry', - deployer - ) - recipientRegistry = await SimpleRecipientRegistry.deploy( - fundingRoundFactory.address - ) - } else if (type === 'optimistic') { - const OptimisticRecipientRegistry = await ethers.getContractFactory( - 'OptimisticRecipientRegistry', - deployer - ) - recipientRegistry = await OptimisticRecipientRegistry.deploy( - ethers.utils.parseUnits(deposit), - challengePeriod, - fundingRoundFactory.address - ) - } else { - throw new Error('unsupported recipient registry type') - } - - await recipientRegistry.deployTransaction.wait() - console.log(`Recipient registry deployed: ${recipientRegistry.address}`) - - const setRecipientRegistryTx = - await fundingRoundFactory.setRecipientRegistry( - recipientRegistry.address - ) - await setRecipientRegistryTx.wait() - console.log('Done!') - } - ) diff --git a/contracts/tasks/helpers/ContractStorage.ts b/contracts/tasks/helpers/ContractStorage.ts new file mode 100644 index 000000000..f1b73160a --- /dev/null +++ b/contracts/tasks/helpers/ContractStorage.ts @@ -0,0 +1,327 @@ +// Modified from https://github.com/privacy-scaling-explorations/maci/blob/dev/contracts/tasks/helpers/ContractStorage.ts +import { + IStorageNamedEntry, + IStorageInstanceEntry, + IRegisterContract, +} from './types' + +import { EContracts } from '../../utils/types' +import { JSONFile } from '../../utils/JSONFile' +import { Libraries } from '../../utils/deployment' + +const DEPLOYED_CONTRACTS = './deployed-contracts.json' + +/** + * Used by JSON.stringify to serialize bigint + * @param _key JSON key + * @param value JSON value + */ +function bigintReplacer(_key: string, value: any) { + if (typeof value === 'bigint') { + return value.toString() + } + return value +} + +/** + * Internal storage structure type. + * named: contracts can be queried by name + * instance: contract can be queried by address + * verified: mark contracts which are already verified + */ +type TStorage = Record< + string, + Partial<{ + named: Record + instance: Record + verified: Record + }> +> + +/** + * @notice Contract storage keeps all deployed contracts with addresses, arguments in the json file. + * This class is using for incremental deployment and verification. + */ +export class ContractStorage { + /** + * Singleton instance for class + */ + private static INSTANCE?: ContractStorage + + /** + * Json file database instance + */ + private db: TStorage + + /** + * Initialize class properties only once + */ + private constructor() { + try { + this.db = JSONFile.read(DEPLOYED_CONTRACTS) as TStorage + } catch { + this.db = {} as TStorage + } + } + + /** + * Get singleton object + * + * @returns {ContractStorage} singleton object + */ + static getInstance(): ContractStorage { + if (!ContractStorage.INSTANCE) { + ContractStorage.INSTANCE = new ContractStorage() + } + + return ContractStorage.INSTANCE + } + + /** + * Register contract and save contract address, constructor args in the json file + * + * @param {IRegisterContract} args - register arguments + */ + async register({ + id, + contract, + network, + args, + tx, + }: IRegisterContract): Promise { + const contractAddress = await contract.getAddress() + + const deploymentTx = contract.deploymentTransaction() + + console.log(`*** ${id} ***\n`) + console.log(`Network: ${network}`) + console.log(`contract address: ${contractAddress}`) + + if (deploymentTx) { + console.log(`tx: ${deploymentTx.hash}`) + console.log(`nonce: ${deploymentTx.nonce}`) + console.log(`deployer address: ${deploymentTx.from}`) + console.log(`gas price: ${deploymentTx.gasPrice}`) + console.log(`gas used: ${deploymentTx.gasLimit}`) + } + + console.log(`\n******`) + console.log() + + const logEntry: IStorageInstanceEntry = { + id, + txHash: deploymentTx?.hash || tx?.hash, + } + + if (args !== undefined) { + logEntry.verify = { + args: JSON.stringify(args, bigintReplacer), + } + } + + if (!this.db[network]) { + this.db[network] = {} + } + + const { instance, named } = this.db[network] + + this.db[network].instance = { + ...instance, + ...{ [contractAddress]: logEntry }, + } + + const count = named?.[id] ? named[id].count : 0 + const namedValue = { + address: contractAddress, + count: count + 1, + } + + this.db[network].named = { + ...named, + ...{ [id]: namedValue }, + } + + JSONFile.write(DEPLOYED_CONTRACTS, this.db) + } + + /** + * Get contract instances from the json file + * + * @param network - selected network + * @returns {[string, IStorageInstanceEntry][]} storage instance entries + */ + getInstances(network: string): [string, IStorageInstanceEntry][] { + if (!this.db[network]) { + return Object.entries([]) + } + + const collection = this.db[network].instance + const value = collection as IStorageInstanceEntry[] | undefined + + return Object.entries(value || []) + } + + /** + * Check if contract is verified or not locally + * + * @param address - contract address + * @param network - selected network + * @returns contract verified or not + */ + getVerified(address: string, network: string): boolean { + return Boolean(this.db[network].verified?.[address]) + } + + /** + * Set contract verification in the json file + * + * @param address - contract address + * @param network - selected network + * @param verified - verified or not + */ + setVerified = (address: string, network: string, verified: boolean): void => { + const verifiedInfo = { [address]: verified } + this.db[network].verified = { + ...this.db[network].verified, + ...verifiedInfo, + } + JSONFile.write(DEPLOYED_CONTRACTS, this.db) + } + + /** + * Get contract deployment transaction haash by address from the json file + * + * @param address - contract address + * @param network - selected network + * @returns contract deployment transaction hash + */ + getTxHash(address: string, network: string): string | undefined { + if (!this.db[network]) { + return undefined + } + + const instance = this.db[network].instance?.[address] + return instance?.txHash + } + + /** + * Get contract address by name from the json file + * + * @param id - contract name + * @param network - selected network + * @returns contract address + */ + getAddress(id: EContracts, network: string): string | undefined { + if (!this.db[network]) { + return undefined + } + const collection = this.db[network].named?.[id] + const namedEntry = collection as IStorageNamedEntry | undefined + + return namedEntry?.address + } + + /** + * Get contract address by name from the json file + * + * @param id - contract name + * @param network - selected network + * @throws {Error} if there is no address the error will be thrown + * @returns contract address + */ + mustGetAddress(id: EContracts, network: string): string { + const address = this.getAddress(id, network) + + if (!address) { + throw new Error(`Contract ${id} is not saved`) + } + + return address + } + + /** + * Get poseidon library addresses from the json file + * + * @param network - selected network + * @throws {Error} if there is no address the error will be thrown + * @returns Poseidon libraries + */ + mustGetPoseidonLibraries(network: string): Libraries { + const poseidonT3ContractAddress = this.mustGetAddress( + EContracts.PoseidonT3, + network + ) + const poseidonT4ContractAddress = this.mustGetAddress( + EContracts.PoseidonT4, + network + ) + const poseidonT5ContractAddress = this.mustGetAddress( + EContracts.PoseidonT5, + network + ) + const poseidonT6ContractAddress = this.mustGetAddress( + EContracts.PoseidonT6, + network + ) + + return { + PoseidonT3: poseidonT3ContractAddress, + PoseidonT4: poseidonT4ContractAddress, + PoseidonT5: poseidonT5ContractAddress, + PoseidonT6: poseidonT6ContractAddress, + } + } + + /** + * Get contract from the json file with sizes and multi count + * + * @param deployer - deployer address + * @param network - selected network + * @returns {[entries: Map, length: number, multiCount: number]} + */ + printContracts( + deployer: string, + network: string + ): [Map, number, number] { + console.log('Contracts deployed at', network, 'by', deployer) + console.log('---------------------------------') + + const entryMap = new Map() + const { named, instance } = this.db[network] + const namedEntries = Object.entries(named || {}) + const instanceEntries = Object.entries( + instance || {} + ) + + let multiCount = 0 + + namedEntries.forEach(([key, value]) => { + if (value.count > 1) { + console.log(`\t${key}: ${value.address} (N=${value.count})`) + multiCount += value.count + } else { + console.log(`\t${key}: ${value.address}`) + entryMap.set(key, value.address) + } + }) + + console.log('---------------------------------') + console.log( + 'N# Contracts:', + entryMap.size + multiCount, + '/', + instanceEntries.length + ) + + return [entryMap, instanceEntries.length, multiCount] + } + + /** + * Clean json file for selected network + * + * @param network - selected network + */ + cleanup(network: string): void { + this.db[network] = {} + } +} diff --git a/contracts/tasks/helpers/ContractVerifier.ts b/contracts/tasks/helpers/ContractVerifier.ts new file mode 100644 index 000000000..24aec1ef9 --- /dev/null +++ b/contracts/tasks/helpers/ContractVerifier.ts @@ -0,0 +1,60 @@ +// Modified from https://github.com/privacy-scaling-explorations/maci/blob/dev/contracts/tasks/helpers/ContractVerifier.ts + +import type { IVerificationSubtaskArgs } from './types' +import type { HardhatRuntimeEnvironment, Libraries } from 'hardhat/types' + +/** + * @notice Contract verifier allows to verify contract using hardhat-etherscan plugin. + */ +export class ContractVerifier { + /** + * Hardhat runtime environment + */ + private hre: HardhatRuntimeEnvironment + + /** + * Initialize class properties + * + * @param hre - Hardhat runtime environment + */ + constructor(hre: HardhatRuntimeEnvironment) { + this.hre = hre + } + + /** + * Verify contract through etherscan + * + * @param address - contract address + * @param constructorArguments - stringified constructor arguments + * @param libraries - stringified libraries which can't be detected automatically + * @returns + */ + async verify( + address: string, + constructorArguments: string, + libraries?: string + ): Promise<[boolean, string]> { + const params: IVerificationSubtaskArgs = { + address, + constructorArguments: JSON.parse(constructorArguments) as unknown[], + } + + if (libraries) { + params.libraries = JSON.parse(libraries) as Libraries + } + + // Run etherscan task + const error = await this.hre + .run('verify:verify', params) + .then(() => '') + .catch((err: Error) => { + if (err.message === 'Contract source code already verified') { + return '' + } + + return err.message + }) + + return [!error, error] + } +} diff --git a/contracts/tasks/helpers/Subtask.ts b/contracts/tasks/helpers/Subtask.ts new file mode 100644 index 000000000..5dc02426c --- /dev/null +++ b/contracts/tasks/helpers/Subtask.ts @@ -0,0 +1,509 @@ +// Modified from https://github.com/privacy-scaling-explorations/maci/blob/dev/contracts/tasks/helpers/Deployment.ts + +import { + BaseContract, + Signer, + NonceManager, + ContractTransactionResponse, + formatUnits, +} from 'ethers' +import { task } from 'hardhat/config' + +import { exit } from 'process' + +import type { HardhatEthersSigner } from '@nomicfoundation/hardhat-ethers/signers' +import type { + ConfigurableTaskDefinition, + TaskArguments, + HardhatRuntimeEnvironment, +} from 'hardhat/types' + +import { EContracts } from '../../utils/types' +import { ContractStorage } from './ContractStorage' +import { + ISubtaskParams, + ISubtaskStep, + ISubtaskStepCatalog, + IGetContractParams, + IDeployContractOptions, +} from './types' +import { JSONFile } from '../../utils/JSONFile' +import { SUBTASK_CATALOGS } from '../subtasks' + +const DEPLOY_CONFIG = './deploy-config.json' + +/** + * Internal deploy config structure type. + */ +type TConfig = { + [key: string]: { [key: string]: { [key: string]: string | number | boolean } } +} + +/** + * @notice Deployment helper class to run sequential deploy using steps and deploy contracts. + */ +export class Subtask { + /** + * Singleton instance for class + */ + private static INSTANCE?: Subtask + + /** + * Hardhat runtime environment + */ + private hre?: HardhatRuntimeEnvironment + + /** + * Whether to use nonce manager to manually set nonce + */ + private nonceManager?: NonceManager + + /** + * Step catalog to create sequential tasks + */ + private stepCatalog: Map + + /** + * Json file database instance + */ + private config: TConfig + + /** + * Contract storage + */ + private storage: ContractStorage + + /** + * Record the start of deployer's balance here + */ + private startBalance: bigint + + /** + * Initialize class properties only once + */ + private constructor(hre?: any) { + this.stepCatalog = new Map(SUBTASK_CATALOGS.map((catalog) => [catalog, []])) + this.hre = hre + try { + this.config = JSONFile.read(DEPLOY_CONFIG) as TConfig + } catch (e) { + console.log('eror =======================', e) + this.config = {} as TConfig + } + + this.storage = ContractStorage.getInstance() + + // this will be set when start() or logStart() is called + this.startBalance = 0n + } + + /** + * Get singleton object + * + * @returns {Subtask} singleton object + */ + static getInstance(hre?: HardhatRuntimeEnvironment): Subtask { + if (!Subtask.INSTANCE) { + Subtask.INSTANCE = new Subtask(hre) + } + + return Subtask.INSTANCE + } + + /** + * Start deploy with console log information + * + * @param {ISubtaskParams} params - deploy params + */ + async start({ incremental }: ISubtaskParams): Promise { + this.checkHre(this.hre) + const deployer = await this.getDeployer() + const deployerAddress = await deployer.getAddress() + this.startBalance = await deployer.provider.getBalance(deployer) + + console.log('Deployer address:', deployerAddress) + console.log('Deployer start balance: ', formatUnits(this.startBalance)) + + 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') + } + + /** + * Log the start of deployment with console log information + * + */ + async logStart(): Promise { + this.checkHre(this.hre) + const deployer = await this.getDeployer() + const deployerAddress = await deployer.getAddress() + this.startBalance = await deployer.provider.getBalance(deployer) + + console.log('Deployer address:', deployerAddress) + console.log('Deployer start balance: ', formatUnits(this.startBalance)) + + console.log('Deployment started\n') + } + + /** + * Run deploy steps + * + * @param steps - deploy steps + * @param skip - skip steps with less or equal index + */ + async runSteps(steps: ISubtaskStep[], skip: number): Promise { + this.checkHre(this.hre) + + // 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 { + this.checkHre(this.hre) + 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 success - success or not + */ + async finish(success: boolean): Promise { + this.checkHre(this.hre) + 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: ', formatUnits(endBalance)) + console.log( + 'Deploy expenses: ', + formatUnits(this.startBalance - endBalance) + ) + + if (gasPrice !== 'auto') { + console.log( + 'Deploy gas: ', + (this.startBalance - endBalance) / BigInt(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 + * + * @returns {Promise} - signer + */ + async getDeployer(signer?: Signer): Promise { + this.checkHre(this.hre) + + let deployer: Signer | NonceManager + + if (this.nonceManager) { + deployer = this.nonceManager + } else { + if (signer) { + deployer = signer + } else { + ;[deployer] = await this.hre.ethers.getSigners() + } + } + + return deployer as unknown as HardhatEthersSigner + } + + /** + * Set hardhat runtime environment + * + * @param hre - hardhat runtime environment + */ + setHre(hre: HardhatRuntimeEnvironment): void { + this.hre = hre + } + + /** + * Check if hardhat runtime environment is set + * + * @throws {Error} error if there is no hardhat runtime environment set + */ + private checkHre( + hre: HardhatRuntimeEnvironment | undefined + ): asserts hre is HardhatRuntimeEnvironment { + if (!hre) { + throw new Error('Hardhat Runtime Environment is not set') + } + } + + /** + * Create a nonce manager to manage nonce for the signer + * + * @param signer - signer + */ + setNonceManager(signer: Signer): void { + this.nonceManager = new NonceManager(signer) + } + + /** + * Register a subtask by updating step catalog and return task definition + * + * @param taskName - unique task name + * @param stepName - task description + * @param paramsFn - optional function to override default task arguments + * @returns {ConfigurableTaskDefinition} hardhat task definition + */ + addTask( + taskName: string, + stepName: string, + paramsFn?: (params: ISubtaskParams) => Promise + ): ConfigurableTaskDefinition { + const deployType = taskName.substring(0, taskName.indexOf(':')) + this.addStep(deployType, { + name: stepName, + taskName, + paramsFn: paramsFn || this.getDefaultParams, + }) + + return task(taskName, stepName) + } + + /** + * Register deployment step + * + * @param deployType - deploy type + * @param {ISubtaskStepCatalog} - deploy step catalog name, description and param mapper + */ + private addStep( + deployType: string, + { name, taskName, paramsFn }: ISubtaskStepCatalog + ): void { + const steps = this.stepCatalog.get(deployType) + + if (!steps) { + throw new Error(`Unknown deploy type: ${deployType}`) + } + + steps.push({ name, taskName, paramsFn }) + } + + /** + * Get default params from hardhat task + * + * @param {ISubtaskParams} params - hardhat task arguments + * @returns {Promise} params for deploy workflow + */ + private getDefaultParams = ({ + verify, + incremental, + }: ISubtaskParams): Promise => + Promise.resolve({ verify, incremental }) + + /** + * Get deploy step sequence + * + * @param deployTypes - list of deploy types + * @param {ISubtaskParams} params - deploy params + * @returns {Promise} deploy steps + */ + async getDeploySteps( + deployTypes: string[], + params: ISubtaskParams + ): Promise { + const catalogSteps = await Promise.all( + deployTypes.map((deployType) => this.stepCatalog.get(deployType)) + ) + + let stepList: ISubtaskStepCatalog[] = [] + for (let i = 0; i < catalogSteps.length; i++) { + const steps = catalogSteps[i] + if (!steps) { + throw new Error(`Unknown deploy type: ${deployTypes[i]}`) + } + + stepList = stepList.concat(steps) + } + + return Promise.all(stepList.map(({ paramsFn }) => paramsFn(params))).then( + (stepArgs) => + stepArgs.map((args, index) => ({ + id: index + 1, + name: stepList[index].name, + taskName: stepList[index].taskName, + args: args as unknown, + })) + ) + } + + /** + * Deploy contract and return it + * + * @param contractName - contract name + * @param signer - signer + * @param args - constructor arguments + * @returns deployed contract + */ + async deployContract( + contractName: EContracts, + options?: IDeployContractOptions + ): Promise { + this.checkHre(this.hre) + const signer = options?.signer + const deployer = await this.getDeployer(signer) + const args = options?.args || [] + const libraries = options?.libraries + + const qualifiedName = contractName.includes('Poseidon') + ? ':' + contractName + : contractName + const contract = await this.hre.ethers.deployContract(qualifiedName, args, { + signer: deployer, + libraries, + }) + await contract.waitForDeployment() + + return contract as unknown as T + } + + /** + * Get deploy config field (see deploy-config.json) + * + * @param id - contract name + * @param field - config field key + * @returns config field value or null + */ + getConfigField( + id: EContracts, + field: string, + mustGet = false + ): T { + this.checkHre(this.hre) + + const value = this.config[this.hre.network.name][id][field] as T + + if (mustGet && (value === null || value === undefined)) { + throw new Error(`Can't find ${this.hre.network.name}.${id}.${field}`) + } + + return value + } + + /** + * Get contract by name + * + * @param {IGetContractParams} params - params + * @returns contract wrapper + */ + async getContract({ + name, + address, + signer, + }: IGetContractParams): Promise { + this.checkHre(this.hre) + const deployer = await this.getDeployer(signer) + const contractAddress = + address || this.storage.mustGetAddress(name, this.hre.network.name) + + const { abi } = await this.hre.artifacts.readArtifact(name.toString()) + + return new BaseContract(contractAddress, abi, deployer) as T + } + + /** + * Log transaction information to console + * + * @param {ContractTransactionResponse} tx - transaction + */ + logTransaction(tx: ContractTransactionResponse): void { + console.log(`tx: ${tx.hash}`) + console.log(`nonce: ${tx.nonce}`) + console.log(`deployer address: ${tx.from}`) + console.log(`gas price: ${tx.gasPrice}`) + console.log(`gas used: ${tx.gasLimit}`) + } +} diff --git a/contracts/tasks/helpers/types.ts b/contracts/tasks/helpers/types.ts new file mode 100644 index 000000000..6ed2a49dc --- /dev/null +++ b/contracts/tasks/helpers/types.ts @@ -0,0 +1,229 @@ +// Modified from https://github.com/privacy-scaling-explorations/maci/blob/dev/contracts/tasks/helpers/types.ts + +import { BaseContract, ContractTransactionResponse, Signer } from 'ethers' +import { EContracts } from '../../utils/types' +import type { Libraries, TaskArguments } from 'hardhat/types' + +/** + * Interface that represents contract storage named entry + */ +export interface IStorageNamedEntry { + /** + * Contract address + */ + address: string + + /** + * Count of deployed instances + */ + count: number +} + +/** + * Interface that represents contract storage instance entry + */ +export interface IStorageInstanceEntry { + /** + * Entry identificator + */ + id: string + + /** + * The transaction hash that this instance was created + */ + txHash?: string + + /** + * Params for verification + */ + verify?: { + args?: string + impl?: string + subType?: string + } +} + +/** + * Interface that represents subtask params + */ +export interface ISubtaskParams { + /** + * Param for verification toggle + */ + verify: boolean + + /** + * Param for incremental task toggle + */ + incremental: boolean + + /** + * Param for manually managed nonce + * This is useful for interacting with testnet nodes + * that are not optimally configured causing nonce too low error + */ + manageNonce?: boolean + + /** + * Consider warning as errors + */ + strict?: boolean + + /** + * Skip steps with less or equal index + */ + skip?: number +} + +/** + * Interface that represents register contract arguments + */ +export interface IRegisterContract { + /** + * Contract enum identifier + */ + id: EContracts + + /** + * Contract instance + */ + contract: BaseContract + + /** + * network name + */ + network: string + + /** + * Contract deployment arguments + */ + args?: unknown[] + + /** + * Contract deployment transaction + */ + tx?: ContractTransactionResponse +} + +/** + * Interface that represents subtask step catalog + */ +export interface ISubtaskStepCatalog { + /** + * Step name + */ + name: string + + /** + * Task name + */ + taskName: string + + /** + * Params function with task arguments + * + * @param params task params + * @returns task arguments + */ + paramsFn: (params: ISubtaskParams) => Promise +} + +/** + * Interface that represents subtask step + */ +export interface ISubtaskStep { + /** + * Sequence step id + */ + id: number + + /** + * Step name + */ + name: string + + /** + * Subtask name + */ + taskName: string + + /** + * Subtask arguments + */ + args: TaskArguments +} + +/** + * Interface that represents `Subtask#getContract` params + */ +export interface IGetContractParams { + /** + * Contract name + */ + name: EContracts + + /** + * Contract address + */ + address?: string + + /** + * Eth signer + */ + signer?: Signer +} + +/** + * Interface that represents verify arguments + */ +export interface IVerifyAllArgs { + /** + * Ignore verified status + */ + force?: boolean +} + +/** + * Interface that represents verification subtask arguments + * This is extracted from hardhat etherscan plugin + */ +export interface IVerificationSubtaskArgs { + /** + * Contract address + */ + address: string + + /** + * Constructor arguments + */ + constructorArguments: unknown[] + + /** + * Fully qualified name of the contract + */ + contract?: string + + /** + * Libraries + */ + libraries?: Libraries +} + +export interface IDeployContractOptions { + /** + * Contract constructor argument + */ + args?: unknown[] + + /** + * Eth signer + */ + signer?: Signer + + /** + * Libraries + */ + libraries?: Libraries +} + +export { EContracts } diff --git a/contracts/tasks/index.ts b/contracts/tasks/index.ts index 5c599a882..77bbf3d44 100644 --- a/contracts/tasks/index.ts +++ b/contracts/tasks/index.ts @@ -1,36 +1,23 @@ -import './verify/verifyAll' -import './verify/verifyMaciFactory' -import './verify/verifyRound' -import './verify/verifyMaci' -import './verify/verifyRecipientRegistry' -import './verify/verifyUserRegistry' -import './verify/verifyPoll' -import './verify/verifyTallyFile' -import './verify/verifyDeployer' -import './deploy/newSponsor' -import './deploy/newClrFund' -import './deploy/newDeployer' -import './deploy/newRound' -import './configure/setFundingRoundFactory' -import './configure/setMaciParameters' -import './configure/setPollFactory' -import './configure/setRecipientRegistry' -import './configure/setUserRegistry' -import './configure/setStorageRoot' -import './configure/setCoordinator' -import './configure/setToken' -import './testutils/vote' -import './testutils/contribute' -import './testutils/addContributors' -import './testutils/addRecipients' -import './testutils/timeTravel' -import './clrCancel' -import './clrClaim' -import './clrExportRound' -import './clrFinalize' -import './clrFindStorageSlot' -import './clrLoadMerkleUsers' -import './clrLoadSimpleUsers' -import './clrMergeAllocation' -import './clrTally' -import './maciPubkey' +import './subtasks' +import './runners/newMaciKey' +import './runners/newClrFund' +import './runners/newRound' +import './runners/timeTravel' +import './runners/setCoordinator' +import './runners/setRecipientRegistry' +import './runners/setMaciParameters' +import './runners/setToken' +import './runners/setUserRegistry' +import './runners/setStorageRoot' +import './runners/tally' +import './runners/finalize' +import './runners/claim' +import './runners/cancel' +import './runners/exportRound' +import './runners/loadSimpleUsers' +import './runners/loadMerkleUsers' +import './runners/contribute' +import './runners/addRecipients' +import './runners/findStorageSlot' +import './runners/verifyTallyFile' +import './runners/verifyAll' diff --git a/contracts/tasks/maciNewKey.ts b/contracts/tasks/maciNewKey.ts deleted file mode 100644 index 448914bfe..000000000 --- a/contracts/tasks/maciNewKey.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Create a new MACI key pair - * - * Sample usage: - * - * yarn ts-node tasks/maciNewKey.ts - */ - -import { newMaciPrivateKey } from '../utils/maci' - -function main() { - newMaciPrivateKey() -} - -main() diff --git a/contracts/tasks/testutils/addRecipients.ts b/contracts/tasks/runners/addRecipients.ts similarity index 84% rename from contracts/tasks/testutils/addRecipients.ts rename to contracts/tasks/runners/addRecipients.ts index f7c7b643d..8f453b450 100644 --- a/contracts/tasks/testutils/addRecipients.ts +++ b/contracts/tasks/runners/addRecipients.ts @@ -8,16 +8,19 @@ */ import { task } from 'hardhat/config' import { EContracts } from '../../utils/types' +import { ContractStorage } from '../helpers/ContractStorage' const bannerImageHash = 'QmPAZJkV2TH2hmudpSwj8buxwiG1ckNa2ZbrPFop3Td5oD' const thumbnailImageHash = 'QmPAZJkV2TH2hmudpSwj8buxwiG1ckNa2ZbrPFop3Td5oD' -task('test-add-recipients', 'Add test recipients') - .addParam('clrfund', 'The ClrFund contract address') - .setAction(async ({ clrfund }, { ethers }) => { +task('add-recipients', 'Add test recipients').setAction( + async (_, { ethers, network }) => { const [signer, ...recipients] = await ethers.getSigners() console.log('Add recipients by', signer.address) + const storage = ContractStorage.getInstance() + const clrfund = storage.mustGetAddress(EContracts.ClrFund, network.name) + const clrfundContract = await ethers.getContractAt( EContracts.ClrFund, clrfund, @@ -54,4 +57,5 @@ task('test-add-recipients', 'Add test recipients') } console.log(`Added ${numAdded} test recipients`) - }) + } +) diff --git a/contracts/tasks/clrCancel.ts b/contracts/tasks/runners/cancel.ts similarity index 78% rename from contracts/tasks/clrCancel.ts rename to contracts/tasks/runners/cancel.ts index 353401b72..ca2986b2a 100644 --- a/contracts/tasks/clrCancel.ts +++ b/contracts/tasks/runners/cancel.ts @@ -2,12 +2,12 @@ * Cancel the current round * * Sample usage: - * yarn hardhat clr-cancel --clrfund --network + * yarn hardhat cancel --clrfund --network */ import { task } from 'hardhat/config' -import { EContracts } from '../utils/types' +import { EContracts } from '../../utils/types' -task('clr-cancel', 'Cancel the current round') +task('cancel', 'Cancel the current round') .addParam('clrfund', 'The ClrFund contract address') .setAction(async ({ clrfund }, { ethers, network }) => { const [deployer] = await ethers.getSigners() diff --git a/contracts/tasks/clrClaim.ts b/contracts/tasks/runners/claim.ts similarity index 75% rename from contracts/tasks/clrClaim.ts rename to contracts/tasks/runners/claim.ts index 254087dae..acce6d3a2 100644 --- a/contracts/tasks/clrClaim.ts +++ b/contracts/tasks/runners/claim.ts @@ -2,23 +2,22 @@ * Claim funds. This script is mainly used by e2e testing * * Sample usage: - * yarn hardhat clr-claim \ - * --funding-round \ + * yarn hardhat claim \ * --tally-file \ * --recipient \ * --network */ -import { getEventArg } from '../utils/contracts' +import { getEventArg } from '../../utils/contracts' import { getRecipientClaimData } from '@clrfund/common' -import { JSONFile } from '../utils/JSONFile' -import { isPathExist } from '../utils/misc' +import { JSONFile } from '../../utils/JSONFile' +import { isPathExist } from '../../utils/misc' import { getNumber } from 'ethers' import { task, types } from 'hardhat/config' -import { EContracts } from '../utils/types' +import { EContracts } from '../../utils/types' +import { ContractStorage } from '../helpers/ContractStorage' -task('clr-claim', 'Claim funnds for test recipients') - .addParam('fundingRound', 'The funding round contract address') +task('claim', 'Claim funnds for test recipients') .addParam( 'recipient', 'The recipient index in the tally file', @@ -26,7 +25,7 @@ task('clr-claim', 'Claim funnds for test recipients') types.int ) .addParam('tallyFile', 'The tally file') - .setAction(async ({ fundingRound, tallyFile, recipient }, { ethers }) => { + .setAction(async ({ tallyFile, recipient }, { ethers, network }) => { if (!isPathExist(tallyFile)) { throw new Error(`Path ${tallyFile} does not exist`) } @@ -35,6 +34,12 @@ task('clr-claim', 'Claim funnds for test recipients') throw new Error('Recipient must be greater than 0') } + const storage = ContractStorage.getInstance() + const fundingRound = storage.mustGetAddress( + EContracts.FundingRound, + network.name + ) + const tally = JSONFile.read(tallyFile) const fundingRoundContract = await ethers.getContractAt( diff --git a/contracts/tasks/runners/contribute.ts b/contracts/tasks/runners/contribute.ts new file mode 100644 index 000000000..6060c17a1 --- /dev/null +++ b/contracts/tasks/runners/contribute.ts @@ -0,0 +1,200 @@ +/** + * Contribute to a funding round. This script is mainly used by e2e testing + * All the input used by the script comes from the state.json file + * + * Sample usage: + * yarn hardhat contribute --network + * + * Make sure deployed-contracts.json exists with the funding round address + */ + +import { Keypair, createMessage, Message, PubKey } from '@clrfund/common' + +import { UNIT } from '../../utils/constants' +import { getEventArg } from '../../utils/contracts' +import type { FundingRound, ERC20, Poll } from '../../typechain-types' +import { task } from 'hardhat/config' +import { EContracts } from '../../utils/types' +import { ContractStorage } from '../helpers/ContractStorage' + +/** + * Cast a vote by the contributor + * + * @param stateIndex The contributor stateIndex + * @param pollId The pollId + * @param contributorKeyPair The contributor MACI key pair + * @param coordinatorPubKey The coordinator MACI public key + * @param voiceCredits The total voice credits the contributor can use + * @param pollContract The poll contract with the vote function + */ +async function vote( + stateIndex: number, + pollId: bigint, + contributorKeyPair: Keypair, + coordinatorPubKey: PubKey, + voiceCredits: bigint, + pollContract: Poll +) { + const messages: Message[] = [] + const encPubKeys: PubKey[] = [] + let nonce = 1 + // Change key + const newContributorKeypair = new Keypair() + const [message, encPubKey] = createMessage( + stateIndex, + contributorKeyPair, + newContributorKeypair, + coordinatorPubKey, + null, + null, + nonce, + pollId + ) + messages.push(message) + encPubKeys.push(encPubKey) + nonce += 1 + // Vote + for (const recipientIndex of [1, 2]) { + const votes = BigInt(voiceCredits) / BigInt(4) + const [message, encPubKey] = createMessage( + stateIndex, + newContributorKeypair, + null, + coordinatorPubKey, + recipientIndex, + votes, + nonce, + pollId + ) + messages.push(message) + encPubKeys.push(encPubKey) + nonce += 1 + } + + const tx = await pollContract.publishMessageBatch( + messages.reverse().map((msg) => msg.asContractParam()), + encPubKeys.reverse().map((key) => key.asContractParam()) + ) + const receipt = await tx.wait() + if (receipt?.status !== 1) { + throw new Error(`Contributor ${stateIndex} failed to vote`) + } +} + +task('contribute', 'Contribute to a funding round').setAction( + async (_, { ethers, network }) => { + const [deployer, , , , , , , , , , , , contributor1, contributor2] = + await ethers.getSigners() + + const storage = ContractStorage.getInstance() + const fundingRoundContractAddress = storage.mustGetAddress( + EContracts.FundingRound, + network.name + ) + const fundingRound = await ethers.getContractAt( + EContracts.FundingRound, + fundingRoundContractAddress + ) + + const pollId = await fundingRound.pollId() + const pollAddress = await fundingRound.poll() + const pollContract = await ethers.getContractAt( + EContracts.Poll, + pollAddress + ) + + const rawCoordinatorPubKey = await pollContract.coordinatorPubKey() + const coordinatorPubKey = new PubKey([ + BigInt(rawCoordinatorPubKey.x), + BigInt(rawCoordinatorPubKey.y), + ]) + + const tokenAddress = await fundingRound.nativeToken() + const token = await ethers.getContractAt( + EContracts.AnyOldERC20Token, + tokenAddress + ) + + const maciAddress = await fundingRound.maci() + const maci = await ethers.getContractAt(EContracts.MACI, maciAddress) + + const userRegistryAddress = await fundingRound.userRegistry() + const userRegistry = await ethers.getContractAt( + EContracts.SimpleUserRegistry, + userRegistryAddress + ) + + const contributionAmount = (UNIT * BigInt(16)) / BigInt(10) + + for (const contributor of [contributor1, contributor2]) { + const contributorAddress = await contributor.getAddress() + + let tx = await userRegistry.addUser(contributorAddress) + let receipt = await tx.wait() + if (receipt.status !== 1) { + throw new Error(`Failed to add user to the user registry`) + } + + // transfer token to contributor first + tx = await token.transfer(contributorAddress, contributionAmount) + receipt = await tx.wait() + if (receipt.status !== 1) { + throw new Error(`Failed to transfer token for ${contributorAddress}`) + } + + const contributorKeypair = new Keypair() + const tokenAsContributor = token.connect(contributor) as ERC20 + tx = await tokenAsContributor.approve( + fundingRound.target, + contributionAmount + ) + receipt = await tx.wait() + if (receipt.status !== 1) { + throw new Error('Failed to approve token') + } + + const fundingRoundAsContributor = fundingRound.connect( + contributor + ) as FundingRound + const contributionTx = await fundingRoundAsContributor.contribute( + contributorKeypair.pubKey.asContractParam(), + contributionAmount + ) + receipt = await contributionTx.wait() + if (receipt.status !== 1) { + throw new Error('Failed to contribute') + } + + const stateIndex = await getEventArg( + contributionTx, + maci, + 'SignUp', + '_stateIndex' + ) + const voiceCredits = await getEventArg( + contributionTx, + maci, + 'SignUp', + '_voiceCreditBalance' + ) + + console.log( + `Contributor ${contributorAddress} registered. State index: ${stateIndex}. Voice credits: ${voiceCredits.toString()}.` + ) + + const pollContractAsContributor = pollContract.connect( + contributor + ) as Poll + + await vote( + stateIndex, + pollId, + contributorKeypair, + coordinatorPubKey, + voiceCredits, + pollContractAsContributor + ) + console.log(`Contributor ${contributorAddress} voted.`) + } + } +) diff --git a/contracts/tasks/clrExportRound.ts b/contracts/tasks/runners/exportRound.ts similarity index 96% rename from contracts/tasks/clrExportRound.ts rename to contracts/tasks/runners/exportRound.ts index 39743e876..4eadf9818 100644 --- a/contracts/tasks/clrExportRound.ts +++ b/contracts/tasks/runners/exportRound.ts @@ -3,7 +3,7 @@ * * Sample usage: * - * yarn hardhat clr-export-round --round-address
\ + * yarn hardhat export-round --round-address
\ * --output-dir ../vue-app/src/rounds \ * --operator --ipfs \ * --start-block --network @@ -13,11 +13,11 @@ import { task, types } from 'hardhat/config' import { Contract, formatUnits, getNumber } from 'ethers' -import { Ipfs } from '../utils/ipfs' -import { Project, Round, RoundFileContent } from '../utils/types' -import { RecipientRegistryLogProcessor } from '../utils/RecipientRegistryLogProcessor' -import { getRecipientAddressAbi } from '../utils/abi' -import { JSONFile } from '../utils/JSONFile' +import { Ipfs } from '../../utils/ipfs' +import { Project, Round, RoundFileContent } from '../../utils/types' +import { RecipientRegistryLogProcessor } from '../../utils/RecipientRegistryLogProcessor' +import { getRecipientAddressAbi } from '../../utils/abi' +import { JSONFile } from '../../utils/JSONFile' import path from 'path' import fs from 'fs' @@ -313,7 +313,7 @@ async function getRoundInfo( /** * Export all the round data for the leaderboard */ -task('clr-export-round', 'Export round data for the leaderboard') +task('export-round', 'Export round data for the leaderboard') .addParam('roundAddress', 'Funding round contract address') .addParam('outputDir', 'Output directory') .addParam('operator', 'Funding round operator, e.g. ETHColombia') diff --git a/contracts/tasks/clrFinalize.ts b/contracts/tasks/runners/finalize.ts similarity index 72% rename from contracts/tasks/clrFinalize.ts rename to contracts/tasks/runners/finalize.ts index fbcb436df..81f45ce94 100644 --- a/contracts/tasks/clrFinalize.ts +++ b/contracts/tasks/runners/finalize.ts @@ -6,33 +6,43 @@ * - clrfund owner's wallet private key to interact with the contract * * Sample usage: - * yarn hardhat clr-finalize \ - * --clrfund \ - * --tally-file + * yarn hardhat finalize --clrfund --tally-file --network */ -import { JSONFile } from '../utils/JSONFile' +import { JSONFile } from '../../utils/JSONFile' import { genTallyResultCommitment } from '@clrfund/common' import { getNumber } from 'ethers' import { task } from 'hardhat/config' -import { EContracts } from '../utils/types' +import { EContracts } from '../../utils/types' +import { ContractStorage } from '../helpers/ContractStorage' +import { Subtask } from '../helpers/Subtask' -task('clr-finalize', 'Finalize a funding round') - .addParam('clrfund', 'The ClrFund contract address') +task('finalize', 'Finalize a funding round') + .addOptionalParam('clrfund', 'The ClrFund contract address') .addOptionalParam( 'tallyFile', 'The tally file path', './proof_output/tally.json' ) - .setAction(async ({ tallyFile, clrfund }, { ethers }) => { + .setAction(async ({ clrfund, tallyFile }, hre) => { + const { ethers, network } = hre const tally = JSONFile.read(tallyFile) if (!tally.maci) { throw Error('Bad tally file ' + tallyFile) } + const storage = ContractStorage.getInstance() + const subtask = Subtask.getInstance(hre) + subtask.setHre(hre) + + await subtask.logStart() + + const clrfundcontractAddress = + clrfund ?? storage.mustGetAddress(EContracts.ClrFund, network.name) + const clrfundContract = await ethers.getContractAt( EContracts.ClrFund, - clrfund + clrfundcontractAddress ) console.log('ClrFund address', clrfund) @@ -83,4 +93,7 @@ task('clr-finalize', 'Finalize a funding round') 'Round finalized, totals verified. Gas used:', receipt.gasUsed.toString() ) + + const success = true + await subtask.finish(success) }) diff --git a/contracts/tasks/clrFindStorageSlot.ts b/contracts/tasks/runners/findStorageSlot.ts similarity index 89% rename from contracts/tasks/clrFindStorageSlot.ts rename to contracts/tasks/runners/findStorageSlot.ts index 17d051747..bcc87bba2 100644 --- a/contracts/tasks/clrFindStorageSlot.ts +++ b/contracts/tasks/runners/findStorageSlot.ts @@ -7,7 +7,7 @@ * https://github.com/vocdoni/storage-proofs-eth-js/blob/main/src/erc20.ts#L62 * * - * Usage: hardhat clr-find-storage-slot --token --holder --network arbitrum + * Usage: hardhat find-storage-slot --token --holder --network arbitrum */ import { task, types } from 'hardhat/config' @@ -18,10 +18,7 @@ const ERC20_ABI = [ 'function balanceOf(address _owner) public view returns (uint256 balance)', ] -task( - 'clr-find-storage-slot', - 'Find the balanceOf storage slot for an ERC20 token' -) +task('find-storage-slot', 'Find the balanceOf storage slot for an ERC20 token') .addParam('token', 'ERC20 contract address') .addParam('holder', 'The address of a token holder') .addOptionalParam('maxSlot', 'Maximum slots to try', 50, types.int) diff --git a/contracts/tasks/clrLoadMerkleUsers.ts b/contracts/tasks/runners/loadMerkleUsers.ts similarity index 91% rename from contracts/tasks/clrLoadMerkleUsers.ts rename to contracts/tasks/runners/loadMerkleUsers.ts index eb312f5b3..80bbfea06 100644 --- a/contracts/tasks/clrLoadMerkleUsers.ts +++ b/contracts/tasks/runners/loadMerkleUsers.ts @@ -2,7 +2,7 @@ import { task, types } from 'hardhat/config' import { Contract, isAddress } from 'ethers' import fs from 'fs' import { StandardMerkleTree } from '@clrfund/common' -import { getIpfsHash } from '../utils/ipfs' +import { getIpfsHash } from '../../utils/ipfs' /* * Load users into the the merkle user registry by generating a merkle tree and @@ -14,7 +14,7 @@ import { getIpfsHash } from '../utils/ipfs' * * Sample usage: * - * yarn hardhat clr-load-merkle-users --address-file addresses.txt --user-registry
--network goerli + * yarn hardhat load-merkle-users --address-file addresses.txt --user-registry
--network goerli */ const MAX_ADDRESSES_SUPPORTED = 10000 @@ -97,10 +97,7 @@ async function loadFile( return tx } -task( - 'clr-load-merkle-users', - 'Bulkload recipients into the simple user registry' -) +task('load-merkle-users', 'Bulkload users into the merkle user registry') .addParam('userRegistry', 'The merkle user registry contract address') .addParam( 'addressFile', @@ -120,13 +117,14 @@ task( ) .setAction( async ({ userRegistry, addressFile, output, silent }, { ethers }) => { + const [deployer] = await ethers.getSigners() const registry = await ethers.getContractAt( 'MerkleUserRegistry', userRegistry ) console.log('User merkle registry', userRegistry) - console.log('Deployer', await registry.signer.getAddress()) + console.log('Deployer', await deployer.getAddress()) const timeMs = new Date().getTime() const outputFile = output ? output : `./merkle_users_${timeMs}.json` const tx = await loadFile(registry, addressFile, outputFile, silent) diff --git a/contracts/tasks/clrLoadSimpleUsers.ts b/contracts/tasks/runners/loadSimpleUsers.ts similarity index 90% rename from contracts/tasks/clrLoadSimpleUsers.ts rename to contracts/tasks/runners/loadSimpleUsers.ts index f680a4b7e..aee25f007 100644 --- a/contracts/tasks/clrLoadSimpleUsers.ts +++ b/contracts/tasks/runners/loadSimpleUsers.ts @@ -1,4 +1,4 @@ -import { task } from 'hardhat/config' +import { task, types } from 'hardhat/config' import { Contract, ContractTransactionReceipt, isAddress } from 'ethers' import fs from 'fs' @@ -9,7 +9,7 @@ import fs from 'fs' * * Sample usage: * - * yarn hardhat clr-load-simple-users --file-path addresses.txt --user-registry
--network goerli + * yarn hardhat load-simple-users --file-path addresses.txt --user-registry
--network goerli */ /** @@ -73,10 +73,7 @@ async function loadFile(registry: Contract, filePath: string) { } } -task( - 'clr-load-simple-users', - 'Bulkload recipients into the simple user registry' -) +task('load-simple-users', 'Bulkload users into the simple user registry') .addParam('userRegistry', 'The simple user registry contract address') .addParam( 'filePath', diff --git a/contracts/tasks/maciPubkey.ts b/contracts/tasks/runners/maciPubkey.ts similarity index 100% rename from contracts/tasks/maciPubkey.ts rename to contracts/tasks/runners/maciPubkey.ts diff --git a/contracts/tasks/clrMergeAllocation.ts b/contracts/tasks/runners/mergeAllocation.ts similarity index 100% rename from contracts/tasks/clrMergeAllocation.ts rename to contracts/tasks/runners/mergeAllocation.ts diff --git a/contracts/tasks/runners/newClrFund.ts b/contracts/tasks/runners/newClrFund.ts new file mode 100644 index 000000000..31ff081a0 --- /dev/null +++ b/contracts/tasks/runners/newClrFund.ts @@ -0,0 +1,68 @@ +/* eslint-disable no-console */ +/** + * Deploy a new instance of ClrFund + * + * Sample usage: + * yarn hardhat new-clrfund --verify --network + * + * Note: + * 1) Make sure you have deploy-config.json (see deploy-config-example.json). + * 2) Make sure you set environment variable COORDINATOR_MACISK with the coordinator MACI private key + * 3) use --incremental to resume a deployment stopped due to a failure + * 4) use --manage-nonce to manually set nonce; useful on optimism-sepolia + * where `nonce too low` errors occur occasionally + */ +import { task, types } from 'hardhat/config' + +import { Subtask } from '../helpers/Subtask' +import { type ISubtaskParams } from '../helpers/types' + +task('new-clrfund', 'Deploy a new instance of ClrFund') + .addFlag('incremental', 'Incremental deployment') + .addFlag('strict', 'Fail on warnings') + .addFlag('verify', 'Verify contracts at Etherscan') + .addFlag('manageNonce', 'Manually increment nonce for each transaction') + .addOptionalParam('skip', 'Skip steps with less or equal index', 0, types.int) + .setAction(async (params: ISubtaskParams, hre) => { + const { verify, manageNonce } = params + const subtask = Subtask.getInstance(hre) + + subtask.setHre(hre) + const deployer = await subtask.getDeployer() + + if (manageNonce) { + subtask.setNonceManager(deployer) + } + + if (!process.env.COORDINATOR_MACISK) { + throw new Error('Please set environment variable COORDINATOR_MACISK') + } + + let success: boolean + try { + await subtask.start(params) + const steps = await subtask.getDeploySteps( + ['clrfund', 'maci', 'coordinator', 'token', 'user', 'recipient'], + params + ) + + const skip = params.skip || 0 + await subtask.runSteps(steps, skip) + await subtask.checkResults(params.strict) + success = true + } catch (err) { + console.error( + '\n=========================================================\nERROR:', + err, + '\n' + ) + success = false + } + + await subtask.finish(success) + + if (verify) { + console.log('Verify all contracts') + await hre.run('verify-all') + } + }) diff --git a/contracts/tasks/runners/newMaciKey.ts b/contracts/tasks/runners/newMaciKey.ts new file mode 100644 index 000000000..4132e91e5 --- /dev/null +++ b/contracts/tasks/runners/newMaciKey.ts @@ -0,0 +1,16 @@ +/** + * Create a new MACI key + * + * Sample usage: + * + * yarn hardhat new-maci-key + */ + +import { newMaciPrivateKey } from '../../utils/maci' + +import { task } from 'hardhat/config' + +task('new-maci-key', 'Generate a new MACI key').setAction(async () => { + const privateKey = newMaciPrivateKey() + console.log('MACI private key:', privateKey) +}) diff --git a/contracts/tasks/runners/newRound.ts b/contracts/tasks/runners/newRound.ts new file mode 100644 index 000000000..5b72e3141 --- /dev/null +++ b/contracts/tasks/runners/newRound.ts @@ -0,0 +1,84 @@ +/** + * Create a new instance of Funding Round + * + * Sample usage: + * + * yarn hardhat new-round --network + * + */ +import { ZERO_ADDRESS } from '../../utils/constants' +import { task } from 'hardhat/config' +import { EContracts } from '../../utils/types' +import { ContractStorage } from '../helpers/ContractStorage' +import { Subtask } from '../helpers/Subtask' +import { ISubtaskParams } from '../helpers/types' + +task('new-round', 'Deploy a new funding round contract') + .addFlag('verify', 'Verify contracts at Etherscan') + .addFlag('manageNonce', 'Manually increment nonce for each transaction') + .setAction(async ({ verify, manageNonce }: ISubtaskParams, hre) => { + const subtask = Subtask.getInstance(hre) + const storage = ContractStorage.getInstance() + const network = hre.network.name + + subtask.setHre(hre) + + if (manageNonce) { + const signer = await subtask.getDeployer() + subtask.setNonceManager(signer) + } + + const deployer = await subtask.getDeployer() + + const clrfund = storage.mustGetAddress(EContracts.ClrFund, network) + const clrfundContract = await hre.ethers.getContractAt( + 'ClrFund', + clrfund, + deployer + ) + + // check if the current round is finalized before starting a new round to avoid revert + const currentRoundAddress = await clrfundContract.getCurrentRound() + if (currentRoundAddress !== ZERO_ADDRESS) { + const currentRound = await hre.ethers.getContractAt( + EContracts.FundingRound, + currentRoundAddress + ) + const isFinalized = await currentRound.isFinalized() + if (!isFinalized) { + throw new Error( + 'Cannot start a new round as the current round is not finalized' + ) + } + } + + let success: boolean + try { + await subtask.logStart() + const params: ISubtaskParams = { + manageNonce, + verify, + incremental: false, + } + const steps = await subtask.getDeploySteps(['round'], params) + + const skip = 0 + await subtask.runSteps(steps, skip) + await subtask.checkResults(params.strict) + success = true + } catch (err) { + console.error( + '\n=========================================================\nERROR:', + err, + '\n' + ) + success = false + } + + await subtask.finish(success) + + if (verify) { + console.log('Verify all contracts') + await hre.run('verify-all') + } + }) diff --git a/contracts/tasks/runners/setCoordinator.ts b/contracts/tasks/runners/setCoordinator.ts new file mode 100644 index 000000000..ae75a4527 --- /dev/null +++ b/contracts/tasks/runners/setCoordinator.ts @@ -0,0 +1,41 @@ +/** + * Set the coordinator in clrfund + * Usage: + * hardhat set-coordinator \ + * --clrfund \ + * --coordinator \ + * --pubkey \ + * --network + */ +import { task } from 'hardhat/config' +import { Subtask } from '../helpers/Subtask' +import { ISubtaskParams } from '../helpers/types' + +task('set-coordinator', 'Set the Clrfund coordinator').setAction( + async (_, hre) => { + const subtask = Subtask.getInstance(hre) + subtask.setHre(hre) + + let success: boolean + try { + await subtask.logStart() + + // set incremental to avoid resetting contract de + const params: ISubtaskParams = { verify: false, incremental: false } + const steps = await subtask.getDeploySteps(['coordinator'], params) + + const skip = params.skip || 0 + await subtask.runSteps(steps, skip) + success = true + } catch (err) { + console.error( + '\n=========================================================\nERROR:', + err, + '\n' + ) + success = false + } + + await subtask.finish(success) + } +) diff --git a/contracts/tasks/runners/setMaciParameters.ts b/contracts/tasks/runners/setMaciParameters.ts new file mode 100644 index 000000000..88a1ac745 --- /dev/null +++ b/contracts/tasks/runners/setMaciParameters.ts @@ -0,0 +1,52 @@ +/** + * Set the zkeys parameters in the MACI factory + * + * Sample usage: + * + * yarn hardhat set-maci-params --network + * + * Make sure you have deploy-config.json (see deploy-config-example.json). + */ + +import { task, types } from 'hardhat/config' +import { Subtask } from '../helpers/Subtask' +import { type ISubtaskParams } from '../helpers/types' + +task('set-maci-params', 'Set the MACI parameters') + .addFlag('incremental', 'Incremental deployment') + .addFlag('strict', 'Fail on warnings') + .addFlag('verify', 'Verify contracts at Etherscan') + .addFlag('manageNonce', 'Manually increment nonce for each transaction') + .addOptionalParam('skip', 'Skip steps with less or equal index', 0, types.int) + .setAction(async (params: ISubtaskParams, hre) => { + const { manageNonce } = params + const subtask = Subtask.getInstance(hre) + + subtask.setHre(hre) + const deployer = await subtask.getDeployer() + + if (manageNonce) { + subtask.setNonceManager(deployer) + } + + let success: boolean + try { + await subtask.logStart() + const steps = await subtask.getDeploySteps(['maciParams'], params) + + const skip = params.skip || 0 + + console.log('steps', steps) + await subtask.runSteps(steps, skip) + success = true + } catch (err) { + console.error( + '\n=========================================================\nERROR:', + err, + '\n' + ) + success = false + } + + await subtask.finish(success) + }) diff --git a/contracts/tasks/runners/setRecipientRegistry.ts b/contracts/tasks/runners/setRecipientRegistry.ts new file mode 100644 index 000000000..bc31749db --- /dev/null +++ b/contracts/tasks/runners/setRecipientRegistry.ts @@ -0,0 +1,61 @@ +/* eslint-disable no-console */ +/** + * Deploy a new instance of ClrFund + * + * Make sure you have deploy-config.json (see deploy-config-example.json). + * + * Sample usage: + * yarn hardhat new-clrfund --verify --network + * + * Note: + * 1) use --incremental to resume a deployment stopped due to a failure + * 2) use --manage-nonce to manually set nonce, useful on optimism-sepolia + * where `nonce too low` errors occur occasionally + */ +import { task, types } from 'hardhat/config' + +import { Subtask } from '../helpers/Subtask' +import { type ISubtaskParams } from '../helpers/types' + +task('set-recipient-registry', 'Set recipient registry in ClrFund') + .addFlag('incremental', 'Incremental deployment') + .addFlag('strict', 'Fail on warnings') + .addFlag('verify', 'Verify contracts at Etherscan') + .addFlag('manageNonce', 'Manually increment nonce for each transaction') + .addOptionalParam('skip', 'Skip steps with less or equal index', 0, types.int) + .setAction(async (params: ISubtaskParams, hre) => { + const { verify, manageNonce } = params + const subtask = Subtask.getInstance(hre) + + subtask.setHre(hre) + const deployer = await subtask.getDeployer() + + if (manageNonce) { + subtask.setNonceManager(deployer) + } + + let success: boolean + try { + await subtask.logStart() + const steps = await subtask.getDeploySteps(['recipientRegistry'], params) + + const skip = params.skip || 0 + await subtask.runSteps(steps, skip) + await subtask.checkResults(params.strict) + success = true + } catch (err) { + console.error( + '\n=========================================================\nERROR:', + err, + '\n' + ) + success = false + } + + await subtask.finish(success) + + if (verify) { + console.log('Verify all contracts') + await hre.run('verify-all') + } + }) diff --git a/contracts/tasks/configure/setStorageRoot.ts b/contracts/tasks/runners/setStorageRoot.ts similarity index 98% rename from contracts/tasks/configure/setStorageRoot.ts rename to contracts/tasks/runners/setStorageRoot.ts index 8ee5691b5..3ed3d84ae 100644 --- a/contracts/tasks/configure/setStorageRoot.ts +++ b/contracts/tasks/runners/setStorageRoot.ts @@ -1,7 +1,7 @@ /** * This script set the storage root in the snapshot user registry * - * Usage: hardhat set-storage-root --registry --slot --token --block --network arbitrum-goerli + * Usage: hardhat set-storage-root --registry --slot --token --block --network * * Note: get the slot number using the `find-storage-slot` task */ diff --git a/contracts/tasks/runners/setToken.ts b/contracts/tasks/runners/setToken.ts new file mode 100644 index 000000000..71459c2ab --- /dev/null +++ b/contracts/tasks/runners/setToken.ts @@ -0,0 +1,56 @@ +/** + * Set the native token in the ClrFund contract + * + * Make sure you have deploy-config.json (see deploy-config-example.json). + * + * Sample usage: + * yarn hardhat set-token --network + */ + +import { task, types } from 'hardhat/config' +import { Subtask } from '../helpers/Subtask' +import { type ISubtaskParams } from '../helpers/types' + +task('set-token', 'Set the token in ClrFund') + .addFlag('incremental', 'Incremental deployment') + .addFlag('strict', 'Fail on warnings') + .addFlag('verify', 'Verify contracts at Etherscan') + .addFlag('manageNonce', 'Manually increment nonce for each transaction') + .addOptionalParam('skip', 'Skip steps with less or equal index', 0, types.int) + .setAction(async (params: ISubtaskParams, hre) => { + const { verify, manageNonce } = params + const subtask = Subtask.getInstance(hre) + + subtask.setHre(hre) + const deployer = await subtask.getDeployer() + + if (manageNonce) { + subtask.setNonceManager(deployer) + } + + let success: boolean + try { + await subtask.logStart() + const steps = await subtask.getDeploySteps(['token'], params) + + const skip = params.skip || 0 + + await subtask.runSteps(steps, skip) + await subtask.checkResults(params.strict) + success = true + } catch (err) { + console.error( + '\n=========================================================\nERROR:', + err, + '\n' + ) + success = false + } + + await subtask.finish(success) + + if (verify) { + console.log('Verify all contracts') + await hre.run('verify-all') + } + }) diff --git a/contracts/tasks/runners/setUserRegistry.ts b/contracts/tasks/runners/setUserRegistry.ts new file mode 100644 index 000000000..ffb8ff850 --- /dev/null +++ b/contracts/tasks/runners/setUserRegistry.ts @@ -0,0 +1,70 @@ +/** + * Set the user registry in the ClrFund contract. + * + * Sample usage: + * + * yarn hardhat set-user-registry --network \ + * --clrfund \ + * [--type ] \ + * [--registry ] \ + * [--context ] \ + * [--verifier ] \ + * [--sponsor ] + * + * Valid user registry types are simple, brightid, merkle, storage + * + * Verifier is the brightid node verifier address. + * Clrfund's brightId node is in the ethSigningAddress field from https://brightid.clr.fund + * + * Context is the bright app id + * The context value can be found here: https://apps.brightid.org/#nodes + */ + +import { task, types } from 'hardhat/config' + +import { Subtask } from '../helpers/Subtask' +import { type ISubtaskParams } from '../helpers/types' + +task('set-user-registry', 'Set the user registry in ClrFund') + .addFlag('incremental', 'Incremental deployment') + .addFlag('strict', 'Fail on warnings') + .addFlag('verify', 'Verify contracts at Etherscan') + .addFlag('manageNonce', 'Manually increment nonce for each transaction') + .addOptionalParam('skip', 'Skip steps with less or equal index', 0, types.int) + .setAction(async (params: ISubtaskParams, hre) => { + const { verify, manageNonce } = params + const subtask = Subtask.getInstance(hre) + + subtask.setHre(hre) + const deployer = await subtask.getDeployer() + + if (manageNonce) { + subtask.setNonceManager(deployer) + } + + let success: boolean + try { + await subtask.logStart() + const steps = await subtask.getDeploySteps(['userRegistry'], params) + + const skip = params.skip || 0 + + await subtask.runSteps(steps, skip) + await subtask.checkResults(params.strict) + success = true + } catch (err) { + console.error( + '\n=========================================================\nERROR:', + err, + '\n' + ) + success = false + } + + await subtask.finish(success) + + if (verify) { + console.log('Verify all contracts') + await hre.run('verify-all') + } + }) diff --git a/contracts/tasks/clrTally.ts b/contracts/tasks/runners/tally.ts similarity index 80% rename from contracts/tasks/clrTally.ts rename to contracts/tasks/runners/tally.ts index 13629d33c..f9ccd1ead 100644 --- a/contracts/tasks/clrTally.ts +++ b/contracts/tasks/runners/tally.ts @@ -12,22 +12,22 @@ * * Sample usage: * - * yarn hardhat clr-tally --clrfund --maci-tx-hash --network + * yarn hardhat tally --clrfund --maci-tx-hash --network * * To rerun: * - * yarn hardhat clr-tally --clrfund --maci-state-file \ + * yarn hardhat tally --clrfund --maci-state-file \ * --tally-file --network */ -import { BaseContract, getNumber, Signer } from 'ethers' +import { BaseContract, getNumber, Signer, NonceManager } from 'ethers' import { task, types } from 'hardhat/config' import { DEFAULT_SR_QUEUE_OPS, DEFAULT_GET_LOG_BATCH_SIZE, -} from '../utils/constants' -import { getIpfsHash } from '../utils/ipfs' -import { JSONFile } from '../utils/JSONFile' +} from '../../utils/constants' +import { getIpfsHash } from '../../utils/ipfs' +import { JSONFile } from '../../utils/JSONFile' import { getGenProofArgs, genProofs, @@ -36,12 +36,13 @@ import { mergeMaciSubtrees, genLocalState, TallyData, -} from '../utils/maci' -import { getMaciStateFilePath, getDirname } from '../utils/misc' -import { DEFAULT_CIRCUIT } from '../utils/circuits' -import { FundingRound, Poll, Tally } from '../typechain-types' +} from '../../utils/maci' +import { getMaciStateFilePath, getDirname } from '../../utils/misc' +import { FundingRound, Poll, Tally } from '../../typechain-types' import { HardhatEthersHelpers } from '@nomicfoundation/hardhat-ethers/types' -import { EContracts } from '../utils/types' +import { EContracts } from '../../utils/types' +import { ContractStorage } from '../helpers/ContractStorage' +import { Subtask } from '../helpers/Subtask' /** * Publish the tally IPFS hash on chain if it's not already published @@ -157,9 +158,11 @@ async function getMessageProcessorAddress( return messageProcessorAddress } -task('clr-tally', 'Tally votes') - .addParam('clrfund', 'ClrFund contract address') +task('tally', 'Tally votes') + .addOptionalParam('clrfund', 'ClrFund contract address') + .addOptionalParam('maciTxHash', 'MACI creation transaction hash') .addOptionalParam('maciStateFile', 'MACI state file') + .addFlag('manageNonce', 'Whether to manually manage transaction nonce') .addOptionalParam('tallyFile', 'The tally file path') .addOptionalParam( 'batchSize', @@ -167,10 +170,7 @@ task('clr-tally', 'Tally votes') 10, types.int ) - .addParam('circuit', 'The circuit type', DEFAULT_CIRCUIT) - .addParam('circuitDirectory', 'The circuit directory', './params') .addParam('outputDir', 'The proof output directory', './proof_output') - .addOptionalParam('maciTxHash', 'The MACI creation transaction hash') .addOptionalParam('rapidsnark', 'The rapidsnark prover path') .addOptionalParam( 'numQueueOps', @@ -195,27 +195,34 @@ task('clr-tally', 'Tally votes') async ( { clrfund, + maciTxHash, quiet, maciStateFile, - maciTxHash, outputDir, numQueueOps, tallyFile, blocksPerBatch, rapidsnark, sleep, - circuit, - circuitDirectory, batchSize, + manageNonce, }, - { ethers, network } + hre ) => { - console.log('Verbose logging disabled:', quiet) + console.log('Verbose logging enabled:', !quiet) + + const { ethers, network } = hre + const storage = ContractStorage.getInstance() + const subtask = Subtask.getInstance(hre) + subtask.setHre(hre) - const [coordinator] = await ethers.getSigners() - if (!coordinator) { + const [coordinatorSigner] = await ethers.getSigners() + if (!coordinatorSigner) { throw new Error('Env. variable WALLET_PRIVATE_KEY not set') } + const coordinator = manageNonce + ? new NonceManager(coordinatorSigner) + : coordinatorSigner console.log('Coordinator address: ', await coordinator.getAddress()) const coordinatorMacisk = process.env.COORDINATOR_MACISK @@ -223,8 +230,21 @@ task('clr-tally', 'Tally votes') throw new Error('Env. variable COORDINATOR_MACISK not set') } + const circuit = subtask.getConfigField( + EContracts.VkRegistry, + 'circuit' + ) + const circuitDirectory = subtask.getConfigField( + EContracts.VkRegistry, + 'paramsDirectory' + ) + + await subtask.logStart() + + const clrfundContractAddress = + clrfund ?? storage.mustGetAddress(EContracts.ClrFund, network.name) const fundingRoundContract = await getFundingRound( - clrfund, + clrfundContractAddress, coordinator, ethers ) @@ -239,6 +259,8 @@ task('clr-tally', 'Tally votes') console.log('PollId', pollId) const maciAddress = await fundingRoundContract.maci() + const maciTransactionHash = + maciTxHash ?? storage.getTxHash(maciAddress, network.name) console.log('MACI address', maciAddress) const tallyAddress = await fundingRoundContract.tally() @@ -271,7 +293,7 @@ task('clr-tally', 'Tally votes') maciContractAddress: maciAddress, coordinatorPrivateKey: coordinatorMacisk, ethereumProvider: providerUrl, - transactionHash: maciTxHash, + transactionHash: maciTransactionHash, blockPerBatch: blocksPerBatch, signer: coordinator, sleep, @@ -287,7 +309,7 @@ task('clr-tally', 'Tally votes') circuitDirectory, outputDir, blocksPerBatch: getNumber(blocksPerBatch), - maciTxHash, + maciTxHash: maciTransactionHash, maciStateFile: outputPath, signer: coordinator, quiet, @@ -323,5 +345,8 @@ task('clr-tally', 'Tally votes') tally, batchSize ) + + const success = true + await subtask.finish(success) } ) diff --git a/contracts/tasks/testutils/timeTravel.ts b/contracts/tasks/runners/timeTravel.ts similarity index 71% rename from contracts/tasks/testutils/timeTravel.ts rename to contracts/tasks/runners/timeTravel.ts index 2ffcff450..f8f6ab225 100644 --- a/contracts/tasks/testutils/timeTravel.ts +++ b/contracts/tasks/runners/timeTravel.ts @@ -2,13 +2,13 @@ * Travel to block timestamp in seconds, for testing * * Sample usage: - * yarn hardhat test-time-travel --seconds --network + * yarn hardhat time-travel --seconds --network */ import { time } from '@nomicfoundation/hardhat-network-helpers' import { task, types } from 'hardhat/config' -task('test-time-travel', 'Travel to block timestamp in seconds') +task('time-travel', 'Travel to block timestamp in seconds') .addParam('seconds', 'The number of seconds to travel', undefined, types.int) .setAction(async ({ seconds }) => { await time.increase(seconds) diff --git a/contracts/tasks/runners/verifyAll.ts b/contracts/tasks/runners/verifyAll.ts new file mode 100644 index 000000000..18d1875b4 --- /dev/null +++ b/contracts/tasks/runners/verifyAll.ts @@ -0,0 +1,98 @@ +/* eslint-disable no-console */ +import { task } from 'hardhat/config' + +import type { IStorageInstanceEntry, IVerifyAllArgs } from '../helpers/types' + +import { ContractStorage } from '../helpers/ContractStorage' +import { ContractVerifier } from '../helpers/ContractVerifier' + +/** + * Main verification task which runs hardhat-etherscan task for all the deployed contract. + */ +task('verify-all', 'Verify contracts listed in storage') + .addFlag('force', 'Ignore verified status') + .setAction(async ({ force = false }: IVerifyAllArgs, hre) => { + const storage = ContractStorage.getInstance() + const verifier = new ContractVerifier(hre) + const addressList: string[] = [] + const entryList: IStorageInstanceEntry[] = [] + let index = 0 + + const addEntry = (address: string, entry: IStorageInstanceEntry) => { + if (!entry.verify) { + return + } + + addressList.push(address) + entryList.push(entry) + index += 1 + } + + const instances = storage.getInstances(hre.network.name) + + instances.forEach(([key, entry]) => { + if (entry.id.includes('Poseidon')) { + return + } + + addEntry(key, entry) + }) + + console.log( + '======================================================================' + ) + console.log( + '======================================================================' + ) + console.log( + `Verification batch with ${addressList.length} entries of ${index} total.` + ) + console.log( + '======================================================================' + ) + + const summary: string[] = [] + for (let i = 0; i < addressList.length; i += 1) { + const address = addressList[i] + const entry = entryList[i] + + const params = entry.verify + + console.log( + '\n======================================================================' + ) + console.log( + `[${i}/${addressList.length}] Verify contract: ${entry.id} ${address}` + ) + console.log('\tArgs:', params?.args) + + const verifiedEntity = storage.getVerified(address, hre.network.name) + + if (!force && verifiedEntity) { + console.log('Already verified') + } else { + // eslint-disable-next-line no-await-in-loop + const [ok, err] = await verifier.verify(address, params?.args ?? '') + + if (ok) { + storage.setVerified(address, hre.network.name, true) + } else { + summary.push(`${address} ${entry.id}: ${err}`) + } + } + } + + console.log( + '\n======================================================================' + ) + console.log( + `Verification batch has finished with ${summary.length} issue(s).` + ) + console.log( + '======================================================================' + ) + console.log(summary.join('\n')) + console.log( + '======================================================================' + ) + }) diff --git a/contracts/tasks/runners/verifyTallyFile.ts b/contracts/tasks/runners/verifyTallyFile.ts new file mode 100644 index 000000000..381f3f806 --- /dev/null +++ b/contracts/tasks/runners/verifyTallyFile.ts @@ -0,0 +1,37 @@ +/** + * Verify the content of a tally file + * + * Usage: + * hardhat verify-tally-file --tally-file --network + */ +import { task } from 'hardhat/config' +import { JSONFile } from '../../utils/JSONFile' +import { verify } from '../../utils/maci' +import { ContractStorage } from '../helpers/ContractStorage' +import { EContracts } from '../helpers/types' + +const storage = ContractStorage.getInstance() + +task('verify-tally-file', 'Verify the content of a tally file') + .addParam('tallyFile', 'The tally file path') + .addOptionalParam('tallyAddress', 'The tally contract address') + .addFlag('quiet', 'Whether to log on the console') + .setAction(async ({ quiet, tallyFile, tallyAddress }, hre) => { + const [signer] = await hre.ethers.getSigners() + const tallyData = JSONFile.read(tallyFile) + + // get the tally contract address from contract storage because the tally file is missing it + const tallyContractAddress = + tallyAddress ?? + (await storage.mustGetAddress(EContracts.Tally, hre.network.name)) + + await verify({ + pollId: tallyData.pollId, + subsidyEnabled: false, + tallyData, + maciAddress: tallyData.maci, + tallyAddress: tallyContractAddress, + signer, + quiet, + }) + }) diff --git a/contracts/tasks/subtasks/clrfund/01-poseidon.ts b/contracts/tasks/subtasks/clrfund/01-poseidon.ts new file mode 100644 index 000000000..5731a8cfd --- /dev/null +++ b/contracts/tasks/subtasks/clrfund/01-poseidon.ts @@ -0,0 +1,88 @@ +import { ContractStorage } from '../../helpers/ContractStorage' +import { Subtask } from '../../helpers/Subtask' +import { ISubtaskParams } from '../../helpers/types' +import { EContracts } from '../../../utils/types' + +const subtask = Subtask.getInstance() +const storage = ContractStorage.getInstance() + +/** + * Deploy step registration and task itself + */ +subtask + .addTask('clrfund:deploy-poseidon', 'Deploy poseidon contracts') + .setAction(async ({ incremental }: ISubtaskParams, hre) => { + subtask.setHre(hre) + const deployer = await subtask.getDeployer() + + const poseidonT3ContractAddress = storage.getAddress( + EContracts.PoseidonT3, + hre.network.name + ) + const poseidonT4ContractAddress = storage.getAddress( + EContracts.PoseidonT4, + hre.network.name + ) + const poseidonT5ContractAddress = storage.getAddress( + EContracts.PoseidonT5, + hre.network.name + ) + const poseidonT6ContractAddress = storage.getAddress( + EContracts.PoseidonT6, + hre.network.name + ) + + if ( + incremental && + poseidonT3ContractAddress && + poseidonT4ContractAddress && + poseidonT5ContractAddress && + poseidonT6ContractAddress + ) { + return + } + + const PoseidonT3Contract = await subtask.deployContract( + EContracts.PoseidonT3, + { signer: deployer } + ) + const PoseidonT4Contract = await subtask.deployContract( + EContracts.PoseidonT4, + { signer: deployer } + ) + const PoseidonT5Contract = await subtask.deployContract( + EContracts.PoseidonT5, + { signer: deployer } + ) + const PoseidonT6Contract = await subtask.deployContract( + EContracts.PoseidonT6, + { signer: deployer } + ) + + await Promise.all([ + storage.register({ + id: EContracts.PoseidonT3, + contract: PoseidonT3Contract, + args: [], + network: hre.network.name, + }), + storage.register({ + id: EContracts.PoseidonT4, + contract: PoseidonT4Contract, + args: [], + network: hre.network.name, + }), + storage.register({ + id: EContracts.PoseidonT5, + contract: PoseidonT5Contract, + args: [], + network: hre.network.name, + }), + storage.register({ + id: EContracts.PoseidonT6, + contract: PoseidonT6Contract, + args: [], + network: hre.network.name, + }), + ]) + }) diff --git a/contracts/tasks/subtasks/clrfund/02-vkRegistry.ts b/contracts/tasks/subtasks/clrfund/02-vkRegistry.ts new file mode 100644 index 000000000..10798f9c4 --- /dev/null +++ b/contracts/tasks/subtasks/clrfund/02-vkRegistry.ts @@ -0,0 +1,55 @@ +import type { VkRegistry } from '../../../typechain-types' + +import { ContractStorage } from '../../helpers/ContractStorage' +import { Subtask } from '../../helpers/Subtask' +import { setVerifyingKeys } from '../../../utils/deployment' +import { MaciParameters } from '../../../utils/maciParameters' +import { EContracts, type ISubtaskParams } from '../../helpers/types' + +const subtask = Subtask.getInstance() +const storage = ContractStorage.getInstance() + +/** + * Deploy step registration and task itself + */ +subtask + .addTask('clrfund:deploy-vk-registry', 'Deploy Vk Registry and set keys') + .setAction(async ({ incremental }: ISubtaskParams, hre) => { + subtask.setHre(hre) + const deployer = await subtask.getDeployer() + + const vkRegistryContractAddress = storage.getAddress( + EContracts.VkRegistry, + hre.network.name + ) + + if (incremental && vkRegistryContractAddress) { + return + } + + const vkRegistryContract = await subtask.deployContract( + EContracts.VkRegistry, + { signer: deployer } + ) + + const circuit = subtask.getConfigField( + EContracts.VkRegistry, + 'circuit', + true + ) + const directory = subtask.getConfigField( + EContracts.VkRegistry, + 'paramsDirectory', + true + ) + + const maciParameters = await MaciParameters.fromConfig(circuit, directory) + await setVerifyingKeys(vkRegistryContract, maciParameters) + + await storage.register({ + id: EContracts.VkRegistry, + contract: vkRegistryContract, + args: [], + network: hre.network.name, + }) + }) diff --git a/contracts/tasks/subtasks/clrfund/03-verifier.ts b/contracts/tasks/subtasks/clrfund/03-verifier.ts new file mode 100644 index 000000000..0b1ec1030 --- /dev/null +++ b/contracts/tasks/subtasks/clrfund/03-verifier.ts @@ -0,0 +1,36 @@ +import { ContractStorage } from '../../helpers/ContractStorage' +import { Subtask } from '../../helpers/Subtask' +import { EContracts, ISubtaskParams } from '../../helpers/types' + +const subtask = Subtask.getInstance() +const storage = ContractStorage.getInstance() + +/** + * Deploy step registration and task itself + */ +subtask + .addTask('clrfund:deploy-verifier', 'Deploy verifier') + .setAction(async ({ incremental }: ISubtaskParams, hre) => { + subtask.setHre(hre) + const deployer = await subtask.getDeployer() + + const verifierContractAddress = storage.getAddress( + EContracts.Verifier, + hre.network.name + ) + + if (incremental && verifierContractAddress) { + return + } + + const verifierContract = await subtask.deployContract(EContracts.Verifier, { + signer: deployer, + }) + + await storage.register({ + id: EContracts.Verifier, + contract: verifierContract, + args: [], + network: hre.network.name, + }) + }) diff --git a/contracts/tasks/subtasks/clrfund/04-messageProcessorFactory.ts b/contracts/tasks/subtasks/clrfund/04-messageProcessorFactory.ts new file mode 100644 index 000000000..5fec1d700 --- /dev/null +++ b/contracts/tasks/subtasks/clrfund/04-messageProcessorFactory.ts @@ -0,0 +1,44 @@ +import { ContractStorage } from '../../helpers/ContractStorage' +import { Subtask } from '../../helpers/Subtask' +import { EContracts, ISubtaskParams } from '../../helpers/types' + +const subtask = Subtask.getInstance() +const storage = ContractStorage.getInstance() + +/** + * Deploy step registration and task itself + */ +subtask + .addTask( + 'clrfund:deploy-message-processor-factory', + 'Deploy message processor factory' + ) + .setAction(async ({ incremental }: ISubtaskParams, hre) => { + subtask.setHre(hre) + const deployer = await subtask.getDeployer() + + const messageProcessorFactoryContractAddress = storage.getAddress( + EContracts.MessageProcessorFactory, + hre.network.name + ) + + if (incremental && messageProcessorFactoryContractAddress) { + return + } + + const libraries = await storage.mustGetPoseidonLibraries(hre.network.name) + const messageProcessorFactoryContract = await subtask.deployContract( + EContracts.MessageProcessorFactory, + { + signer: deployer, + libraries, + } + ) + + await storage.register({ + id: EContracts.MessageProcessorFactory, + contract: messageProcessorFactoryContract, + args: [], + network: hre.network.name, + }) + }) diff --git a/contracts/tasks/subtasks/clrfund/05-tallyFactory.ts b/contracts/tasks/subtasks/clrfund/05-tallyFactory.ts new file mode 100644 index 000000000..673e77089 --- /dev/null +++ b/contracts/tasks/subtasks/clrfund/05-tallyFactory.ts @@ -0,0 +1,41 @@ +import { ContractStorage } from '../../helpers/ContractStorage' +import { Subtask } from '../../helpers/Subtask' +import { EContracts, ISubtaskParams } from '../../helpers/types' + +const subtask = Subtask.getInstance() +const storage = ContractStorage.getInstance() + +/** + * Deploy step registration and task itself + */ +subtask + .addTask('clrfund:deploy-tally-factory', 'Deploy tally factory') + .setAction(async ({ incremental }: ISubtaskParams, hre) => { + subtask.setHre(hre) + const deployer = await subtask.getDeployer() + + const tallyFactoryContractAddress = storage.getAddress( + EContracts.TallyFactory, + hre.network.name + ) + + if (incremental && tallyFactoryContractAddress) { + return + } + + const libraries = storage.mustGetPoseidonLibraries(hre.network.name) + const tallyFactoryContract = await subtask.deployContract( + EContracts.TallyFactory, + { + libraries, + signer: deployer, + } + ) + + await storage.register({ + id: EContracts.TallyFactory, + contract: tallyFactoryContract, + args: [], + network: hre.network.name, + }) + }) diff --git a/contracts/tasks/subtasks/clrfund/06-pollFactory.ts b/contracts/tasks/subtasks/clrfund/06-pollFactory.ts new file mode 100644 index 000000000..c84f1ed98 --- /dev/null +++ b/contracts/tasks/subtasks/clrfund/06-pollFactory.ts @@ -0,0 +1,41 @@ +import { ContractStorage } from '../../helpers/ContractStorage' +import { Subtask } from '../../helpers/Subtask' +import { EContracts, ISubtaskParams } from '../../helpers/types' + +const subtask = Subtask.getInstance() +const storage = ContractStorage.getInstance() + +/** + * Deploy step registration and task itself + */ +subtask + .addTask('clrfund:deploy-poll-factory', 'Deploy poll factory') + .setAction(async ({ incremental }: ISubtaskParams, hre) => { + subtask.setHre(hre) + const deployer = await subtask.getDeployer() + + const pollFactoryContractAddress = storage.getAddress( + EContracts.PollFactory, + hre.network.name + ) + + if (incremental && pollFactoryContractAddress) { + return + } + + const libraries = storage.mustGetPoseidonLibraries(hre.network.name) + const pollFactoryContract = await subtask.deployContract( + EContracts.PollFactory, + { + signer: deployer, + libraries, + } + ) + + await storage.register({ + id: EContracts.PollFactory, + contract: pollFactoryContract, + args: [], + network: hre.network.name, + }) + }) diff --git a/contracts/tasks/subtasks/clrfund/07-maciFactory.ts b/contracts/tasks/subtasks/clrfund/07-maciFactory.ts new file mode 100644 index 000000000..cfaf146a6 --- /dev/null +++ b/contracts/tasks/subtasks/clrfund/07-maciFactory.ts @@ -0,0 +1,74 @@ +import { ContractStorage } from '../../helpers/ContractStorage' +import { Subtask } from '../../helpers/Subtask' +import { EContracts, ISubtaskParams } from '../../helpers/types' +import { ZERO_ADDRESS } from '../../../utils/constants' + +const subtask = Subtask.getInstance() +const storage = ContractStorage.getInstance() + +/** + * Deploy step registration and task itself + */ +subtask + .addTask('clrfund:deploy-maci-factory', 'Deploy maci factory') + .setAction(async ({ incremental }: ISubtaskParams, hre) => { + subtask.setHre(hre) + const deployer = await subtask.getDeployer() + + const maciFactoryContractAddress = storage.getAddress( + EContracts.MACIFactory, + hre.network.name + ) + + if (incremental && maciFactoryContractAddress) { + return + } + + const verifierContractAddress = storage.mustGetAddress( + EContracts.Verifier, + hre.network.name + ) + const vkRegistryContractAddress = storage.mustGetAddress( + EContracts.VkRegistry, + hre.network.name + ) + const pollFactoryContractAddress = storage.mustGetAddress( + EContracts.PollFactory, + hre.network.name + ) + const tallyFactoryContractAddress = storage.mustGetAddress( + EContracts.TallyFactory, + hre.network.name + ) + const messageProcessorFactoryContractAddress = storage.mustGetAddress( + EContracts.MessageProcessorFactory, + hre.network.name + ) + // all the factories to deploy MACI contracts + const factories = { + pollFactory: pollFactoryContractAddress, + tallyFactory: tallyFactoryContractAddress, + // subsidy is not currently used + subsidyFactory: ZERO_ADDRESS, + messageProcessorFactory: messageProcessorFactoryContractAddress, + } + + const args = [vkRegistryContractAddress, factories, verifierContractAddress] + const libraries = storage.mustGetPoseidonLibraries(hre.network.name) + + const maciFactoryContract = await subtask.deployContract( + EContracts.MACIFactory, + { + args, + signer: deployer, + libraries, + } + ) + + await storage.register({ + id: EContracts.MACIFactory, + contract: maciFactoryContract, + args, + network: hre.network.name, + }) + }) diff --git a/contracts/tasks/subtasks/clrfund/08-fundingRoundFactory.ts b/contracts/tasks/subtasks/clrfund/08-fundingRoundFactory.ts new file mode 100644 index 000000000..7c617df4e --- /dev/null +++ b/contracts/tasks/subtasks/clrfund/08-fundingRoundFactory.ts @@ -0,0 +1,40 @@ +import { ContractStorage } from '../../helpers/ContractStorage' +import { Subtask } from '../../helpers/Subtask' +import { EContracts, ISubtaskParams } from '../../helpers/types' + +const subtask = Subtask.getInstance() +const storage = ContractStorage.getInstance() + +/** + * Deploy step registration and task itself + */ +subtask + .addTask( + 'clrfund:deploy-funding-round-factory', + 'Deploy funding round factory' + ) + .setAction(async ({ incremental }: ISubtaskParams, hre) => { + subtask.setHre(hre) + const deployer = await subtask.getDeployer() + + const fundingRoundFactoryContractAddress = storage.getAddress( + EContracts.FundingRoundFactory, + hre.network.name + ) + + if (incremental && fundingRoundFactoryContractAddress) { + return + } + + const fundingRoundFactoryContract = await subtask.deployContract( + EContracts.FundingRoundFactory, + { signer: deployer } + ) + + await storage.register({ + id: EContracts.FundingRoundFactory, + contract: fundingRoundFactoryContract, + args: [], + network: hre.network.name, + }) + }) diff --git a/contracts/tasks/subtasks/clrfund/09-clrfund.ts b/contracts/tasks/subtasks/clrfund/09-clrfund.ts new file mode 100644 index 000000000..0cfc7d837 --- /dev/null +++ b/contracts/tasks/subtasks/clrfund/09-clrfund.ts @@ -0,0 +1,62 @@ +import { ClrFund } from '../../../typechain-types' +import { ContractStorage } from '../../helpers/ContractStorage' +import { Subtask } from '../../helpers/Subtask' +import { EContracts, ISubtaskParams } from '../../helpers/types' + +const subtask = Subtask.getInstance() +const storage = ContractStorage.getInstance() + +/** + * Deploy step registration and task itself + */ +subtask + .addTask('clrfund:deploy-clrfund', 'Deploy ClrFund') + .setAction(async ({ incremental }: ISubtaskParams, hre) => { + subtask.setHre(hre) + const deployer = await subtask.getDeployer() + + const clrfundContractAddress = storage.getAddress( + EContracts.ClrFund, + hre.network.name + ) + + if (incremental && clrfundContractAddress) { + return + } + + const template = subtask.getConfigField( + EContracts.ClrFund, + 'template' + ) + const maciFactoryAddress = storage.mustGetAddress( + EContracts.MACIFactory, + hre.network.name + ) + const fundingRoundFactoryContractAddress = storage.mustGetAddress( + EContracts.FundingRoundFactory, + hre.network.name + ) + + const clrfundContract = await subtask.deployContract( + EContracts.ClrFund, + { signer: deployer } + ) + + if (!template) { + const tx = await clrfundContract.init( + maciFactoryAddress, + fundingRoundFactoryContractAddress + ) + const receipt = await tx.wait() + if (receipt?.status !== 1) { + throw new Error('Failed to initialize ClrFund') + } + } + + await storage.register({ + id: EContracts.ClrFund, + contract: clrfundContract, + args: [], + network: hre.network.name, + }) + }) diff --git a/contracts/tasks/subtasks/coordinator/01-coordinator.ts b/contracts/tasks/subtasks/coordinator/01-coordinator.ts new file mode 100644 index 000000000..dad05ed8d --- /dev/null +++ b/contracts/tasks/subtasks/coordinator/01-coordinator.ts @@ -0,0 +1,77 @@ +/** + * Deploy an instance of the BrightID sponsor contract + * + */ +import { Subtask } from '../../helpers/Subtask' +import { EContracts } from '../../../utils/types' +import { setCoordinator } from '../../../utils/deployment' +import { Contract, getAddress } from 'ethers' +import { Keypair, PrivKey, PubKey } from '@clrfund/common' + +const subtask = Subtask.getInstance() + +/** + * Deploy step registration and task itself + */ +subtask + .addTask('coordinator:set-coordinator', 'Set the clrfund coordinator') + .setAction(async (_, hre) => { + subtask.setHre(hre) + const deployer = await subtask.getDeployer() + + const clrfundContract = await subtask.getContract({ + name: EContracts.ClrFund, + }) + + const coordinator = await subtask.getConfigField( + EContracts.ClrFund, + 'coordinaotor' + ) + const coordinatorAddress = coordinator || (await deployer.getAddress()) + + if (!process.env.COORDINATOR_MACISK) { + throw new Error('Please set environment variable COORDINATOR_MACISK') + } + const coordinatorMacisk = process.env.COORDINATOR_MACISK + + const [currentCoordinator, coordinatorPubKey] = await Promise.all([ + clrfundContract.coordinator(), + clrfundContract.coordinatorPubKey(), + ]) + + const currentPubKey = new PubKey([coordinatorPubKey.x, coordinatorPubKey.y]) + const newPrivKey = PrivKey.deserialize(coordinatorMacisk) + const newKeypair = new Keypair(newPrivKey) + + const normalizedCurrentCoordinator = getAddress(currentCoordinator) + const normalizedNewCoordinator = getAddress(coordinatorAddress) + console.log('Current coordinator', normalizedCurrentCoordinator) + console.log(' New coordinator', normalizedNewCoordinator) + + const serializedCurrentPubKey = currentPubKey.serialize() + const serializedNewPubKey = newKeypair.pubKey.serialize() + console.log('Current MACI key', serializedCurrentPubKey) + console.log(' New MACI key', serializedNewPubKey) + console.log() + + if ( + normalizedCurrentCoordinator === normalizedNewCoordinator && + serializedCurrentPubKey === serializedNewPubKey + ) { + console.log('Coordinator address and MACI key already set, skipping...') + return + } + + const tx = await setCoordinator({ + clrfundContract, + coordinatorAddress: normalizedNewCoordinator, + coordinatorMacisk, + }) + + const receipt = await tx.wait() + if (receipt?.status !== 1) { + throw new Error('Failed to set coordinator') + } + + subtask.logTransaction(tx) + }) diff --git a/contracts/tasks/subtasks/deployer/01-clrfundDeployer.ts b/contracts/tasks/subtasks/deployer/01-clrfundDeployer.ts new file mode 100644 index 000000000..cc2642289 --- /dev/null +++ b/contracts/tasks/subtasks/deployer/01-clrfundDeployer.ts @@ -0,0 +1,66 @@ +/** + * Create a new instance of the ClrFundDeployer contract + */ +import { ContractStorage } from '../../helpers/ContractStorage' +import { Subtask } from '../../helpers/Subtask' +import { ISubtaskParams } from '../../helpers/types' +import { EContracts } from '../../../utils/types' + +const subtask = Subtask.getInstance() +const storage = ContractStorage.getInstance() + +/** + * Deploy step registration and task itself + */ +subtask + .addTask( + 'brightid:deploy-brightid-sponsor', + 'Deploy BrightID sponsor contracts' + ) + .setAction(async ({ incremental }: ISubtaskParams, hre) => { + subtask.setHre(hre) + const deployer = await subtask.getDeployer() + const network = hre.network.name + + const clrfundDeployerContractAddress = storage.getAddress( + EContracts.ClrFundDeployer, + network + ) + + if (incremental && clrfundDeployerContractAddress) { + return + } + + const clrfundContractAddress = storage.mustGetAddress( + EContracts.ClrFund, + network + ) + + const maciFactoryContractAddress = storage.mustGetAddress( + EContracts.MACIFactory, + network + ) + + const fundingRoundFactoryContractAddress = storage.mustGetAddress( + EContracts.FundingRoundFactory, + network + ) + + const args = [ + clrfundContractAddress, + maciFactoryContractAddress, + fundingRoundFactoryContractAddress, + ] + + const ClrfundDeployerContract = await subtask.deployContract( + EContracts.ClrFundDeployer, + { signer: deployer, args } + ) + + await storage.register({ + id: EContracts.ClrFundDeployer, + contract: ClrfundDeployerContract, + args, + network: hre.network.name, + }) + }) diff --git a/contracts/tasks/subtasks/index.ts b/contracts/tasks/subtasks/index.ts new file mode 100644 index 000000000..d24d48cef --- /dev/null +++ b/contracts/tasks/subtasks/index.ts @@ -0,0 +1,29 @@ +import fs from 'fs' +import path from 'path' + +export const SUBTASK_CATALOGS = [ + 'brightid', + 'clrfund', + 'coordinator', + 'deployer', + 'maci', + 'recipient', + 'round', + 'user', + 'token', +] + +/** + * The same as individual imports but doesn't require to add new import line every time + */ +SUBTASK_CATALOGS.forEach((catalog) => { + const tasksPath = path.resolve(__dirname, catalog) + + if (fs.existsSync(tasksPath)) { + fs.readdirSync(tasksPath) + .filter((p) => p.includes('.ts') && !p.includes('index.ts')) + .forEach((task) => { + import(`${tasksPath}/${task}`) + }) + } +}) diff --git a/contracts/tasks/subtasks/maci/01-setMaciParams.ts b/contracts/tasks/subtasks/maci/01-setMaciParams.ts new file mode 100644 index 000000000..964cd0a8c --- /dev/null +++ b/contracts/tasks/subtasks/maci/01-setMaciParams.ts @@ -0,0 +1,67 @@ +/** + * Deploy an instance of the BrightID sponsor contract + * + */ +import { Subtask } from '../../helpers/Subtask' +import { EContracts } from '../../../utils/types' +import { Contract, getNumber } from 'ethers' +import { MaciParameters } from '../../../utils/maciParameters' + +const subtask = Subtask.getInstance() + +/** + * Deploy step registration and task itself + */ +subtask + .addTask('maci:set-maci-params', 'Set the MACI parameters') + .setAction(async (_, hre) => { + subtask.setHre(hre) + + const circuit = subtask.getConfigField( + EContracts.VkRegistry, + 'circuit' + ) + const directory = subtask.getConfigField( + EContracts.VkRegistry, + 'paramsDirectory' + ) + const maciParameters = await MaciParameters.fromConfig(circuit, directory) + + const maciFactoryContract = await subtask.getContract({ + name: EContracts.MACIFactory, + }) + + const stateTreeDepth = await maciFactoryContract.stateTreeDepth() + const treeDepths = await maciFactoryContract.treeDepths() + + const { + intStateTreeDepth, + messageTreeSubDepth, + messageTreeDepth, + voteOptionTreeDepth, + } = maciParameters.treeDepths + + if ( + getNumber(stateTreeDepth) === maciParameters.stateTreeDepth && + getNumber(treeDepths.intStateTreeDepth) === intStateTreeDepth && + getNumber(treeDepths.messageTreeSubDepth) === messageTreeSubDepth && + getNumber(treeDepths.messageTreeDepth) === messageTreeDepth && + getNumber(treeDepths.voteOptionTreeDepth) === voteOptionTreeDepth + ) { + // MACI parameters already set + console.log('Skip - parameters already set') + return + } + + const tx = await maciFactoryContract.setMaciParameters( + ...maciParameters.asContractParam() + ) + await tx.wait() + + const receipt = await tx.wait() + if (receipt?.status !== 1) { + throw new Error('Failed to set MACI parameters') + } + + subtask.logTransaction(tx) + }) diff --git a/contracts/tasks/subtasks/recipient/01-simpleRecipientRegistry.ts b/contracts/tasks/subtasks/recipient/01-simpleRecipientRegistry.ts new file mode 100644 index 000000000..968d4f2c1 --- /dev/null +++ b/contracts/tasks/subtasks/recipient/01-simpleRecipientRegistry.ts @@ -0,0 +1,60 @@ +/** + * Deploy a new recipient registry + */ + +import { ContractStorage } from '../../helpers/ContractStorage' +import { Subtask } from '../../helpers/Subtask' +import { ISubtaskParams } from '../../helpers/types' +import { EContracts } from '../../../utils/types' + +const subtask = Subtask.getInstance() +const storage = ContractStorage.getInstance() + +/** + * Deploy step registration and task itself + */ +subtask + .addTask( + 'recipient:deploy-simple-recipient-registry', + 'Deploy a simple recipient regsitry' + ) + .setAction(async ({ incremental }: ISubtaskParams, hre) => { + subtask.setHre(hre) + const deployer = await subtask.getDeployer() + + const recipientRegistryName = subtask.getConfigField( + EContracts.ClrFund, + 'recipientRegistry' + ) + + if (recipientRegistryName !== EContracts.SimpleRecipientRegistry) { + return + } + + const simpleRecipientRegistryContractAddress = storage.getAddress( + EContracts.SimpleRecipientRegistry, + hre.network.name + ) + + if (incremental && simpleRecipientRegistryContractAddress) { + return + } + + const clrfundContractAddress = storage.mustGetAddress( + EContracts.ClrFund, + hre.network.name + ) + + const args = [clrfundContractAddress] + const simpleRecipientRegistryContract = await subtask.deployContract( + EContracts.SimpleRecipientRegistry, + { signer: deployer, args } + ) + + await storage.register({ + id: EContracts.SimpleRecipientRegistry, + contract: simpleRecipientRegistryContract, + args, + network: hre.network.name, + }) + }) diff --git a/contracts/tasks/subtasks/recipient/02-optimisticRecipientRegistry.ts b/contracts/tasks/subtasks/recipient/02-optimisticRecipientRegistry.ts new file mode 100644 index 000000000..2d1e91ec5 --- /dev/null +++ b/contracts/tasks/subtasks/recipient/02-optimisticRecipientRegistry.ts @@ -0,0 +1,102 @@ +/** + * Deploy a new recipient registry + * + */ +import { ContractStorage } from '../../helpers/ContractStorage' +import { Subtask } from '../../helpers/Subtask' +import { ISubtaskParams } from '../../helpers/types' +import { EContracts } from '../../../utils/types' +import { parseUnits } from 'ethers' +import { AnyOldERC20Token, ClrFund } from '../../../typechain-types' + +// Number.MAX_SAFE_INTEGER - 1 +const defaultChallengePeriodSeconds = 9007199254740990 + +const subtask = Subtask.getInstance() +const storage = ContractStorage.getInstance() + +/** + * Get the deposit token decimals + * + * @param clrfund Clrfund contract + * @returns Token decimals + */ +async function getTokenDecimals(clrfundContract: ClrFund): Promise { + const nativeTokenAddress = await clrfundContract.nativeToken() + const tokenContract = await task.getContract({ + name: EContracts.AnyOldERC20Token, + address: nativeTokenAddress, + }) + const decimals = await tokenContract.decimals() + return decimals +} + +/** + * Deploy step registration and task itself + */ + +subtask + .addTask( + 'recipient:deploy-optimistic-recipient-registry', + 'Deploy an optimistic recipient regsitry' + ) + .setAction(async ({ incremental }: ISubtaskParams, hre) => { + subtask.setHre(hre) + const deployer = await subtask.getDeployer() + const network = hre.network.name + + const recipientRegistryName = subtask.getConfigField( + EContracts.ClrFund, + 'recipientRegistry' + ) + if (recipientRegistryName !== EContracts.OptimisticRecipientRegistry) { + return + } + + const optimisticRecipientRegistryContractAddress = storage.getAddress( + EContracts.OptimisticRecipientRegistry, + network + ) + + if (incremental && optimisticRecipientRegistryContractAddress) { + return + } + + const deposit = subtask.getConfigField( + EContracts.OptimisticRecipientRegistry, + 'deposit' + ) + + const challengePeriodSeconds = subtask.getConfigField( + EContracts.OptimisticRecipientRegistry, + 'challengePeriodSeconds' + ) + + const clrfundContractAddress = storage.mustGetAddress( + EContracts.ClrFund, + network + ) + + const clrfundContract = await subtask.getContract({ + name: EContracts.ClrFund, + address: clrfundContractAddress, + }) + const decimals = await getTokenDecimals(clrfundContract) + + const args = [ + parseUnits(deposit, decimals), + challengePeriodSeconds || defaultChallengePeriodSeconds, + clrfundContractAddress, + ] + const optimisticRecipientRegistryContract = await subtask.deployContract( + EContracts.OptimisticRecipientRegistry, + { signer: deployer, args } + ) + + await storage.register({ + id: EContracts.OptimisticRecipientRegistry, + contract: optimisticRecipientRegistryContract, + args, + network: hre.network.name, + }) + }) diff --git a/contracts/tasks/subtasks/recipient/99-setRecipientRegistry.ts b/contracts/tasks/subtasks/recipient/99-setRecipientRegistry.ts new file mode 100644 index 000000000..8b23dc10a --- /dev/null +++ b/contracts/tasks/subtasks/recipient/99-setRecipientRegistry.ts @@ -0,0 +1,64 @@ +/** + * Deploy a new recipient registry + * + */ +import { ContractStorage } from '../../helpers/ContractStorage' +import { Subtask } from '../../helpers/Subtask' +import { ISubtaskParams } from '../../helpers/types' +import { EContracts } from '../../../utils/types' +import { ClrFund } from '../../../typechain-types' +import { getAddress } from 'ethers' + +const subtask = Subtask.getInstance() +const storage = ContractStorage.getInstance() + +/** + * Deploy step registration and task itself + */ +subtask + .addTask( + 'recipient:set-recipient-registry', + 'Set recipient registry in the ClrFund contract' + ) + .setAction(async ({ incremental }: ISubtaskParams, hre) => { + subtask.setHre(hre) + const network = hre.network.name + + const recipientRegistryName = subtask.getConfigField( + EContracts.ClrFund, + 'recipientRegistry' + ) + + const recipientRegistryContractAddress = storage.mustGetAddress( + recipientRegistryName as EContracts, + network + ) + + const clrfundContract = await subtask.getContract({ + name: EContracts.ClrFund, + }) + + if (incremental) { + const currentRecipientRegistryAddress = + await clrfundContract.recipientRegistry() + + if ( + getAddress(currentRecipientRegistryAddress) === + getAddress(recipientRegistryContractAddress) + ) { + // already set + return + } + } + + const tx = await clrfundContract.setRecipientRegistry( + recipientRegistryContractAddress + ) + const receipt = await tx.wait() + + if (receipt?.status !== 1) { + throw new Error( + `Failed to set recipient registry ${recipientRegistryContractAddress}` + ) + } + }) diff --git a/contracts/tasks/subtasks/round/01-deploy-brightid-user-registry.ts b/contracts/tasks/subtasks/round/01-deploy-brightid-user-registry.ts new file mode 100644 index 000000000..016590197 --- /dev/null +++ b/contracts/tasks/subtasks/round/01-deploy-brightid-user-registry.ts @@ -0,0 +1,44 @@ +/** + * Deploy a brightid user registry + */ + +import { Subtask } from '../../helpers/Subtask' +import { ISubtaskParams } from '../../helpers/types' +import { EContracts } from '../../../utils/types' + +const subtask = Subtask.getInstance() + +/** + * Deploy step registration and task itself + */ +subtask + .addTask( + 'round:deploy-brightid-user-registry', + 'Deploy a BrightId user registry for a new round' + ) + .setAction(async (params: ISubtaskParams, hre) => { + subtask.setHre(hre) + + const userRegistryName = subtask.getConfigField( + EContracts.ClrFund, + 'userRegistry' + ) + + if (userRegistryName !== EContracts.BrightIdUserRegistry) { + // only create a new BrightID user registry for each new round + return + } + + const shouldDeploy = subtask.getConfigField( + EContracts.BrightIdUserRegistry, + 'deploy' + ) + + if (!shouldDeploy) { + // for testing, we don't deploy for every round + return + } + + const steps = await subtask.getDeploySteps(['user'], params) + await subtask.runSteps(steps, 0) + }) diff --git a/contracts/tasks/subtasks/round/02-deploy-round.ts b/contracts/tasks/subtasks/round/02-deploy-round.ts new file mode 100644 index 000000000..e31a894f1 --- /dev/null +++ b/contracts/tasks/subtasks/round/02-deploy-round.ts @@ -0,0 +1,217 @@ +/** + * Deploy a funding round + */ + +import { ContractStorage } from '../../helpers/ContractStorage' +import { Subtask } from '../../helpers/Subtask' +import { EContracts } from '../../../utils/types' +import { + ClrFund, + FundingRound, + MACI, + Poll, + Tally, +} from '../../../typechain-types' +import { ContractTransactionResponse } from 'ethers' + +const subtask = Subtask.getInstance() +const storage = ContractStorage.getInstance() + +/** + * Register MACI contract in the contract storage for verification + * + * @param fundingRoundContract The funding round contract + * @param network The network + * @param tx The funding round deployment transaction + */ +async function registerMaci( + fundingRoundContract: FundingRound, + network: string, + tx: ContractTransactionResponse +) { + const maciContractAddress = await fundingRoundContract.maci() + const maciContract = await subtask.getContract({ + name: EContracts.MACI, + address: maciContractAddress, + }) + + const args = await Promise.all([ + maciContract.pollFactory(), + maciContract.messageProcessorFactory(), + maciContract.tallyFactory(), + maciContract.subsidyFactory(), + maciContract.signUpGatekeeper(), + maciContract.initialVoiceCreditProxy(), + maciContract.topupCredit(), + maciContract.stateTreeDepth(), + ]) + + await storage.register({ + id: EContracts.MACI, + contract: maciContract, + network, + args, + tx, + }) +} + +/** + * Register the Poll contract in the contract storage + * + * @param duration The round duration + * @param fundingRoundContract The funding round contract + * @param network The network + * @param tx The funding round deployment transaction + + */ +async function registerPoll( + duration: number, + fundingRoundContract: FundingRound, + network: string, + tx: ContractTransactionResponse +) { + const pollContractAddress = await fundingRoundContract.poll() + const pollContract = await subtask.getContract({ + name: EContracts.Poll, + address: pollContractAddress, + }) + + const [maxValues, treeDepths, coordinatorPubKey, extContracts] = + await Promise.all([ + pollContract.maxValues(), + pollContract.treeDepths(), + pollContract.coordinatorPubKey(), + pollContract.extContracts(), + ]) + + const args = [ + [ + duration, + { + maxMessages: maxValues.maxMessages, + maxVoteOptions: maxValues.maxVoteOptions, + }, + { + intStateTreeDepth: treeDepths.intStateTreeDepth, + messageTreeSubDepth: treeDepths.messageTreeSubDepth, + messageTreeDepth: treeDepths.messageTreeDepth, + voteOptionTreeDepth: treeDepths.voteOptionTreeDepth, + }, + { + x: coordinatorPubKey.x, + y: coordinatorPubKey.y, + }, + { + maci: extContracts.maci, + messageAq: extContracts.messageAq, + topupCredit: extContracts.topupCredit, + }, + ], + ] + + await storage.register({ + id: EContracts.Poll, + contract: pollContract, + network, + args, + tx, + }) +} + +/** + * Register the Tally and the MessageProcessor contracts on the contract storage + * + * @param fundingRoundContract The funding round contract + * @param network The network + * @param tx The funding round deployment transaction + + */ +async function registerTallyAndMessageProcessor( + fundingRoundContract: FundingRound, + network: string, + tx: ContractTransactionResponse +) { + const [tally, poll] = await Promise.all([ + fundingRoundContract.tally(), + fundingRoundContract.poll(), + ]) + + const tallyContract = await subtask.getContract({ + name: EContracts.Tally, + address: tally, + }) + + const mp = await tallyContract.messageProcessor() + const messageProcessorContract = await subtask.getContract({ + name: EContracts.MessageProcessor, + address: mp, + }) + + const [verifier, vkRegistry] = await Promise.all([ + tallyContract.verifier(), + tallyContract.vkRegistry(), + ]) + + let args = [verifier, vkRegistry, poll, mp] + await storage.register({ + id: EContracts.Tally, + contract: tallyContract, + network, + args, + tx, + }) + + args = [verifier, vkRegistry, poll] + await storage.register({ + id: EContracts.MessageProcessor, + contract: messageProcessorContract, + network, + args, + tx, + }) +} + +/** + * Deploy step registration and task itself + */ +subtask + .addTask('round:deploy-round', 'Deploy a funding round') + .setAction(async (_, hre) => { + subtask.setHre(hre) + const network = hre.network.name + + const duration = subtask.getConfigField( + EContracts.FundingRound, + 'duration' + ) + + const clrfundContract = await subtask.getContract({ + name: EContracts.ClrFund, + }) + + const tx = await clrfundContract.deployNewRound(duration) + const receipt = await tx.wait() + if (receipt?.status !== 1) { + throw new Error('Failed to deploy funding round') + } + subtask.logTransaction(tx) + + const fundingRoundContractAddress = await clrfundContract.getCurrentRound() + + const fundingRoundContract = await subtask.getContract({ + name: EContracts.FundingRound, + address: fundingRoundContractAddress, + }) + + await storage.register({ + id: EContracts.FundingRound, + contract: fundingRoundContract, + args: [duration], + network, + tx, + }) + + await registerMaci(fundingRoundContract, network, tx) + await registerPoll(duration, fundingRoundContract, network, tx) + await registerTallyAndMessageProcessor(fundingRoundContract, network, tx) + }) diff --git a/contracts/tasks/subtasks/token/01-anyOldERC20Token.ts b/contracts/tasks/subtasks/token/01-anyOldERC20Token.ts new file mode 100644 index 000000000..d53c33073 --- /dev/null +++ b/contracts/tasks/subtasks/token/01-anyOldERC20Token.ts @@ -0,0 +1,55 @@ +/** + * Deploy an instance of the BrightID sponsor contract + * + */ +import { ContractStorage } from '../../helpers/ContractStorage' +import { Subtask } from '../../helpers/Subtask' +import { ISubtaskParams } from '../../helpers/types' +import { EContracts } from '../../../utils/types' +import { isAddress } from 'ethers' + +const subtask = Subtask.getInstance() +const storage = ContractStorage.getInstance() + +/** + * Deploy step registration and task itself + */ +subtask + .addTask('token:deploy-erc20-token', 'Deploy an ERC20 contract') + .setAction(async ({ incremental }: ISubtaskParams, hre) => { + subtask.setHre(hre) + const deployer = await subtask.getDeployer() + + const token = subtask.getConfigField(EContracts.ClrFund, 'token') + if (isAddress(token)) { + // using an existing token, no need to deploy + return + } + + const initialSupply = subtask.getConfigField( + EContracts.AnyOldERC20Token, + 'initialSupply' + ) + + const anyOldERC20TokenContractAddress = storage.getAddress( + EContracts.AnyOldERC20Token, + hre.network.name + ) + + if (incremental && anyOldERC20TokenContractAddress) { + return + } + + const args = [initialSupply] + const anyOldERC20TokenContract = await subtask.deployContract( + EContracts.AnyOldERC20Token, + { signer: deployer, args } + ) + + await storage.register({ + id: EContracts.AnyOldERC20Token, + contract: anyOldERC20TokenContract, + args, + network: hre.network.name, + }) + }) diff --git a/contracts/tasks/subtasks/token/02-setToken.ts b/contracts/tasks/subtasks/token/02-setToken.ts new file mode 100644 index 000000000..a728aed7b --- /dev/null +++ b/contracts/tasks/subtasks/token/02-setToken.ts @@ -0,0 +1,52 @@ +/** + * Deploy an instance of the BrightID sponsor contract + * + */ +import { ContractStorage } from '../../helpers/ContractStorage' +import { Subtask } from '../../helpers/Subtask' +import { ISubtaskParams } from '../../helpers/types' +import { EContracts } from '../../../utils/types' +import { ClrFund } from '../../../typechain-types' +import { getAddress } from 'ethers' + +const subtask = Subtask.getInstance() +const storage = ContractStorage.getInstance() + +/** + * Register task and step + */ +subtask + .addTask('token:set-token', 'Set token in the ClrFund contract') + .setAction(async ({ incremental }: ISubtaskParams, hre) => { + subtask.setHre(hre) + const network = hre.network.name + + let tokenAddress = subtask.getConfigField( + EContracts.ClrFund, + 'token' + ) + if (!tokenAddress) { + tokenAddress = storage.mustGetAddress( + EContracts.AnyOldERC20Token, + network + ) + } + + const clrfundContract = await subtask.getContract({ + name: EContracts.ClrFund, + }) + + if (incremental) { + const currentTokenAddress = await clrfundContract.nativeToken() + if (getAddress(currentTokenAddress) === getAddress(tokenAddress)) { + // already set to this token + return + } + } + + const tx = await clrfundContract.setToken(tokenAddress) + const receipt = await tx.wait() + if (receipt?.status !== 1) { + throw new Error('Failed to set token') + } + }) diff --git a/contracts/tasks/subtasks/user/01-simpleUserRegistry.ts b/contracts/tasks/subtasks/user/01-simpleUserRegistry.ts new file mode 100644 index 000000000..10ed2a9a3 --- /dev/null +++ b/contracts/tasks/subtasks/user/01-simpleUserRegistry.ts @@ -0,0 +1,52 @@ +/** + * Deploy a simple user registry + */ + +import { ContractStorage } from '../../helpers/ContractStorage' +import { Subtask } from '../../helpers/Subtask' +import { ISubtaskParams } from '../../helpers/types' +import { EContracts } from '../../../utils/types' + +const subtask = Subtask.getInstance() +const storage = ContractStorage.getInstance() + +/** + * Deploy step registration and task itself + */ +subtask + .addTask('user:deploy-simple-user-registry', 'Deploy a simple user registry') + .setAction(async ({ incremental }: ISubtaskParams, hre) => { + subtask.setHre(hre) + const deployer = await subtask.getDeployer() + + const userRegistryName = subtask.getConfigField( + EContracts.ClrFund, + 'userRegistry' + ) + + if (userRegistryName !== EContracts.SimpleUserRegistry) { + return + } + + const simpleUserRegistryContractAddress = storage.getAddress( + EContracts.SimpleUserRegistry, + hre.network.name + ) + + if (incremental && simpleUserRegistryContractAddress) { + return + } + + const args = [] + const simpleUserRegistryContract = await subtask.deployContract( + EContracts.SimpleUserRegistry, + { signer: deployer, args } + ) + + await storage.register({ + id: EContracts.SimpleUserRegistry, + contract: simpleUserRegistryContract, + args, + network: hre.network.name, + }) + }) diff --git a/contracts/tasks/subtasks/user/02-semaphoreUserRegistry.ts b/contracts/tasks/subtasks/user/02-semaphoreUserRegistry.ts new file mode 100644 index 000000000..5d15f3566 --- /dev/null +++ b/contracts/tasks/subtasks/user/02-semaphoreUserRegistry.ts @@ -0,0 +1,55 @@ +/** + * Deploy a semaphore user registry + */ + +import { ContractStorage } from '../../helpers/ContractStorage' +import { Subtask } from '../../helpers/Subtask' +import { ISubtaskParams } from '../../helpers/types' +import { EContracts } from '../../../utils/types' + +const subtask = Subtask.getInstance() +const storage = ContractStorage.getInstance() + +/** + * Deploy step registration and task itself + */ +subtask + .addTask( + 'user:deploy-semaphore-user-registry', + 'Deploy a semaphore user registry' + ) + .setAction(async ({ incremental }: ISubtaskParams, hre) => { + subtask.setHre(hre) + const deployer = await subtask.getDeployer() + + const userRegistryName = subtask.getConfigField( + EContracts.ClrFund, + 'userRegistry' + ) + + if (userRegistryName !== EContracts.SemaphoreUserRegistry) { + return + } + + const semaphoreUserRegistryContractAddress = storage.getAddress( + EContracts.SemaphoreUserRegistry, + hre.network.name + ) + + if (incremental && semaphoreUserRegistryContractAddress) { + return + } + + const args = [] + const semaphoreUserRegistryContract = await subtask.deployContract( + EContracts.SemaphoreUserRegistry, + { signer: deployer, args } + ) + + await storage.register({ + id: EContracts.SemaphoreUserRegistry, + contract: semaphoreUserRegistryContract, + args, + network: hre.network.name, + }) + }) diff --git a/contracts/tasks/subtasks/user/03-brightidSponsor.ts b/contracts/tasks/subtasks/user/03-brightidSponsor.ts new file mode 100644 index 000000000..34a0264d4 --- /dev/null +++ b/contracts/tasks/subtasks/user/03-brightidSponsor.ts @@ -0,0 +1,51 @@ +/** + * Deploy an instance of the BrightID sponsor contract + * + */ +import { ContractStorage } from '../../helpers/ContractStorage' +import { Subtask } from '../../helpers/Subtask' +import { EContracts } from '../../../utils/types' + +const subtask = Subtask.getInstance() +const storage = ContractStorage.getInstance() + +/** + * Deploy step registration and task itself + */ +subtask + .addTask('user:deploy-brightid-sponsor', 'Deploy BrightID sponsor contract') + .setAction(async (_, hre) => { + subtask.setHre(hre) + const deployer = await subtask.getDeployer() + + let brightidSponsorContractAddress: string | undefined = + subtask.getConfigField(EContracts.BrightIdUserRegistry, 'sponsor') + + if (brightidSponsorContractAddress) { + console.log( + `Skip BrightIdSponsor deployment, use ${brightidSponsorContractAddress}` + ) + return + } + + brightidSponsorContractAddress = storage.getAddress( + EContracts.BrightIdSponsor, + hre.network.name + ) + + if (brightidSponsorContractAddress) { + return + } + + const BrightIdSponsorContract = await subtask.deployContract( + EContracts.BrightIdSponsor, + { signer: deployer } + ) + + await storage.register({ + id: EContracts.BrightIdSponsor, + contract: BrightIdSponsorContract, + args: [], + network: hre.network.name, + }) + }) diff --git a/contracts/tasks/subtasks/user/04-brightidUserRegistry.ts b/contracts/tasks/subtasks/user/04-brightidUserRegistry.ts new file mode 100644 index 000000000..f5bb0608e --- /dev/null +++ b/contracts/tasks/subtasks/user/04-brightidUserRegistry.ts @@ -0,0 +1,73 @@ +/** + * Deploy a new recipient registry + */ + +import { ContractStorage } from '../../helpers/ContractStorage' +import { Subtask } from '../../helpers/Subtask' +import { ISubtaskParams } from '../../helpers/types' +import { EContracts } from '../../../utils/types' + +const subtask = Subtask.getInstance() +const storage = ContractStorage.getInstance() + +/** + * Deploy step registration and task itself + */ +subtask + .addTask( + 'user:deploy-brightid-user-registry', + 'Deploy a brightid user registry' + ) + .setAction(async ({ incremental }: ISubtaskParams, hre) => { + subtask.setHre(hre) + const deployer = await subtask.getDeployer() + const network = hre.network.name + + const userRegistryName = subtask.getConfigField( + EContracts.ClrFund, + 'userRegistry' + ) + + if (userRegistryName !== EContracts.BrightIdUserRegistry) { + return + } + + const brightidUserRegistryContractAddress = storage.getAddress( + EContracts.SimpleRecipientRegistry, + network + ) + + if (incremental && brightidUserRegistryContractAddress) { + return + } + + const context = subtask.getConfigField( + EContracts.BrightIdUserRegistry, + 'context' + ) + const verifier = subtask.getConfigField( + EContracts.BrightIdUserRegistry, + 'verifier' + ) + + let sponsor = subtask.getConfigField( + EContracts.BrightIdUserRegistry, + 'sponsor' + ) + if (!sponsor) { + sponsor = storage.mustGetAddress(EContracts.BrightIdSponsor, network) + } + + const args = [context, verifier, sponsor] + const brightidUserRegistryContract = await subtask.deployContract( + EContracts.BrightIdUserRegistry, + { signer: deployer, args } + ) + + await storage.register({ + id: EContracts.BrightIdUserRegistry, + contract: brightidUserRegistryContract, + args, + network, + }) + }) diff --git a/contracts/tasks/subtasks/user/99-setUserRegistry.ts b/contracts/tasks/subtasks/user/99-setUserRegistry.ts new file mode 100644 index 000000000..d2e56d0fb --- /dev/null +++ b/contracts/tasks/subtasks/user/99-setUserRegistry.ts @@ -0,0 +1,63 @@ +/** + * Deploy a new recipient registry + * + */ +import { ContractStorage } from '../../helpers/ContractStorage' +import { Subtask } from '../../helpers/Subtask' +import { ISubtaskParams } from '../../helpers/types' +import { EContracts } from '../../../utils/types' +import { ClrFund } from '../../../typechain-types' +import { getAddress } from 'ethers' + +const subtask = Subtask.getInstance() +const storage = ContractStorage.getInstance() + +/** + * Register task and step + */ +subtask + .addTask( + 'user:set-user-registry', + 'Set user registry in the ClrFund contract' + ) + .setAction(async ({ incremental }: ISubtaskParams, hre) => { + subtask.setHre(hre) + const network = hre.network.name + + const userRegistryName = subtask.getConfigField( + EContracts.ClrFund, + 'userRegistry' + ) + + const userRegistryContractAddress = storage.mustGetAddress( + userRegistryName as EContracts, + network + ) + + const clrfundContract = await subtask.getContract({ + name: EContracts.ClrFund, + }) + + if (incremental) { + const currentUserRegistryAddress = await clrfundContract.userRegistry() + + if ( + getAddress(currentUserRegistryAddress) === + getAddress(userRegistryContractAddress) + ) { + // already set + return + } + } + + const tx = await clrfundContract.setUserRegistry( + userRegistryContractAddress + ) + const receipt = await tx.wait() + + if (receipt?.status !== 1) { + throw new Error( + `Failed to se user registry ${userRegistryContractAddress}` + ) + } + }) diff --git a/contracts/tasks/testutils/addContributors.ts b/contracts/tasks/testutils/addContributors.ts deleted file mode 100644 index da574d5cc..000000000 --- a/contracts/tasks/testutils/addContributors.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Add contributors for testing purposes - * - * Sample usage: - * - * yarn hardhat test-add-contributors --network - * - */ -import { task } from 'hardhat/config' -import { EContracts } from '../../utils/types' - -task('test-add-contributors', 'Add test contributors') - .addParam('clrfund', 'The ClrFund contract address') - .setAction(async ({ clrfund }, { ethers }) => { - const [signer, , , , , , , , , , , , contributor1, contributor2] = - await ethers.getSigners() - console.log('Adding contributors by', signer.address) - - const clrfundContract = await ethers.getContractAt( - EContracts.ClrFund, - clrfund, - signer - ) - const userRegistryAddress = await clrfundContract.userRegistry() - console.log('User registry address', userRegistryAddress) - - const userRegistry = await ethers.getContractAt( - EContracts.SimpleUserRegistry, - userRegistryAddress, - signer - ) - const users = [contributor1, contributor2] - let addUserTx - for (const account of users) { - addUserTx = await userRegistry.addUser(account.address) - addUserTx.wait() - } - - console.log(`Added ${users.length} contributors`) - }) diff --git a/contracts/tasks/testutils/contribute.ts b/contracts/tasks/testutils/contribute.ts deleted file mode 100644 index 4b56df813..000000000 --- a/contracts/tasks/testutils/contribute.ts +++ /dev/null @@ -1,88 +0,0 @@ -/** - * Contribute to a funding round. This script is mainly used by e2e testing - * All the input used by the script comes from the state.json file - * - * Sample usage: - * yarn hardhat test-contribute --state-file --network - */ - -import { Keypair } from '@clrfund/common' - -import { JSONFile } from '../../utils/JSONFile' -import { UNIT } from '../../utils/constants' -import { getEventArg } from '../../utils/contracts' -import { isPathExist } from '../../utils/misc' -import type { FundingRound, ERC20 } from '../../typechain-types' -import { task } from 'hardhat/config' -import { EContracts } from '../../utils/types' - -task('test-contribute', 'Contribute to a funding round') - .addParam('stateFile', 'The file to store the state information') - .setAction(async ({ stateFile }, { ethers }) => { - const [, , , , , , , , , , , , contributor1, contributor2] = - await ethers.getSigners() - - if (!isPathExist(stateFile)) { - throw new Error(`File ${stateFile} not found`) - } - - const state = JSONFile.read(stateFile) - const fundingRound = await ethers.getContractAt( - EContracts.FundingRound, - state.fundingRound - ) - const tokenAddress = await fundingRound.nativeToken() - const token = await ethers.getContractAt( - EContracts.AnyOldERC20Token, - tokenAddress - ) - const maciAddress = await fundingRound.maci() - const maci = await ethers.getContractAt(EContracts.MACI, maciAddress) - - const contributionAmount = (UNIT * BigInt(16)) / BigInt(10) - - state.contributors = {} - for (const contributor of [contributor1, contributor2]) { - const contributorAddress = await contributor.getAddress() - - // transfer token to contributor first - await token.transfer(contributorAddress, contributionAmount) - - const contributorKeypair = new Keypair() - const tokenAsContributor = token.connect(contributor) as ERC20 - await tokenAsContributor.approve(fundingRound.target, contributionAmount) - - const fundingRoundAsContributor = fundingRound.connect( - contributor - ) as FundingRound - const contributionTx = await fundingRoundAsContributor.contribute( - contributorKeypair.pubKey.asContractParam(), - contributionAmount - ) - const stateIndex = await getEventArg( - contributionTx, - maci, - 'SignUp', - '_stateIndex' - ) - const voiceCredits = await getEventArg( - contributionTx, - maci, - 'SignUp', - '_voiceCreditBalance' - ) - - state.contributors[contributorAddress] = { - privKey: contributorKeypair.privKey.serialize(), - pubKey: contributorKeypair.pubKey.serialize(), - stateIndex: parseInt(stateIndex), - voiceCredits: voiceCredits.toString(), - } - console.log( - `Contributor ${contributorAddress} registered. State index: ${stateIndex}. Voice credits: ${voiceCredits.toString()}.` - ) - } - - // Update state file - JSONFile.update(stateFile, state) - }) diff --git a/contracts/tasks/testutils/vote.ts b/contracts/tasks/testutils/vote.ts deleted file mode 100644 index ade378cb8..000000000 --- a/contracts/tasks/testutils/vote.ts +++ /dev/null @@ -1,113 +0,0 @@ -/** - * Voting used in the e2e testing - * Most of the input used by the script comes from the state.json file - * - * Set the COORDINATOR_MACISK env. variable for the coordinator MACI secret key - * - * Sample usage: - * yarn hardhat test-vote --stateFile --network - */ -import { task } from 'hardhat/config' -import { HardhatEthersHelpers } from '@nomicfoundation/hardhat-ethers/types' -import { JSONFile } from '../../utils/JSONFile' -import { - PrivKey, - Keypair, - createMessage, - Message, - PubKey, -} from '@clrfund/common' -import dotenv from 'dotenv' -import { isPathExist } from '../../utils/misc' - -dotenv.config() - -type Args = { - stateFile: string - ethers: HardhatEthersHelpers -} - -async function main({ stateFile, ethers }: Args) { - if (!isPathExist(stateFile)) { - throw new Error(`File ${stateFile} not found`) - } - - const coordinatorMacisk = process.env.COORDINATOR_MACISK - if (!coordinatorMacisk) { - throw Error('Env. variable COORDINATOR_MACISK not set') - } - - const [, , , , , , , , , , , , contributor1, contributor2] = - await ethers.getSigners() - - const state = JSONFile.read(stateFile) - const coordinatorKeyPair = new Keypair(PrivKey.deserialize(coordinatorMacisk)) - - const pollId = state.pollId - for (const contributor of [contributor1, contributor2]) { - const contributorAddress = await contributor.getAddress() - const contributorData = state.contributors[contributorAddress] - const contributorKeyPair = new Keypair( - PrivKey.deserialize(contributorData.privKey) - ) - - const messages: Message[] = [] - const encPubKeys: PubKey[] = [] - let nonce = 1 - // Change key - const newContributorKeypair = new Keypair() - const [message, encPubKey] = createMessage( - contributorData.stateIndex, - contributorKeyPair, - newContributorKeypair, - coordinatorKeyPair.pubKey, - null, - null, - nonce, - pollId - ) - messages.push(message) - encPubKeys.push(encPubKey) - nonce += 1 - // Vote - for (const recipientIndex of [1, 2]) { - const votes = BigInt(contributorData.voiceCredits) / BigInt(4) - const [message, encPubKey] = createMessage( - contributorData.stateIndex, - newContributorKeypair, - null, - coordinatorKeyPair.pubKey, - recipientIndex, - votes, - nonce, - pollId - ) - messages.push(message) - encPubKeys.push(encPubKey) - nonce += 1 - } - - const fundingRound = await ethers.getContractAt( - 'FundingRound', - state.fundingRound, - contributor - ) - const pollAddress = await fundingRound.poll() - const pollContract = await ethers.getContractAt( - 'Poll', - pollAddress, - contributor - ) - await pollContract.publishMessageBatch( - messages.reverse().map((msg) => msg.asContractParam()), - encPubKeys.reverse().map((key) => key.asContractParam()) - ) - console.log(`Contributor ${contributorAddress} voted.`) - } -} - -task('test-vote', 'Cast votes for test users') - .addParam('stateFile', 'The file to store the state information') - .setAction(async ({ stateFile }, { ethers }) => { - await main({ stateFile, ethers }) - }) diff --git a/contracts/tasks/verify/verifyAll.ts b/contracts/tasks/verify/verifyAll.ts deleted file mode 100644 index 7ac0632de..000000000 --- a/contracts/tasks/verify/verifyAll.ts +++ /dev/null @@ -1,290 +0,0 @@ -/** - * Verifies all the ClrFund contracts - * - * Sample usage: - * yarn hardhat verify-all --clrfund --network - */ - -import { Contract } from 'ethers' -import { task } from 'hardhat/config' -import { ZERO_ADDRESS } from '../../utils/constants' -import { EContracts } from '../../utils/types' - -const SUCCESS = 'success' - -type Result = { - name: string - status: string -} - -async function verifyMaciFactory(clrfund: Contract, run: any): Promise { - try { - const address = await clrfund.maciFactory() - if (address !== ZERO_ADDRESS) { - await run('verify-maci-factory', { address }) - } - return SUCCESS - } catch (error) { - return (error as Error).message - } -} - -async function verifyClrFund(clrfund: Contract, run: any): Promise { - try { - await run('verify:verify', { address: clrfund.target }) - return SUCCESS - } catch (error) { - return (error as Error).message - } -} - -async function verifyFundingRoundFactory( - clrfund: Contract, - run: any -): Promise { - try { - const address = await clrfund.roundFactory() - if (address !== ZERO_ADDRESS) { - await run('verify:verify', { address }) - } - return SUCCESS - } catch (error) { - return (error as Error).message - } -} - -async function verifyRecipientRegistry( - clrfund: Contract, - run: any -): Promise { - try { - const address = await clrfund.recipientRegistry() - if (address !== ZERO_ADDRESS) { - await run('verify-recipient-registry', { address }) - } - return SUCCESS - } catch (error) { - return (error as Error).message - } -} - -async function verifyUserRegistry( - clrfund: Contract, - run: any -): Promise { - try { - const address = await clrfund.userRegistry() - if (address !== ZERO_ADDRESS) { - await run('verify-user-registry', { address }) - } - return SUCCESS - } catch (error) { - return (error as Error).message - } -} - -async function verifyRound(address: string, run: any): Promise { - try { - await run('verify-round', { address }) - return SUCCESS - } catch (error) { - return (error as Error).message - } -} - -async function verifyMaci(maciAddress: string, run: any): Promise { - try { - await run('verify-maci', { maciAddress }) - return SUCCESS - } catch (error) { - return (error as Error).message - } -} - -async function verifyTally(tally: Contract, run: any): Promise { - try { - const constructorArguments = await Promise.all([ - tally.verifier(), - tally.vkRegistry(), - tally.poll(), - tally.messageProcessor(), - ]) - await run('verify:verify', { address: tally.target, constructorArguments }) - return SUCCESS - } catch (error) { - return (error as Error).message - } -} - -async function verifyMessageProcessor( - messageProcesor: Contract, - run: any -): Promise { - try { - const constructorArguments = await Promise.all([ - messageProcesor.verifier(), - messageProcesor.vkRegistry(), - messageProcesor.poll(), - ]) - await run('verify:verify', { - address: messageProcesor.target, - constructorArguments, - }) - return SUCCESS - } catch (error) { - return (error as Error).message - } -} - -async function verifyPoll(address: string, run: any): Promise { - try { - await run('verify-poll', { address }) - return SUCCESS - } catch (error) { - return (error as Error).message - } -} - -async function verifyContract( - name: string, - address: string, - run: any, - results: Result[] -) { - let result = SUCCESS - try { - await run('verify:verify', { address }) - } catch (error) { - result = (error as Error).message - } - results.push({ name, status: result }) -} - -async function getBrightIdSponsor( - clrfund: Contract, - ethers: any -): Promise { - const userRegistryAddress = await clrfund.userRegistry() - const userRegistry = await ethers.getContractAt( - EContracts.BrightIdUserRegistry, - userRegistryAddress - ) - try { - const sponsor = await userRegistry.brightIdSponsor() - return sponsor - } catch { - return null - } -} - -/** - * Verifies all the contracts created for clrfund app - */ -task('verify-all', 'Verify all clrfund contracts') - .addParam('clrfund', 'ClrFund contract address') - .setAction(async ({ clrfund }, { run, ethers }) => { - const clrfundContract = await ethers.getContractAt( - EContracts.ClrFund, - clrfund - ) - const maciFactoryAddress = await clrfundContract.maciFactory() - const maciFactory = await ethers.getContractAt( - EContracts.MACIFactory, - maciFactoryAddress - ) - - const results: Result[] = [] - let status = await verifyMaciFactory(clrfundContract, run) - results.push({ name: EContracts.MACIFactory, status }) - - await verifyContract( - 'VkRegistry', - await maciFactory.vkRegistry(), - run, - results - ) - - const factories = await maciFactory.factories() - await verifyContract( - EContracts.PollFactory, - factories.pollFactory, - run, - results - ) - await verifyContract( - EContracts.TallyFactory, - factories.tallyFactory, - run, - results - ) - await verifyContract( - EContracts.MessageProcessorFactory, - factories.messageProcessorFactory, - run, - results - ) - - status = await verifyClrFund(clrfundContract, run) - results.push({ name: EContracts.ClrFund, status }) - status = await verifyRecipientRegistry(clrfundContract, run) - results.push({ name: 'RecipientRegistry', status }) - status = await verifyUserRegistry(clrfundContract, run) - results.push({ name: 'UserRegistry', status }) - const sponsor = await getBrightIdSponsor(clrfundContract, ethers) - if (sponsor) { - await verifyContract(EContracts.BrightIdSponsor, sponsor, run, results) - } - status = await verifyFundingRoundFactory(clrfundContract, run) - results.push({ name: EContracts.FundingRoundFactory, status }) - - const roundAddress = await clrfundContract.getCurrentRound() - if (roundAddress !== ZERO_ADDRESS) { - const round = await ethers.getContractAt( - EContracts.FundingRound, - roundAddress - ) - const maciAddress = await round.maci() - status = await verifyRound(roundAddress, run) - results.push({ name: EContracts.FundingRound, status }) - status = await verifyMaci(maciAddress, run) - results.push({ name: EContracts.MACI, status }) - - const poll = await round.poll() - if (poll !== ZERO_ADDRESS) { - status = await verifyPoll(poll, run) - results.push({ name: EContracts.Poll, status }) - } - - const tally = await round.tally() - if (tally !== ZERO_ADDRESS) { - const tallyContract = await ethers.getContractAt( - EContracts.Tally, - tally - ) - status = await verifyTally(tallyContract, run) - results.push({ name: EContracts.Tally, status }) - - const messageProcessorAddress = await tallyContract.messageProcessor() - if (messageProcessorAddress !== ZERO_ADDRESS) { - const mpContract = await ethers.getContractAt( - EContracts.MessageProcessor, - messageProcessorAddress - ) - status = await verifyMessageProcessor(mpContract, run) - results.push({ name: EContracts.MessageProcessor, status }) - } - } - - await verifyContract( - EContracts.TopupToken, - await round.topupToken(), - run, - results - ) - } - - results.forEach(({ name, status }, i) => { - const color = status === SUCCESS ? '32' : '31' - console.log(`${i} ${name}: \x1b[%sm%s\x1b[0m`, color, status) - }) - }) diff --git a/contracts/tasks/verify/verifyDeployer.ts b/contracts/tasks/verify/verifyDeployer.ts deleted file mode 100644 index 08c15fce3..000000000 --- a/contracts/tasks/verify/verifyDeployer.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { task } from 'hardhat/config' -import { Contract } from 'ethers' -import { EContracts } from '../../utils/types' - -async function getConstructorArguments(contract: Contract): Promise { - const constructorArguments = await Promise.all([ - contract.clrfundTemplate(), - contract.maciFactory(), - contract.roundFactory(), - ]) - - return constructorArguments -} - -/** - * Verifies the ClrFundDeployer contract - * - it constructs the constructor arguments by querying the ClrFundDeployer contract - * - it calls the etherscan hardhat plugin to verify the contract - */ -task('verify-deployer', 'Verify a ClrFundDeployer contract') - .addParam('address', 'Poll contract address') - .setAction(async ({ address }, { run, ethers }) => { - const contract = await ethers.getContractAt( - EContracts.ClrFundDeployer, - address - ) - - const constructorArguments = await getConstructorArguments(contract) - console.log('Constructor arguments', constructorArguments) - - await run('verify:verify', { - address, - constructorArguments, - }) - }) diff --git a/contracts/tasks/verify/verifyMaci.ts b/contracts/tasks/verify/verifyMaci.ts deleted file mode 100644 index 8ff25247c..000000000 --- a/contracts/tasks/verify/verifyMaci.ts +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Verifies the MACI contract - * - it constructs the constructor arguments by querying the MACI contract - * - it calls the etherscan hardhat plugin to verify the contract - * - * Sample usage: - * yarn hardhat verify-maci --network - */ - -import { task } from 'hardhat/config' -import { EContracts } from '../../utils/types' - -task('verify-maci', 'Verify a MACI contract') - .addPositionalParam('maciAddress', 'MACI contract address') - .setAction(async ({ maciAddress }, { run, ethers }) => { - const maci = await ethers.getContractAt(EContracts.MACI, maciAddress) - const constructorArguments = await Promise.all([ - maci.pollFactory(), - maci.messageProcessorFactory(), - maci.tallyFactory(), - maci.subsidyFactory(), - maci.signUpGatekeeper(), - maci.initialVoiceCreditProxy(), - maci.topupCredit(), - maci.stateTreeDepth(), - ]) - - console.log('Verifying the MACI contract', maciAddress) - console.log('Constructor arguments', constructorArguments) - - await run('verify:verify', { - address: maciAddress, - constructorArguments, - }) - }) diff --git a/contracts/tasks/verify/verifyMaciFactory.ts b/contracts/tasks/verify/verifyMaciFactory.ts deleted file mode 100644 index cdeeb3f52..000000000 --- a/contracts/tasks/verify/verifyMaciFactory.ts +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Verifies the MACI factory contract - * - * Sample usage: - * yarn hardhat verify-maci-factory --address --network - */ - -import { task } from 'hardhat/config' -import { Contract } from 'ethers' -import { EContracts } from '../../utils/types' - -async function getConstructorArguments(maciFactory: Contract): Promise { - const result = await Promise.all([ - maciFactory.vkRegistry(), - maciFactory.factories(), - maciFactory.verifier(), - ]) - return result -} - -task('verify-maci-factory', 'Verify a MACI factory contract') - .addParam('address', 'MACI factory contract address') - .setAction(async ({ address }, { run, ethers }) => { - const maciFactory = await ethers.getContractAt( - EContracts.MACIFactory, - address - ) - - const constructorArguments = await getConstructorArguments(maciFactory) - console.log('Constructor arguments', constructorArguments) - - await run('verify:verify', { - address, - constructorArguments, - }) - }) diff --git a/contracts/tasks/verify/verifyPoll.ts b/contracts/tasks/verify/verifyPoll.ts deleted file mode 100644 index 5c6e358d9..000000000 --- a/contracts/tasks/verify/verifyPoll.ts +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Verifies the Poll contract - * - * Sample usage: - * yarn hardhat verify-poll --address --network - */ - -import { task } from 'hardhat/config' -import { Contract } from 'ethers' -import { EContracts } from '../../utils/types' - -async function getConstructorArguments(pollContract: Contract): Promise { - const [, duration] = await pollContract.getDeployTimeAndDuration() - const [maxValues, treeDepths, coordinatorPubKey, extContracts] = - await Promise.all([ - pollContract.maxValues(), - pollContract.treeDepths(), - pollContract.coordinatorPubKey(), - pollContract.extContracts(), - ]) - - const result = [ - duration, - { - maxMessages: maxValues.maxMessages, - maxVoteOptions: maxValues.maxVoteOptions, - }, - { - intStateTreeDepth: treeDepths.intStateTreeDepth, - messageTreeSubDepth: treeDepths.messageTreeSubDepth, - messageTreeDepth: treeDepths.messageTreeDepth, - voteOptionTreeDepth: treeDepths.voteOptionTreeDepth, - }, - { - x: coordinatorPubKey.x, - y: coordinatorPubKey.y, - }, - { - maci: extContracts.maci, - messageAq: extContracts.messageAq, - topupCredit: extContracts.topupCredit, - }, - ] - return result -} - -task('verify-poll', 'Verify a Poll contract') - .addParam('address', 'Poll contract address') - .setAction(async ({ address }, { run, ethers }) => { - const poll = await ethers.getContractAt(EContracts.Poll, address) - - const constructorArguments = await getConstructorArguments(poll) - console.log('Constructor arguments', constructorArguments) - - await run('verify:verify', { - address, - constructorArguments, - }) - }) diff --git a/contracts/tasks/verify/verifyRecipientRegistry.ts b/contracts/tasks/verify/verifyRecipientRegistry.ts deleted file mode 100644 index 797c1b515..000000000 --- a/contracts/tasks/verify/verifyRecipientRegistry.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { task } from 'hardhat/config' -import { EContracts } from '../../utils/types' - -type ProvidedArgs = { - baseDeposit?: string - challengePeriodDuration?: string -} - -async function getConstructorArguments( - address: string, - ethers: any, - provided: ProvidedArgs = {} -): Promise { - const registry = await ethers.getContractAt( - EContracts.OptimisticRecipientRegistry, - address - ) - - const controller = await registry.controller() - try { - let baseDeposit = await registry.baseDeposit() - if (provided.baseDeposit) { - baseDeposit = provided.baseDeposit - } - - const challengePeriodDuration = provided.challengePeriodDuration - ? provided.challengePeriodDuration - : await registry.challengePeriodDuration() - - return [baseDeposit, challengePeriodDuration, controller] - } catch { - try { - const klerosRegistry = await ethers.getContractAt( - EContracts.KlerosGTCRAdapter, - address - ) - const tcr = await klerosRegistry.tcr() - return [tcr, controller] - } catch { - return [controller] - } - } -} - -task('verify-recipient-registry', 'Verify a recipient registry contract') - .addPositionalParam('address', 'Recipient registry contract address') - .setAction(async ({ address }, { run, ethers }) => { - const constructorArguments = await getConstructorArguments(address, ethers) - console.log('Constructor arguments', constructorArguments) - - await run('verify:verify', { - address, - constructorArguments, - }) - }) diff --git a/contracts/tasks/verify/verifyRound.ts b/contracts/tasks/verify/verifyRound.ts deleted file mode 100644 index a442a923f..000000000 --- a/contracts/tasks/verify/verifyRound.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { task } from 'hardhat/config' -import { EContracts } from '../../utils/types' - -async function getConstructorArguments( - address: string, - ethers: any -): Promise { - const round = await ethers.getContractAt(EContracts.FundingRound, address) - - const args = await Promise.all([ - round.nativeToken(), - round.userRegistry(), - round.recipientRegistry(), - round.coordinator(), - ]) - return args -} - -task('verify-round', 'Verify a funding round contract') - .addParam('address', 'Funding round contract address') - .setAction(async ({ address }, { run, ethers }) => { - const constructorArguments = await getConstructorArguments(address, ethers) - console.log('Constructor arguments', constructorArguments) - - await run('verify:verify', { - address, - constructorArguments, - }) - }) diff --git a/contracts/tasks/verify/verifyTallyFile.ts b/contracts/tasks/verify/verifyTallyFile.ts deleted file mode 100644 index 6e055a870..000000000 --- a/contracts/tasks/verify/verifyTallyFile.ts +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Verify the tally file - * - * Sample usage: - * yarn hardhat verify-tally-file \ - * --tally-file \ - * --tally-address \ - * --network - */ -import { task } from 'hardhat/config' -import { Signer } from 'ethers' -import { verify } from '../../utils/maci' -import { JSONFile } from '../../utils/JSONFile' - -type Args = { - tallyAddress: string - tallyFile: string - signer: Signer -} - -async function main({ tallyAddress, tallyFile, signer }: Args) { - const tallyData = JSONFile.read(tallyFile) - const pollId = tallyData.pollId - const maciAddress = tallyData.maci - - await verify({ - subsidyEnabled: false, - tallyFile, - pollId: BigInt(pollId), - maciAddress, - tallyAddress, - signer, - quiet: false, - }) -} - -task('verify-tally-file', 'Verify the tally file') - .addParam('tallyFile', 'The tally file') - .addParam('tallyAddress', 'The tally contract address') - .setAction(async ({ tallyFile, tallyAddress }, { ethers }) => { - const [signer] = await ethers.getSigners() - await main({ tallyFile, tallyAddress, signer }) - }) diff --git a/contracts/tasks/verify/verifyUserRegistry.ts b/contracts/tasks/verify/verifyUserRegistry.ts deleted file mode 100644 index 78737cecd..000000000 --- a/contracts/tasks/verify/verifyUserRegistry.ts +++ /dev/null @@ -1,66 +0,0 @@ -/** - * Verifies the user registry - * - * Sample usage: - * yarn hardhat verify-user-registry --address --network - */ -import { task } from 'hardhat/config' -import { EContracts } from '../../utils/types' - -type ProvidedArgs = { - context?: string - verifier?: string - sponsor?: string -} - -async function getConstructorArguments( - address: string, - ethers: any, - provided: ProvidedArgs = {} -): Promise { - const registry = await ethers.getContractAt( - EContracts.BrightIdUserRegistry, - address - ) - - try { - let verifier = await registry.verifier() - if (provided.verifier) { - verifier = provided.verifier - } - - const sponsor = provided.sponsor - ? provided.sponsor - : await registry.brightIdSponsor() - - const context = provided.context - ? provided.context - : await registry.context() - - return [context, verifier, sponsor] - } catch { - // simple user registry - return [] - } -} - -task('verify-user-registry', 'Verify a user registry contract') - .addParam('address', 'User registry contract address') - .addOptionalParam('sponsor', 'BrightId sponsor contract address') - .addOptionalParam('verifier', 'BrightId verifier address') - .addOptionalParam('context', 'BrightId context') - .setAction( - async ({ address, sponsor, verifier, context }, { run, ethers }) => { - const constructorArguments = await getConstructorArguments( - address, - ethers, - { sponsor, verifier, context } - ) - console.log('Constructor arguments', constructorArguments) - - await run('verify:verify', { - address, - constructorArguments, - }) - } - ) diff --git a/contracts/utils/maci.ts b/contracts/utils/maci.ts index 77ec70d63..6065f383d 100644 --- a/contracts/utils/maci.ts +++ b/contracts/utils/maci.ts @@ -325,11 +325,6 @@ export async function mergeMaciSubtrees({ export function newMaciPrivateKey(): string { const keypair = new Keypair() const secretKey = keypair.privKey.serialize() - const publicKey = keypair.pubKey.serialize() - - console.log(`SecretKey: ${secretKey}`) - console.log(`PublicKey: ${publicKey}`) - return secretKey } diff --git a/contracts/utils/types.ts b/contracts/utils/types.ts index 574ac06b8..dee39b710 100644 --- a/contracts/utils/types.ts +++ b/contracts/utils/types.ts @@ -8,6 +8,7 @@ export enum EContracts { MACIFactory = 'MACIFactory', MACI = 'MACI', Verifier = 'Verifier', + VkRegistry = 'VkRegistry', TopupCredit = 'TopupCredit', PollFactory = 'PollFactory', Poll = 'Poll', @@ -32,12 +33,6 @@ export enum EContracts { TopupToken = 'TopupToken', } -export enum recipientRegistryType { - simple = 0, - kleros = 1, - optimistic = 2, -} - export interface Project { id: string requester?: string diff --git a/docs/deployment.md b/docs/deployment.md index ae9e6a18d..c29be3d51 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -13,16 +13,6 @@ Check the MACI doc, https://maci.pse.dev/docs/installation#install-rapidsnark-if sudo apt-get install libgmp-dev nlohmann-json3-dev nasm g++ ``` -### Download MACI circuit files - -The following script will download the files in the params folder under the current folder where the script is run - -``` -monorepo/.github/scripts/download-6-9-2-3.sh -``` - -Make a note of this `params` folder as you'll need it to run the tally script. - ## Setup BrightID If using BrightID as the user registry type: @@ -45,27 +35,42 @@ Once the app is registered, you will get an appId which will be set to `BRIGHTID Goto the `contracts` folder. -### Deploy the BrightID sponsor contract (if using BrightID) - -1. Deploy the BrightID sponsor contract +### Generate the coordinator MACI private key ``` -HARDHAT_NETWORK={network} yarn ts-node cli/deploySponsor.ts +yarn hardhat new-maci-key ``` -2. Verify the contract by running `yarn hardhat --network {network} verify {contract address}` -3. Set `BRIGHTID_SPONSOR` to the contract address in the next step - -### Edit the `/contracts/.env` file +### Edit the `contracts/.env` file E.g. ``` JSONRPC_HTTP_URL=https://NETWORK.alchemyapi.io/v2/ADD_API_KEY WALLET_PRIVATE_KEY= +COORDINATOR_MACISK=The coordinator's private key from previous step which starts with `macisk.` +# API keys for verifying contracts, update hardhat.config for additional keys if needed ARBISCAN_API_KEY= ``` +### Download MACI circuit files + +The following script will download the files in the params folder under the current folder where the script is run + +``` +monorepo/.github/scripts/download-6-9-2-3.sh +``` + + +### Edit the `contracts/deploy-config.json` file + +``` +cp deploy-config-example.json deploy-config.json +``` + +Update the `VkRegistry.paramsDirectory` with the circuit parameter folder. If you ran the `monorepo/.github/scripts/download-6-9-2-3.sh` in the `contracts` folder, it should be `./params`. + + ### Run the deploy script Use the `-h` switch to print the command line help menu for all the scripts in the `cli` folder. For hardhat help, use `yarn hardhat help`. @@ -73,30 +78,18 @@ Use the `-h` switch to print the command line help menu for all the scripts in t 1. Deploy an instance of ClrFund ``` -yarn hardhat new-clrfund \ - --directory \ - --token \ - --coordinator \ - --user-registry-type \ - --recipient-registry-type \ - --network +yarn hardhat new-clrfund --network ``` 2. deploy new funding round ``` -yarn hardhat new-round \ - --clrfund \ - --duration \ - --network +yarn hardhat new-round --network ``` -3. Make sure to save in a safe place the serializedCoordinatorPrivKey, you are going to need it for tallying the votes in future steps. - - -4. To load a list of users into the simple user registry, +3. To load a list of users into the simple user registry, ``` -yarn hardhat clr-load-simple-users --file-path addresses.txt --user-registry
--network +yarn hardhat load-simple-users --file-path addresses.txt --user-registry
--network ``` @@ -106,22 +99,23 @@ If using a snapshot user registry, run the `set-storage-root` task to set the st yarn hardhat --network {network} set-storage-root --registry 0x7113b39Eb26A6F0a4a5872E7F6b865c57EDB53E0 --slot 2 --token 0x65bc8dd04808d99cf8aa6749f128d55c2051edde --block 34677758 --network arbitrum-goerli ``` -Note: to get the storage slot '--slot' value, run the `clr-find-storage-slot` task. +Note: to get the storage slot '--slot' value, run the `find-storage-slot` task. 5. If using a merkle user registry, run the `load-merkle-users` task to set the merkle root for all the authorized users ``` # for example: -yarn hardhat clr-load-merkle-users --address-file ./addresses.txt --user-registry 0x9E1c12Af45504e66D16D592cAF3B877ddc6fF643 --network arbitrum-goerli +yarn hardhat load-merkle-users --address-file ./addresses.txt --user-registry 0x9E1c12Af45504e66D16D592cAF3B877ddc6fF643 --network arbitrum-goerli ``` Note: Make sure to upload generated merkle tree file to IPFS. 8. Verify all deployed contracts: +Make sure the `deployed-contracts.json` file is present as it stores contract constructor arguments used by the verify-all script. ``` -yarn hardhat verify-all --clrfund {clrfund-address} --network {network} +yarn hardhat verify-all --network {network} ``` ### Deploy the subgraph diff --git a/docs/tally-verify.md b/docs/tally-verify.md index d2083a8f8..cbf8761c1 100644 --- a/docs/tally-verify.md +++ b/docs/tally-verify.md @@ -23,10 +23,15 @@ WALLET_PRIVATE_KEY Decrypt messages and tally the votes: ``` -HARDHAT_NETWORK= yarn ts-node cli/tally.ts --clrfund --start-block +yarn hardhat tally --rapidsnark ${RAPID_SNARK} --output-dir ${OUTPUT_DIR} --network ``` -If there's error and the tally task was stopped prematurely, it can be resumed by passing 2 additional parameters, '--maci-logs' and/or '--maci-state-file', if the files were generated. +If there's error and the tally task was stopped prematurely, it can be resumed by passing 2 additional parameters, '--tally-file' and/or '--maci-state-file', if the files were generated. + +``` +# for rerun +yarn hardhat tally --maci-state-file --tally-file --network +``` Result will be saved to `tally.json` file, which must then be published via IPFS. @@ -53,7 +58,7 @@ WALLET_PRIVATE_KEY= Once you have the `tally.json` from the tally script, run: ``` -yarn hardhat clr-finalize --clrfund --tally-file --network +yarn hardhat finalize --tally-file --network ``` # How to verify the tally results @@ -63,7 +68,7 @@ Anyone can verify the tally results in the tally.json. From the clrfund contracts folder, run the following command to verify the result: ``` -yarn hardhat verify-tally-file --tally-file --tally-address --network +yarn hardhat verify-tally-file --tally-file --network ``` # How to enable the leaderboard view @@ -76,7 +81,7 @@ After finalizing the round, enable the leaderboard view in the vue-app by export ```sh cd contracts -yarn hardhat clr-export-round --output-dir ../vue-app/src/rounds --network --round-address --operator --start-block --ipfs +yarn hardhat export-round --output-dir ../vue-app/src/rounds --network --round-address --operator --start-block --ipfs ``` 3) Build and deploy the app @@ -90,3 +95,5 @@ Also, lack of memory can also cause `core dump`, try to work around it by settin ``` export NODE_OPTIONS=--max-old-space-size=4096 ``` + +The following tally script error, `Error at message index 0 - failed decryption due to either wrong encryption public key or corrupted ciphertext`, can be ignored. \ No newline at end of file From 535efe29319841433dc6bb706b7015be06f51cb2 Mon Sep 17 00:00:00 2001 From: yuetloo Date: Mon, 11 Mar 2024 13:23:54 -0400 Subject: [PATCH 02/17] update circuit params directory --- .github/workflows/test-e2e.yml | 5 +++-- .github/workflows/test-scripts.yml | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-e2e.yml index 7454c72d9..c7c8babbc 100644 --- a/.github/workflows/test-e2e.yml +++ b/.github/workflows/test-e2e.yml @@ -44,7 +44,8 @@ jobs: path: monorepo - name: Download micro zk circuit params run: | - $GITHUB_WORKSPACE/monorepo/.github/scripts/${ZKEYS_DOWNLOAD_SCRIPT} + cd monorepo/contracts + ../.github/scripts/${ZKEYS_DOWNLOAD_SCRIPT} - name: Build Clrfund run: | # use https to avoid error: unable to connect to github.com @@ -57,7 +58,7 @@ jobs: yarn start:node & - name: Run tests run: | - export CIRCUIT_DIRECTORY="$GITHUB_WORKSPACE/params" + export CIRCUIT_DIRECTORY="$GITHUB_WORKSPACE/monorepo/contracts/params" export RAPID_SNARK="$GITHUB_WORKSPACE/rapidsnark/package/bin/prover" cd monorepo yarn test:e2e diff --git a/.github/workflows/test-scripts.yml b/.github/workflows/test-scripts.yml index baa562568..5ebe7d346 100644 --- a/.github/workflows/test-scripts.yml +++ b/.github/workflows/test-scripts.yml @@ -44,7 +44,8 @@ jobs: path: monorepo - name: Download circuit params run: | - $GITHUB_WORKSPACE/monorepo/.github/scripts/${ZKEYS_DOWNLOAD_SCRIPT} + cd monorepo/contracts + ../.github/scripts/${ZKEYS_DOWNLOAD_SCRIPT} - name: Build CLR run: | cd monorepo From a1486c2ff13058dcba29a77723972f9faaa63a71 Mon Sep 17 00:00:00 2001 From: yuetloo Date: Mon, 11 Mar 2024 17:20:51 -0400 Subject: [PATCH 03/17] skip the test for fixing later as infura has dropped support for arbitrum-goerli --- contracts/tests/userRegistrySnapshot.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/tests/userRegistrySnapshot.ts b/contracts/tests/userRegistrySnapshot.ts index 2426cd048..624947cc3 100644 --- a/contracts/tests/userRegistrySnapshot.ts +++ b/contracts/tests/userRegistrySnapshot.ts @@ -77,7 +77,7 @@ async function addUser( return userRegistry.addUser(userAccount, proofRlpBytes) } -describe('SnapshotUserRegistry', function () { +describe.skip('SnapshotUserRegistry', function () { let userRegistry: Contract let block: Block From 6057fe9b3c32012e5882f8b4ab9777fbf6142676 Mon Sep 17 00:00:00 2001 From: yuetloo Date: Tue, 12 Mar 2024 13:10:48 -0400 Subject: [PATCH 04/17] replace arbitrum-goerli with optimism-sepolia and remove ERC721 test case as token does not exist on optimism-sepolia --- contracts/tests/userRegistrySnapshot.ts | 31 ++++++++----------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/contracts/tests/userRegistrySnapshot.ts b/contracts/tests/userRegistrySnapshot.ts index 624947cc3..3a52f0a26 100644 --- a/contracts/tests/userRegistrySnapshot.ts +++ b/contracts/tests/userRegistrySnapshot.ts @@ -3,7 +3,7 @@ import { expect } from 'chai' import { Contract, ContractTransaction, - InfuraProvider, + JsonRpcProvider, ZeroAddress, ZeroHash, } from 'ethers' @@ -15,37 +15,25 @@ import { rlpEncodeProof, } from '@clrfund/common' -// Accounts from arbitrum-goerli to call eth_getProof as hardhat network +// use optimism-sepolia as hardhat network // does not support eth_getProof -const provider = new InfuraProvider('arbitrum-goerli') +const provider = new JsonRpcProvider('https://sepolia.optimism.io') const tokens = [ { address: '0x65bc8dd04808d99cf8aa6749f128d55c2051edde', - type: 'ERC20', // get proof with this block number - snapshotBlock: 35904051, - // storage slot for balances in the token (0x65bc8dd04808d99cf8aa6749f128d55c2051edde) on arbitrum goerli + snapshotBlock: 8388839, + // storage slot for balances in the token (0x65bc8dd04808d99cf8aa6749f128d55c2051edde) storageSlot: 2, + type: 'ERC20', holders: { - currentAndSnapshotHolder: '0x0B0Fe9D858F7e3751A3dcC7ffd0B9236be5E4bf5', - snapshotHolder: '0xBa8aC318F2dd829AF3D0D93882b4F1a9F3307bFD', + currentAndSnapshotHolder: '0xb5BdAa442BB34F27e793861C456CD5bDC527Ac8C', + snapshotHolder: '0xf8C6704E6b595B4B72085fACdf83b7FaDF767Ae3', currentHolderOnly: '0x5Fd5b076F6Ba8E8195cffb38A028afe5694b3d27', zeroBalance: '0xfb96F12fDD64D674631DB7B40bC35cFE74E98aF7', }, }, - { - address: '0x7E8206276F8FE511bfa44c9135B222DEe75e58f4', - type: 'ERC721', - snapshotBlock: 35904824, - storageSlot: 3, - holders: { - currentAndSnapshotHolder: '0x980825655805509f47EbDE41515966aeD5Df883D', - snapshotHolder: '0x326850D078c34cBF6996756523b00f0f1731dF12', - currentHolderOnly: '0x8D4EFdF0891DC38AC3DA2C3C5E683C982D3F7426', - zeroBalance: '0x99c68BFfF94d70f736491E9824caeDd19307167B', - }, - }, ] /** @@ -77,7 +65,7 @@ async function addUser( return userRegistry.addUser(userAccount, proofRlpBytes) } -describe.skip('SnapshotUserRegistry', function () { +describe('SnapshotUserRegistry', function () { let userRegistry: Contract let block: Block @@ -151,6 +139,7 @@ describe.skip('SnapshotUserRegistry', function () { provider ) const accountProofRlpBytes = rlpEncodeProof(proof.accountProof) + const tx = await userRegistry.setStorageRoot( token.address, block.hash, From e125a51a0428a69cc4e12fb4953adfac478a85f3 Mon Sep 17 00:00:00 2001 From: yuetloo Date: Wed, 13 Mar 2024 03:04:15 -0400 Subject: [PATCH 05/17] refactor and bug fixes --- contracts/e2e/index.ts | 54 +- contracts/tasks/helpers/ContractStorage.ts | 2 +- contracts/tasks/helpers/ContractVerifier.ts | 4 +- contracts/tasks/helpers/Subtask.ts | 52 +- contracts/tasks/runners/cancel.ts | 6 +- .../tasks/runners/setRecipientRegistry.ts | 2 +- contracts/tasks/runners/setUserRegistry.ts | 2 +- contracts/tasks/runners/verifyAll.ts | 12 +- .../tasks/subtasks/clrfund/01-poseidon.ts | 107 ++-- .../tasks/subtasks/clrfund/02-vkRegistry.ts | 2 +- .../subtasks/coordinator/01-coordinator.ts | 10 +- .../subtasks/deployer/01-clrfundDeployer.ts | 7 +- .../02-optimisticRecipientRegistry.ts | 2 +- .../recipient/99-setRecipientRegistry.ts | 3 + .../round/01-deploy-brightid-user-registry.ts | 4 +- .../tasks/subtasks/round/02-deploy-round.ts | 79 +-- .../tasks/subtasks/user/03-brightidSponsor.ts | 6 +- .../subtasks/user/04-brightidUserRegistry.ts | 20 +- .../tasks/subtasks/user/99-setUserRegistry.ts | 2 + contracts/tests/deployer.ts | 114 ++-- contracts/tests/maciFactory.ts | 14 +- contracts/tests/recipientRegistry.ts | 32 +- contracts/utils/contracts.ts | 75 ++- contracts/utils/deployment.ts | 505 ------------------ contracts/utils/maciParameters.ts | 8 +- contracts/utils/testutils.ts | 163 +++++- contracts/utils/types.ts | 2 + 27 files changed, 522 insertions(+), 767 deletions(-) delete mode 100644 contracts/utils/deployment.ts diff --git a/contracts/e2e/index.ts b/contracts/e2e/index.ts index 9a9d881fb..5a22205dd 100644 --- a/contracts/e2e/index.ts +++ b/contracts/e2e/index.ts @@ -17,11 +17,7 @@ import { DEFAULT_SR_QUEUE_OPS, } from '../utils/constants' import { getEventArg } from '../utils/contracts' -import { - deployContract, - deployPoseidonLibraries, - deployMaciFactory, -} from '../utils/deployment' +import { deployPoseidonLibraries, deployMaciFactory } from '../utils/testutils' import { getIpfsHash } from '../utils/ipfs' import { bnSqrt, @@ -39,6 +35,7 @@ import { existsSync, mkdirSync } from 'fs' import path from 'path' import { FundingRound } from '../typechain-types' import { JSONFile } from '../utils/JSONFile' +import { EContracts } from '../utils/types' type VoteData = { recipientIndex: number; voiceCredits: bigint } type ClaimData = { [index: number]: bigint } @@ -142,7 +139,6 @@ describe('End-to-end Tests', function () { params = await MaciParameters.fromConfig(circuit, circuitDirectory) poseidonLibraries = await deployPoseidonLibraries({ ethers, - artifactsPath: config.paths.artifacts, signer: deployer, }) }) @@ -168,17 +164,14 @@ describe('End-to-end Tests', function () { maciParameters: params, }) - clrfund = await deployContract({ - name: 'ClrFund', - signer: deployer, - ethers, - }) + clrfund = await ethers.deployContract(EContracts.ClrFund, deployer) + await clrfund.waitForDeployment() - const roundFactory = await deployContract({ - name: 'FundingRoundFactory', - signer: deployer, - ethers, - }) + const roundFactory = await ethers.deployContract( + EContracts.FundingRoundFactory, + deployer + ) + await roundFactory.waitForDeployment() const initClrfundTx = await clrfund.init( maciFactory.target, @@ -188,17 +181,23 @@ describe('End-to-end Tests', function () { const transferTx = await maciFactory.transferOwnership(clrfund.target) await transferTx.wait() - userRegistry = await ethers.deployContract('SimpleUserRegistry', deployer) - await clrfund.setUserRegistry(userRegistry.target) - const SimpleRecipientRegistry = await ethers.getContractFactory( - 'SimpleRecipientRegistry', + userRegistry = await ethers.deployContract( + EContracts.SimpleUserRegistry, deployer ) - recipientRegistry = await SimpleRecipientRegistry.deploy(clrfund.target) + await clrfund.setUserRegistry(userRegistry.target) + recipientRegistry = await ethers.deployContract( + EContracts.SimpleRecipientRegistry, + [clrfund.target], + { signer: deployer } + ) await clrfund.setRecipientRegistry(recipientRegistry.target) // Deploy ERC20 token contract - const Token = await ethers.getContractFactory('AnyOldERC20Token', deployer) + const Token = await ethers.getContractFactory( + EContracts.AnyOldERC20Token, + deployer + ) const tokenInitialSupply = UNIT * BigInt(10000) token = await Token.deploy(tokenInitialSupply) await token.transfer(await poolContributor1.getAddress(), UNIT * BigInt(50)) @@ -262,15 +261,15 @@ describe('End-to-end Tests', function () { const fundingRoundAddress = await clrfund.getCurrentRound() fundingRound = await ethers.getContractAt( - 'FundingRound', + EContracts.FundingRound, fundingRoundAddress ) const maciAddress = await fundingRound.maci() - maci = await ethers.getContractAt('MACI', maciAddress) + maci = await ethers.getContractAt(EContracts.MACI, maciAddress) pollId = await fundingRound.pollId() const pollAddress = await fundingRound.poll() - pollContract = await ethers.getContractAt('Poll', pollAddress) + pollContract = await ethers.getContractAt(EContracts.Poll, pollAddress) await mine() }) @@ -331,7 +330,10 @@ describe('End-to-end Tests', function () { messageProcessorAddress: string }> { const tallyAddress = await fundingRound.tally() - const tallyContact = await ethers.getContractAt('Tally', tallyAddress) + const tallyContact = await ethers.getContractAt( + EContracts.Tally, + tallyAddress + ) const messageProcessorAddress = await tallyContact.messageProcessor() return { tallyAddress, messageProcessorAddress } diff --git a/contracts/tasks/helpers/ContractStorage.ts b/contracts/tasks/helpers/ContractStorage.ts index f1b73160a..9c092bd24 100644 --- a/contracts/tasks/helpers/ContractStorage.ts +++ b/contracts/tasks/helpers/ContractStorage.ts @@ -7,7 +7,7 @@ import { import { EContracts } from '../../utils/types' import { JSONFile } from '../../utils/JSONFile' -import { Libraries } from '../../utils/deployment' +import { Libraries } from 'hardhat/types' const DEPLOYED_CONTRACTS = './deployed-contracts.json' diff --git a/contracts/tasks/helpers/ContractVerifier.ts b/contracts/tasks/helpers/ContractVerifier.ts index 24aec1ef9..6d71952bc 100644 --- a/contracts/tasks/helpers/ContractVerifier.ts +++ b/contracts/tasks/helpers/ContractVerifier.ts @@ -32,11 +32,13 @@ export class ContractVerifier { async verify( address: string, constructorArguments: string, - libraries?: string + libraries?: string, + contract?: string ): Promise<[boolean, string]> { const params: IVerificationSubtaskArgs = { address, constructorArguments: JSON.parse(constructorArguments) as unknown[], + contract, } if (libraries) { diff --git a/contracts/tasks/helpers/Subtask.ts b/contracts/tasks/helpers/Subtask.ts index 5dc02426c..756189629 100644 --- a/contracts/tasks/helpers/Subtask.ts +++ b/contracts/tasks/helpers/Subtask.ts @@ -7,7 +7,7 @@ import { ContractTransactionResponse, formatUnits, } from 'ethers' -import { task } from 'hardhat/config' +import { subtask as HardhatSubtask } from 'hardhat/config' import { exit } from 'process' @@ -18,6 +18,7 @@ import type { HardhatRuntimeEnvironment, } from 'hardhat/types' +import { deployContract } from '../../utils/contracts' import { EContracts } from '../../utils/types' import { ContractStorage } from './ContractStorage' import { @@ -87,7 +88,7 @@ export class Subtask { try { this.config = JSONFile.read(DEPLOY_CONFIG) as TConfig } catch (e) { - console.log('eror =======================', e) + //console.log('eror =======================', e) this.config = {} as TConfig } @@ -350,7 +351,7 @@ export class Subtask { paramsFn: paramsFn || this.getDefaultParams, }) - return task(taskName, stepName) + return HardhatSubtask(taskName, stepName) } /** @@ -438,10 +439,8 @@ export class Subtask { const args = options?.args || [] const libraries = options?.libraries - const qualifiedName = contractName.includes('Poseidon') - ? ':' + contractName - : contractName - const contract = await this.hre.ethers.deployContract(qualifiedName, args, { + const contract = await deployContract(contractName, this.hre.ethers, { + args, signer: deployer, libraries, }) @@ -455,19 +454,46 @@ export class Subtask { * * @param id - contract name * @param field - config field key - * @returns config field value or null + * @returns config field value */ getConfigField( id: EContracts, - field: string, - mustGet = false + field: string ): T { this.checkHre(this.hre) - const value = this.config[this.hre.network.name][id][field] as T + let value: T | null | undefined + try { + value = this.config[this.hre.network.name][id][field] as T + } catch { + value = undefined + } + + if (value === null || value === undefined) { + throw new Error( + `Can't find ${this.hre.network.name}.${id}.${field} in ${DEPLOY_CONFIG}` + ) + } + + return value + } - if (mustGet && (value === null || value === undefined)) { - throw new Error(`Can't find ${this.hre.network.name}.${id}.${field}`) + /** + * Try to get deploy config field (see deploy-config.json) + * + * @param id - contract name + * @param field - config field key + * @returns config field value or undefined + */ + tryGetConfigField( + id: EContracts, + field: string + ): T | undefined { + let value: T | undefined + try { + value = this.getConfigField(id, field) + } catch { + value = undefined } return value diff --git a/contracts/tasks/runners/cancel.ts b/contracts/tasks/runners/cancel.ts index ca2986b2a..915ca5937 100644 --- a/contracts/tasks/runners/cancel.ts +++ b/contracts/tasks/runners/cancel.ts @@ -21,6 +21,10 @@ task('cancel', 'Cancel the current round') ) const cancelTx = await clrfundContract.cancelCurrentRound() - await cancelTx.wait() + const receipt = await cancelTx.wait() + if (receipt.status !== 1) { + throw new Error('Failed to cancel current round') + } + console.log('Cancel transaction hash: ', cancelTx.hash) }) diff --git a/contracts/tasks/runners/setRecipientRegistry.ts b/contracts/tasks/runners/setRecipientRegistry.ts index bc31749db..cb2ace2f8 100644 --- a/contracts/tasks/runners/setRecipientRegistry.ts +++ b/contracts/tasks/runners/setRecipientRegistry.ts @@ -37,7 +37,7 @@ task('set-recipient-registry', 'Set recipient registry in ClrFund') let success: boolean try { await subtask.logStart() - const steps = await subtask.getDeploySteps(['recipientRegistry'], params) + const steps = await subtask.getDeploySteps(['recipient'], params) const skip = params.skip || 0 await subtask.runSteps(steps, skip) diff --git a/contracts/tasks/runners/setUserRegistry.ts b/contracts/tasks/runners/setUserRegistry.ts index ffb8ff850..9c686e6ca 100644 --- a/contracts/tasks/runners/setUserRegistry.ts +++ b/contracts/tasks/runners/setUserRegistry.ts @@ -45,7 +45,7 @@ task('set-user-registry', 'Set the user registry in ClrFund') let success: boolean try { await subtask.logStart() - const steps = await subtask.getDeploySteps(['userRegistry'], params) + const steps = await subtask.getDeploySteps(['user'], params) const skip = params.skip || 0 diff --git a/contracts/tasks/runners/verifyAll.ts b/contracts/tasks/runners/verifyAll.ts index 18d1875b4..ab4647563 100644 --- a/contracts/tasks/runners/verifyAll.ts +++ b/contracts/tasks/runners/verifyAll.ts @@ -71,8 +71,18 @@ task('verify-all', 'Verify contracts listed in storage') if (!force && verifiedEntity) { console.log('Already verified') } else { + let contract: string | undefined + let libraries: string | undefined + if (entry.id === 'AnyOldERC20Token') { + contract = 'contracts/AnyOldERC20Token.sol:AnyOldERC20Token' + } // eslint-disable-next-line no-await-in-loop - const [ok, err] = await verifier.verify(address, params?.args ?? '') + const [ok, err] = await verifier.verify( + address, + params?.args ?? '', + libraries, + contract + ) if (ok) { storage.setVerified(address, hre.network.name, true) diff --git a/contracts/tasks/subtasks/clrfund/01-poseidon.ts b/contracts/tasks/subtasks/clrfund/01-poseidon.ts index 5731a8cfd..96455dffd 100644 --- a/contracts/tasks/subtasks/clrfund/01-poseidon.ts +++ b/contracts/tasks/subtasks/clrfund/01-poseidon.ts @@ -2,10 +2,42 @@ import { ContractStorage } from '../../helpers/ContractStorage' import { Subtask } from '../../helpers/Subtask' import { ISubtaskParams } from '../../helpers/types' import { EContracts } from '../../../utils/types' +import { Signer } from 'ethers' const subtask = Subtask.getInstance() const storage = ContractStorage.getInstance() +/** + * Deploy a Poseidon contract + * @param poseidonName Poseidon contract name + * @param network Deployment network + * @param signer Signer of the transaction + * @param incremental Whether to check for previous deployment + */ +async function deployPoseidon( + poseidonName: EContracts, + network: string, + signer: Signer, + incremental?: boolean +) { + const poseidonAddress = storage.getAddress(poseidonName, network) + + if (incremental && poseidonAddress) { + return + } + + const PoseidonContract = await subtask.deployContract(poseidonName, { + signer, + }) + + await storage.register({ + id: poseidonName, + contract: PoseidonContract, + args: [], + network, + }) +} + /** * Deploy step registration and task itself */ @@ -14,75 +46,10 @@ subtask .setAction(async ({ incremental }: ISubtaskParams, hre) => { subtask.setHre(hre) const deployer = await subtask.getDeployer() + const network = hre.network.name - const poseidonT3ContractAddress = storage.getAddress( - EContracts.PoseidonT3, - hre.network.name - ) - const poseidonT4ContractAddress = storage.getAddress( - EContracts.PoseidonT4, - hre.network.name - ) - const poseidonT5ContractAddress = storage.getAddress( - EContracts.PoseidonT5, - hre.network.name - ) - const poseidonT6ContractAddress = storage.getAddress( - EContracts.PoseidonT6, - hre.network.name - ) - - if ( - incremental && - poseidonT3ContractAddress && - poseidonT4ContractAddress && - poseidonT5ContractAddress && - poseidonT6ContractAddress - ) { - return - } - - const PoseidonT3Contract = await subtask.deployContract( - EContracts.PoseidonT3, - { signer: deployer } - ) - const PoseidonT4Contract = await subtask.deployContract( - EContracts.PoseidonT4, - { signer: deployer } - ) - const PoseidonT5Contract = await subtask.deployContract( - EContracts.PoseidonT5, - { signer: deployer } - ) - const PoseidonT6Contract = await subtask.deployContract( - EContracts.PoseidonT6, - { signer: deployer } - ) - - await Promise.all([ - storage.register({ - id: EContracts.PoseidonT3, - contract: PoseidonT3Contract, - args: [], - network: hre.network.name, - }), - storage.register({ - id: EContracts.PoseidonT4, - contract: PoseidonT4Contract, - args: [], - network: hre.network.name, - }), - storage.register({ - id: EContracts.PoseidonT5, - contract: PoseidonT5Contract, - args: [], - network: hre.network.name, - }), - storage.register({ - id: EContracts.PoseidonT6, - contract: PoseidonT6Contract, - args: [], - network: hre.network.name, - }), - ]) + await deployPoseidon(EContracts.PoseidonT3, network, deployer, incremental) + await deployPoseidon(EContracts.PoseidonT4, network, deployer, incremental) + await deployPoseidon(EContracts.PoseidonT5, network, deployer, incremental) + await deployPoseidon(EContracts.PoseidonT6, network, deployer, incremental) }) diff --git a/contracts/tasks/subtasks/clrfund/02-vkRegistry.ts b/contracts/tasks/subtasks/clrfund/02-vkRegistry.ts index 10798f9c4..077debd48 100644 --- a/contracts/tasks/subtasks/clrfund/02-vkRegistry.ts +++ b/contracts/tasks/subtasks/clrfund/02-vkRegistry.ts @@ -2,7 +2,7 @@ import type { VkRegistry } from '../../../typechain-types' import { ContractStorage } from '../../helpers/ContractStorage' import { Subtask } from '../../helpers/Subtask' -import { setVerifyingKeys } from '../../../utils/deployment' +import { setVerifyingKeys } from '../../../utils/contracts' import { MaciParameters } from '../../../utils/maciParameters' import { EContracts, type ISubtaskParams } from '../../helpers/types' diff --git a/contracts/tasks/subtasks/coordinator/01-coordinator.ts b/contracts/tasks/subtasks/coordinator/01-coordinator.ts index dad05ed8d..c4430aff1 100644 --- a/contracts/tasks/subtasks/coordinator/01-coordinator.ts +++ b/contracts/tasks/subtasks/coordinator/01-coordinator.ts @@ -4,7 +4,6 @@ */ import { Subtask } from '../../helpers/Subtask' import { EContracts } from '../../../utils/types' -import { setCoordinator } from '../../../utils/deployment' import { Contract, getAddress } from 'ethers' import { Keypair, PrivKey, PubKey } from '@clrfund/common' @@ -62,11 +61,10 @@ subtask return } - const tx = await setCoordinator({ - clrfundContract, - coordinatorAddress: normalizedNewCoordinator, - coordinatorMacisk, - }) + const tx = await clrfundContract.setCoordinator( + normalizedNewCoordinator, + newKeypair.pubKey.asContractParam() + ) const receipt = await tx.wait() if (receipt?.status !== 1) { diff --git a/contracts/tasks/subtasks/deployer/01-clrfundDeployer.ts b/contracts/tasks/subtasks/deployer/01-clrfundDeployer.ts index cc2642289..0f26fd734 100644 --- a/contracts/tasks/subtasks/deployer/01-clrfundDeployer.ts +++ b/contracts/tasks/subtasks/deployer/01-clrfundDeployer.ts @@ -9,13 +9,10 @@ import { EContracts } from '../../../utils/types' const subtask = Subtask.getInstance() const storage = ContractStorage.getInstance() -/** - * Deploy step registration and task itself - */ subtask .addTask( - 'brightid:deploy-brightid-sponsor', - 'Deploy BrightID sponsor contracts' + 'deployer:deploy-clrfund-deployer', + 'Deploy ClrFundDeployer contract' ) .setAction(async ({ incremental }: ISubtaskParams, hre) => { subtask.setHre(hre) diff --git a/contracts/tasks/subtasks/recipient/02-optimisticRecipientRegistry.ts b/contracts/tasks/subtasks/recipient/02-optimisticRecipientRegistry.ts index 2d1e91ec5..2ccfb74b2 100644 --- a/contracts/tasks/subtasks/recipient/02-optimisticRecipientRegistry.ts +++ b/contracts/tasks/subtasks/recipient/02-optimisticRecipientRegistry.ts @@ -23,7 +23,7 @@ const storage = ContractStorage.getInstance() */ async function getTokenDecimals(clrfundContract: ClrFund): Promise { const nativeTokenAddress = await clrfundContract.nativeToken() - const tokenContract = await task.getContract({ + const tokenContract = await subtask.getContract({ name: EContracts.AnyOldERC20Token, address: nativeTokenAddress, }) diff --git a/contracts/tasks/subtasks/recipient/99-setRecipientRegistry.ts b/contracts/tasks/subtasks/recipient/99-setRecipientRegistry.ts index 8b23dc10a..9565dd8b3 100644 --- a/contracts/tasks/subtasks/recipient/99-setRecipientRegistry.ts +++ b/contracts/tasks/subtasks/recipient/99-setRecipientRegistry.ts @@ -47,6 +47,7 @@ subtask getAddress(recipientRegistryContractAddress) ) { // already set + console.log('Recipient registry already set, skipping..') return } } @@ -61,4 +62,6 @@ subtask `Failed to set recipient registry ${recipientRegistryContractAddress}` ) } + + subtask.logTransaction(tx) }) diff --git a/contracts/tasks/subtasks/round/01-deploy-brightid-user-registry.ts b/contracts/tasks/subtasks/round/01-deploy-brightid-user-registry.ts index 016590197..ac8eed08c 100644 --- a/contracts/tasks/subtasks/round/01-deploy-brightid-user-registry.ts +++ b/contracts/tasks/subtasks/round/01-deploy-brightid-user-registry.ts @@ -29,12 +29,12 @@ subtask return } - const shouldDeploy = subtask.getConfigField( + const shouldDeploy = subtask.tryGetConfigField( EContracts.BrightIdUserRegistry, 'deploy' ) - if (!shouldDeploy) { + if (shouldDeploy === false) { // for testing, we don't deploy for every round return } diff --git a/contracts/tasks/subtasks/round/02-deploy-round.ts b/contracts/tasks/subtasks/round/02-deploy-round.ts index e31a894f1..8c1210a6c 100644 --- a/contracts/tasks/subtasks/round/02-deploy-round.ts +++ b/contracts/tasks/subtasks/round/02-deploy-round.ts @@ -17,6 +17,34 @@ import { ContractTransactionResponse } from 'ethers' const subtask = Subtask.getInstance() const storage = ContractStorage.getInstance() +/** + * Register the FundingRound contract in the contract storage for verification + * + * @param fundingRoundContract The funding round contract + * @param network The network + * @param tx The funding round deployment transaction + */ +async function registerFundingRound( + fundingRoundContract: FundingRound, + network: string, + tx: ContractTransactionResponse +) { + const args = await Promise.all([ + fundingRoundContract.nativeToken(), + fundingRoundContract.userRegistry(), + fundingRoundContract.recipientRegistry(), + fundingRoundContract.coordinator(), + ]) + + await storage.register({ + id: EContracts.FundingRound, + contract: fundingRoundContract, + network, + args, + tx, + }) +} + /** * Register MACI contract in the contract storage for verification * @@ -85,28 +113,26 @@ async function registerPoll( ]) const args = [ - [ - duration, - { - maxMessages: maxValues.maxMessages, - maxVoteOptions: maxValues.maxVoteOptions, - }, - { - intStateTreeDepth: treeDepths.intStateTreeDepth, - messageTreeSubDepth: treeDepths.messageTreeSubDepth, - messageTreeDepth: treeDepths.messageTreeDepth, - voteOptionTreeDepth: treeDepths.voteOptionTreeDepth, - }, - { - x: coordinatorPubKey.x, - y: coordinatorPubKey.y, - }, - { - maci: extContracts.maci, - messageAq: extContracts.messageAq, - topupCredit: extContracts.topupCredit, - }, - ], + duration, + { + maxMessages: maxValues.maxMessages, + maxVoteOptions: maxValues.maxVoteOptions, + }, + { + intStateTreeDepth: treeDepths.intStateTreeDepth, + messageTreeSubDepth: treeDepths.messageTreeSubDepth, + messageTreeDepth: treeDepths.messageTreeDepth, + voteOptionTreeDepth: treeDepths.voteOptionTreeDepth, + }, + { + x: coordinatorPubKey.x, + y: coordinatorPubKey.y, + }, + { + maci: extContracts.maci, + messageAq: extContracts.messageAq, + topupCredit: extContracts.topupCredit, + }, ] await storage.register({ @@ -203,14 +229,7 @@ subtask address: fundingRoundContractAddress, }) - await storage.register({ - id: EContracts.FundingRound, - contract: fundingRoundContract, - args: [duration], - network, - tx, - }) - + await registerFundingRound(fundingRoundContract, network, tx) await registerMaci(fundingRoundContract, network, tx) await registerPoll(duration, fundingRoundContract, network, tx) await registerTallyAndMessageProcessor(fundingRoundContract, network, tx) diff --git a/contracts/tasks/subtasks/user/03-brightidSponsor.ts b/contracts/tasks/subtasks/user/03-brightidSponsor.ts index 34a0264d4..bd7759cf6 100644 --- a/contracts/tasks/subtasks/user/03-brightidSponsor.ts +++ b/contracts/tasks/subtasks/user/03-brightidSponsor.ts @@ -18,8 +18,10 @@ subtask subtask.setHre(hre) const deployer = await subtask.getDeployer() - let brightidSponsorContractAddress: string | undefined = - subtask.getConfigField(EContracts.BrightIdUserRegistry, 'sponsor') + let brightidSponsorContractAddress = subtask.tryGetConfigField( + EContracts.BrightIdUserRegistry, + 'sponsor' + ) if (brightidSponsorContractAddress) { console.log( diff --git a/contracts/tasks/subtasks/user/04-brightidUserRegistry.ts b/contracts/tasks/subtasks/user/04-brightidUserRegistry.ts index f5bb0608e..6db98dc2c 100644 --- a/contracts/tasks/subtasks/user/04-brightidUserRegistry.ts +++ b/contracts/tasks/subtasks/user/04-brightidUserRegistry.ts @@ -1,7 +1,7 @@ /** * Deploy a new recipient registry */ - +import { encodeBytes32String } from 'ethers' import { ContractStorage } from '../../helpers/ContractStorage' import { Subtask } from '../../helpers/Subtask' import { ISubtaskParams } from '../../helpers/types' @@ -33,7 +33,7 @@ subtask } const brightidUserRegistryContractAddress = storage.getAddress( - EContracts.SimpleRecipientRegistry, + EContracts.BrightIdUserRegistry, network ) @@ -41,16 +41,24 @@ subtask return } - const context = subtask.getConfigField( + const shouldDeploy = subtask.tryGetConfigField( + EContracts.BrightIdUserRegistry, + 'deploy' + ) + if (shouldDeploy === false) { + return + } + + const context = subtask.getConfigField( EContracts.BrightIdUserRegistry, 'context' ) - const verifier = subtask.getConfigField( + const verifier = subtask.getConfigField( EContracts.BrightIdUserRegistry, 'verifier' ) - let sponsor = subtask.getConfigField( + let sponsor = subtask.tryGetConfigField( EContracts.BrightIdUserRegistry, 'sponsor' ) @@ -58,7 +66,7 @@ subtask sponsor = storage.mustGetAddress(EContracts.BrightIdSponsor, network) } - const args = [context, verifier, sponsor] + const args = [encodeBytes32String(context), verifier, sponsor] const brightidUserRegistryContract = await subtask.deployContract( EContracts.BrightIdUserRegistry, { signer: deployer, args } diff --git a/contracts/tasks/subtasks/user/99-setUserRegistry.ts b/contracts/tasks/subtasks/user/99-setUserRegistry.ts index d2e56d0fb..5591eaa16 100644 --- a/contracts/tasks/subtasks/user/99-setUserRegistry.ts +++ b/contracts/tasks/subtasks/user/99-setUserRegistry.ts @@ -60,4 +60,6 @@ subtask `Failed to se user registry ${userRegistryContractAddress}` ) } + + subtask.logTransaction(tx) }) diff --git a/contracts/tests/deployer.ts b/contracts/tests/deployer.ts index e9c9514ac..f21353557 100644 --- a/contracts/tests/deployer.ts +++ b/contracts/tests/deployer.ts @@ -1,19 +1,22 @@ import { ethers, config, artifacts } from 'hardhat' import { time } from '@nomicfoundation/hardhat-network-helpers' import { expect } from 'chai' -import { Contract } from 'ethers' +import { BaseContract, Contract } from 'ethers' import { genRandomSalt } from 'maci-crypto' import { Keypair } from '@clrfund/common' import { TREE_ARITY, ZERO_ADDRESS, UNIT } from '../utils/constants' -import { getGasUsage, getEventArg } from '../utils/contracts' -import { - deployContract, - deployPoseidonLibraries, - deployMaciFactory, -} from '../utils/deployment' +import { getGasUsage, getEventArg, deployContract } from '../utils/contracts' +import { deployPoseidonLibraries, deployMaciFactory } from '../utils/testutils' import { MaciParameters } from '../utils/maciParameters' import { HardhatEthersSigner } from '@nomicfoundation/hardhat-ethers/signers' +import { + ClrFund, + ClrFundDeployer, + FundingRoundFactory, + MACIFactory, +} from '../typechain-types' +import { EContracts } from '../utils/types' const roundDuration = 10000 @@ -21,12 +24,12 @@ describe('Clr fund deployer', async () => { let deployer: HardhatEthersSigner let coordinator: HardhatEthersSigner let contributor: HardhatEthersSigner - let maciFactory: Contract + let maciFactory: MACIFactory let userRegistry: Contract let recipientRegistry: Contract - let factoryTemplate: Contract - let clrfund: Contract - let clrFundDeployer: Contract + let factoryTemplate: ClrFund + let clrfund: ClrFund + let clrFundDeployer: ClrFundDeployer let token: Contract const coordinatorPubKey = new Keypair().pubKey.asContractParam() let poseidonContracts: { [name: string]: string } @@ -45,7 +48,6 @@ describe('Clr fund deployer', async () => { beforeEach(async () => { if (!poseidonContracts) { poseidonContracts = await deployPoseidonLibraries({ - artifactsPath: config.paths.artifacts, ethers, }) } @@ -58,11 +60,13 @@ describe('Clr fund deployer', async () => { maciParameters: params, }) - factoryTemplate = await deployContract({ - name: 'ClrFund', + factoryTemplate = await deployContract( + EContracts.ClrFund, ethers, - signer: deployer, - }) + { + signer: deployer, + } + ) expect(await factoryTemplate.getAddress()).to.be.properAddress const tx = factoryTemplate.deploymentTransaction() @@ -72,10 +76,10 @@ describe('Clr fund deployer', async () => { expect(tx).to.not.be.null } - const roundFactory = await deployContract({ - name: 'FundingRoundFactory', - ethers, - }) + const roundFactory = await deployContract( + EContracts.FundingRoundFactory, + ethers + ) const roundFactoryTx = roundFactory.deploymentTransaction() if (roundFactoryTx) { expect(await getGasUsage(roundFactoryTx)).lessThan(4600000) @@ -83,16 +87,14 @@ describe('Clr fund deployer', async () => { expect(roundFactoryTx).to.not.be.null } - clrFundDeployer = await deployContract({ - name: 'ClrFundDeployer', - contractArgs: [ - factoryTemplate.target, - maciFactory.target, - roundFactory.target, - ], + clrFundDeployer = await deployContract( + EContracts.ClrFundDeployer, ethers, - signer: deployer, - }) + { + args: [factoryTemplate.target, maciFactory.target, roundFactory.target], + signer: deployer, + } + ) expect(clrFundDeployer.target).to.be.properAddress const deployerTx = clrFundDeployer.deploymentTransaction() @@ -105,12 +107,16 @@ describe('Clr fund deployer', async () => { const newInstanceTx = await clrFundDeployer.deployClrFund() const instanceAddress = await getEventArg( newInstanceTx, - clrFundDeployer, + clrFundDeployer as BaseContract as Contract, 'NewInstance', 'clrfund' ) - clrfund = await ethers.getContractAt('ClrFund', instanceAddress, deployer) + clrfund = (await ethers.getContractAt( + EContracts.ClrFund, + instanceAddress, + deployer + )) as BaseContract as ClrFund const SimpleUserRegistry = await ethers.getContractFactory( 'SimpleUserRegistry', @@ -161,9 +167,7 @@ describe('Clr fund deployer', async () => { it('allows only owner to set user registry', async () => { await expect( - (clrfund.connect(contributor) as Contract).setUserRegistry( - userRegistry.target - ) + clrfund.connect(contributor).setUserRegistry(userRegistry.target) ).to.be.revertedWith('Ownable: caller is not the owner') }) @@ -196,9 +200,9 @@ describe('Clr fund deployer', async () => { it('allows only owner to set recipient registry', async () => { await expect( - (clrfund.connect(contributor) as Contract).setRecipientRegistry( - recipientRegistry.target - ) + clrfund + .connect(contributor) + .setRecipientRegistry(recipientRegistry.target) ).to.be.revertedWith('Ownable: caller is not the owner') }) @@ -227,9 +231,7 @@ describe('Clr fund deployer', async () => { it('allows only owner to add funding source', async () => { await expect( - (clrfund.connect(contributor) as Contract).addFundingSource( - contributor.address - ) + clrfund.connect(contributor).addFundingSource(contributor.address) ).to.be.revertedWith('Ownable: caller is not the owner') }) @@ -250,9 +252,7 @@ describe('Clr fund deployer', async () => { it('allows only owner to remove funding source', async () => { await clrfund.addFundingSource(contributor.address) await expect( - (clrfund.connect(contributor) as Contract).removeFundingSource( - contributor.address - ) + clrfund.connect(contributor).removeFundingSource(contributor.address) ).to.be.revertedWith('Ownable: caller is not the owner') }) @@ -304,7 +304,7 @@ describe('Clr fund deployer', async () => { const maciAddress = await getEventArg( deployTx, - maciFactory, + maciFactory as BaseContract as Contract, 'MaciDeployed', '_maci' ) @@ -396,7 +396,7 @@ describe('Clr fund deployer', async () => { await clrfund.setToken(token.target) await clrfund.setCoordinator(coordinator.address, coordinatorPubKey) - const clrfundAsContributor = clrfund.connect(contributor) as Contract + const clrfundAsContributor = clrfund.connect(contributor) await expect( clrfundAsContributor.deployNewRound(roundDuration) ).to.be.revertedWith('Ownable: caller is not the owner') @@ -508,12 +508,14 @@ describe('Clr fund deployer', async () => { await clrfund.deployNewRound(roundDuration) await time.increase(roundDuration) await expect( - (clrfund.connect(contributor) as Contract).transferMatchingFunds( - totalSpent, - totalSpentSalt, - resultsCommitment, - perVOVoiceCreditCommitment - ) + clrfund + .connect(contributor) + .transferMatchingFunds( + totalSpent, + totalSpentSalt, + resultsCommitment, + perVOVoiceCreditCommitment + ) ).to.be.revertedWith('Ownable: caller is not the owner') }) @@ -553,7 +555,7 @@ describe('Clr fund deployer', async () => { it('allows only owner to cancel round', async () => { await clrfund.deployNewRound(roundDuration) await expect( - (clrfund.connect(contributor) as Contract).cancelCurrentRound() + clrfund.connect(contributor).cancelCurrentRound() ).to.be.revertedWith('Ownable: caller is not the owner') }) @@ -582,7 +584,7 @@ describe('Clr fund deployer', async () => { }) it('only owner can set native token', async () => { - const clrfundAsContributor = clrfund.connect(contributor) as Contract + const clrfundAsContributor = clrfund.connect(contributor) await expect( clrfundAsContributor.setToken(token.target) ).to.be.revertedWith('Ownable: caller is not the owner') @@ -596,7 +598,7 @@ describe('Clr fund deployer', async () => { }) it('allows only the owner to set a new coordinator', async () => { - const clrfundAsContributor = clrfund.connect(contributor) as Contract + const clrfundAsContributor = clrfund.connect(contributor) await expect( clrfundAsContributor.setCoordinator( coordinator.address, @@ -607,7 +609,7 @@ describe('Clr fund deployer', async () => { it('allows coordinator to call coordinatorQuit and sets coordinator to null', async () => { await clrfund.setCoordinator(coordinator.address, coordinatorPubKey) - const clrfundAsCoordinator = clrfund.connect(coordinator) as Contract + const clrfundAsCoordinator = clrfund.connect(coordinator) await expect(clrfundAsCoordinator.coordinatorQuit()) .to.emit(clrfund, 'CoordinatorChanged') .withArgs(ZERO_ADDRESS) @@ -634,7 +636,7 @@ describe('Clr fund deployer', async () => { fundingRoundAddress ) - const clrfundAsCoordinator = clrfund.connect(coordinator) as Contract + const clrfundAsCoordinator = clrfund.connect(coordinator) await expect(clrfundAsCoordinator.coordinatorQuit()) .to.emit(clrfund, 'RoundFinalized') .withArgs(fundingRoundAddress) diff --git a/contracts/tests/maciFactory.ts b/contracts/tests/maciFactory.ts index f630b1d27..44296bbf9 100644 --- a/contracts/tests/maciFactory.ts +++ b/contracts/tests/maciFactory.ts @@ -4,17 +4,18 @@ import { expect } from 'chai' import { deployMockContract, MockContract } from '@clrfund/waffle-mock-contract' import { getEventArg, getGasUsage } from '../utils/contracts' -import { deployMaciFactory, deployPoseidonLibraries } from '../utils/deployment' +import { deployMaciFactory, deployPoseidonLibraries } from '../utils/testutils' import { MaciParameters } from '../utils/maciParameters' import { HardhatEthersSigner } from '@nomicfoundation/hardhat-ethers/signers' import { Keypair } from '@clrfund/common' +import { MACIFactory } from '../typechain-types' describe('MACI factory', () => { let deployer: HardhatEthersSigner let coordinator: HardhatEthersSigner const duration = 100 - let maciFactory: Contract + let maciFactory: MACIFactory let signUpGatekeeper: MockContract let initialVoiceCreditProxy: MockContract let topupContract: MockContract @@ -29,7 +30,6 @@ describe('MACI factory', () => { beforeEach(async () => { if (!poseidonContracts) { poseidonContracts = await deployPoseidonLibraries({ - artifactsPath: config.paths.artifacts, ethers, signer: deployer, }) @@ -84,7 +84,7 @@ describe('MACI factory', () => { }) it('allows only owner to set MACI parameters', async () => { - const coordinatorMaciFactory = maciFactory.connect(coordinator) as Contract + const coordinatorMaciFactory = maciFactory.connect(coordinator) await expect( coordinatorMaciFactory.setMaciParameters( ...maciParameters.asContractParam() @@ -118,7 +118,7 @@ describe('MACI factory', () => { ...maciParameters.asContractParam() ) await setParamTx.wait() - const coordinatorMaciFactory = maciFactory.connect(coordinator) as Contract + const coordinatorMaciFactory = maciFactory.connect(coordinator) const deployTx = await coordinatorMaciFactory.deployMaci( signUpGatekeeper.target, @@ -149,7 +149,7 @@ describe('MACI factory', () => { const maciAddress = await getEventArg( deployTx, - maciFactory, + maciFactory as unknown as Contract, 'MaciDeployed', '_maci' ) @@ -173,7 +173,7 @@ describe('MACI factory', () => { const maciAddress = await getEventArg( deployTx, - maciFactory, + maciFactory as unknown as Contract, 'MaciDeployed', '_maci' ) diff --git a/contracts/tests/recipientRegistry.ts b/contracts/tests/recipientRegistry.ts index 70d0c5d32..ea5932993 100644 --- a/contracts/tests/recipientRegistry.ts +++ b/contracts/tests/recipientRegistry.ts @@ -6,8 +6,9 @@ import { time } from '@nomicfoundation/hardhat-network-helpers' import { UNIT, ZERO_ADDRESS } from '../utils/constants' import { getTxFee, getEventArg } from '../utils/contracts' -import { deployContract } from '../utils/deployment' +import { deployContract } from '../utils/contracts' import { HardhatEthersSigner } from '@nomicfoundation/hardhat-ethers/signers' +import { EContracts } from '../utils/types' const MAX_RECIPIENTS = 15 @@ -587,12 +588,14 @@ describe('Optimistic recipient registry', () => { ;[, deployer, controller, recipient, requester] = await ethers.getSigners() }) beforeEach(async () => { - registry = await deployContract({ - name: 'OptimisticRecipientRegistry', - contractArgs: [baseDeposit, challengePeriodDuration, controller.address], + registry = await deployContract( + EContracts.OptimisticRecipientRegistry, ethers, - signer: deployer, - }) + { + args: [baseDeposit, challengePeriodDuration, controller.address], + signer: deployer, + } + ) registryAddress = await registry.getAddress() }) @@ -1042,16 +1045,13 @@ describe('Optimistic recipient registry', () => { '_recipientId' ) - const anotherRegistry = await deployContract({ - name: 'OptimisticRecipientRegistry', - contractArgs: [ - baseDeposit, - challengePeriodDuration, - controller.address, - ], - ethers, - signer: deployer, - }) + const anotherRegistry = await ethers.deployContract( + EContracts.OptimisticRecipientRegistry, + [baseDeposit, challengePeriodDuration, controller.address], + { + signer: deployer, + } + ) const txTwo = await anotherRegistry.addRecipient( recipientAddress, metadata, diff --git a/contracts/utils/contracts.ts b/contracts/utils/contracts.ts index 8dbc0734a..e60d11a76 100644 --- a/contracts/utils/contracts.ts +++ b/contracts/utils/contracts.ts @@ -1,6 +1,74 @@ -import { TransactionResponse } from 'ethers' +import { + BaseContract, + ContractTransactionResponse, + TransactionResponse, +} from 'ethers' import { getEventArg } from '@clrfund/common' +import { EContracts } from './types' +import { + DeployContractOptions, + HardhatEthersHelpers, +} from '@nomicfoundation/hardhat-ethers/types' +import { VkRegistry } from '../typechain-types' +import { MaciParameters } from './maciParameters' +import { IVerifyingKeyStruct } from 'maci-contracts' +/** + * Deploy a contract + * @param name Name of the contract + * @param ethers hardhat ethers handle + * @param options options with signer, libraries or contract constructor args + * @returns contract + */ +export async function deployContract( + name: EContracts, + ethers: HardhatEthersHelpers, + options?: DeployContractOptions & { args?: unknown[]; quiet?: boolean } +): Promise { + const args = options?.args || [] + const contractName = String(name).includes('Poseidon') ? ':' + name : name + const contract = await ethers.deployContract(contractName, args, options) + await contract.waitForDeployment() + + if (options?.quiet === false) { + console.log(`Deployed ${name} at ${contract.target}`) + } + return contract as BaseContract as T +} + +/** + * Set Verifying key + * @param vkRegistry VKRegistry contract + * @param maciParameters MACI tree depths and verifying key information + * @returns transaction response + */ +export async function setVerifyingKeys( + vkRegistry: VkRegistry, + params: MaciParameters +): Promise { + const messageBatchSize = params.getMessageBatchSize() + const tx = await vkRegistry.setVerifyingKeys( + params.stateTreeDepth, + params.treeDepths.intStateTreeDepth, + params.treeDepths.messageTreeDepth, + params.treeDepths.voteOptionTreeDepth, + messageBatchSize, + params.processVk.asContractParam() as IVerifyingKeyStruct, + params.tallyVk.asContractParam() as IVerifyingKeyStruct + ) + + const receipt = await tx.wait() + if (receipt?.status !== 1) { + throw new Error('Failed to set verifying key; transaction receipt status 1') + } + return tx +} + +/** + * Get the gas usage + * @param transaction The transaction handle + * @returns Gas used + */ export async function getGasUsage( transaction: TransactionResponse ): Promise { @@ -8,6 +76,11 @@ export async function getGasUsage( return receipt ? Number(receipt.gasUsed) : 0 } +/** + * Get the transaction fee + * @param transaction The transaction handle + * @returns Gas fee + */ export async function getTxFee( transaction: TransactionResponse ): Promise { diff --git a/contracts/utils/deployment.ts b/contracts/utils/deployment.ts deleted file mode 100644 index 79e29be54..000000000 --- a/contracts/utils/deployment.ts +++ /dev/null @@ -1,505 +0,0 @@ -import { - Signer, - Contract, - ContractTransactionResponse, - encodeBytes32String, - BaseContract, -} from 'ethers' -import path from 'path' - -import { readFileSync } from 'fs' -import { HardhatEthersHelpers } from '@nomicfoundation/hardhat-ethers/types' -import { DEFAULT_CIRCUIT } from './circuits' -import { isPathExist } from './misc' -import { MaciParameters } from './maciParameters' -import { PrivKey, Keypair } from '@clrfund/common' -import { ZERO_ADDRESS } from './constants' -import { VkRegistry } from '../typechain-types' -import { IVerifyingKeyStruct } from 'maci-contracts' - -// Number.MAX_SAFE_INTEGER - 1 -export const challengePeriodSeconds = '9007199254740990' - -export type Libraries = { [name: string]: string } - -// Mapping of the user registry type and the contract name -const userRegistryNames: Record = { - simple: 'SimpleUserRegistry', - brightid: 'BrightIdUserRegistry', - snapshot: 'SnapshotUserRegistry', - merkle: 'MerkleUserRegistry', - semaphore: 'SemaphoreUserRegistry', -} - -// Mapping of recipient registry type to the contract name -const recipientRegistries: Record = { - simple: 'SimpleRecipientRegistry', - optimistic: 'OptimisticRecipientRegistry', -} - -// BrightId contract deployment parameters -export interface BrightIdParams { - context: string - verifierAddress: string - sponsor: string -} - -type PoseidonName = 'PoseidonT3' | 'PoseidonT4' | 'PoseidonT5' | 'PoseidonT6' - -/** - * Log the message based on the quiet flag - * @param quiet whether to log the message - * @param message the message to log - */ -function logInfo(quiet = true, message: string, ...args: any[]) { - if (!quiet) { - console.log(message, ...args) - } -} - -/** - * Deploy the Poseidon contracts. These contracts - * have a custom artifact location that the hardhat library cannot - * retrieve using the standard getContractFactory() function, so, we manually - * read the artifact content and pass to the getContractFactory function - * - * NOTE: there are 2 copies of the Poseidon artifacts, the one in the build/contracts - * folder has the actual contract bytecode, the other one in the build/contracts/@clrfund/maci-contracts - * only has the library interface. If the wrong bytecode is used to deploy the contract, - * the hash functions will always return 0. - * - * @param name PoseidonT3, PoseidonT4, PoseidonT5, PoseidonT6 - * @param ethers - * @param signer the account that deploys the contract - * @returns contract object - */ -export async function deployPoseidon({ - name, - artifactsPath, - ethers, - signer, -}: { - name: PoseidonName - artifactsPath: string - ethers: HardhatEthersHelpers - signer?: Signer -}): Promise { - const artifact = JSON.parse( - readFileSync(path.join(artifactsPath, `${name}.json`)).toString() - ) - - const Poseidon = await ethers.getContractFactory( - artifact.abi, - artifact.bytecode, - signer - ) - - const poseidonContract = await Poseidon.deploy() - - return await poseidonContract.waitForDeployment() -} - -export type deployContractOptions = { - name: string - libraries?: Libraries - contractArgs?: any[] - // hardhat ethers handle - ethers: HardhatEthersHelpers - // if signer is not provided, use the default signer from ethers - signer?: Signer -} - -export async function deployContract({ - name, - libraries, - contractArgs = [], - ethers, - signer, -}: deployContractOptions): Promise { - const contract = await ethers.deployContract(name, contractArgs, { - signer, - libraries, - }) - - return await contract.waitForDeployment() -} - -/** - * Deploy a user registry - * @param userRegistryType user registry type, e.g. brightid, simple, etc - * @param ethers Hardhat ethers handle - * @param signer The user registry contract deployer - * @param brightidContext The BrightId context - * @param brightidVerifier The BrightId verifier address - * @param brightidSponsor The BrightId sponsor contract address - * @returns the newly deployed user registry contract - */ -export async function deployUserRegistry({ - userRegistryType, - ethers, - signer, - brightidContext, - brightidVerifier, - brightidSponsor, -}: { - userRegistryType: string - ethers: HardhatEthersHelpers - signer?: Signer - brightidContext?: string - brightidVerifier?: string - brightidSponsor?: string -}): Promise { - let contractArgs: any[] = [] - const registryType = (userRegistryType || '').toLowerCase() - - if (registryType === 'brightid') { - if (!brightidContext) { - throw new Error('Missing BrightId context') - } - if (!brightidVerifier) { - throw new Error('Missing BrightId verifier address') - } - if (!brightidSponsor) { - throw new Error('Missing BrightId sponsor contract address') - } - - contractArgs = [ - encodeBytes32String(brightidContext), - brightidVerifier, - brightidSponsor, - ] - } - - const userRegistryName = userRegistryNames[registryType] - if (!userRegistryName) { - throw new Error('unsupported user registry type: ' + registryType) - } - - return deployContract({ - name: userRegistryName, - contractArgs, - ethers, - signer, - }) -} - -/** - * Deploy a recipient registry - * @param type recipient registry type, e.g. simple, optimistic, etc - * @param controller the controller address of the registry - * @param deposit the optimistic recipient registry base deposit amount - * @param challengePeriod the optimistic recipient registry challenge period - * @param ethers Hardhat ethers handle - * @param signer The deployer account - * @returns the newly deployed registry contract - */ -export async function deployRecipientRegistry({ - type, - controller, - deposit, - challengePeriod, - ethers, - signer, -}: { - type: string - controller: string - deposit?: bigint - challengePeriod?: bigint - ethers: HardhatEthersHelpers - signer?: Signer -}): Promise { - const registryType = (type || '').toLowerCase() - const registryName = recipientRegistries[registryType] - if (!registryName) { - throw new Error('Unsupported recipient registry type: ' + registryType) - } - - if (registryType === 'optimistic') { - if (!deposit) { - throw new Error('Missing base deposit amount') - } - if (!challengePeriod) { - throw new Error('Missing challenge period') - } - } - - const args = - registryType === 'simple' - ? [controller] - : [deposit, challengePeriod, controller] - - const recipientRegistry = await ethers.deployContract( - registryName, - args, - signer - ) - - await recipientRegistry.waitForDeployment() - return recipientRegistry -} - -/** - * Deploy all the poseidon contracts - * - * @param signer The signer for the deployment transaction - * @param ethers Hardhat ethers handle - * @param artifactsPath Contract artifacts path - * @returns the deployed poseidon contracts - */ -export async function deployPoseidonLibraries({ - signer, - ethers, - artifactsPath, -}: { - signer?: Signer - ethers: HardhatEthersHelpers - artifactsPath: string -}): Promise<{ [name: string]: string }> { - const PoseidonT3Contract = await deployPoseidon({ - name: 'PoseidonT3', - artifactsPath, - ethers, - signer, - }) - - const PoseidonT4Contract = await deployPoseidon({ - name: 'PoseidonT4', - artifactsPath, - ethers, - signer, - }) - - const PoseidonT5Contract = await deployPoseidon({ - name: 'PoseidonT5', - artifactsPath, - signer, - ethers, - }) - - const PoseidonT6Contract = await deployPoseidon({ - name: 'PoseidonT6', - artifactsPath, - ethers, - signer, - }) - - const libraries = { - PoseidonT3: await PoseidonT3Contract.getAddress(), - PoseidonT4: await PoseidonT4Contract.getAddress(), - PoseidonT5: await PoseidonT5Contract.getAddress(), - PoseidonT6: await PoseidonT6Contract.getAddress(), - } - return libraries -} - -/** - * Deploy the poll factory - * @param signer Contract creator - * @param ethers Hardhat ethers handle - * @param libraries Poseidon libraries - * @param artifactPath Poseidon contract artifacts path - * - */ -export async function deployPollFactory({ - signer, - ethers, - libraries, - artifactsPath, -}: { - signer: Signer - ethers: HardhatEthersHelpers - libraries?: Libraries - artifactsPath?: string -}): Promise { - let poseidonLibraries = libraries - if (!libraries) { - if (!artifactsPath) { - throw Error('Failed to dpeloy PollFactory, artifact path is missing') - } - poseidonLibraries = await deployPoseidonLibraries({ - artifactsPath: artifactsPath || '', - ethers, - signer, - }) - } - - return deployContract({ - name: 'PollFactory', - libraries: poseidonLibraries, - signer, - ethers, - }) -} - -/** - * Deploy an instance of MACI factory - * libraries - poseidon contracts - * ethers - hardhat ethers handle - * signer - if signer is not provided, use default signer in ethers - * @returns MACI factory contract - */ -export async function deployMaciFactory({ - libraries, - ethers, - signer, - maciParameters, - quiet, -}: { - libraries: Libraries - ethers: HardhatEthersHelpers - signer?: Signer - maciParameters: MaciParameters - quiet?: boolean -}): Promise { - const vkRegistry = await deployContract({ - name: 'VkRegistry', - ethers, - signer, - }) - logInfo(quiet, 'Deployed VkRegistry at', vkRegistry.target) - - await setVerifyingKeys( - vkRegistry as BaseContract as VkRegistry, - maciParameters - ) - - const verifier = await deployContract({ - name: 'Verifier', - ethers, - signer, - }) - logInfo(quiet, 'Deployed Verifier at', verifier.target) - - const pollFactory = await deployContract({ - name: 'PollFactory', - libraries, - ethers, - signer, - }) - logInfo(quiet, 'Deployed PollFactory at', pollFactory.target) - - const tallyFactory = await deployContract({ - name: 'TallyFactory', - libraries, - ethers, - signer, - }) - logInfo(quiet, 'Deployed TallyFactory at', tallyFactory.target) - - const messageProcessorFactory = await deployContract({ - name: 'MessageProcessorFactory', - libraries, - ethers, - signer, - }) - logInfo( - quiet, - 'Deployed MessageProcessorFactory at', - messageProcessorFactory.target - ) - - // all the factories to deploy MACI contracts - const factories = { - pollFactory: pollFactory.target, - tallyFactory: tallyFactory.target, - // subsidy is not currently used - subsidyFactory: ZERO_ADDRESS, - messageProcessorFactory: messageProcessorFactory.target, - } - - const maciFactory = await deployContract({ - name: 'MACIFactory', - libraries, - contractArgs: [vkRegistry.target, factories, verifier.target], - ethers, - signer, - }) - logInfo(quiet, 'Deployed MACIFactory at', maciFactory.target) - - const setTx = await maciFactory.setMaciParameters( - ...maciParameters.asContractParam() - ) - await setTx.wait() - - return maciFactory -} - -/** - * Set MACI parameters in the MACI factory - * @param maciFactory - * @param directory - * @param circuit - */ -export async function setMaciParameters( - maciFactory: Contract, - directory: string, - circuit = DEFAULT_CIRCUIT -): Promise { - if (!isPathExist(directory)) { - throw new Error(`Path ${directory} does not exists`) - } - const maciParameters = await MaciParameters.fromConfig(circuit, directory) - const setMaciTx = await maciFactory.setMaciParameters( - ...maciParameters.asContractParam() - ) - await setMaciTx.wait() - - return setMaciTx -} - -/** - * Set Verifying key - * @param vkRegistry VKRegistry contract - * @param maciParameters MACI tree depths and verifying key information - * @returns transaction response - */ -export async function setVerifyingKeys( - vkRegistry: VkRegistry, - params: MaciParameters -): Promise { - const messageBatchSize = params.getMessageBatchSize() - const tx = await vkRegistry.setVerifyingKeys( - params.stateTreeDepth, - params.treeDepths.intStateTreeDepth, - params.treeDepths.messageTreeDepth, - params.treeDepths.voteOptionTreeDepth, - messageBatchSize, - params.processVk.asContractParam() as IVerifyingKeyStruct, - params.tallyVk.asContractParam() as IVerifyingKeyStruct - ) - - const receipt = await tx.wait() - if (receipt?.status !== 1) { - throw new Error('Failed to set verifying key; transaction receipt status 1') - } - return tx -} - -/** - * Set the coordinator address and maci public key in the funding round factory - * @param fundingRoundFactory funding round factory contract - * @param coordinatorAddress - * @param MaciPrivateKey - */ -export async function setCoordinator({ - clrfundContract, - coordinatorAddress, - coordinatorMacisk, -}: { - clrfundContract: Contract - coordinatorAddress: string - coordinatorMacisk?: string -}): Promise { - // Generate or use the passed in coordinator key - const privKey = coordinatorMacisk - ? PrivKey.deserialize(coordinatorMacisk) - : undefined - - const keypair = new Keypair(privKey) - const coordinatorPubKey = keypair.pubKey - const setCoordinatorTx = await clrfundContract.setCoordinator( - coordinatorAddress, - coordinatorPubKey.asContractParam() - ) - return setCoordinatorTx -} diff --git a/contracts/utils/maciParameters.ts b/contracts/utils/maciParameters.ts index 68342625c..e498a15e9 100644 --- a/contracts/utils/maciParameters.ts +++ b/contracts/utils/maciParameters.ts @@ -1,9 +1,10 @@ -import { Contract } from 'ethers' +import { Contract, BigNumberish } from 'ethers' import { VerifyingKey } from 'maci-domainobjs' import { extractVk } from 'maci-circuits' import { CIRCUITS, getCircuitFiles } from './circuits' import { TREE_ARITY } from './constants' +import { Params } from '../typechain-types/contracts/MACIFactory' type TreeDepths = { intStateTreeDepth: number @@ -38,7 +39,10 @@ export class MaciParameters { return TREE_ARITY ** this.treeDepths.messageTreeSubDepth } - asContractParam(): any[] { + asContractParam(): [ + _stateTreeDepth: BigNumberish, + _treeDepths: Params.TreeDepthsStruct, + ] { return [ this.stateTreeDepth, { diff --git a/contracts/utils/testutils.ts b/contracts/utils/testutils.ts index dde82cfa6..db63401ec 100644 --- a/contracts/utils/testutils.ts +++ b/contracts/utils/testutils.ts @@ -1,10 +1,14 @@ import { Signer, Contract } from 'ethers' import { MockContract, deployMockContract } from '@clrfund/waffle-mock-contract' import { artifacts, ethers, config } from 'hardhat' -import { deployMaciFactory, deployPoseidonLibraries } from './deployment' import { MaciParameters } from './maciParameters' import { PubKey } from '@clrfund/common' -import { getEventArg } from './contracts' +import { deployContract, getEventArg, setVerifyingKeys } from './contracts' +import { HardhatEthersHelpers } from '@nomicfoundation/hardhat-ethers/types' +import { EContracts } from './types' +import { Libraries } from 'hardhat/types' +import { MACIFactory, VkRegistry } from '../typechain-types' +import { ZERO_ADDRESS } from './constants' /** * Deploy a mock contract with the given contract name @@ -20,6 +24,133 @@ export async function deployMockContractByName( return deployMockContract(signer, ContractArtifacts.abi) } +/** + * Deploy all the poseidon contracts + * + * @param signer The signer for the deployment transaction + * @param ethers Hardhat ethers handle + * @returns the deployed poseidon contracts + */ +export async function deployPoseidonLibraries({ + signer, + ethers, +}: { + signer?: Signer + ethers: HardhatEthersHelpers +}): Promise<{ [name: string]: string }> { + const PoseidonT3Contract = await deployContract( + EContracts.PoseidonT3, + ethers, + { signer } + ) + + const PoseidonT4Contract = await deployContract( + EContracts.PoseidonT4, + ethers, + { signer } + ) + + const PoseidonT5Contract = await deployContract( + EContracts.PoseidonT5, + ethers, + { signer } + ) + + const PoseidonT6Contract = await deployContract( + EContracts.PoseidonT6, + ethers, + { + signer, + } + ) + + const libraries = { + PoseidonT3: await PoseidonT3Contract.getAddress(), + PoseidonT4: await PoseidonT4Contract.getAddress(), + PoseidonT5: await PoseidonT5Contract.getAddress(), + PoseidonT6: await PoseidonT6Contract.getAddress(), + } + return libraries +} + +/** + * Deploy an instance of MACI factory + * libraries - poseidon contracts + * ethers - hardhat ethers handle + * signer - if signer is not provided, use default signer in ethers + * @returns MACI factory contract + */ +export async function deployMaciFactory({ + libraries, + ethers, + signer, + maciParameters, + quiet, +}: { + libraries: Libraries + ethers: HardhatEthersHelpers + signer?: Signer + maciParameters: MaciParameters + quiet?: boolean +}): Promise { + const vkRegistry = await deployContract( + EContracts.VkRegistry, + ethers, + { signer, quiet } + ) + await setVerifyingKeys(vkRegistry, maciParameters) + + const verifier = await deployContract(EContracts.Verifier, ethers, { + signer, + quiet, + }) + + const pollFactory = await deployContract( + EContracts.PollFactory, + ethers, + { libraries, signer, quiet } + ) + + const tallyFactory = await deployContract( + EContracts.TallyFactory, + ethers, + { libraries, signer, quiet } + ) + + const messageProcessorFactory = await deployContract( + EContracts.MessageProcessorFactory, + ethers, + { libraries, signer, quiet } + ) + + // all the factories to deploy MACI contracts + const factories = { + pollFactory: pollFactory.target, + tallyFactory: tallyFactory.target, + // subsidy is not currently used + subsidyFactory: ZERO_ADDRESS, + messageProcessorFactory: messageProcessorFactory.target, + } + + const maciFactory = await deployContract( + EContracts.MACIFactory, + ethers, + { + args: [vkRegistry.target, factories, verifier.target], + libraries, + signer, + quiet, + } + ) + + const setTx = await maciFactory.setMaciParameters( + ...maciParameters.asContractParam() + ) + await setTx.wait() + + return maciFactory +} + /** * Output from the deployTestFundingRound() function */ @@ -47,22 +178,22 @@ export async function deployTestFundingRound( deployer: Signer ): Promise { const token = await ethers.deployContract( - 'AnyOldERC20Token', + EContracts.AnyOldERC20Token, [tokenSupply], deployer ) const mockUserRegistry = await deployMockContractByName( - 'IUserRegistry', + EContracts.IUserRegistry, deployer ) const mockRecipientRegistry = await deployMockContractByName( - 'IRecipientRegistry', + EContracts.IRecipientRegistry, deployer ) const fundingRound = await ethers.deployContract( - 'FundingRound', + EContracts.FundingRound, [ token.target, mockUserRegistry.target, @@ -75,7 +206,6 @@ export async function deployTestFundingRound( const libraries = await deployPoseidonLibraries({ signer: deployer, ethers, - artifactsPath: config.paths.artifacts, }) const maciParameters = MaciParameters.mock() @@ -86,13 +216,22 @@ export async function deployTestFundingRound( maciParameters, }) const factories = await maciFactory.factories() - const topupToken = await ethers.deployContract('TopupToken', deployer) - const vkRegistry = await ethers.deployContract('VkRegistry', deployer) - const mockVerifier = await deployMockContractByName('Verifier', deployer) - const mockTally = await deployMockContractByName('Tally', deployer) + const topupToken = await ethers.deployContract( + EContracts.TopupToken, + deployer + ) + const vkRegistry = await ethers.deployContract( + EContracts.VkRegistry, + deployer + ) + const mockVerifier = await deployMockContractByName( + EContracts.Verifier, + deployer + ) + const mockTally = await deployMockContractByName(EContracts.Tally, deployer) const maciInstance = await ethers.deployContract( - 'MACI', + EContracts.MACI, [ factories.pollFactory, factories.messageProcessorFactory, diff --git a/contracts/utils/types.ts b/contracts/utils/types.ts index dee39b710..ba8f753f4 100644 --- a/contracts/utils/types.ts +++ b/contracts/utils/types.ts @@ -20,9 +20,11 @@ export enum EContracts { PoseidonT4 = 'PoseidonT4', PoseidonT5 = 'PoseidonT5', PoseidonT6 = 'PoseidonT6', + IRecipientRegistry = 'IRecipientRegistry', SimpleRecipientRegistry = 'SimpleRecipientRegistry', OptimisticRecipientRegistry = 'OptimisticRecipientRegistry', KlerosGTCRAdapter = 'KlerosGTCRAdapter', + IUserRegistry = 'IUserRegistry', SimpleUserRegistry = 'SimpleUserRegistry', SemaphoreUserRegistry = 'SemaphoreUserRegistry', BrightIdUserRegistry = 'BrightIdUserRegistry', From 55dad6dcc806040dc5de136ef5eb0219bb2a72c2 Mon Sep 17 00:00:00 2001 From: yuetloo Date: Mon, 18 Mar 2024 23:59:08 -0400 Subject: [PATCH 06/17] add deploy task for ClrFundDeployer and code cleanup --- .github/workflows/cancel-round.yml | 2 +- .github/workflows/finalize-round.yml | 9 +- .github/workflows/new-round.yml | 21 ++- contracts/deploy-config-example.json | 10 +- contracts/tasks/helpers/Subtask.ts | 14 +- contracts/tasks/helpers/types.ts | 16 ++- contracts/tasks/index.ts | 2 + contracts/tasks/runners/cancel.ts | 4 +- contracts/tasks/runners/findStorageSlot.ts | 2 +- contracts/tasks/runners/loadMerkleUsers.ts | 2 +- contracts/tasks/runners/loadSimpleUsers.ts | 2 +- contracts/tasks/runners/mergeAllocation.ts | 21 ++- contracts/tasks/runners/newDeployer.ts | 69 ++++++++++ contracts/tasks/runners/newRound.ts | 130 ++++++++++-------- contracts/tasks/runners/setCoordinator.ts | 13 +- contracts/tasks/runners/setUserRegistry.ts | 17 +-- contracts/tasks/runners/tally.ts | 3 +- .../tasks/subtasks/clrfund/02-vkRegistry.ts | 18 +-- .../subtasks/clrfund/03-setVkRegsitry.ts | 68 +++++++++ .../{03-verifier.ts => 04-verifier.ts} | 0 ...ctory.ts => 05-messageProcessorFactory.ts} | 0 ...{05-tallyFactory.ts => 07-tallyFactory.ts} | 0 .../{07-maciFactory.ts => 08-maciFactory.ts} | 0 ...ndFactory.ts => 09-fundingRoundFactory.ts} | 0 .../clrfund/{09-clrfund.ts => 10-clrfund.ts} | 24 ---- .../tasks/subtasks/clrfund/11-initClrfund.ts | 50 +++++++ contracts/tasks/subtasks/index.ts | 16 +-- .../round/01-deploy-brightid-user-registry.ts | 2 +- .../tasks/subtasks/round/02-deploy-round.ts | 33 +++-- docs/brightid.md | 1 + 30 files changed, 366 insertions(+), 183 deletions(-) create mode 100644 contracts/tasks/runners/newDeployer.ts create mode 100644 contracts/tasks/subtasks/clrfund/03-setVkRegsitry.ts rename contracts/tasks/subtasks/clrfund/{03-verifier.ts => 04-verifier.ts} (100%) rename contracts/tasks/subtasks/clrfund/{04-messageProcessorFactory.ts => 05-messageProcessorFactory.ts} (100%) rename contracts/tasks/subtasks/clrfund/{05-tallyFactory.ts => 07-tallyFactory.ts} (100%) rename contracts/tasks/subtasks/clrfund/{07-maciFactory.ts => 08-maciFactory.ts} (100%) rename contracts/tasks/subtasks/clrfund/{08-fundingRoundFactory.ts => 09-fundingRoundFactory.ts} (100%) rename contracts/tasks/subtasks/clrfund/{09-clrfund.ts => 10-clrfund.ts} (61%) create mode 100644 contracts/tasks/subtasks/clrfund/11-initClrfund.ts diff --git a/.github/workflows/cancel-round.yml b/.github/workflows/cancel-round.yml index 02b0e804a..0c37001aa 100644 --- a/.github/workflows/cancel-round.yml +++ b/.github/workflows/cancel-round.yml @@ -28,4 +28,4 @@ jobs: cd contracts export CLRFUND=$(curl -X POST -d '{"query":"{clrFunds {id}}"}' $SUBGRPAH_URL) export CLRFUND_ADDRESS=$(node -e 'console.log(JSON.parse(process.env.CLRFUND).data.clrFunds[0].id)') - yarn hardhat clr-cancel --clrfund "${CLRFUND_ADDRESS}" --network ${NETWORK} + yarn hardhat cancel-round --clrfund "${CLRFUND_ADDRESS}" --network ${NETWORK} diff --git a/.github/workflows/finalize-round.yml b/.github/workflows/finalize-round.yml index f21a1c5ec..adfcdfdfc 100644 --- a/.github/workflows/finalize-round.yml +++ b/.github/workflows/finalize-round.yml @@ -9,8 +9,7 @@ on: default: '0x87790498127ff044f43b9230506833ca89113757' maci_tx_hash: description: 'MACI creation transaction hash' - required: true - default: '0x1280527bdb23cbd4906c94e152722792fc59400bfccfc89709beb2531fc55365' + required: false blocks_per_batch: description: 'Blocks of logs to fetch per batch' required: true @@ -18,7 +17,7 @@ on: network: description: 'Network' required: true - default: 'optimism-sepolia' + default: 'arbitrum-sepolia' jsonrpc_url: description: jsonrpc url to node required: false @@ -83,7 +82,7 @@ jobs: # tally and finalize cd monorepo/contracts mkdir -p proof_output - yarn hardhat clr-tally --clrfund "${CLRFUND_ADDRESS}" --network "${NETWORK}" \ + yarn hardhat tally --clrfund "${CLRFUND_ADDRESS}" --network "${NETWORK}" \ --rapidsnark ${RAPID_SNARK} \ --circuit-directory ${CIRCUIT_DIRECTORY} \ --blocks-per-batch ${BLOCKS_PER_BATCH} \ @@ -91,4 +90,4 @@ jobs: curl --location --request POST 'https://api.pinata.cloud/pinning/pinFileToIPFS' \ --header "Authorization: Bearer ${{ secrets.PINATA_JWT }}" \ --form 'file=@"./proof_output/tally.json"' - yarn hardhat --network "${NETWORK}" clr-finalize --clrfund "${CLRFUND_ADDRESS}" + yarn hardhat --network "${NETWORK}" finalize --clrfund "${CLRFUND_ADDRESS}" diff --git a/.github/workflows/new-round.yml b/.github/workflows/new-round.yml index 15e154211..2c788842d 100644 --- a/.github/workflows/new-round.yml +++ b/.github/workflows/new-round.yml @@ -3,17 +3,25 @@ name: Create new round on: workflow_dispatch: inputs: + clrfund_address: + description: 'Clrfund contract address' + required: true + default: '0x87790498127ff044f43b9230506833ca89113757' duration: description: 'Round duration' required: true default: 3600 - + network: + description: 'Network' + required: true + default: 'arbitrum-sepolia' + jsonrpc_url: + description: jsonrpc url to node + required: false env: NODE_VERSION: 20.x - SUBGRPAH_URL: "https://api.thegraph.com/subgraphs/name/clrfund/clrfund-testnet" WALLET_PRIVATE_KEY: ${{ secrets.ARBITRUM_GOERLI_COORDINATOR_WALLET_PRIVATE_KEY }} - NETWORK: arbitrum-sepolia jobs: create-new-round: @@ -33,8 +41,7 @@ jobs: - name: Run create new round script run: | cd contracts - export CLRFUND=$(curl -X POST -d '{"query":"{clrFunds {id}}"}' $SUBGRPAH_URL) - export CLRFUND_ADDRESS=$(node -e 'console.log(JSON.parse(process.env.CLRFUND).data.clrFunds[0].id)') - yarn hardhat new-round --network "${NETWORK}" \ + yarn hardhat new-round \ + --network ${{ github.event.inputs.network }} \ --duration ${{ github.event.inputs.duration }} \ - --clrfund ${CLRFUND_ADDRESS} + --clrfund ${{ github.event.inputs.clrfund_address }} diff --git a/contracts/deploy-config-example.json b/contracts/deploy-config-example.json index 783782855..65086870d 100644 --- a/contracts/deploy-config-example.json +++ b/contracts/deploy-config-example.json @@ -11,9 +11,6 @@ "userRegistry": "SimpleUserRegistry", "recipientRegistry": "SimpleRecipientRegistry" }, - "FundingRound": { - "duration": 900 - }, "AnyOldERC20Token": { "initialSupply": "10000000000000000000000" }, @@ -27,7 +24,7 @@ "verifier": "0xdbf0b2ee9887fe11934789644096028ed3febe9c" } }, - "optimism-sepolia": { + "arbitrum-sepolia": { "VkRegistry": { "circuit": "micro", "paramsDirectory": "./params" @@ -36,12 +33,9 @@ "template": false, "coordinator": "", "token": "0x65bc8dd04808d99cf8aa6749f128d55c2051edde", - "userRegistry": "SemaphoreUserRegistry", + "userRegistry": "BrightIdUserRegistry", "recipientRegistry": "OptimisticRecipientRegistry" }, - "FundingRound": { - "duration": 900 - }, "AnyOldERC20Token": { "initialSupply": "10000000000000000000000" }, diff --git a/contracts/tasks/helpers/Subtask.ts b/contracts/tasks/helpers/Subtask.ts index 756189629..ccbac3796 100644 --- a/contracts/tasks/helpers/Subtask.ts +++ b/contracts/tasks/helpers/Subtask.ts @@ -179,9 +179,10 @@ export class Subtask { async runSteps(steps: ISubtaskStep[], skip: number): Promise { this.checkHre(this.hre) + let stepNumber = 1 // eslint-disable-next-line no-restricted-syntax for (const step of steps) { - const stepId = `0${step.id}` + const stepId = `0${stepNumber}` console.log( '\n======================================================================' ) @@ -190,12 +191,13 @@ export class Subtask { '======================================================================\n' ) - if (step.id <= skip) { - console.log(`STEP ${step.id} WAS SKIPPED`) + if (stepNumber <= skip) { + console.log(`STEP ${stepNumber} WAS SKIPPED`) } else { // eslint-disable-next-line no-await-in-loop await this.hre.run(step.taskName, step.args) } + stepNumber++ } } @@ -396,8 +398,8 @@ export class Subtask { deployTypes: string[], params: ISubtaskParams ): Promise { - const catalogSteps = await Promise.all( - deployTypes.map((deployType) => this.stepCatalog.get(deployType)) + const catalogSteps = deployTypes.map((deployType) => + this.stepCatalog.get(deployType) ) let stepList: ISubtaskStepCatalog[] = [] @@ -413,7 +415,6 @@ export class Subtask { return Promise.all(stepList.map(({ paramsFn }) => paramsFn(params))).then( (stepArgs) => stepArgs.map((args, index) => ({ - id: index + 1, name: stepList[index].name, taskName: stepList[index].taskName, args: args as unknown, @@ -531,5 +532,6 @@ export class Subtask { console.log(`deployer address: ${tx.from}`) console.log(`gas price: ${tx.gasPrice}`) console.log(`gas used: ${tx.gasLimit}`) + console.log() } } diff --git a/contracts/tasks/helpers/types.ts b/contracts/tasks/helpers/types.ts index 6ed2a49dc..920f135da 100644 --- a/contracts/tasks/helpers/types.ts +++ b/contracts/tasks/helpers/types.ts @@ -73,6 +73,17 @@ export interface ISubtaskParams { * Skip steps with less or equal index */ skip?: number + + /** + * The duration of a new funding round. This is only used when starting + * a new round + */ + roundDuration?: number + + /** + * The ClrFund contract address + */ + clrfund?: string } /** @@ -132,11 +143,6 @@ export interface ISubtaskStepCatalog { * Interface that represents subtask step */ export interface ISubtaskStep { - /** - * Sequence step id - */ - id: number - /** * Step name */ diff --git a/contracts/tasks/index.ts b/contracts/tasks/index.ts index 77bbf3d44..5bcc7e4fc 100644 --- a/contracts/tasks/index.ts +++ b/contracts/tasks/index.ts @@ -1,5 +1,6 @@ import './subtasks' import './runners/newMaciKey' +import './runners/newDeployer' import './runners/newClrFund' import './runners/newRound' import './runners/timeTravel' @@ -14,6 +15,7 @@ import './runners/finalize' import './runners/claim' import './runners/cancel' import './runners/exportRound' +import './runners/mergeAllocation' import './runners/loadSimpleUsers' import './runners/loadMerkleUsers' import './runners/contribute' diff --git a/contracts/tasks/runners/cancel.ts b/contracts/tasks/runners/cancel.ts index 915ca5937..7c7e3d88b 100644 --- a/contracts/tasks/runners/cancel.ts +++ b/contracts/tasks/runners/cancel.ts @@ -2,12 +2,12 @@ * Cancel the current round * * Sample usage: - * yarn hardhat cancel --clrfund --network + * yarn hardhat cancel-round --clrfund --network */ import { task } from 'hardhat/config' import { EContracts } from '../../utils/types' -task('cancel', 'Cancel the current round') +task('cancel-round', 'Cancel the current round') .addParam('clrfund', 'The ClrFund contract address') .setAction(async ({ clrfund }, { ethers, network }) => { const [deployer] = await ethers.getSigners() diff --git a/contracts/tasks/runners/findStorageSlot.ts b/contracts/tasks/runners/findStorageSlot.ts index bcc87bba2..35cd795b1 100644 --- a/contracts/tasks/runners/findStorageSlot.ts +++ b/contracts/tasks/runners/findStorageSlot.ts @@ -7,7 +7,7 @@ * https://github.com/vocdoni/storage-proofs-eth-js/blob/main/src/erc20.ts#L62 * * - * Usage: hardhat find-storage-slot --token --holder --network arbitrum + * Usage: hardhat find-storage-slot --token --holder --network */ import { task, types } from 'hardhat/config' diff --git a/contracts/tasks/runners/loadMerkleUsers.ts b/contracts/tasks/runners/loadMerkleUsers.ts index 80bbfea06..e26560251 100644 --- a/contracts/tasks/runners/loadMerkleUsers.ts +++ b/contracts/tasks/runners/loadMerkleUsers.ts @@ -14,7 +14,7 @@ import { getIpfsHash } from '../../utils/ipfs' * * Sample usage: * - * yarn hardhat load-merkle-users --address-file addresses.txt --user-registry
--network goerli + * yarn hardhat load-merkle-users --address-file addresses.txt --user-registry
--network */ const MAX_ADDRESSES_SUPPORTED = 10000 diff --git a/contracts/tasks/runners/loadSimpleUsers.ts b/contracts/tasks/runners/loadSimpleUsers.ts index aee25f007..d48abbe82 100644 --- a/contracts/tasks/runners/loadSimpleUsers.ts +++ b/contracts/tasks/runners/loadSimpleUsers.ts @@ -9,7 +9,7 @@ import fs from 'fs' * * Sample usage: * - * yarn hardhat load-simple-users --file-path addresses.txt --user-registry
--network goerli + * yarn hardhat load-simple-users --file-path addresses.txt --user-registry
--network */ /** diff --git a/contracts/tasks/runners/mergeAllocation.ts b/contracts/tasks/runners/mergeAllocation.ts index 5ce859fb2..ea38e2178 100644 --- a/contracts/tasks/runners/mergeAllocation.ts +++ b/contracts/tasks/runners/mergeAllocation.ts @@ -4,14 +4,14 @@ * data into the output file of the export-round task * * Sample usage: - * yarn hardhat clr-merge-allocations --allocation-file /tmp/downloads/clr.fund-round8.tsv --round-file ../vue-app/src/rounds/xdai/0xd07aa7faeba14efde87f2538699c0d6c9a566c20.json + * yarn hardhat merge-allocations --allocation-file /tmp/downloads/clr.fund-round8.tsv --round-file ../vue-app/src/rounds/xdai/0xd07aa7faeba14efde87f2538699c0d6c9a566c20.json */ import { task, types } from 'hardhat/config' import { formatUnits, parseUnits } from 'ethers' import fs from 'fs' -import { Project, RoundFileContent, Tally } from '../utils/types' -import { JSONFile } from '../utils/JSONFile' +import { Project, RoundFileContent } from '../../utils/types' +import { JSONFile } from '../../utils/JSONFile' const COLUMN_PROJECT_NAME = 0 const COLUMN_RECIPIENT_ADDRESS = 1 @@ -25,6 +25,16 @@ type Allocation = { payoutAmount: string } +// legacy Tally file format +interface Tally { + results: { + tally: string[] + } + totalVoiceCreditsPerVoteOption: { + tally: string[] + } +} + /** * Map null in the array to zero * @param array array to check for null and map nulls to zero @@ -113,10 +123,7 @@ function readAllocationFile( return allocations } -task( - 'clr-merge-allocations', - 'Merge the allocations data into the round JSON file' -) +task('merge-allocations', 'Merge the allocations data into the round JSON file') .addParam('roundFile', 'The JSON file exported from the export-round task') .addParam('allocationFile', 'The allocation file in tab separated format') .addOptionalParam( diff --git a/contracts/tasks/runners/newDeployer.ts b/contracts/tasks/runners/newDeployer.ts new file mode 100644 index 000000000..14157d4f8 --- /dev/null +++ b/contracts/tasks/runners/newDeployer.ts @@ -0,0 +1,69 @@ +/* eslint-disable no-console */ +/** + * Deploy a new ClrFundDeployer contract + * + * Sample usage: + * yarn hardhat new-deployer --verify --network + * + * Note: + * 1) Make sure you have deploy-config.json (see deploy-config-example.json). + * 2) Make sure you set environment variable COORDINATOR_MACISK with the coordinator MACI private key + * 3) use --incremental to resume a deployment stopped due to a failure + * 4) use --manage-nonce to manually set nonce; useful on optimism-sepolia + * where `nonce too low` errors occur occasionally + */ +import { task, types } from 'hardhat/config' + +import { Subtask } from '../helpers/Subtask' +import { type ISubtaskParams } from '../helpers/types' + +task('new-deployer', 'Deploy a new instance of ClrFund') + .addFlag('incremental', 'Incremental deployment') + .addFlag('strict', 'Fail on warnings') + .addFlag('verify', 'Verify contracts at Etherscan') + .addFlag('manageNonce', 'Manually increment nonce for each transaction') + .addOptionalParam('skip', 'Skip steps with less or equal index', 0, types.int) + .setAction(async (params: ISubtaskParams, hre) => { + const { verify, manageNonce } = params + const subtask = Subtask.getInstance(hre) + + subtask.setHre(hre) + const deployer = await subtask.getDeployer() + + if (manageNonce) { + subtask.setNonceManager(deployer) + } + + let success: boolean + try { + await subtask.start(params) + const steps = await subtask.getDeploySteps( + ['clrfund', 'deployer', 'maci'], + params + ) + + const skip = params.skip || 0 + + // run all the steps except for the init-clrfund step + await subtask.runSteps( + steps.filter((step) => step.taskName !== 'clrfund:init-clrfund'), + skip + ) + await subtask.checkResults(params.strict) + success = true + } catch (err) { + console.error( + '\n=========================================================\nERROR:', + err, + '\n' + ) + success = false + } + + await subtask.finish(success) + + if (verify) { + console.log('Verify all contracts') + await hre.run('verify-all') + } + }) diff --git a/contracts/tasks/runners/newRound.ts b/contracts/tasks/runners/newRound.ts index 5b72e3141..acbe22e2a 100644 --- a/contracts/tasks/runners/newRound.ts +++ b/contracts/tasks/runners/newRound.ts @@ -2,83 +2,97 @@ * Create a new instance of Funding Round * * Sample usage: + * yarn hardhat new-round --round-duration --network * - * yarn hardhat new-round --network - * + * Note: + * 1) Make sure you have deploy-config.json (see deploy-config-example.json). + * 2) Make sure you have deployed-contracts.json created from the new-clrfund task */ +import { types } from 'hardhat/config' import { ZERO_ADDRESS } from '../../utils/constants' import { task } from 'hardhat/config' import { EContracts } from '../../utils/types' -import { ContractStorage } from '../helpers/ContractStorage' import { Subtask } from '../helpers/Subtask' import { ISubtaskParams } from '../helpers/types' +import { ClrFund, FundingRound } from '../../typechain-types' task('new-round', 'Deploy a new funding round contract') .addFlag('verify', 'Verify contracts at Etherscan') .addFlag('manageNonce', 'Manually increment nonce for each transaction') - .setAction(async ({ verify, manageNonce }: ISubtaskParams, hre) => { - const subtask = Subtask.getInstance(hre) - const storage = ContractStorage.getInstance() - const network = hre.network.name + .addParam( + 'roundDuration', + 'The duration of the funding round in seconds', + undefined, + types.int + ) + .addOptionalParam('clrfund', 'The ClrFund contract address') + .setAction( + async ( + { verify, manageNonce, roundDuration, clrfund }: ISubtaskParams, + hre + ) => { + const subtask = Subtask.getInstance(hre) - subtask.setHre(hre) + subtask.setHre(hre) - if (manageNonce) { - const signer = await subtask.getDeployer() - subtask.setNonceManager(signer) - } + if (manageNonce) { + const signer = await subtask.getDeployer() + subtask.setNonceManager(signer) + } - const deployer = await subtask.getDeployer() + const deployer = await subtask.getDeployer() - const clrfund = storage.mustGetAddress(EContracts.ClrFund, network) - const clrfundContract = await hre.ethers.getContractAt( - 'ClrFund', - clrfund, - deployer - ) + const clrfundContract = await subtask.getContract({ + name: EContracts.ClrFund, + signer: deployer, + address: clrfund, + }) - // check if the current round is finalized before starting a new round to avoid revert - const currentRoundAddress = await clrfundContract.getCurrentRound() - if (currentRoundAddress !== ZERO_ADDRESS) { - const currentRound = await hre.ethers.getContractAt( - EContracts.FundingRound, - currentRoundAddress - ) - const isFinalized = await currentRound.isFinalized() - if (!isFinalized) { - throw new Error( - 'Cannot start a new round as the current round is not finalized' - ) + // check if the current round is finalized before starting a new round to avoid revert + const currentRoundAddress = await clrfundContract.getCurrentRound() + if (currentRoundAddress !== ZERO_ADDRESS) { + const currentRound = await subtask.getContract({ + name: EContracts.FundingRound, + address: currentRoundAddress, + }) + const isFinalized = await currentRound.isFinalized() + if (!isFinalized) { + throw new Error( + 'Cannot start a new round as the current round is not finalized' + ) + } } - } - let success: boolean - try { - await subtask.logStart() - const params: ISubtaskParams = { - manageNonce, - verify, - incremental: false, - } - const steps = await subtask.getDeploySteps(['round'], params) + let success: boolean + try { + await subtask.logStart() + const params: ISubtaskParams = { + manageNonce, + verify, + incremental: false, + roundDuration, + clrfund, + } + const steps = await subtask.getDeploySteps(['round'], params) - const skip = 0 - await subtask.runSteps(steps, skip) - await subtask.checkResults(params.strict) - success = true - } catch (err) { - console.error( - '\n=========================================================\nERROR:', - err, - '\n' - ) - success = false - } + const skip = 0 + await subtask.runSteps(steps, skip) + await subtask.checkResults(params.strict) + success = true + } catch (err) { + console.error( + '\n=========================================================\nERROR:', + err, + '\n' + ) + success = false + } - await subtask.finish(success) + await subtask.finish(success) - if (verify) { - console.log('Verify all contracts') - await hre.run('verify-all') + if (verify) { + console.log('Verify all contracts') + await hre.run('verify-all') + } } - }) + ) diff --git a/contracts/tasks/runners/setCoordinator.ts b/contracts/tasks/runners/setCoordinator.ts index ae75a4527..92da61fda 100644 --- a/contracts/tasks/runners/setCoordinator.ts +++ b/contracts/tasks/runners/setCoordinator.ts @@ -1,11 +1,12 @@ /** * Set the coordinator in clrfund * Usage: - * hardhat set-coordinator \ - * --clrfund \ - * --coordinator \ - * --pubkey \ - * --network + * hardhat set-coordinator --network + * + * Note: + * 1) Make sure you have deploy-config.json (see deploy-config-example.json). + * 2) Make sure you have deployed-contracts.json created from the new-clrfund task + * 3) Make sure that the COORDINATOR_MACISK (coordinator's MACI private key) is set in the .env file */ import { task } from 'hardhat/config' import { Subtask } from '../helpers/Subtask' @@ -24,7 +25,7 @@ task('set-coordinator', 'Set the Clrfund coordinator').setAction( const params: ISubtaskParams = { verify: false, incremental: false } const steps = await subtask.getDeploySteps(['coordinator'], params) - const skip = params.skip || 0 + const skip = 0 await subtask.runSteps(steps, skip) success = true } catch (err) { diff --git a/contracts/tasks/runners/setUserRegistry.ts b/contracts/tasks/runners/setUserRegistry.ts index 9c686e6ca..c8b71c342 100644 --- a/contracts/tasks/runners/setUserRegistry.ts +++ b/contracts/tasks/runners/setUserRegistry.ts @@ -3,21 +3,12 @@ * * Sample usage: * - * yarn hardhat set-user-registry --network \ - * --clrfund \ - * [--type ] \ - * [--registry ] \ - * [--context ] \ - * [--verifier ] \ - * [--sponsor ] + * yarn hardhat set-user-registry --network * - * Valid user registry types are simple, brightid, merkle, storage + * Note: + * 1) Make sure you have deploy-config.json (see deploy-config-example.json). + * 2) Make sure you have deployed-contracts.json created from the new-clrfund task * - * Verifier is the brightid node verifier address. - * Clrfund's brightId node is in the ethSigningAddress field from https://brightid.clr.fund - * - * Context is the bright app id - * The context value can be found here: https://apps.brightid.org/#nodes */ import { task, types } from 'hardhat/config' diff --git a/contracts/tasks/runners/tally.ts b/contracts/tasks/runners/tally.ts index f9ccd1ead..eb135b3e8 100644 --- a/contracts/tasks/runners/tally.ts +++ b/contracts/tasks/runners/tally.ts @@ -1,5 +1,6 @@ /** - * Script for tallying votes + * Script for tallying votes which involves fetching MACI logs, generating proofs, + * and proving on chain * * This script can be rerun by passing in --maci-state-file and --tally-file * If the --maci-state-file is passed, it will skip MACI log fetching diff --git a/contracts/tasks/subtasks/clrfund/02-vkRegistry.ts b/contracts/tasks/subtasks/clrfund/02-vkRegistry.ts index 077debd48..c4a656ee2 100644 --- a/contracts/tasks/subtasks/clrfund/02-vkRegistry.ts +++ b/contracts/tasks/subtasks/clrfund/02-vkRegistry.ts @@ -2,8 +2,6 @@ import type { VkRegistry } from '../../../typechain-types' import { ContractStorage } from '../../helpers/ContractStorage' import { Subtask } from '../../helpers/Subtask' -import { setVerifyingKeys } from '../../../utils/contracts' -import { MaciParameters } from '../../../utils/maciParameters' import { EContracts, type ISubtaskParams } from '../../helpers/types' const subtask = Subtask.getInstance() @@ -13,7 +11,7 @@ const storage = ContractStorage.getInstance() * Deploy step registration and task itself */ subtask - .addTask('clrfund:deploy-vk-registry', 'Deploy Vk Registry and set keys') + .addTask('clrfund:deploy-vk-registry', 'Deploy Vk Registry') .setAction(async ({ incremental }: ISubtaskParams, hre) => { subtask.setHre(hre) const deployer = await subtask.getDeployer() @@ -32,20 +30,6 @@ subtask { signer: deployer } ) - const circuit = subtask.getConfigField( - EContracts.VkRegistry, - 'circuit', - true - ) - const directory = subtask.getConfigField( - EContracts.VkRegistry, - 'paramsDirectory', - true - ) - - const maciParameters = await MaciParameters.fromConfig(circuit, directory) - await setVerifyingKeys(vkRegistryContract, maciParameters) - await storage.register({ id: EContracts.VkRegistry, contract: vkRegistryContract, diff --git a/contracts/tasks/subtasks/clrfund/03-setVkRegsitry.ts b/contracts/tasks/subtasks/clrfund/03-setVkRegsitry.ts new file mode 100644 index 000000000..a3fefb412 --- /dev/null +++ b/contracts/tasks/subtasks/clrfund/03-setVkRegsitry.ts @@ -0,0 +1,68 @@ +import { VkRegistry } from '../../../typechain-types' +import { setVerifyingKeys } from '../../../utils/contracts' +import { MaciParameters } from '../../../utils/maciParameters' +import { Subtask } from '../../helpers/Subtask' +import { EContracts, ISubtaskParams } from '../../helpers/types' + +const subtask = Subtask.getInstance() + +/** + * Deploy step registration and task itself + */ +subtask + .addTask('clrfund:set-vkRegistry', 'Set verifying keys in VkRegistry') + .setAction(async ({ incremental }: ISubtaskParams, hre) => { + subtask.setHre(hre) + const deployer = await subtask.getDeployer() + + const vkRegistryContract = await subtask.getContract({ + name: EContracts.VkRegistry, + signer: deployer, + }) + + const circuit = subtask.getConfigField( + EContracts.VkRegistry, + 'circuit' + ) + const directory = subtask.getConfigField( + EContracts.VkRegistry, + 'paramsDirectory' + ) + + const maciParameters = await MaciParameters.fromConfig(circuit, directory) + const messageBatchSize = maciParameters.getMessageBatchSize() + const stateTreeDepth = maciParameters.stateTreeDepth + const voteOptionTreeDepth = maciParameters.treeDepths.voteOptionTreeDepth + const messageTreeDepth = maciParameters.treeDepths.messageTreeDepth + const intStateTreeDepth = maciParameters.treeDepths.intStateTreeDepth + const hasProcessVk = await vkRegistryContract.hasProcessVk( + stateTreeDepth, + messageTreeDepth, + voteOptionTreeDepth, + messageBatchSize + ) + + const hasTallyVk = await vkRegistryContract.hasTallyVk( + stateTreeDepth, + intStateTreeDepth, + voteOptionTreeDepth + ) + + if (incremental) { + if (hasProcessVk && hasTallyVk) { + console.log('VkRegistry has already been set, skipping...') + return + } + } + + if (hasProcessVk) { + throw new Error('Error: process verification key has already been set') + } + + if (hasTallyVk) { + throw new Error('Error: tally verification key has already been set') + } + + const tx = await setVerifyingKeys(vkRegistryContract, maciParameters) + subtask.logTransaction(tx) + }) diff --git a/contracts/tasks/subtasks/clrfund/03-verifier.ts b/contracts/tasks/subtasks/clrfund/04-verifier.ts similarity index 100% rename from contracts/tasks/subtasks/clrfund/03-verifier.ts rename to contracts/tasks/subtasks/clrfund/04-verifier.ts diff --git a/contracts/tasks/subtasks/clrfund/04-messageProcessorFactory.ts b/contracts/tasks/subtasks/clrfund/05-messageProcessorFactory.ts similarity index 100% rename from contracts/tasks/subtasks/clrfund/04-messageProcessorFactory.ts rename to contracts/tasks/subtasks/clrfund/05-messageProcessorFactory.ts diff --git a/contracts/tasks/subtasks/clrfund/05-tallyFactory.ts b/contracts/tasks/subtasks/clrfund/07-tallyFactory.ts similarity index 100% rename from contracts/tasks/subtasks/clrfund/05-tallyFactory.ts rename to contracts/tasks/subtasks/clrfund/07-tallyFactory.ts diff --git a/contracts/tasks/subtasks/clrfund/07-maciFactory.ts b/contracts/tasks/subtasks/clrfund/08-maciFactory.ts similarity index 100% rename from contracts/tasks/subtasks/clrfund/07-maciFactory.ts rename to contracts/tasks/subtasks/clrfund/08-maciFactory.ts diff --git a/contracts/tasks/subtasks/clrfund/08-fundingRoundFactory.ts b/contracts/tasks/subtasks/clrfund/09-fundingRoundFactory.ts similarity index 100% rename from contracts/tasks/subtasks/clrfund/08-fundingRoundFactory.ts rename to contracts/tasks/subtasks/clrfund/09-fundingRoundFactory.ts diff --git a/contracts/tasks/subtasks/clrfund/09-clrfund.ts b/contracts/tasks/subtasks/clrfund/10-clrfund.ts similarity index 61% rename from contracts/tasks/subtasks/clrfund/09-clrfund.ts rename to contracts/tasks/subtasks/clrfund/10-clrfund.ts index 0cfc7d837..56ac40f35 100644 --- a/contracts/tasks/subtasks/clrfund/09-clrfund.ts +++ b/contracts/tasks/subtasks/clrfund/10-clrfund.ts @@ -24,35 +24,11 @@ subtask return } - const template = subtask.getConfigField( - EContracts.ClrFund, - 'template' - ) - const maciFactoryAddress = storage.mustGetAddress( - EContracts.MACIFactory, - hre.network.name - ) - const fundingRoundFactoryContractAddress = storage.mustGetAddress( - EContracts.FundingRoundFactory, - hre.network.name - ) - const clrfundContract = await subtask.deployContract( EContracts.ClrFund, { signer: deployer } ) - if (!template) { - const tx = await clrfundContract.init( - maciFactoryAddress, - fundingRoundFactoryContractAddress - ) - const receipt = await tx.wait() - if (receipt?.status !== 1) { - throw new Error('Failed to initialize ClrFund') - } - } - await storage.register({ id: EContracts.ClrFund, contract: clrfundContract, diff --git a/contracts/tasks/subtasks/clrfund/11-initClrfund.ts b/contracts/tasks/subtasks/clrfund/11-initClrfund.ts new file mode 100644 index 000000000..415327f52 --- /dev/null +++ b/contracts/tasks/subtasks/clrfund/11-initClrfund.ts @@ -0,0 +1,50 @@ +import { ClrFund } from '../../../typechain-types' +import { ContractStorage } from '../../helpers/ContractStorage' +import { Subtask } from '../../helpers/Subtask' +import { EContracts } from '../../helpers/types' +import { ZERO_ADDRESS } from '../../../utils/constants' + +const subtask = Subtask.getInstance() +const storage = ContractStorage.getInstance() + +/** + * Deploy step registration and task itself + */ +subtask + .addTask('clrfund:init-clrfund', 'Initialize ClrFund') + .setAction(async (_, hre) => { + subtask.setHre(hre) + const deployer = await subtask.getDeployer() + + const clrfundContract = await subtask.getContract({ + name: EContracts.ClrFund, + signer: deployer, + }) + + const clrfundMaciFactoryAddress = await clrfundContract.maciFactory() + if (clrfundMaciFactoryAddress !== ZERO_ADDRESS) { + const clrfundContractAddress = await clrfundContract.getAddress() + console.log( + `Clrfund contract ${clrfundContractAddress} already initialized, skipping...` + ) + return + } + + const maciFactoryAddress = storage.mustGetAddress( + EContracts.MACIFactory, + hre.network.name + ) + const fundingRoundFactoryAddress = storage.mustGetAddress( + EContracts.FundingRoundFactory, + hre.network.name + ) + + const tx = await clrfundContract.init( + maciFactoryAddress, + fundingRoundFactoryAddress + ) + const receipt = await tx.wait() + if (receipt?.status !== 1) { + throw new Error('Failed to initialize ClrFund') + } + }) diff --git a/contracts/tasks/subtasks/index.ts b/contracts/tasks/subtasks/index.ts index d24d48cef..ca942d851 100644 --- a/contracts/tasks/subtasks/index.ts +++ b/contracts/tasks/subtasks/index.ts @@ -1,17 +1,11 @@ import fs from 'fs' import path from 'path' -export const SUBTASK_CATALOGS = [ - 'brightid', - 'clrfund', - 'coordinator', - 'deployer', - 'maci', - 'recipient', - 'round', - 'user', - 'token', -] +// get the names of the directories under subtasks +export const SUBTASK_CATALOGS = fs + .readdirSync(__dirname, { withFileTypes: true }) + .filter((p) => p.isDirectory()) + .map((p) => p.name) /** * The same as individual imports but doesn't require to add new import line every time diff --git a/contracts/tasks/subtasks/round/01-deploy-brightid-user-registry.ts b/contracts/tasks/subtasks/round/01-deploy-brightid-user-registry.ts index ac8eed08c..479f3f7e9 100644 --- a/contracts/tasks/subtasks/round/01-deploy-brightid-user-registry.ts +++ b/contracts/tasks/subtasks/round/01-deploy-brightid-user-registry.ts @@ -19,7 +19,7 @@ subtask .setAction(async (params: ISubtaskParams, hre) => { subtask.setHre(hre) - const userRegistryName = subtask.getConfigField( + const userRegistryName = subtask.tryGetConfigField( EContracts.ClrFund, 'userRegistry' ) diff --git a/contracts/tasks/subtasks/round/02-deploy-round.ts b/contracts/tasks/subtasks/round/02-deploy-round.ts index 8c1210a6c..812bf75b7 100644 --- a/contracts/tasks/subtasks/round/02-deploy-round.ts +++ b/contracts/tasks/subtasks/round/02-deploy-round.ts @@ -2,6 +2,7 @@ * Deploy a funding round */ +import { TaskArguments } from 'hardhat/types' import { ContractStorage } from '../../helpers/ContractStorage' import { Subtask } from '../../helpers/Subtask' import { EContracts } from '../../../utils/types' @@ -13,6 +14,7 @@ import { Tally, } from '../../../typechain-types' import { ContractTransactionResponse } from 'ethers' +import { ISubtaskParams } from '../../helpers/types' const subtask = Subtask.getInstance() const storage = ContractStorage.getInstance() @@ -197,25 +199,40 @@ async function registerTallyAndMessageProcessor( }) } +/** + * Get the hardhat task params + * + * @param {ISubtaskParams} params - hardhat task arguments + * @returns {Promise} params for deploy workflow + */ +async function getParams({ + verify, + incremental, + roundDuration, + clrfund, +}: ISubtaskParams): Promise { + return Promise.resolve({ verify, incremental, clrfund, roundDuration }) +} + /** * Deploy step registration and task itself */ subtask - .addTask('round:deploy-round', 'Deploy a funding round') - .setAction(async (_, hre) => { + .addTask('round:deploy-round', 'Deploy a funding round', getParams) + .setAction(async ({ roundDuration, clrfund }, hre) => { subtask.setHre(hre) const network = hre.network.name - const duration = subtask.getConfigField( - EContracts.FundingRound, - 'duration' - ) + if (!roundDuration) { + throw new Error('Missing the roundDuration subtask parameter') + } const clrfundContract = await subtask.getContract({ name: EContracts.ClrFund, + address: clrfund, }) - const tx = await clrfundContract.deployNewRound(duration) + const tx = await clrfundContract.deployNewRound(roundDuration) const receipt = await tx.wait() if (receipt?.status !== 1) { throw new Error('Failed to deploy funding round') @@ -231,6 +248,6 @@ subtask await registerFundingRound(fundingRoundContract, network, tx) await registerMaci(fundingRoundContract, network, tx) - await registerPoll(duration, fundingRoundContract, network, tx) + await registerPoll(roundDuration, fundingRoundContract, network, tx) await registerTallyAndMessageProcessor(fundingRoundContract, network, tx) }) diff --git a/docs/brightid.md b/docs/brightid.md index 136a4067e..efd01636d 100644 --- a/docs/brightid.md +++ b/docs/brightid.md @@ -37,6 +37,7 @@ BRIGHTID_CONTEXT={CONTEXT} Note: the BrightID context is specific to the BrightID network - it's independent from the Ethereum network you choose to run the app on. It refers to the BrightID app context where you want to burn sponsorship tokens. The `Sponsor Contract` is the contract set up in the BrightID node to track the sponsorship event. +The BrightID context can be found here: https://apps.brightid.org/#nodes [Learn more about context in the BrightID docs](https://dev.brightid.org/docs/guides/ZG9jOjQxNTE1NDU-basic-integration). From a16b13380e2dabc8547396cfbe6b85d81a25d2a0 Mon Sep 17 00:00:00 2001 From: yuetloo Date: Tue, 19 Mar 2024 21:22:01 -0400 Subject: [PATCH 07/17] fix typo --- contracts/tasks/subtasks/coordinator/01-coordinator.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/tasks/subtasks/coordinator/01-coordinator.ts b/contracts/tasks/subtasks/coordinator/01-coordinator.ts index c4430aff1..e13a14d21 100644 --- a/contracts/tasks/subtasks/coordinator/01-coordinator.ts +++ b/contracts/tasks/subtasks/coordinator/01-coordinator.ts @@ -24,7 +24,7 @@ subtask const coordinator = await subtask.getConfigField( EContracts.ClrFund, - 'coordinaotor' + 'coordinator' ) const coordinatorAddress = coordinator || (await deployer.getAddress()) From f35c26795a8b3ba8ea73ae7ec696362de3963f04 Mon Sep 17 00:00:00 2001 From: yuetloo Date: Tue, 19 Mar 2024 21:36:02 -0400 Subject: [PATCH 08/17] add optional clrfund param --- contracts/tasks/runners/setCoordinator.ts | 14 +++++++++----- .../tasks/subtasks/coordinator/01-coordinator.ts | 10 ++++++++-- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/contracts/tasks/runners/setCoordinator.ts b/contracts/tasks/runners/setCoordinator.ts index 92da61fda..68edb777f 100644 --- a/contracts/tasks/runners/setCoordinator.ts +++ b/contracts/tasks/runners/setCoordinator.ts @@ -12,8 +12,9 @@ import { task } from 'hardhat/config' import { Subtask } from '../helpers/Subtask' import { ISubtaskParams } from '../helpers/types' -task('set-coordinator', 'Set the Clrfund coordinator').setAction( - async (_, hre) => { +task('set-coordinator', 'Set the Clrfund coordinator') + .addOptionalParam('clrfund', 'The ClrFund contract address') + .setAction(async ({ clrfund }, hre) => { const subtask = Subtask.getInstance(hre) subtask.setHre(hre) @@ -22,7 +23,11 @@ task('set-coordinator', 'Set the Clrfund coordinator').setAction( await subtask.logStart() // set incremental to avoid resetting contract de - const params: ISubtaskParams = { verify: false, incremental: false } + const params: ISubtaskParams = { + verify: false, + incremental: false, + clrfund, + } const steps = await subtask.getDeploySteps(['coordinator'], params) const skip = 0 @@ -38,5 +43,4 @@ task('set-coordinator', 'Set the Clrfund coordinator').setAction( } await subtask.finish(success) - } -) + }) diff --git a/contracts/tasks/subtasks/coordinator/01-coordinator.ts b/contracts/tasks/subtasks/coordinator/01-coordinator.ts index e13a14d21..a963e68e6 100644 --- a/contracts/tasks/subtasks/coordinator/01-coordinator.ts +++ b/contracts/tasks/subtasks/coordinator/01-coordinator.ts @@ -6,6 +6,7 @@ import { Subtask } from '../../helpers/Subtask' import { EContracts } from '../../../utils/types' import { Contract, getAddress } from 'ethers' import { Keypair, PrivKey, PubKey } from '@clrfund/common' +import { ISubtaskParams } from '../../helpers/types' const subtask = Subtask.getInstance() @@ -13,13 +14,18 @@ const subtask = Subtask.getInstance() * Deploy step registration and task itself */ subtask - .addTask('coordinator:set-coordinator', 'Set the clrfund coordinator') - .setAction(async (_, hre) => { + .addTask( + 'coordinator:set-coordinator', + 'Set the clrfund coordinator', + ({ clrfund }) => Promise.resolve({ clrfund }) + ) + .setAction(async ({ clrfund }: ISubtaskParams, hre) => { subtask.setHre(hre) const deployer = await subtask.getDeployer() const clrfundContract = await subtask.getContract({ name: EContracts.ClrFund, + address: clrfund, }) const coordinator = await subtask.getConfigField( From cce45e5b962fc88097f85e8d6cfff7a8812803f0 Mon Sep 17 00:00:00 2001 From: yuetloo Date: Wed, 20 Mar 2024 15:44:03 -0400 Subject: [PATCH 09/17] add clrfund switch to command lines --- contracts/tasks/helpers/Subtask.ts | 9 ++++++--- contracts/tasks/runners/setCoordinator.ts | 8 +++++--- .../tasks/runners/setRecipientRegistry.ts | 18 ++++++++++-------- contracts/tasks/runners/setToken.ts | 11 ++++++++--- contracts/tasks/runners/setUserRegistry.ts | 11 ++++++----- .../subtasks/coordinator/01-coordinator.ts | 2 +- .../recipient/01-simpleRecipientRegistry.ts | 11 ++++++----- .../02-optimisticRecipientRegistry.ts | 10 +++------- .../recipient/99-setRecipientRegistry.ts | 3 ++- .../subtasks/token/01-anyOldERC20Token.ts | 13 ++++++------- contracts/tasks/subtasks/token/02-setToken.ts | 10 ++++++++-- .../tasks/subtasks/user/03-brightidSponsor.ts | 9 +++++++++ .../tasks/subtasks/user/99-setUserRegistry.ts | 3 ++- 13 files changed, 72 insertions(+), 46 deletions(-) diff --git a/contracts/tasks/helpers/Subtask.ts b/contracts/tasks/helpers/Subtask.ts index ccbac3796..2a79fc670 100644 --- a/contracts/tasks/helpers/Subtask.ts +++ b/contracts/tasks/helpers/Subtask.ts @@ -88,8 +88,9 @@ export class Subtask { try { this.config = JSONFile.read(DEPLOY_CONFIG) as TConfig } catch (e) { - //console.log('eror =======================', e) - this.config = {} as TConfig + console.log('=======================') + console.log('Failed to read', DEPLOY_CONFIG) + throw e } this.storage = ContractStorage.getInstance() @@ -384,8 +385,10 @@ export class Subtask { private getDefaultParams = ({ verify, incremental, + clrfund, + roundDuration, }: ISubtaskParams): Promise => - Promise.resolve({ verify, incremental }) + Promise.resolve({ verify, incremental, clrfund, roundDuration }) /** * Get deploy step sequence diff --git a/contracts/tasks/runners/setCoordinator.ts b/contracts/tasks/runners/setCoordinator.ts index 68edb777f..10408dd6d 100644 --- a/contracts/tasks/runners/setCoordinator.ts +++ b/contracts/tasks/runners/setCoordinator.ts @@ -1,12 +1,14 @@ /** * Set the coordinator in clrfund + * * Usage: * hardhat set-coordinator --network * * Note: - * 1) Make sure you have deploy-config.json (see deploy-config-example.json). - * 2) Make sure you have deployed-contracts.json created from the new-clrfund task - * 3) Make sure that the COORDINATOR_MACISK (coordinator's MACI private key) is set in the .env file + * 1) The script will use the signer address as the coordinator address if + * it is not configured in the clrfund.coordinator field in the deploy-config.json file (see deploy-config-example.json). + * 2) Use --clrfund to specify the clrfund address if you do not have the deployed-contracts.json file + * 3) Make sure that the COORDINATOR_MACISK (the coordinator's MACI private key) is set in the .env file */ import { task } from 'hardhat/config' import { Subtask } from '../helpers/Subtask' diff --git a/contracts/tasks/runners/setRecipientRegistry.ts b/contracts/tasks/runners/setRecipientRegistry.ts index cb2ace2f8..69f4b1412 100644 --- a/contracts/tasks/runners/setRecipientRegistry.ts +++ b/contracts/tasks/runners/setRecipientRegistry.ts @@ -1,16 +1,18 @@ /* eslint-disable no-console */ /** - * Deploy a new instance of ClrFund - * - * Make sure you have deploy-config.json (see deploy-config-example.json). + * Set the recipient registry in the ClrFund contract. It will create + * the recipient registry contract if it is not deployed and recorded in the + * deployed-contract.json file * * Sample usage: - * yarn hardhat new-clrfund --verify --network + * yarn hardhat set-recipient-registry --verify --network * * Note: - * 1) use --incremental to resume a deployment stopped due to a failure - * 2) use --manage-nonce to manually set nonce, useful on optimism-sepolia - * where `nonce too low` errors occur occasionally + * 1) use --incremental to resume a previously interrupted deployment + * 2) use --manage-nonce to manually set the nonce. This is useful on the optimism-sepolia + * public node where `nonce too low` errors occur occasionally + * 3) use --clrfund to provide the clrfund address if you do not have the deployed-contracts.json file + * 4) Make sure you have the deploy-config.json file (see deploy-config-example.json). */ import { task, types } from 'hardhat/config' @@ -22,7 +24,7 @@ task('set-recipient-registry', 'Set recipient registry in ClrFund') .addFlag('strict', 'Fail on warnings') .addFlag('verify', 'Verify contracts at Etherscan') .addFlag('manageNonce', 'Manually increment nonce for each transaction') - .addOptionalParam('skip', 'Skip steps with less or equal index', 0, types.int) + .addOptionalParam('clrfund', 'The ClrFund contract address') .setAction(async (params: ISubtaskParams, hre) => { const { verify, manageNonce } = params const subtask = Subtask.getInstance(hre) diff --git a/contracts/tasks/runners/setToken.ts b/contracts/tasks/runners/setToken.ts index 71459c2ab..f9fc9272a 100644 --- a/contracts/tasks/runners/setToken.ts +++ b/contracts/tasks/runners/setToken.ts @@ -1,10 +1,14 @@ /** - * Set the native token in the ClrFund contract - * - * Make sure you have deploy-config.json (see deploy-config-example.json). + * Set the native token in the ClrFund contract, create a test token + * if a token address is not configured in the ClrFund.token field in + * the deploy-config.json file * * Sample usage: * yarn hardhat set-token --network + * + * Notes: + * 1) Make sure you have the deploy-config.json file (see deploy-config-example.json). + * 2) Use --clrfund
to provide the clrfund address if deployed-contracts.json does not exist */ import { task, types } from 'hardhat/config' @@ -16,6 +20,7 @@ task('set-token', 'Set the token in ClrFund') .addFlag('strict', 'Fail on warnings') .addFlag('verify', 'Verify contracts at Etherscan') .addFlag('manageNonce', 'Manually increment nonce for each transaction') + .addOptionalParam('clrfund', 'The ClrFund contract address') .addOptionalParam('skip', 'Skip steps with less or equal index', 0, types.int) .setAction(async (params: ISubtaskParams, hre) => { const { verify, manageNonce } = params diff --git a/contracts/tasks/runners/setUserRegistry.ts b/contracts/tasks/runners/setUserRegistry.ts index c8b71c342..c8f9487a5 100644 --- a/contracts/tasks/runners/setUserRegistry.ts +++ b/contracts/tasks/runners/setUserRegistry.ts @@ -1,13 +1,14 @@ /** - * Set the user registry in the ClrFund contract. + * Set the user registry in the ClrFund contract. It will create the user registry + * contract if it is not deployed and found in the deployed-contracts.json file. * * Sample usage: * - * yarn hardhat set-user-registry --network + * yarn hardhat set-user-registry --verify --network * * Note: - * 1) Make sure you have deploy-config.json (see deploy-config-example.json). - * 2) Make sure you have deployed-contracts.json created from the new-clrfund task + * 1) Make sure you have the deploy-config.json (see deploy-config-example.json). + * 2) Use --clrfund to specify clrfund address if you don't have the deployed-contracts.json * */ @@ -22,6 +23,7 @@ task('set-user-registry', 'Set the user registry in ClrFund') .addFlag('verify', 'Verify contracts at Etherscan') .addFlag('manageNonce', 'Manually increment nonce for each transaction') .addOptionalParam('skip', 'Skip steps with less or equal index', 0, types.int) + .addOptionalParam('clrfund', 'The ClrFund contract address') .setAction(async (params: ISubtaskParams, hre) => { const { verify, manageNonce } = params const subtask = Subtask.getInstance(hre) @@ -39,7 +41,6 @@ task('set-user-registry', 'Set the user registry in ClrFund') const steps = await subtask.getDeploySteps(['user'], params) const skip = params.skip || 0 - await subtask.runSteps(steps, skip) await subtask.checkResults(params.strict) success = true diff --git a/contracts/tasks/subtasks/coordinator/01-coordinator.ts b/contracts/tasks/subtasks/coordinator/01-coordinator.ts index a963e68e6..3b2f0b767 100644 --- a/contracts/tasks/subtasks/coordinator/01-coordinator.ts +++ b/contracts/tasks/subtasks/coordinator/01-coordinator.ts @@ -28,7 +28,7 @@ subtask address: clrfund, }) - const coordinator = await subtask.getConfigField( + const coordinator = await subtask.tryGetConfigField( EContracts.ClrFund, 'coordinator' ) diff --git a/contracts/tasks/subtasks/recipient/01-simpleRecipientRegistry.ts b/contracts/tasks/subtasks/recipient/01-simpleRecipientRegistry.ts index 968d4f2c1..ee072465b 100644 --- a/contracts/tasks/subtasks/recipient/01-simpleRecipientRegistry.ts +++ b/contracts/tasks/subtasks/recipient/01-simpleRecipientRegistry.ts @@ -18,7 +18,7 @@ subtask 'recipient:deploy-simple-recipient-registry', 'Deploy a simple recipient regsitry' ) - .setAction(async ({ incremental }: ISubtaskParams, hre) => { + .setAction(async ({ incremental, clrfund }: ISubtaskParams, hre) => { subtask.setHre(hre) const deployer = await subtask.getDeployer() @@ -40,11 +40,12 @@ subtask return } - const clrfundContractAddress = storage.mustGetAddress( - EContracts.ClrFund, - hre.network.name - ) + const clrfundContract = await subtask.getContract({ + name: EContracts.ClrFund, + address: clrfund, + }) + const clrfundContractAddress = await clrfundContract.getAddress() const args = [clrfundContractAddress] const simpleRecipientRegistryContract = await subtask.deployContract( EContracts.SimpleRecipientRegistry, diff --git a/contracts/tasks/subtasks/recipient/02-optimisticRecipientRegistry.ts b/contracts/tasks/subtasks/recipient/02-optimisticRecipientRegistry.ts index 2ccfb74b2..4db14252f 100644 --- a/contracts/tasks/subtasks/recipient/02-optimisticRecipientRegistry.ts +++ b/contracts/tasks/subtasks/recipient/02-optimisticRecipientRegistry.ts @@ -40,7 +40,7 @@ subtask 'recipient:deploy-optimistic-recipient-registry', 'Deploy an optimistic recipient regsitry' ) - .setAction(async ({ incremental }: ISubtaskParams, hre) => { + .setAction(async ({ incremental, clrfund }: ISubtaskParams, hre) => { subtask.setHre(hre) const deployer = await subtask.getDeployer() const network = hre.network.name @@ -72,17 +72,13 @@ subtask 'challengePeriodSeconds' ) - const clrfundContractAddress = storage.mustGetAddress( - EContracts.ClrFund, - network - ) - const clrfundContract = await subtask.getContract({ name: EContracts.ClrFund, - address: clrfundContractAddress, + address: clrfund, }) const decimals = await getTokenDecimals(clrfundContract) + const clrfundContractAddress = await clrfundContract.getAddress() const args = [ parseUnits(deposit, decimals), challengePeriodSeconds || defaultChallengePeriodSeconds, diff --git a/contracts/tasks/subtasks/recipient/99-setRecipientRegistry.ts b/contracts/tasks/subtasks/recipient/99-setRecipientRegistry.ts index 9565dd8b3..4bbc52faa 100644 --- a/contracts/tasks/subtasks/recipient/99-setRecipientRegistry.ts +++ b/contracts/tasks/subtasks/recipient/99-setRecipientRegistry.ts @@ -20,7 +20,7 @@ subtask 'recipient:set-recipient-registry', 'Set recipient registry in the ClrFund contract' ) - .setAction(async ({ incremental }: ISubtaskParams, hre) => { + .setAction(async ({ incremental, clrfund }: ISubtaskParams, hre) => { subtask.setHre(hre) const network = hre.network.name @@ -36,6 +36,7 @@ subtask const clrfundContract = await subtask.getContract({ name: EContracts.ClrFund, + address: clrfund, }) if (incremental) { diff --git a/contracts/tasks/subtasks/token/01-anyOldERC20Token.ts b/contracts/tasks/subtasks/token/01-anyOldERC20Token.ts index d53c33073..dc1fcaf06 100644 --- a/contracts/tasks/subtasks/token/01-anyOldERC20Token.ts +++ b/contracts/tasks/subtasks/token/01-anyOldERC20Token.ts @@ -20,17 +20,12 @@ subtask subtask.setHre(hre) const deployer = await subtask.getDeployer() - const token = subtask.getConfigField(EContracts.ClrFund, 'token') - if (isAddress(token)) { + const token = subtask.tryGetConfigField(EContracts.ClrFund, 'token') + if (token && isAddress(token)) { // using an existing token, no need to deploy return } - const initialSupply = subtask.getConfigField( - EContracts.AnyOldERC20Token, - 'initialSupply' - ) - const anyOldERC20TokenContractAddress = storage.getAddress( EContracts.AnyOldERC20Token, hre.network.name @@ -40,6 +35,10 @@ subtask return } + const initialSupply = subtask.getConfigField( + EContracts.AnyOldERC20Token, + 'initialSupply' + ) const args = [initialSupply] const anyOldERC20TokenContract = await subtask.deployContract( EContracts.AnyOldERC20Token, diff --git a/contracts/tasks/subtasks/token/02-setToken.ts b/contracts/tasks/subtasks/token/02-setToken.ts index a728aed7b..72a75557e 100644 --- a/contracts/tasks/subtasks/token/02-setToken.ts +++ b/contracts/tasks/subtasks/token/02-setToken.ts @@ -17,14 +17,16 @@ const storage = ContractStorage.getInstance() */ subtask .addTask('token:set-token', 'Set token in the ClrFund contract') - .setAction(async ({ incremental }: ISubtaskParams, hre) => { + .setAction(async ({ incremental, clrfund }: ISubtaskParams, hre) => { subtask.setHre(hre) const network = hre.network.name + const deployer = await subtask.getDeployer() - let tokenAddress = subtask.getConfigField( + let tokenAddress = subtask.tryGetConfigField( EContracts.ClrFund, 'token' ) + if (!tokenAddress) { tokenAddress = storage.mustGetAddress( EContracts.AnyOldERC20Token, @@ -34,6 +36,8 @@ subtask const clrfundContract = await subtask.getContract({ name: EContracts.ClrFund, + address: clrfund, + signer: deployer, }) if (incremental) { @@ -49,4 +53,6 @@ subtask if (receipt?.status !== 1) { throw new Error('Failed to set token') } + + subtask.logTransaction(tx) }) diff --git a/contracts/tasks/subtasks/user/03-brightidSponsor.ts b/contracts/tasks/subtasks/user/03-brightidSponsor.ts index bd7759cf6..9482a449e 100644 --- a/contracts/tasks/subtasks/user/03-brightidSponsor.ts +++ b/contracts/tasks/subtasks/user/03-brightidSponsor.ts @@ -18,6 +18,15 @@ subtask subtask.setHre(hre) const deployer = await subtask.getDeployer() + const userRegistryName = subtask.getConfigField( + EContracts.ClrFund, + 'userRegistry' + ) + + if (userRegistryName !== EContracts.BrightIdUserRegistry) { + return + } + let brightidSponsorContractAddress = subtask.tryGetConfigField( EContracts.BrightIdUserRegistry, 'sponsor' diff --git a/contracts/tasks/subtasks/user/99-setUserRegistry.ts b/contracts/tasks/subtasks/user/99-setUserRegistry.ts index 5591eaa16..3589a1227 100644 --- a/contracts/tasks/subtasks/user/99-setUserRegistry.ts +++ b/contracts/tasks/subtasks/user/99-setUserRegistry.ts @@ -20,7 +20,7 @@ subtask 'user:set-user-registry', 'Set user registry in the ClrFund contract' ) - .setAction(async ({ incremental }: ISubtaskParams, hre) => { + .setAction(async ({ incremental, clrfund }: ISubtaskParams, hre) => { subtask.setHre(hre) const network = hre.network.name @@ -36,6 +36,7 @@ subtask const clrfundContract = await subtask.getContract({ name: EContracts.ClrFund, + address: clrfund, }) if (incremental) { From f1801f0d53ac19765cd5e3d248e4ce9babf43782 Mon Sep 17 00:00:00 2001 From: yuetloo Date: Wed, 20 Mar 2024 18:56:51 -0400 Subject: [PATCH 10/17] update documentation --- docs/deployment.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/deployment.md b/docs/deployment.md index 7a0026050..d0c3f585d 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -83,6 +83,8 @@ Use the `-h` switch to print the command line help menu for all the scripts in t yarn hardhat new-clrfund --network ``` +Notice that the file `deployed-contracts.json` is created or updated (if already exists). Make a copy of this file now in case you run the `new-clrfund` command without the --incremental flag, this file will be overwritten. You'll need this file for the `new-round` and `verify-all` commands. + 2. deploy new funding round ``` yarn hardhat new-round --network @@ -98,7 +100,7 @@ yarn hardhat load-simple-users --file-path addresses.txt --user-registry Date: Wed, 20 Mar 2024 21:45:38 -0400 Subject: [PATCH 11/17] fix missing round duration --- contracts/sh/runScriptTests.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/sh/runScriptTests.sh b/contracts/sh/runScriptTests.sh index bff6b4e07..f03a47bf9 100755 --- a/contracts/sh/runScriptTests.sh +++ b/contracts/sh/runScriptTests.sh @@ -11,6 +11,7 @@ export OUTPUT_DIR="./proof_output/${NOW}" export TALLY_FILE=${OUTPUT_DIR}/tally.json export HARDHAT_NETWORK=localhost export RAPID_SNARK=${RAPID_SNARK:-~/rapidsnark/package/bin/prover} +export ROUND_DURATION=1000 mkdir -p ${OUTPUT_DIR} @@ -25,12 +26,11 @@ export COORDINATOR_MACISK=$(echo "${MACI_KEYPAIR}" | grep -o "macisk.*$") yarn hardhat new-clrfund --network ${HARDHAT_NETWORK} # deploy a new funding round -yarn hardhat new-round --network ${HARDHAT_NETWORK} +yarn hardhat new-round --round-duration ${ROUND_DURATION} --network ${HARDHAT_NETWORK} yarn hardhat add-recipients --network ${HARDHAT_NETWORK} yarn hardhat contribute --network ${HARDHAT_NETWORK} -ROUND_DURATION=$(node -e 'const config=JSON.parse(fs.readFileSync(`./deploy-config.json`).toString()); console.log(config.localhost?.FundingRound?.duration || 1000)') yarn hardhat time-travel --seconds ${ROUND_DURATION} --network ${HARDHAT_NETWORK} # run the tally script From c16eb5a193f1f0a8771deddc984bd3b05cb31249 Mon Sep 17 00:00:00 2001 From: yuetloo Date: Wed, 20 Mar 2024 22:50:18 -0400 Subject: [PATCH 12/17] remove obsolete field --- contracts/deploy-config-example.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/contracts/deploy-config-example.json b/contracts/deploy-config-example.json index 65086870d..77032cfed 100644 --- a/contracts/deploy-config-example.json +++ b/contracts/deploy-config-example.json @@ -5,7 +5,6 @@ "paramsDirectory": "./params" }, "ClrFund": { - "template": false, "coordinator": "", "token": "", "userRegistry": "SimpleUserRegistry", @@ -30,7 +29,6 @@ "paramsDirectory": "./params" }, "ClrFund": { - "template": false, "coordinator": "", "token": "0x65bc8dd04808d99cf8aa6749f128d55c2051edde", "userRegistry": "BrightIdUserRegistry", From 4bdd92369a7d405f9833e14095781be5989c6ea5 Mon Sep 17 00:00:00 2001 From: yuetloo Date: Wed, 20 Mar 2024 23:23:24 -0400 Subject: [PATCH 13/17] add input parameters --- .github/workflows/cancel-round.yml | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/.github/workflows/cancel-round.yml b/.github/workflows/cancel-round.yml index 0c37001aa..5091547fe 100644 --- a/.github/workflows/cancel-round.yml +++ b/.github/workflows/cancel-round.yml @@ -1,12 +1,23 @@ name: Cancel current round -on: workflow_dispatch +on: + workflow_dispatch: + inputs: + clrfund: + description: 'Clrfund contract address' + required: true + default: '0x87790498127ff044f43b9230506833ca89113757' + network: + description: 'Network' + required: true + default: 'arbitrum-sepolia' + jsonrpc_url: + description: jsonrpc url to node + required: false env: NODE_VERSION: 20.x - SUBGRPAH_URL: "https://api.thegraph.com/subgraphs/name/clrfund/clrfund-testnet" WALLET_PRIVATE_KEY: ${{ secrets.ARBITRUM_GOERLI_COORDINATOR_WALLET_PRIVATE_KEY }} - NETWORK: "arbitrum-sepolia" jobs: cancel-round: @@ -26,6 +37,6 @@ jobs: - name: Run the cancel round script run: | cd contracts - export CLRFUND=$(curl -X POST -d '{"query":"{clrFunds {id}}"}' $SUBGRPAH_URL) - export CLRFUND_ADDRESS=$(node -e 'console.log(JSON.parse(process.env.CLRFUND).data.clrFunds[0].id)') - yarn hardhat cancel-round --clrfund "${CLRFUND_ADDRESS}" --network ${NETWORK} + yarn hardhat cancel-round \ + --clrfund ${{ github.event.inputs.clrfund }} \ + --network ${{ github.event.inputs.network }} From 64dbc9fefa27131e572072dd3b6c40ab0e796f33 Mon Sep 17 00:00:00 2001 From: yuetloo Date: Thu, 21 Mar 2024 00:20:31 -0400 Subject: [PATCH 14/17] update documentation --- docs/tally-verify.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/tally-verify.md b/docs/tally-verify.md index 736d0a7ec..b2edd2a50 100644 --- a/docs/tally-verify.md +++ b/docs/tally-verify.md @@ -13,7 +13,7 @@ Set the following env vars in `/contracts/.env`: ``` # private key for decrypting messages -COORDINATOR_MACISK= +COORDINATOR_MACISK= # private key for interacting with contracts WALLET_MNEMONIC= @@ -23,14 +23,16 @@ WALLET_PRIVATE_KEY Decrypt messages and tally the votes: ``` -yarn hardhat tally --rapidsnark ${RAPID_SNARK} --output-dir ${OUTPUT_DIR} --network +yarn hardhat tally --rapidsnark {RAPID_SNARK} --output-dir {OUTPUT_DIR} --network {network} ``` +You only need to provide `--rapidsnark` if you are running the `tally` command on an intel chip. + If there's error and the tally task was stopped prematurely, it can be resumed by passing 2 additional parameters, '--tally-file' and/or '--maci-state-file', if the files were generated. ``` # for rerun -yarn hardhat tally --maci-state-file --tally-file --network +yarn hardhat tally --maci-state-file {maci-state.json} --tally-file {tally.json} --output-dir {OUTPUT_DIR} --network {network} ``` Result will be saved to `tally.json` file, which must then be published via IPFS. @@ -58,7 +60,7 @@ WALLET_PRIVATE_KEY= Once you have the `tally.json` from the tally script, run: ``` -yarn hardhat finalize --tally-file --network +yarn hardhat finalize --tally-file {tally.json} --network {network} ``` # How to verify the tally results @@ -68,7 +70,7 @@ Anyone can verify the tally results in the tally.json. From the clrfund contracts folder, run the following command to verify the result: ``` -yarn hardhat verify-tally-file --tally-file --network +yarn hardhat verify-tally-file --tally-file {tally.json} --network {network} ``` # How to enable the leaderboard view @@ -81,7 +83,7 @@ After finalizing the round, enable the leaderboard view in the vue-app by export ```sh cd contracts -yarn hardhat export-round --output-dir ../vue-app/src/rounds --network --round-address --operator --start-block --ipfs +yarn hardhat export-round --output-dir ../vue-app/src/rounds --network {network} --round-address {round_address} --operator {operator} --start-block {recipient-registry-start-block} --ipfs {ipfs-gateway-url} ``` 3) Build and deploy the app From c50193021cd3dbcbf521f0378e760f15e8d630ab Mon Sep 17 00:00:00 2001 From: yuetloo Date: Thu, 21 Mar 2024 11:14:45 -0400 Subject: [PATCH 15/17] update to use node v20 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b2bf0f7c7..470a4e1b7 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ In a future version, we plan to address this by routing ETH and token contributi ## Development -### Install Node v18 with nvm +### Install Node v20 with nvm ```sh nvm install 20 From 6371bd5895fafcde29b2a19c60caece9a33e0ca6 Mon Sep 17 00:00:00 2001 From: yuetloo Date: Thu, 21 Mar 2024 11:25:46 -0400 Subject: [PATCH 16/17] add --skip option --- contracts/tasks/runners/newRound.ts | 115 ++++++++++++++-------------- 1 file changed, 56 insertions(+), 59 deletions(-) diff --git a/contracts/tasks/runners/newRound.ts b/contracts/tasks/runners/newRound.ts index acbe22e2a..51ece6374 100644 --- a/contracts/tasks/runners/newRound.ts +++ b/contracts/tasks/runners/newRound.ts @@ -26,73 +26,70 @@ task('new-round', 'Deploy a new funding round contract') types.int ) .addOptionalParam('clrfund', 'The ClrFund contract address') - .setAction( - async ( - { verify, manageNonce, roundDuration, clrfund }: ISubtaskParams, - hre - ) => { - const subtask = Subtask.getInstance(hre) + .addOptionalParam('skip', 'Skip steps with less or equal index', 0, types.int) + .setAction(async (params: ISubtaskParams, hre) => { + const { verify, manageNonce, roundDuration, clrfund } = params + const subtask = Subtask.getInstance(hre) - subtask.setHre(hre) + subtask.setHre(hre) - if (manageNonce) { - const signer = await subtask.getDeployer() - subtask.setNonceManager(signer) - } + if (manageNonce) { + const signer = await subtask.getDeployer() + subtask.setNonceManager(signer) + } - const deployer = await subtask.getDeployer() + const deployer = await subtask.getDeployer() - const clrfundContract = await subtask.getContract({ - name: EContracts.ClrFund, - signer: deployer, - address: clrfund, - }) + const clrfundContract = await subtask.getContract({ + name: EContracts.ClrFund, + signer: deployer, + address: clrfund, + }) - // check if the current round is finalized before starting a new round to avoid revert - const currentRoundAddress = await clrfundContract.getCurrentRound() - if (currentRoundAddress !== ZERO_ADDRESS) { - const currentRound = await subtask.getContract({ - name: EContracts.FundingRound, - address: currentRoundAddress, - }) - const isFinalized = await currentRound.isFinalized() - if (!isFinalized) { - throw new Error( - 'Cannot start a new round as the current round is not finalized' - ) - } + // check if the current round is finalized before starting a new round to avoid revert + const currentRoundAddress = await clrfundContract.getCurrentRound() + if (currentRoundAddress !== ZERO_ADDRESS) { + const currentRound = await subtask.getContract({ + name: EContracts.FundingRound, + address: currentRoundAddress, + }) + const isFinalized = await currentRound.isFinalized() + if (!isFinalized) { + throw new Error( + 'Cannot start a new round as the current round is not finalized' + ) } + } - let success: boolean - try { - await subtask.logStart() - const params: ISubtaskParams = { - manageNonce, - verify, - incremental: false, - roundDuration, - clrfund, - } - const steps = await subtask.getDeploySteps(['round'], params) - - const skip = 0 - await subtask.runSteps(steps, skip) - await subtask.checkResults(params.strict) - success = true - } catch (err) { - console.error( - '\n=========================================================\nERROR:', - err, - '\n' - ) - success = false + let success: boolean + try { + await subtask.logStart() + const params: ISubtaskParams = { + manageNonce, + verify, + incremental: false, + roundDuration, + clrfund, } + const steps = await subtask.getDeploySteps(['round'], params) + + const skip = params.skip || 0 + await subtask.runSteps(steps, skip) + await subtask.checkResults(params.strict) + success = true + } catch (err) { + console.error( + '\n=========================================================\nERROR:', + err, + '\n' + ) + success = false + } - await subtask.finish(success) + await subtask.finish(success) - if (verify) { - console.log('Verify all contracts') - await hre.run('verify-all') - } + if (verify) { + console.log('Verify all contracts') + await hre.run('verify-all') } - ) + }) From 24d7117d29af86c070b6c1f27b76d26ab9cb2998 Mon Sep 17 00:00:00 2001 From: yuetloo Date: Thu, 21 Mar 2024 12:31:08 -0400 Subject: [PATCH 17/17] fix typo --- contracts/tasks/runners/finalize.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/tasks/runners/finalize.ts b/contracts/tasks/runners/finalize.ts index 81f45ce94..0240ba16b 100644 --- a/contracts/tasks/runners/finalize.ts +++ b/contracts/tasks/runners/finalize.ts @@ -37,14 +37,14 @@ task('finalize', 'Finalize a funding round') await subtask.logStart() - const clrfundcontractAddress = + const clrfundContractAddress = clrfund ?? storage.mustGetAddress(EContracts.ClrFund, network.name) const clrfundContract = await ethers.getContractAt( EContracts.ClrFund, - clrfundcontractAddress + clrfundContractAddress ) - console.log('ClrFund address', clrfund) + console.log('ClrFund address', clrfundContractAddress) const currentRoundAddress = await clrfundContract.getCurrentRound() const fundingRound = await ethers.getContractAt(