From 8f451fa996205d20f596ece2565336e74f255b22 Mon Sep 17 00:00:00 2001 From: yuetloo Date: Wed, 17 Jan 2024 01:14:10 -0500 Subject: [PATCH] fix unit test cases --- common/package.json | 4 +- contracts/cli/newClrFund.ts | 8 +- contracts/contracts/ClrFund.sol | 4 + contracts/contracts/FundingRound.sol | 136 +-- contracts/contracts/FundingRoundFactory.sol | 30 +- contracts/contracts/MACIFactory.sol | 21 +- .../contracts/interfaces/IMACIFactory.sol | 5 +- contracts/package.json | 8 +- contracts/tests/deployer.ts | 13 +- contracts/tests/maciFactory.ts | 17 +- contracts/tests/round.ts | 996 +++++++++--------- contracts/tsconfig.json | 2 +- contracts/utils/deployment.ts | 69 +- contracts/utils/maci.ts | 13 +- contracts/utils/maciParameters.ts | 41 +- contracts/utils/testutils.ts | 159 +++ yarn.lock | 93 +- 17 files changed, 865 insertions(+), 754 deletions(-) create mode 100644 contracts/utils/testutils.ts diff --git a/common/package.json b/common/package.json index 87fcfc32e..c07368ce6 100644 --- a/common/package.json +++ b/common/package.json @@ -16,8 +16,8 @@ "dependencies": { "@openzeppelin/merkle-tree": "^1.0.5", "ethers": "^6.9.2", - "maci-crypto": "0.0.0-ci.2e62774", - "maci-domainobjs": "0.0.0-ci.2e62774" + "maci-crypto": "0.0.0-ci.ae1f52e", + "maci-domainobjs": "0.0.0-ci.ae1f52e" }, "repository": { "type": "git", diff --git a/contracts/cli/newClrFund.ts b/contracts/cli/newClrFund.ts index 22a29ec8a..a6317a4b4 100644 --- a/contracts/cli/newClrFund.ts +++ b/contracts/cli/newClrFund.ts @@ -20,7 +20,7 @@ * * If token is not provided, a new ERC20 token will be created */ -import { BigNumber } from 'ethers' +import { parseUnits } from 'ethers' import { ethers, network } from 'hardhat' import { getEventArg } from '../utils/contracts' import { newMaciPrivateKey } from '../utils/maci' @@ -143,7 +143,7 @@ async function main(args: any) { // set token let tokenAddress = args.token if (!tokenAddress) { - const initialTokenSupply = UNIT.mul(args.initialTokenSupply) + const initialTokenSupply = UNIT * BigInt(args.initialTokenSupply) const tokenContract = await deployContract({ name: 'AnyOldERC20Token', contractArgs: [initialTokenSupply], @@ -205,9 +205,9 @@ async function main(args: any) { } } -function parseDeposit(deposit: string): BigNumber { +function parseDeposit(deposit: string): bigint { try { - return ethers.utils.parseUnits(deposit) + return parseUnits(deposit) } catch (e) { throw new Error(`Error parsing deposit ${(e as Error).message}`) } diff --git a/contracts/contracts/ClrFund.sol b/contracts/contracts/ClrFund.sol index eaca327cb..d312abac9 100644 --- a/contracts/contracts/ClrFund.sol +++ b/contracts/contracts/ClrFund.sol @@ -59,6 +59,7 @@ contract ClrFund is OwnableUpgradeable, DomainObjs, Params { error NotOwnerOfMaciFactory(); error InvalidFundingRoundFactory(); error InvalidMaciFactory(); + error RecipientRegistryNotSet(); /** * @dev Initialize clrfund instance with MACI factory and new round templates @@ -207,6 +208,9 @@ contract ClrFund is OwnableUpgradeable, DomainObjs, Params { if (address(currentRound) != address(0) && !currentRound.isFinalized()) { revert NotFinalized(); } + + if (address(recipientRegistry) == address(0)) revert RecipientRegistryNotSet(); + // Make sure that the max number of recipients is set correctly MaxValues memory maxValues = maciFactory.maxValues(); recipientRegistry.setMaxRecipients(maxValues.maxVoteOptions); diff --git a/contracts/contracts/FundingRound.sol b/contracts/contracts/FundingRound.sol index 139a0cf35..e4e41ec60 100644 --- a/contracts/contracts/FundingRound.sol +++ b/contracts/contracts/FundingRound.sol @@ -40,6 +40,7 @@ contract FundingRound is error NotCoordinator(); error InvalidPoll(); error InvalidTally(); + error InvalidMessageProcessor(); error MaciAlreadySet(); error ContributionAmountIsZero(); error ContributionAmountTooLarge(); @@ -61,12 +62,12 @@ contract FundingRound is error IncorrectTallyResult(); error IncorrectSpentVoiceCredits(); error IncorrectPerVOSpentVoiceCredits(); - error VotingIsNotOver(); error FundsAlreadyClaimed(); error TallyHashNotPublished(); - error BudgetGreaterThanVotes(); - error IncompleteTallyResults(); + error IncompleteTallyResults(uint256 total, uint256 actual); error NoVotes(); + error MaciNotSet(); + error PollNotSet(); error InvalidMaci(); error InvalidNativeToken(); error InvalidUserRegistry(); @@ -118,8 +119,8 @@ contract FundingRound is TopupToken public topupToken; IUserRegistry public userRegistry; IRecipientRegistry public recipientRegistry; - ITallySubsidyFactory public immutable tallyFactory; - IMessageProcessorFactory public immutable messageProcessorFactory; + ITallySubsidyFactory public tallyFactory; + IMessageProcessorFactory public messageProcessorFactory; string public tallyHash; // The alpha used in quadratic funding formula @@ -150,56 +151,27 @@ contract FundingRound is /** * @dev Set round parameters. - * @param _duration the funding round duration - * @param _clrfund the clrfund contract containing information - * to deploy a funding round, e.g. nativeToke, coordinator address - * coordinator public key, etc. */ constructor( - uint256 _duration, - IClrFund _clrfund + ERC20 _nativeToken, + IUserRegistry _userRegistry, + IRecipientRegistry _recipientRegistry, + address _coordinator ) { - _validate(_clrfund); - nativeToken = _clrfund.nativeToken(); + if (isAddressZero(address(_nativeToken))) revert InvalidNativeToken(); + if (isAddressZero(address(_userRegistry))) revert InvalidUserRegistry(); + if (isAddressZero(address(_recipientRegistry))) revert InvalidRecipientRegistry(); + if (isAddressZero(_coordinator)) revert invalidCoordinator(); + + nativeToken = _nativeToken; voiceCreditFactor = (MAX_CONTRIBUTION_AMOUNT * uint256(10) ** nativeToken.decimals()) / MAX_VOICE_CREDITS; voiceCreditFactor = voiceCreditFactor > 0 ? voiceCreditFactor : 1; - userRegistry = _clrfund.userRegistry(); - recipientRegistry = _clrfund.recipientRegistry(); - coordinator = _clrfund.coordinator(); + userRegistry = _userRegistry; + recipientRegistry = _recipientRegistry; + coordinator = _coordinator; topupToken = new TopupToken(); - - IMACIFactory maciFactory = _clrfund.maciFactory(); - Factories memory factories = maciFactory.factories(); - tallyFactory = ITallySubsidyFactory(factories.tallyFactory); - messageProcessorFactory = IMessageProcessorFactory(factories.messageProcessorFactory); - - maci = maciFactory.deployMaci( - SignUpGatekeeper(this), - InitialVoiceCreditProxy(this), - address(topupToken), - address(this) - ); - if (isAddressZero(address(maci))) revert InvalidMaci(); - - MACI.PollContracts memory pollContracts = maci.deployPoll( - _duration, - maciFactory.maxValues(), - maciFactory.treeDepths(), - _clrfund.coordinatorPubKey(), - address(maciFactory.verifier()), - address(maciFactory.vkRegistry()), - // pass false to not deploy the subsidy contract - false - ); - - _setPollAndTally(pollContracts); - - // transfer poll contracts ownership to coordinator for vote tallying - Ownable(pollContracts.poll).transferOwnership(coordinator); - Ownable(pollContracts.messageProcessor).transferOwnership(coordinator); - Ownable(pollContracts.tally).transferOwnership(coordinator); } /** @@ -209,29 +181,10 @@ contract FundingRound is return (addressValue == address(0)); } - /** - * @dev validate that the clrfund contract has been initialized properly - * with non zero addresses - * @param _clrfund the clrfund contract - */ - function _validate(IClrFund _clrfund) private view { - if (isAddressZero(address(_clrfund.nativeToken()))) revert InvalidNativeToken(); - if (isAddressZero(address(_clrfund.userRegistry()))) revert InvalidUserRegistry(); - if (isAddressZero(address(_clrfund.recipientRegistry()))) revert InvalidRecipientRegistry(); - if (isAddressZero(_clrfund.coordinator())) revert invalidCoordinator(); - if (isAddressZero(address(_clrfund.maciFactory()))) revert invalidMaciFactory(); - - IMACIFactory maciFactory = _clrfund.maciFactory(); - MACICommon.Factories memory factories = maciFactory.factories(); - if (isAddressZero(factories.tallyFactory)) revert InvalidTallyFactory(); - if (isAddressZero(factories.messageProcessorFactory)) revert InvalidMessageProcessorFactory(); - } - - /** * @dev Have the votes been tallied */ - function isTallied() internal view returns (bool) { + function isTallied() private view returns (bool) { (uint256 numSignUps, ) = poll.numSignUpsAndMessages(); (, uint256 tallyBatchSize, ) = poll.batchSizes(); uint256 tallyBatchNum = tally.tallyBatchNum(); @@ -244,10 +197,9 @@ contract FundingRound is * @dev Set the tally contract * @param _tally The tally contract address */ - function _setTally(address _tally) - private + function _setTally(address _tally) private { - if (_tally == address(0)) { + if (isAddressZero(_tally)) { revert InvalidTally(); } @@ -263,6 +215,8 @@ contract FundingRound is external onlyCoordinator { + if (isAddressZero(address(maci))) revert MaciNotSet(); + _votingPeriodOver(poll); if (isFinalized) { revert RoundAlreadyFinalized(); @@ -276,28 +230,41 @@ contract FundingRound is } /** - * @dev Link Poll and Tally contracts to this funding round. + * @dev Link MACI related contracts to this funding round. */ - function _setPollAndTally(MACI.PollContracts memory _pollContracts) private + function setMaci( + MACI _maci, + MACI.PollContracts memory _pollContracts, + Factories memory _factories + ) + external + onlyOwner { - if (_pollContracts.poll == address(0)) { - revert InvalidPoll(); - } + if (!isAddressZero(address(maci))) revert MaciAlreadySet(); + + if (isAddressZero(address(_maci))) revert InvalidMaci(); + if (isAddressZero(_pollContracts.poll)) revert InvalidPoll(); + if (isAddressZero(_pollContracts.messageProcessor)) revert InvalidMessageProcessor(); + if (isAddressZero(_factories.tallyFactory)) revert InvalidTallyFactory(); + if (isAddressZero(_factories.messageProcessorFactory)) revert InvalidMessageProcessorFactory(); // we only create 1 poll per maci, make sure MACI use pollId = 0 // as the first poll index pollId = 0; - address expectedPoll = maci.getPoll(pollId); + + address expectedPoll = _maci.getPoll(pollId); if( _pollContracts.poll != expectedPoll ) { revert UnexpectedPollAddress(expectedPoll, _pollContracts.poll); } - if (address(_pollContracts.tally) == address(0)) { - revert InvalidTally(); - } - + maci = _maci; poll = Poll(_pollContracts.poll); _setTally(_pollContracts.tally); + + // save a copy of the factory in case we need to reset the tally result by recreating the + // the tally and message processor contracts + tallyFactory = ITallySubsidyFactory(_factories.tallyFactory); + messageProcessorFactory = IMessageProcessorFactory(_factories.messageProcessorFactory); } /** @@ -311,6 +278,7 @@ contract FundingRound is ) external { + if (isAddressZero(address(maci))) revert MaciNotSet(); if (isFinalized) revert RoundAlreadyFinalized(); if (amount == 0) revert ContributionAmountIsZero(); if (amount > MAX_VOICE_CREDITS * voiceCreditFactor) revert ContributionAmountTooLarge(); @@ -400,6 +368,8 @@ contract FundingRound is ) external { + if (isAddressZero(address(poll))) revert PollNotSet(); + uint256 batchSize = _messages.length; for (uint8 i = 0; i < batchSize; i++) { poll.publishMessage(_messages[i], _encPubKeys[i]); @@ -523,6 +493,8 @@ contract FundingRound is revert RoundAlreadyFinalized(); } + if (isAddressZero(address(maci))) revert MaciNotSet(); + _votingPeriodOver(poll); if (!isTallied()) { @@ -536,7 +508,7 @@ contract FundingRound is (,,, uint8 voteOptionTreeDepth) = poll.treeDepths(); uint256 totalResults = uint256(LEAVES_PER_NODE) ** uint256(voteOptionTreeDepth); if ( totalTallyResults != totalResults ) { - revert IncompleteTallyResults(); + revert IncompleteTallyResults(totalResults, totalTallyResults); } // If nobody voted, the round should be cancelled to avoid locking of matching funds @@ -728,6 +700,8 @@ contract FundingRound is external onlyCoordinator { + if (isAddressZero(address(maci))) revert MaciNotSet(); + if (!isTallied()) { revert VotesNotTallied(); } diff --git a/contracts/contracts/FundingRoundFactory.sol b/contracts/contracts/FundingRoundFactory.sol index 064c21021..984a73484 100644 --- a/contracts/contracts/FundingRoundFactory.sol +++ b/contracts/contracts/FundingRoundFactory.sol @@ -4,11 +4,16 @@ pragma solidity ^0.8.10; import {FundingRound} from './FundingRound.sol'; import {IClrFund} from './interfaces/IClrFund.sol'; +import {IMACIFactory} from './interfaces/IMACIFactory.sol'; +import {MACICommon} from './MACICommon.sol'; +import {MACI} from 'maci-contracts/contracts/MACI.sol'; +import {SignUpGatekeeper} from 'maci-contracts/contracts/gatekeepers/SignUpGatekeeper.sol'; +import {InitialVoiceCreditProxy} from 'maci-contracts/contracts/initialVoiceCreditProxy/InitialVoiceCreditProxy.sol'; /** * @dev A factory to deploy the funding round contract */ -contract FundingRoundFactory { +contract FundingRoundFactory is MACICommon { /** * @dev Deploy the funding round contract * @param _duration the funding round duration @@ -23,8 +28,29 @@ contract FundingRoundFactory { external returns (address) { - FundingRound newRound = new FundingRound(_duration, IClrFund(_clrfund)); + IClrFund clrfund = IClrFund(_clrfund); + FundingRound newRound = new FundingRound( + clrfund.nativeToken(), + clrfund.userRegistry(), + clrfund.recipientRegistry(), + clrfund.coordinator() + ); + + IMACIFactory maciFactory = clrfund.maciFactory(); + (MACI maci, MACI.PollContracts memory pollContracts) = maciFactory.deployMaci( + SignUpGatekeeper(newRound), + InitialVoiceCreditProxy(newRound), + address(newRound.topupToken()), + _duration, + newRound.coordinator(), + clrfund.coordinatorPubKey(), + address(this) + ); + + // link funding round with maci related contracts + newRound.setMaci(maci, pollContracts, maciFactory.factories()); newRound.transferOwnership(_clrfund); + maci.transferOwnership(address(newRound)); return address(newRound); } } diff --git a/contracts/contracts/MACIFactory.sol b/contracts/contracts/MACIFactory.sol index bffe78acd..0b1c46595 100644 --- a/contracts/contracts/MACIFactory.sol +++ b/contracts/contracts/MACIFactory.sol @@ -164,10 +164,13 @@ contract MACIFactory is Ownable, Params, SnarkCommon, DomainObjs, MACICommon { SignUpGatekeeper signUpGatekeeper, InitialVoiceCreditProxy initialVoiceCreditProxy, address topupCredit, + uint256 duration, + address coordinator, + PubKey calldata coordinatorPubKey, address maciOwner ) external - returns (MACI _maci) + returns (MACI _maci, MACI.PollContracts memory _pollContracts) { if (!vkRegistry.hasProcessVk( stateTreeDepth, @@ -197,6 +200,22 @@ contract MACIFactory is Ownable, Params, SnarkCommon, DomainObjs, MACICommon { stateTreeDepth ); + _pollContracts = _maci.deployPoll( + duration, + maxValues, + treeDepths, + coordinatorPubKey, + address(verifier), + address(vkRegistry), + // pass false to not deploy the subsidy contract + false + ); + + // transfer ownership to coordinator to run the tally scripts + Ownable(_pollContracts.poll).transferOwnership(coordinator); + Ownable(_pollContracts.messageProcessor).transferOwnership(coordinator); + Ownable(_pollContracts.tally).transferOwnership(coordinator); + _maci.transferOwnership(maciOwner); emit MaciDeployed(address(_maci)); diff --git a/contracts/contracts/interfaces/IMACIFactory.sol b/contracts/contracts/interfaces/IMACIFactory.sol index 3895de63c..a6df046c8 100644 --- a/contracts/contracts/interfaces/IMACIFactory.sol +++ b/contracts/contracts/interfaces/IMACIFactory.sol @@ -34,6 +34,9 @@ interface IMACIFactory { SignUpGatekeeper signUpGatekeeper, InitialVoiceCreditProxy initialVoiceCreditProxy, address topupCredit, + uint256 duration, + address coordinator, + DomainObjs.PubKey calldata coordinatorPubKey, address maciOwner - ) external returns (MACI _maci); + ) external returns (MACI _maci, MACI.PollContracts memory _pollContracts); } \ No newline at end of file diff --git a/contracts/package.json b/contracts/package.json index e12dd1f01..6cf2f96ae 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -20,7 +20,7 @@ "big-integer": "^1.6.52", "commander": "^11.1.0", "dotenv": "^8.2.0", - "maci-contracts": "0.0.0-ci.2e62774", + "maci-contracts": "0.0.0-ci.ae1f52e", "solidity-rlp": "2.0.8" }, "devDependencies": { @@ -41,9 +41,9 @@ "hardhat-contract-sizer": "^2.10.0", "hardhat-gas-reporter": "^1.0.8", "ipfs-only-hash": "^2.0.1", - "maci-circuits": "0.0.0-ci.2e62774", - "maci-cli": "0.0.0-ci.2e62774", - "maci-domainobjs": "0.0.0-ci.2e62774", + "maci-circuits": "0.0.0-ci.ae1f52e", + "maci-cli": "0.0.0-ci.ae1f52e", + "maci-domainobjs": "0.0.0-ci.ae1f52e", "mocha": "^10.2.0", "solidity-coverage": "^0.8.1", "ts-node": "^10.9.2", diff --git a/contracts/tests/deployer.ts b/contracts/tests/deployer.ts index 0f8511c9e..e82bb665b 100644 --- a/contracts/tests/deployer.ts +++ b/contracts/tests/deployer.ts @@ -13,11 +13,9 @@ import { deployMaciFactory, } from '../utils/deployment' import { MaciParameters } from '../utils/maciParameters' -import { DEFAULT_CIRCUIT } from '../utils/circuits' import { HardhatEthersSigner } from '@nomicfoundation/hardhat-ethers/signers' const roundDuration = 10000 -const circuit = DEFAULT_CIRCUIT describe('Clr fund deployer', async () => { let deployer: HardhatEthersSigner @@ -57,7 +55,7 @@ describe('Clr fund deployer', async () => { signer: deployer, ethers, }) - const params = MaciParameters.mock(circuit) + const params = MaciParameters.mock() await maciFactory.setMaciParameters(...params.asContractParam()) factoryTemplate = await deployContract({ @@ -209,7 +207,7 @@ describe('Clr fund deployer', async () => { recipientRegistry.target ) expect(await recipientRegistry.controller()).to.equal(clrfund.target) - const params = MaciParameters.mock(circuit) + const params = MaciParameters.mock() expect(await recipientRegistry.maxRecipients()).to.equal( 5 ** params.voteOptionTreeDepth ) @@ -356,17 +354,14 @@ describe('Clr fund deployer', async () => { }) // TODO investigate why this fails - it.skip('reverts if recipient registry is not set', async () => { + it('reverts if recipient registry is not set', async () => { await clrfund.setUserRegistry(userRegistry.target) await clrfund.setToken(token.target) await clrfund.setCoordinator(coordinator.address, coordinatorPubKey) await expect( clrfund.deployNewRound(roundDuration) - ).to.be.revertedWithCustomError( - roundInterface, - 'InvalidRecipientRegistry' - ) + ).to.be.revertedWithCustomError(clrfund, 'RecipientRegistryNotSet') }) it('reverts if native token is not set', async () => { diff --git a/contracts/tests/maciFactory.ts b/contracts/tests/maciFactory.ts index 74de0fa30..056e71e91 100644 --- a/contracts/tests/maciFactory.ts +++ b/contracts/tests/maciFactory.ts @@ -2,13 +2,12 @@ import { artifacts, ethers, config } from 'hardhat' import { Contract, TransactionResponse } from 'ethers' import { expect } from 'chai' import { deployMockContract, MockContract } from '@clrfund/waffle-mock-contract' -import { Keypair } from '@clrfund/common' import { getEventArg, getGasUsage } from '../utils/contracts' import { deployMaciFactory, deployPoseidonLibraries } from '../utils/deployment' import { MaciParameters } from '../utils/maciParameters' -import { DEFAULT_CIRCUIT } from '../utils/circuits' import { HardhatEthersSigner } from '@nomicfoundation/hardhat-ethers/signers' +import { Keypair } from '@clrfund/common' describe('MACI factory', () => { let deployer: HardhatEthersSigner @@ -46,7 +45,7 @@ describe('MACI factory', () => { 5600000 ) - maciParameters = MaciParameters.mock(DEFAULT_CIRCUIT) + maciParameters = MaciParameters.mock() const SignUpGatekeeperArtifact = await artifacts.readArtifact('SignUpGatekeeper') @@ -101,6 +100,9 @@ describe('MACI factory', () => { signUpGatekeeper.target, initialVoiceCreditProxy.target, topupContract.target, + duration, + coordinator.address, + coordinatorPubKey, deployer.address ) await expect(maciDeployed).to.emit(maciFactory, 'MaciDeployed') @@ -121,6 +123,9 @@ describe('MACI factory', () => { signUpGatekeeper.target, initialVoiceCreditProxy.target, topupContract.target, + duration, + coordinator.address, + coordinatorPubKey, coordinator.address ) await expect(deployTx).to.emit(maciFactory, 'MaciDeployed') @@ -135,6 +140,9 @@ describe('MACI factory', () => { signUpGatekeeper.target, initialVoiceCreditProxy.target, topupContract.target, + duration, + coordinator.address, + coordinatorPubKey, deployer.address ) @@ -160,6 +168,9 @@ describe('MACI factory', () => { signUpGatekeeper.target, initialVoiceCreditProxy.target, topupContract.target, + duration, + coordinator.address, + coordinatorPubKey, deployer.address ) diff --git a/contracts/tests/round.ts b/contracts/tests/round.ts index 7fee60fd9..3d400798a 100644 --- a/contracts/tests/round.ts +++ b/contracts/tests/round.ts @@ -1,16 +1,20 @@ import { ethers, artifacts, config } from 'hardhat' import { expect } from 'chai' -import { time } from '@nomicfoundation/hardhat-network-helpers' +import { HardhatEthersSigner } from '@nomicfoundation/hardhat-ethers/signers' import { deployMockContract, MockContract } from '@clrfund/waffle-mock-contract' import { Contract, - ContractTransaction, + AbiCoder, + Signer, parseEther, sha256, - AbiCoder, + randomBytes, + hexlify, + toNumber, } from 'ethers' import { genRandomSalt } from 'maci-crypto' import { Keypair } from '@clrfund/common' +import { time } from '@nomicfoundation/hardhat-network-helpers' import { ZERO_ADDRESS, @@ -19,11 +23,7 @@ import { ALPHA_PRECISION, } from '../utils/constants' import { getEventArg, getGasUsage } from '../utils/contracts' -import { - deployContract, - deployPoseidonLibraries, - deployMaciFactory, -} from '../utils/deployment' +import { deployMaciFactory, deployPoseidonLibraries } from '../utils/deployment' import { bnSqrt, createMessage, @@ -31,17 +31,20 @@ import { getRecipientClaimData, getRecipientTallyResultsBatch, } from '../utils/maci' -import { MaciParameters } from '../utils/maciParameters' import { DEFAULT_CIRCUIT } from '../utils/circuits' +import { MaciParameters } from '../utils/maciParameters' +import { deployTestFundingRound } from '../utils/testutils' // ethStaker test vectors for Quadratic Funding with alpha import smallTallyTestData from './data/testTallySmall.json' -import { HardhatEthersSigner } from '@nomicfoundation/hardhat-ethers/signers' + +const newResultCommitment = hexlify(randomBytes(32)) +const perVOSpentVoiceCreditsHash = hexlify(randomBytes(32)) const totalSpent = BigInt(smallTallyTestData.totalSpentVoiceCredits.spent) const budget = BigInt(totalSpent) * VOICE_CREDIT_FACTOR * 2n const totalQuadraticVotes = smallTallyTestData.results.tally.reduce( (total, tally) => { - return BigInt(tally) ** 2n + BigInt(total) + return BigInt(tally) ** 2n + total }, BigInt(0) ) @@ -49,67 +52,42 @@ const matchingPoolSize = budget - totalSpent * VOICE_CREDIT_FACTOR const expectedAlpha = (matchingPoolSize * ALPHA_PRECISION) / - ((totalQuadraticVotes - totalSpent) * VOICE_CREDIT_FACTOR) + (totalQuadraticVotes - totalSpent) / + VOICE_CREDIT_FACTOR -// ABI encoder -const encoder = new AbiCoder() - -function asContract(contract: MockContract): Contract { - return contract as unknown as Contract -} +const abiCoder = new AbiCoder() function calcAllocationAmount(tally: string, voiceCredit: string): bigint { const quadratic = expectedAlpha * VOICE_CREDIT_FACTOR * BigInt(tally) ** 2n + const linear = (ALPHA_PRECISION - expectedAlpha) * (VOICE_CREDIT_FACTOR * BigInt(voiceCredit)) + const allocation = quadratic + linear return allocation / ALPHA_PRECISION } -async function finalizeRound( - fundingRound: Contract, - totalSpent: string | bigint, - totalSpentSalt: string -): Promise { - // generate random 32 bytes for test only - const newResultCommitment = genRandomSalt() - const perVOVoiceCreditsCommitment = genRandomSalt() - return fundingRound.finalize( - totalSpent, - totalSpentSalt, - newResultCommitment, - perVOVoiceCreditsCommitment - ) -} - describe('Funding Round', () => { const coordinatorPubKey = new Keypair().pubKey + const roundDuration = 86400 * 7 const userKeypair = new Keypair() - const contributionAmount = UNIT * 10n + const userPubKey = userKeypair.pubKey.asContractParam() + const contributionAmount = UNIT * BigInt(10) const tallyHash = 'test' - const tallyTreeDepth = 2 - const pollDuration = 86400 * 7 - const halfPollDuration = Math.floor(pollDuration / 2) - const numSignUps = 4 - const tallyBatchSize = 2 - const tallyBatchNum = 2 + let tallyTreeDepth: number let token: Contract let tokenAsContributor: Contract let userRegistry: MockContract let recipientRegistry: MockContract - + let tally: MockContract let fundingRound: Contract let fundingRoundAsCoordinator: Contract let fundingRoundAsContributor: Contract - let fundingRoundAsRecipient: Contract - let fundingRoundAsAnotherContributor: Contract - - let maci: MockContract + let maci: Contract + let poll: Contract let pollId: bigint - let poll: MockContract - let tally: MockContract let deployer: HardhatEthersSigner let coordinator: HardhatEthersSigner @@ -122,113 +100,53 @@ describe('Funding Round', () => { await ethers.getSigners() }) - async function deployMaciMock(): Promise { - const MACIArtifact = await artifacts.readArtifact('MACI') - const maci = await deployMockContract(deployer, MACIArtifact.abi) - await maci.mock.signUp.returns(10) - return maci - } - - async function deployMockContractByName(name: string): Promise { - const artifact = await artifacts.readArtifact(name) - const contract = await deployMockContract(deployer, artifact.abi) - return contract - } - beforeEach(async () => { - const tokenInitialSupply = UNIT * 1000000n - const Token = await ethers.getContractFactory('AnyOldERC20Token', deployer) - token = await Token.deploy(tokenInitialSupply + budget) - await token.transfer(contributor.address, tokenInitialSupply / 4n) - await token.transfer(anotherContributor.address, tokenInitialSupply / 4n) - await token.transfer(coordinator.address, tokenInitialSupply / 4n) - tokenAsContributor = token.connect(contributor) as Contract - - const IUserRegistryArtifact = await artifacts.readArtifact('IUserRegistry') - userRegistry = await deployMockContract(deployer, IUserRegistryArtifact.abi) - await userRegistry.mock.isVerifiedUser.returns(true) - - const IRecipientRegistryArtifact = - await artifacts.readArtifact('IRecipientRegistry') - recipientRegistry = await deployMockContract( - deployer, - IRecipientRegistryArtifact.abi + const tokenInitialSupply = UNIT * BigInt(1000000) + const deployed = await deployTestFundingRound( + tokenInitialSupply + budget, + coordinator.address, + coordinatorPubKey, + roundDuration, + deployer ) + token = deployed.token + fundingRound = deployed.fundingRound + userRegistry = deployed.mockUserRegistry + recipientRegistry = deployed.mockRecipientRegistry + tally = deployed.mockTally + const mockVerifier = deployed.mockVerifier + + // make the verifier to alwasy returns true + await mockVerifier.mock.verify.returns(true) + await userRegistry.mock.isVerifiedUser.returns(true) + await tally.mock.tallyBatchNum.returns(1) + await tally.mock.verifyTallyResult.returns(true) + await tally.mock.verifySpentVoiceCredits.returns(true) - const libraries = await deployPoseidonLibraries({ - artifactsPath: config.paths.artifacts, - ethers, - signer: deployer, - }) - fundingRound = await deployContract({ - name: 'FundingRound', - libraries, - contractArgs: [ - token.address, - userRegistry.target, - recipientRegistry.target, - coordinator.address, - ], - ethers, - signer: deployer, - }) - + tokenAsContributor = token.connect(contributor) as Contract fundingRoundAsCoordinator = fundingRound.connect(coordinator) as Contract fundingRoundAsContributor = fundingRound.connect(contributor) as Contract - fundingRoundAsRecipient = fundingRound.connect(recipient) as Contract - fundingRoundAsAnotherContributor = fundingRound.connect( - anotherContributor - ) as Contract - - const maciFactory = await deployMaciFactory({ - ethers, - signer: deployer, - libraries, - }) - - const maciParams = MaciParameters.mock(DEFAULT_CIRCUIT) - await maciFactory.setMaciParameters(...maciParams.asContractParam()) - - const maciDeployed = await maciFactory.deployMaci( - fundingRound.target, - fundingRound.target, - token.address, - pollDuration, - coordinator.address, - coordinatorPubKey.asContractParam() - ) - const maciAddress = await getEventArg( - maciDeployed, - maciFactory, - 'MaciDeployed', - '_maci' - ) + await token.transfer(contributor.address, tokenInitialSupply / 4n) + await token.transfer(anotherContributor.address, tokenInitialSupply / 4n) + await token.transfer(coordinator.address, tokenInitialSupply / 4n) + const maciAddress = await fundingRound.maci() maci = await ethers.getContractAt('MACI', maciAddress) - - pollId = await getEventArg( - maciDeployed, - asContract(maci), - 'DeployPoll', - '_pollId' - ) - const pollAddress = await getEventArg( - maciDeployed, - maci, - 'DeployPoll', - '_pollAddr' - ) + const pollAddress = await fundingRound.poll() poll = await ethers.getContractAt('Poll', pollAddress) + pollId = await fundingRound.pollId() + + const treeDepths = await poll.treeDepths() + tallyTreeDepth = toNumber(treeDepths.voteOptionTreeDepth) }) it('initializes funding round correctly', async () => { expect(await fundingRound.owner()).to.equal(deployer.address) - expect(await fundingRound.nativeToken()).to.equal(token.address) + expect(await fundingRound.nativeToken()).to.equal(token.target) expect(await fundingRound.voiceCreditFactor()).to.equal(VOICE_CREDIT_FACTOR) expect(await fundingRound.matchingPoolSize()).to.equal(0) expect(await fundingRound.totalSpent()).to.equal(0) - expect(await fundingRound.totalVotes()).to.equal(0) expect(await fundingRound.userRegistry()).to.equal(userRegistry.target) expect(await fundingRound.recipientRegistry()).to.equal( recipientRegistry.target @@ -236,43 +154,26 @@ describe('Funding Round', () => { expect(await fundingRound.isFinalized()).to.equal(false) expect(await fundingRound.isCancelled()).to.equal(false) expect(await fundingRound.coordinator()).to.equal(coordinator.address) - expect(await fundingRound.maci()).to.equal(ZERO_ADDRESS) - }) - - it('allows owner to set MACI address', async () => { - await fundingRound.setMaci(maci.target) - expect(await fundingRound.maci()).to.equal(maci.target) - }) - - it('allows to set MACI address only once', async () => { - await fundingRound.setMaci(maci.target) - await expect(fundingRound.setMaci(maci.target)).to.be.revertedWith( - 'MaciAlreadySet' - ) - }) - - it('allows only owner to set MACI address', async () => { - await expect( - fundingRoundAsCoordinator.setMaci(maci.target) - ).to.be.revertedWith('Ownable: caller is not the owner') + expect(await fundingRound.maci()).to.be.properAddress }) describe('accepting contributions', () => { const userPubKey = userKeypair.pubKey.asContractParam() - const encodedContributorAddress = encoder.encode( - ['address'], - [contributor.address] - ) + let encodedContributorAddress: string + let tokenAsContributor: Contract let fundingRoundAsContributor: Contract beforeEach(async () => { tokenAsContributor = token.connect(contributor) as Contract fundingRoundAsContributor = fundingRound.connect(contributor) as Contract + encodedContributorAddress = abiCoder.encode( + ['address'], + [contributor.address] + ) }) it('accepts contributions from everyone', async () => { - await fundingRound.setMaci(maci.target) await tokenAsContributor.approve(fundingRound.target, contributionAmount) const expectedVoiceCredits = contributionAmount / VOICE_CREDIT_FACTOR await expect( @@ -283,7 +184,8 @@ describe('Funding Round', () => { .to.emit(maci, 'SignUp') // We use [] to skip argument matching, otherwise it will fail // Possibly related: https://github.com/EthWorks/Waffle/issues/245 - //.withArgs([], 1, expectedVoiceCredits, []) + //.withArgs([], 1, expectedVoiceCredits) + expect(await token.balanceOf(fundingRound.target)).to.equal( contributionAmount ) @@ -297,87 +199,75 @@ describe('Funding Round', () => { ).to.equal(expectedVoiceCredits) }) - it('rejects contributions if MACI has not been linked to a round', async () => { - await tokenAsContributor.approve(fundingRound.target, contributionAmount) - await expect( - fundingRoundAsContributor.contribute(userPubKey, contributionAmount) - ).to.be.revertedWith('MaciNotSet') - }) - it('limits the number of contributors', async () => { // TODO: add test later }) it('rejects contributions if funding round has been finalized', async () => { - await fundingRound.setMaci(maci.target) await fundingRound.cancel() await tokenAsContributor.approve(fundingRound.target, contributionAmount) await expect( fundingRoundAsContributor.contribute(userPubKey, contributionAmount) - ).to.be.revertedWith('AlreadyFinalized') + ).to.be.revertedWithCustomError(fundingRound, 'RoundAlreadyFinalized') }) it('rejects contributions with zero amount', async () => { - await fundingRound.setMaci(maci.target) await tokenAsContributor.approve(fundingRound.target, contributionAmount) await expect( fundingRoundAsContributor.contribute(userPubKey, 0) - ).to.be.revertedWith('ContributionAmountIsZero') + ).to.be.revertedWithCustomError(fundingRound, 'ContributionAmountIsZero') }) it('rejects contributions that are too large', async () => { - await fundingRound.setMaci(maci.target) - const contributionAmount = UNIT + 10001n + const contributionAmount = UNIT * BigInt(10001) await tokenAsContributor.approve(fundingRound.target, contributionAmount) await expect( fundingRoundAsContributor.contribute(userPubKey, contributionAmount) - ).to.be.revertedWith('ContributionAmountTooLarge') + ).to.be.revertedWithCustomError( + fundingRound, + 'ContributionAmountTooLarge' + ) }) it('allows to contribute only once per round', async () => { - await fundingRound.setMaci(maci.target) await tokenAsContributor.approve( fundingRound.target, - contributionAmount * 2n + contributionAmount * BigInt(2) ) await fundingRoundAsContributor.contribute(userPubKey, contributionAmount) await expect( fundingRoundAsContributor.contribute(userPubKey, contributionAmount) - ).to.be.revertedWith('AlreadyContributed') + ).to.be.revertedWithCustomError(fundingRound, 'AlreadyContributed') }) it('requires approval', async () => { - await fundingRound.setMaci(maci.target) await expect( fundingRoundAsContributor.contribute(userPubKey, contributionAmount) ).to.be.revertedWith('ERC20: insufficient allowance') }) it('rejects contributions from unverified users', async () => { - await fundingRound.setMaci(maci.target) await tokenAsContributor.approve(fundingRound.target, contributionAmount) await userRegistry.mock.isVerifiedUser.returns(false) await expect( fundingRoundAsContributor.contribute(userPubKey, contributionAmount) - ).to.be.revertedWith('UserNotVerified') + ).to.be.revertedWithCustomError(fundingRound, 'UserNotVerified') }) it('should not allow users who have not contributed to sign up directly in MACI', async () => { - await fundingRound.setMaci(maci.target) - const signUpData = encoder.encode(['address'], [contributor.address]) + const signUpData = abiCoder.encode(['address'], [contributor.address]) await expect( maci.signUp(userPubKey, signUpData, encodedContributorAddress) - ).to.be.revertedWith('UserHasNotContributed') + ).to.be.revertedWithCustomError(fundingRound, 'UserHasNotContributed') }) it('should not allow users who have already signed up to sign up directly in MACI', async () => { - await fundingRound.setMaci(maci.target) await tokenAsContributor.approve(fundingRound.target, contributionAmount) await fundingRoundAsContributor.contribute(userPubKey, contributionAmount) - const signUpData = encoder.encode(['address'], [contributor.address]) + const signUpData = abiCoder.encode(['address'], [contributor.address]) await expect( maci.signUp(userPubKey, signUpData, encodedContributorAddress) - ).to.be.revertedWith('UserAlreadyRegistered') + ).to.be.revertedWithCustomError(fundingRound, 'UserAlreadyRegistered') }) it('should not return the amount of voice credits for user who has not contributed', async () => { @@ -386,19 +276,19 @@ describe('Funding Round', () => { fundingRound.target, encodedContributorAddress ) - ).to.be.revertedWith('NoVoiceCredits') + ).to.be.revertedWithCustomError(fundingRound, 'NoVoiceCredits') }) }) describe('voting', () => { - const singleVote = UNIT * 4n + const singleVote = UNIT * BigInt(4) let fundingRoundAsContributor: Contract let userStateIndex: number let recipientIndex = 1 let nonce = 1 beforeEach(async () => { - await fundingRound.setMaci(maci.target) + const tokenAsContributor = token.connect(contributor) as Contract await tokenAsContributor.approve(fundingRound.target, contributionAmount) fundingRoundAsContributor = fundingRound.connect(contributor) as Contract const contributionTx = await fundingRoundAsContributor.contribute( @@ -424,7 +314,6 @@ describe('Funding Round', () => { nonce, pollId ) - const messagePublished = poll.publishMessage( message.asContractParam(), encPubKey.asContractParam() @@ -446,14 +335,13 @@ describe('Funding Round', () => { nonce, pollId ) - const messagePublished = await poll.publishMessage( + await poll.publishMessage( message.asContractParam(), encPubKey.asContractParam() ) - await expect(messagePublished).to.emit(poll, 'PublishMessage') }) - it('use a seed to generate new key and submit change message', async () => { + it('use a seed to generate new key and submit change change message', async () => { const signature = await contributor.signMessage('hello world') const hash = sha256(signature) const newUserKeypair = Keypair.createFromSeed(hash) @@ -467,11 +355,10 @@ describe('Funding Round', () => { nonce, pollId ) - const messagePublished = await poll.publishMessage( + await poll.publishMessage( message.asContractParam(), encPubKey.asContractParam() ) - await expect(messagePublished).to.emit(poll, 'PublishMessage') }) it('submits an invalid vote', async () => { @@ -486,11 +373,10 @@ describe('Funding Round', () => { nonce, pollId ) - const publishTx1 = await poll.publishMessage( + await poll.publishMessage( message1.asContractParam(), encPubKey1.asContractParam() ) - await expect(publishTx1).to.emit(poll, 'PublishMessage') const [message2, encPubKey2] = createMessage( userStateIndex, userKeypair, @@ -501,11 +387,10 @@ describe('Funding Round', () => { nonce + 1, pollId ) - const publishTx2 = await poll.publishMessage( + await poll.publishMessage( message2.asContractParam(), encPubKey2.asContractParam() ) - await expect(publishTx2).to.emit(poll, 'PublishMessage') }) it('submits a vote for invalid vote option', async () => { @@ -520,16 +405,13 @@ describe('Funding Round', () => { nonce, pollId ) - const messagePublished = await poll.publishMessage( + await poll.publishMessage( message.asContractParam(), encPubKey.asContractParam() ) - await expect(messagePublished).to.emit(poll, 'PublishMessage') }) it('submits a batch of messages', async () => { - await fundingRound.setPoll(pollId) - const messages = [] const encPubKeys = [] const numMessages = 3 @@ -575,69 +457,51 @@ describe('Funding Round', () => { }) it('allows only coordinator to publish tally hash', async () => { - await expect(fundingRound.publishTallyHash(tallyHash)).to.be.revertedWith( - 'NotCoordinator' - ) + await expect( + fundingRound.publishTallyHash(tallyHash) + ).to.be.revertedWithCustomError(fundingRound, 'NotCoordinator') }) it('reverts if round has been finalized', async () => { await fundingRound.cancel() await expect( fundingRoundAsCoordinator.publishTallyHash(tallyHash) - ).to.be.revertedWith('RoundAlreadyFinalized') + ).to.be.revertedWithCustomError(fundingRound, 'RoundAlreadyFinalized') }) it('rejects empty string', async () => { await expect( fundingRoundAsCoordinator.publishTallyHash('') - ).to.be.revertedWith('EmptyTallyHash') + ).to.be.revertedWithCustomError(fundingRound, 'EmptyTallyHash') }) }) describe('finalizing round', () => { - const matchingPoolSize = UNIT * 10000n - const totalContributions = UNIT * 1000n + const matchingPoolSize = UNIT * BigInt(10000) + const totalContributions = UNIT * BigInt(1000) const totalSpent = totalContributions / VOICE_CREDIT_FACTOR const totalSpentSalt = genRandomSalt().toString() const totalVotes = bnSqrt(totalSpent) - const tallyTreeDepth = 2 - - expect(totalVotes).to.equal(10000n) + expect(totalVotes).to.equal(BigInt(10000)) beforeEach(async () => { - maci = await deployMaciMock() - poll = await deployMockContractByName('Poll') - tally = await deployMockContractByName('Tally') - pollId = BigInt(0) - await poll.mock.treeDepths.returns(1, 1, 1, tallyTreeDepth) - await maci.mock.getPoll.returns(poll.target) - - // round.isTallied() = tallyBatchSize * tallyBatchNum >= numSignups - await poll.mock.numSignUpsAndMessages.returns(numSignUps, 1) - await poll.mock.batchSizes.returns(1, tallyBatchSize, 1) - await tally.mock.tallyBatchNum.returns(tallyBatchNum) - await tally.mock.verifyTallyResult.returns(true) - await tally.mock.verifySpentVoiceCredits.returns(true) - - // round.isVotingOver() - const deployTime = await time.latest() - await poll.mock.getDeployTimeAndDuration.returns(deployTime, pollDuration) - - await tokenAsContributor.approve(fundingRound.target, totalContributions) + await (token.connect(contributor) as Contract).approve( + fundingRound.target, + totalContributions + ) }) it('allows owner to finalize round', async () => { - await fundingRound.setMaci(maci.target) - await fundingRound.setPoll(pollId) - await fundingRoundAsCoordinator.setTally(tally.target) - - await fundingRoundAsContributor.contribute( + await (fundingRound.connect(contributor) as Contract).contribute( userKeypair.pubKey.asContractParam(), totalContributions ) - await time.increase(pollDuration) + await time.increase(roundDuration) + + const fundingRoundAsCoordinator = fundingRound.connect( + coordinator + ) as Contract await fundingRoundAsCoordinator.publishTallyHash(tallyHash) - expect(await fundingRound.tallyHash()).to.equal(tallyHash) await token.transfer(fundingRound.target, matchingPoolSize) await addTallyResultsBatch( @@ -646,8 +510,12 @@ describe('Funding Round', () => { smallTallyTestData, 5 ) - - await finalizeRound(fundingRound, totalSpent, totalSpentSalt.toString()) + await fundingRound.finalize( + totalSpent, + totalSpentSalt, + newResultCommitment, + perVOSpentVoiceCreditsHash + ) expect(await fundingRound.isFinalized()).to.equal(true) expect(await fundingRound.isCancelled()).to.equal(false) expect(await fundingRound.totalSpent()).to.equal(totalSpent) @@ -655,15 +523,11 @@ describe('Funding Round', () => { }) it('allows owner to finalize round when matching pool is empty', async () => { - await fundingRound.setMaci(maci.target) - await fundingRound.setPoll(pollId) - await fundingRoundAsCoordinator.setTally(tally.target) - - await fundingRoundAsContributor.contribute( + await (fundingRound.connect(contributor) as Contract).contribute( userKeypair.pubKey.asContractParam(), totalContributions ) - await time.increase(pollDuration) + await time.increase(roundDuration) await fundingRoundAsCoordinator.publishTallyHash(tallyHash) await addTallyResultsBatch( @@ -672,26 +536,28 @@ describe('Funding Round', () => { smallTallyTestData, 5 ) - await finalizeRound(fundingRound, totalSpent, totalSpentSalt) + await fundingRound.finalize( + totalSpent, + totalSpentSalt, + newResultCommitment, + perVOSpentVoiceCreditsHash + ) expect(await fundingRound.totalSpent()).to.equal(totalSpent) - // TODO: how to get totalVotes from maci v1? - //expect(await fundingRound.totalVotes()).to.equal(totalVotes) expect(await fundingRound.matchingPoolSize()).to.equal(0) }) it('counts direct token transfers to funding round as matching pool contributions', async () => { - await fundingRound.setMaci(maci.target) - await fundingRound.setPoll(pollId) - await fundingRoundAsCoordinator.setTally(tally.target) - - await fundingRoundAsContributor.contribute( + await (fundingRound.connect(contributor) as Contract).contribute( userKeypair.pubKey.asContractParam(), totalContributions ) - await time.increase(pollDuration) + await time.increase(roundDuration) await fundingRoundAsCoordinator.publishTallyHash(tallyHash) await token.transfer(fundingRound.target, matchingPoolSize) - await tokenAsContributor.transfer(fundingRound.target, contributionAmount) + await (token.connect(contributor) as Contract).transfer( + fundingRound.target, + contributionAmount + ) await addTallyResultsBatch( fundingRoundAsCoordinator, @@ -699,22 +565,24 @@ describe('Funding Round', () => { smallTallyTestData, 5 ) - await finalizeRound(fundingRound, totalSpent, totalSpentSalt) + await fundingRound.finalize( + totalSpent, + totalSpentSalt, + newResultCommitment, + perVOSpentVoiceCreditsHash + ) expect(await fundingRound.matchingPoolSize()).to.equal( matchingPoolSize + contributionAmount ) }) it('reverts if round has been finalized already', async () => { - await fundingRound.setMaci(maci.target) - await fundingRound.setPoll(pollId) - await fundingRoundAsCoordinator.setTally(tally.target) - - await fundingRoundAsContributor.contribute( + await (fundingRound.connect(contributor) as Contract).contribute( userKeypair.pubKey.asContractParam(), totalContributions ) - await time.increase(pollDuration) + await time.increase(roundDuration) + await fundingRoundAsCoordinator.publishTallyHash(tallyHash) await token.transfer(fundingRound.target, matchingPoolSize) await addTallyResultsBatch( @@ -724,82 +592,85 @@ describe('Funding Round', () => { 5 ) - await finalizeRound(fundingRound, totalSpent, totalSpentSalt) - await expect( - finalizeRound(fundingRound, totalSpent, totalSpentSalt) - ).to.be.revertedWith('RoundAlreadyFinalized') - }) - - it('reverts MACI has not been deployed', async () => { - await time.increase(pollDuration) + await fundingRound.finalize( + totalSpent, + totalSpentSalt, + newResultCommitment, + perVOSpentVoiceCreditsHash + ) await expect( - finalizeRound(fundingRound, totalSpent, totalSpentSalt) - ).to.be.revertedWith('MaciNotSet') + fundingRound.finalize( + totalSpent, + totalSpentSalt, + newResultCommitment, + perVOSpentVoiceCreditsHash + ) + ).to.be.revertedWithCustomError(fundingRound, 'RoundAlreadyFinalized') }) it('reverts if voting is still in progress', async () => { - await fundingRound.setMaci(maci.target) - await fundingRound.setPoll(pollId) - await fundingRoundAsCoordinator.setTally(tally.target) - - await fundingRoundAsContributor.contribute( + await (fundingRound.connect(contributor) as Contract).contribute( userKeypair.pubKey.asContractParam(), totalContributions ) - await time.increase(halfPollDuration) + await time.increase(roundDuration / 2) await expect( - finalizeRound(fundingRound, totalSpent, totalSpentSalt) - ).to.be.revertedWith('VotingIsNotOver') + fundingRound.finalize( + totalSpent, + totalSpentSalt, + newResultCommitment, + perVOSpentVoiceCreditsHash + ) + ).to.be.revertedWithCustomError(fundingRound, 'VotingPeriodNotPassed') }) it('reverts if votes has not been tallied', async () => { - await fundingRound.setMaci(maci.target) - await fundingRound.setPoll(pollId) - await fundingRoundAsCoordinator.setTally(tally.target) - - await fundingRoundAsContributor.contribute( + await (fundingRound.connect(contributor) as Contract).contribute( userKeypair.pubKey.asContractParam(), totalContributions ) - await time.increase(pollDuration) - await poll.mock.numSignUpsAndMessages.returns(numSignUps * 2, 1) + await time.increase(roundDuration) + await tally.mock.tallyBatchNum.returns(0) await expect( - finalizeRound(fundingRound, totalSpent, totalSpentSalt) - ).to.be.revertedWith('VotesNotTallied') + fundingRound.finalize( + totalSpent, + totalSpentSalt, + newResultCommitment, + perVOSpentVoiceCreditsHash + ) + ).to.be.revertedWithCustomError(fundingRound, 'VotesNotTallied') }) it('reverts if tally hash has not been published', async () => { - await fundingRound.setMaci(maci.target) - await fundingRound.setPoll(pollId) - await fundingRoundAsCoordinator.setTally(tally.target) - - await fundingRoundAsContributor.contribute( + await (fundingRound.connect(contributor) as Contract).contribute( userKeypair.pubKey.asContractParam(), totalContributions ) - await time.increase(pollDuration) + await time.increase(roundDuration) await expect( - finalizeRound(fundingRound, totalSpent, totalSpentSalt) - ).to.be.revertedWith('TallyHashNotPublished') + fundingRound.finalize( + totalSpent, + totalSpentSalt, + newResultCommitment, + perVOSpentVoiceCreditsHash + ) + ).to.be.revertedWithCustomError(fundingRound, 'TallyHashNotPublished') }) - // TODO: get total votes in maci v1 - it.skip('reverts if total votes is zero', async () => { - await fundingRound.setMaci(maci.target) - await fundingRound.setPoll(pollId) - await fundingRoundAsCoordinator.setTally(tally.target) - - await fundingRoundAsContributor.contribute( + it('reverts if total votes (== totalSpent) is zero', async () => { + await (fundingRound.connect(contributor) as Contract).contribute( userKeypair.pubKey.asContractParam(), totalContributions ) - await time.increase(pollDuration) + await time.increase(roundDuration) + const fundingRoundAsCoordinator = fundingRound.connect( + coordinator + ) as Contract await fundingRoundAsCoordinator.publishTallyHash(tallyHash) await token.transfer(fundingRound.target, matchingPoolSize) - //await maci.mock.totalVotes.returns(0) await addTallyResultsBatch( fundingRoundAsCoordinator, @@ -808,23 +679,29 @@ describe('Funding Round', () => { 5 ) await expect( - await finalizeRound(fundingRound, totalSpent, totalSpentSalt) - ).to.be.revertedWith('FundingRound: No votes') + fundingRound.finalize( + 0, // totalSpent + totalSpentSalt, + newResultCommitment, + perVOSpentVoiceCreditsHash + ) + ).to.be.revertedWithCustomError(fundingRound, 'NoVotes') }) - it.skip('reverts if total amount of spent voice credits is incorrect', async () => { - await fundingRound.setMaci(maci.target) - await fundingRound.setPoll(pollId) - await fundingRoundAsCoordinator.setTally(tally.target) - - await fundingRoundAsContributor.contribute( + it('reverts if total amount of spent voice credits is incorrect', async () => { + await (fundingRound.connect(contributor) as Contract).contribute( userKeypair.pubKey.asContractParam(), totalContributions ) - await time.increase(pollDuration) + + await time.increase(roundDuration) + + const fundingRoundAsCoordinator = fundingRound.connect( + coordinator + ) as Contract + await fundingRoundAsCoordinator.publishTallyHash(tallyHash) await token.transfer(fundingRound.target, matchingPoolSize) - await poll.mock.verifySpentVoiceCredits.returns(false) await addTallyResultsBatch( fundingRoundAsCoordinator, @@ -833,26 +710,41 @@ describe('Funding Round', () => { 5 ) + await tally.mock.verifySpentVoiceCredits.returns(false) await expect( - finalizeRound(fundingRound, totalSpent, totalSpentSalt) - ).to.be.revertedWith('IncorrectSpentVoiceCredits') + fundingRound.finalize( + totalSpent, + totalSpentSalt, + newResultCommitment, + perVOSpentVoiceCreditsHash + ) + ).to.be.revertedWithCustomError( + fundingRound, + 'IncorrectSpentVoiceCredits' + ) }) it('allows only owner to finalize round', async () => { - await fundingRound.setMaci(maci.target) - await fundingRound.setPoll(pollId) - await fundingRoundAsCoordinator.setTally(tally.target) - - await fundingRoundAsContributor.contribute( + await (fundingRound.connect(contributor) as Contract).contribute( userKeypair.pubKey.asContractParam(), totalContributions ) - await time.increase(pollDuration) + await time.increase(roundDuration) + + const fundingRoundAsCoordinator = fundingRound.connect( + coordinator + ) as Contract + await fundingRoundAsCoordinator.publishTallyHash(tallyHash) await token.transfer(fundingRound.target, matchingPoolSize) await expect( - finalizeRound(fundingRoundAsCoordinator, totalSpent, totalSpentSalt) + fundingRoundAsCoordinator.finalize( + totalSpent, + totalSpentSalt, + newResultCommitment, + perVOSpentVoiceCreditsHash + ) ).to.be.revertedWith('Ownable: caller is not the owner') }) }) @@ -865,38 +757,24 @@ describe('Funding Round', () => { }) it('reverts if round has been finalized already', async () => { - const matchingPoolSize = UNIT * 10000n - const totalContributions = UNIT * 1000n + const matchingPoolSize = UNIT * BigInt(10000) + const totalContributions = UNIT * BigInt(1000) const totalSpent = totalContributions / VOICE_CREDIT_FACTOR const totalSpentSalt = genRandomSalt().toString() - maci = await deployMaciMock() - poll = await deployMockContractByName('Poll') - tally = await deployMockContractByName('Tally') - pollId = BigInt(0) - await tally.mock.verifyTallyResult.returns(true) - await tally.mock.verifySpentVoiceCredits.returns(true) - await poll.mock.treeDepths.returns(1, 2, 3, tallyTreeDepth) - await maci.mock.getPoll.returns(poll.target) - - // round.isTallied() = tallyBatchSize * tallyBatchNum >= numSignups - await poll.mock.numSignUpsAndMessages.returns(numSignUps, 1) - await poll.mock.batchSizes.returns(1, tallyBatchSize, 1) - await tally.mock.tallyBatchNum.returns(tallyBatchNum) - - // round.isVotingOver() - const deployTime = await time.latest() - await poll.mock.getDeployTimeAndDuration.returns(deployTime, pollDuration) - - await fundingRound.setMaci(maci.target) - await fundingRound.setPoll(pollId) - await fundingRoundAsCoordinator.setTally(tally.target) - await tokenAsContributor.approve(fundingRound.target, totalContributions) - await fundingRoundAsContributor.contribute( + await (token.connect(contributor) as Contract).approve( + fundingRound.target, + totalContributions + ) + await (fundingRound.connect(contributor) as Contract).contribute( userKeypair.pubKey.asContractParam(), totalContributions ) - await time.increase(pollDuration) + await time.increase(roundDuration) + + const fundingRoundAsCoordinator = fundingRound.connect( + coordinator + ) as Contract await fundingRoundAsCoordinator.publishTallyHash(tallyHash) await token.transfer(fundingRound.target, matchingPoolSize) await addTallyResultsBatch( @@ -905,21 +783,31 @@ describe('Funding Round', () => { smallTallyTestData, 3 ) - await finalizeRound(fundingRound, totalSpent, totalSpentSalt) + await fundingRound.finalize( + totalSpent, + totalSpentSalt, + newResultCommitment, + perVOSpentVoiceCreditsHash + ) - await expect(fundingRound.cancel()).to.be.revertedWith( + await expect(fundingRound.cancel()).to.be.revertedWithCustomError( + fundingRound, 'RoundAlreadyFinalized' ) }) it('reverts if round has been cancelled already', async () => { await fundingRound.cancel() - await expect(fundingRound.cancel()).to.be.revertedWith( + await expect(fundingRound.cancel()).to.be.revertedWithCustomError( + fundingRound, 'RoundAlreadyFinalized' ) }) it('allows only owner to cancel round', async () => { + const fundingRoundAsCoordinator = fundingRound.connect( + coordinator + ) as Contract await expect(fundingRoundAsCoordinator.cancel()).to.be.revertedWith( 'Ownable: caller is not the owner' ) @@ -929,22 +817,24 @@ describe('Funding Round', () => { describe('withdrawing funds', () => { const userPubKey = userKeypair.pubKey.asContractParam() const anotherUserPubKey = userKeypair.pubKey.asContractParam() - const contributionAmount = UNIT * 10n + const contributionAmount = UNIT * BigInt(10) let fundingRoundAsContributor: Contract beforeEach(async () => { - await fundingRound.setMaci(maci.target) - await token - .connect(contributor) - .approve(fundingRound.target, contributionAmount) - await token - .connect(anotherContributor) - .approve(fundingRound.target, contributionAmount) + fundingRoundAsContributor = fundingRound.connect(contributor) as Contract + await (token.connect(contributor) as Contract).approve( + fundingRound.target, + contributionAmount + ) + await (token.connect(anotherContributor) as Contract).approve( + fundingRound.target, + contributionAmount + ) }) it('allows contributors to withdraw funds', async () => { await fundingRoundAsContributor.contribute(userPubKey, contributionAmount) - await fundingRoundAsAnotherContributor.contribute( + await (fundingRound.connect(anotherContributor) as Contract).contribute( anotherUserPubKey, contributionAmount ) @@ -953,7 +843,9 @@ describe('Funding Round', () => { await expect(fundingRoundAsContributor.withdrawContribution()) .to.emit(fundingRound, 'ContributionWithdrawn') .withArgs(contributor.address) - await fundingRoundAsAnotherContributor.withdrawContribution() + await ( + fundingRound.connect(anotherContributor) as Contract + ).withdrawContribution() expect(await token.balanceOf(fundingRound.target)).to.equal(0) }) @@ -961,19 +853,22 @@ describe('Funding Round', () => { await fundingRoundAsContributor.contribute(userPubKey, contributionAmount) await expect( fundingRoundAsContributor.withdrawContribution() - ).to.be.revertedWith('RoundNotCancelled') + ).to.be.revertedWithCustomError(fundingRound, 'RoundNotCancelled') }) it('reverts if user did not contribute to the round', async () => { await fundingRound.cancel() await expect( fundingRoundAsContributor.withdrawContribution() - ).to.be.revertedWith('NothingToWithdraw') + ).to.be.revertedWithCustomError(fundingRound, 'NothingToWithdraw') }) it('reverts if funds are already withdrawn', async () => { + const fundingRoundAsContributor = fundingRound.connect( + contributor + ) as Contract await fundingRoundAsContributor.contribute(userPubKey, contributionAmount) - await fundingRoundAsAnotherContributor.contribute( + await (fundingRound.connect(anotherContributor) as Contract).contribute( anotherUserPubKey, contributionAmount ) @@ -982,33 +877,37 @@ describe('Funding Round', () => { await fundingRoundAsContributor.withdrawContribution() await expect( fundingRoundAsContributor.withdrawContribution() - ).to.be.revertedWith('NothingToWithdraw') + ).to.be.revertedWithCustomError(fundingRound, 'NothingToWithdraw') }) it('allows anyone to withdraw multiple contributions', async () => { - await fundingRoundAsContributor.contribute(userPubKey, contributionAmount) - await fundingRoundAsAnotherContributor.contribute( + await (fundingRound.connect(contributor) as Contract).contribute( + userPubKey, + contributionAmount + ) + await (fundingRound.connect(anotherContributor) as Contract).contribute( anotherUserPubKey, contributionAmount ) await fundingRound.cancel() - const tx = await fundingRoundAsCoordinator.withdrawContributions([ - contributor.address, - anotherContributor.address, - ]) + const tx = await ( + fundingRound.connect(coordinator) as Contract + ).withdrawContributions([contributor.address, anotherContributor.address]) await tx.wait() expect(await token.balanceOf(fundingRound.target)).to.equal(0) }) it('allows transaction to complete even if some contributions fail to withdraw', async () => { - await fundingRoundAsContributor.contribute(userPubKey, contributionAmount) + await (fundingRound.connect(contributor) as Contract).contribute( + userPubKey, + contributionAmount + ) await fundingRound.cancel() - const tx = await fundingRoundAsCoordinator.withdrawContributions([ - contributor.address, - anotherContributor.address, - ]) + const tx = await ( + fundingRound.connect(coordinator) as Contract + ).withdrawContributions([contributor.address, anotherContributor.address]) await tx.wait() expect(await token.balanceOf(fundingRound.target)).to.equal(0) }) @@ -1016,7 +915,6 @@ describe('Funding Round', () => { describe('claiming funds', () => { const recipientIndex = 3 - const { spent: totalSpent, salt: totalSpentSalt } = smallTallyTestData.totalSpentVoiceCredits const contributions = @@ -1025,49 +923,41 @@ describe('Funding Round', () => { const expectedAllocatedAmount = calcAllocationAmount( smallTallyTestData.results.tally[recipientIndex], smallTallyTestData.perVOSpentVoiceCredits.tally[recipientIndex] - ).toString() + ) + let fundingRoundAsRecipient: Contract + let fundingRoundAsContributor: Contract beforeEach(async () => { - maci = await deployMaciMock() - poll = await deployMockContractByName('Poll') - tally = await deployMockContractByName('Tally') - pollId = BigInt(0) - await poll.mock.treeDepths.returns(1, 1, 1, tallyTreeDepth) - await maci.mock.getPoll.returns(poll.target) - - // round.isTallied() = tallyBatchSize * tallyBatchNum >= numSignups - await poll.mock.numSignUpsAndMessages.returns(numSignUps, 1) - await poll.mock.batchSizes.returns(1, tallyBatchSize, 1) - await tally.mock.verifyPerVOSpentVoiceCredits.returns(true) - await tally.mock.verifyTallyResult.returns(true) - await tally.mock.verifySpentVoiceCredits.returns(true) - await tally.mock.tallyBatchNum.returns(tallyBatchNum) - - // round.isVotingOver() - const deployTime = await time.latest() - await poll.mock.getDeployTimeAndDuration.returns(deployTime, pollDuration) await recipientRegistry.mock.getRecipientAddress.returns( recipient.address ) - await fundingRound.setMaci(maci.target) - await fundingRound.setPoll(pollId) - await fundingRoundAsCoordinator.setTally(tally.target) + const tokenAsContributor = token.connect(contributor) as Contract await tokenAsContributor.approve(fundingRound.target, contributions) + fundingRoundAsContributor = fundingRound.connect(contributor) as Contract - await time.increase(pollDuration) - await fundingRoundAsCoordinator.publishTallyHash(tallyHash) + await time.increase(roundDuration) + await (fundingRound.connect(coordinator) as Contract).publishTallyHash( + tallyHash + ) + fundingRoundAsRecipient = fundingRound.connect(recipient) as Contract + await tally.mock.verifyPerVOSpentVoiceCredits.returns(true) }) it('allows recipient to claim allocated funds', async () => { await token.transfer(fundingRound.target, budget) await addTallyResultsBatch( - fundingRoundAsCoordinator, + fundingRound.connect(coordinator) as Contract, tallyTreeDepth, smallTallyTestData, 3 ) - await finalizeRound(fundingRound, totalSpent, totalSpentSalt) + await fundingRound.finalize( + totalSpent, + totalSpentSalt, + newResultCommitment, + perVOSpentVoiceCreditsHash + ) const { results, perVOSpentVoiceCredits } = smallTallyTestData expect( @@ -1082,11 +972,9 @@ describe('Funding Round', () => { tallyTreeDepth, smallTallyTestData ) - await expect(fundingRoundAsRecipient.claimFunds(...claimData)) .to.emit(fundingRound, 'FundsClaimed') .withArgs(recipientIndex, recipient.address, expectedAllocatedAmount) - expect(await token.balanceOf(recipient.address)).to.equal( expectedAllocatedAmount, 'mismatch token balance' @@ -1096,12 +984,17 @@ describe('Funding Round', () => { it('allows address different than recipient to claim allocated funds', async () => { await token.transfer(fundingRound.target, budget) await addTallyResultsBatch( - fundingRoundAsCoordinator, + fundingRound.connect(coordinator) as Contract, tallyTreeDepth, smallTallyTestData, 3 ) - await finalizeRound(fundingRound, totalSpent, totalSpentSalt) + await fundingRound.finalize( + totalSpent, + totalSpentSalt, + newResultCommitment, + perVOSpentVoiceCreditsHash + ) const claimData = getRecipientClaimData( recipientIndex, @@ -1120,12 +1013,17 @@ describe('Funding Round', () => { it('allows recipient to claim zero amount', async () => { await token.transfer(fundingRound.target, budget) await addTallyResultsBatch( - fundingRoundAsCoordinator, + fundingRound.connect(coordinator) as Contract, tallyTreeDepth, smallTallyTestData, 3 ) - await finalizeRound(fundingRound, totalSpent, totalSpentSalt) + await fundingRound.finalize( + totalSpent, + totalSpentSalt, + newResultCommitment, + perVOSpentVoiceCreditsHash + ) const recipientWithZeroFunds = 2 const claimData = getRecipientClaimData( @@ -1143,12 +1041,17 @@ describe('Funding Round', () => { const totalContributions = BigInt(totalSpent) * VOICE_CREDIT_FACTOR await token.transfer(fundingRound.target, totalContributions) await addTallyResultsBatch( - fundingRoundAsCoordinator, + fundingRound.connect(coordinator) as Contract, tallyTreeDepth, smallTallyTestData, 3 ) - await finalizeRound(fundingRound, totalSpent, totalSpentSalt) + await fundingRound.finalize( + totalSpent, + totalSpentSalt, + newResultCommitment, + perVOSpentVoiceCreditsHash + ) const expectedWithoutMatching = BigInt(contributions) * VOICE_CREDIT_FACTOR @@ -1173,7 +1076,7 @@ describe('Funding Round', () => { ) await expect( fundingRoundAsRecipient.claimFunds(...claimData) - ).to.be.revertedWith('RoundNotFinalized') + ).to.be.revertedWithCustomError(fundingRound, 'RoundNotFinalized') }) it('should not allow recipient to claim funds if round has been cancelled', async () => { @@ -1187,18 +1090,23 @@ describe('Funding Round', () => { ) await expect( fundingRoundAsRecipient.claimFunds(...claimData) - ).to.be.revertedWith('RoundCancelled') + ).to.be.revertedWithCustomError(fundingRound, 'RoundCancelled') }) it('sends funds allocated to unverified recipients back to matching pool', async () => { await token.transfer(fundingRound.target, budget) await addTallyResultsBatch( - fundingRoundAsCoordinator, + fundingRound.connect(coordinator) as Contract, tallyTreeDepth, smallTallyTestData, 3 ) - await finalizeRound(fundingRound, totalSpent, totalSpentSalt) + await fundingRound.finalize( + totalSpent, + totalSpentSalt, + newResultCommitment, + perVOSpentVoiceCreditsHash + ) await recipientRegistry.mock.getRecipientAddress.returns(ZERO_ADDRESS) const claimData = getRecipientClaimData( @@ -1211,19 +1119,24 @@ describe('Funding Round', () => { .to.emit(fundingRound, 'FundsClaimed') .withArgs(recipientIndex, deployer.address, expectedAllocatedAmount) expect(await token.balanceOf(deployer.address)).to.equal( - initialDeployerBalance.add(expectedAllocatedAmount) + BigInt(initialDeployerBalance) + expectedAllocatedAmount ) }) it('allows recipient to claim allocated funds only once', async () => { await token.transfer(fundingRound.target, budget) await addTallyResultsBatch( - fundingRoundAsCoordinator, + fundingRound.connect(coordinator) as Contract, tallyTreeDepth, smallTallyTestData, 3 ) - await finalizeRound(fundingRound, totalSpent, totalSpentSalt) + await fundingRound.finalize( + totalSpent, + totalSpentSalt, + newResultCommitment, + perVOSpentVoiceCreditsHash + ) const claimData = getRecipientClaimData( recipientIndex, @@ -1233,98 +1146,83 @@ describe('Funding Round', () => { await fundingRoundAsRecipient.claimFunds(...claimData) await expect( fundingRoundAsRecipient.claimFunds(...claimData) - ).to.be.revertedWith('FundsAlreadyClaimed') + ).to.be.revertedWithCustomError(fundingRound, 'FundsAlreadyClaimed') }) it('should verify that tally result is correct', async () => { await token.transfer(fundingRound.target, budget) - await tally.mock.verifyTallyResult.returns(false) await expect( addTallyResultsBatch( - fundingRoundAsCoordinator, + fundingRound.connect(coordinator) as Contract, tallyTreeDepth, smallTallyTestData, 3 ) - ).to.be.revertedWith('IncorrectTallyResult') + ).to.be.revertedWithCustomError(fundingRound, 'IncorrectTallyResult') }) - it.skip('should verify that amount of spent voice credits is correct', async () => { + it('should verify that amount of spent voice credits is correct', async () => { await token.transfer(fundingRound.target, budget) - await tally.mock.verifyPerVOSpentVoiceCredits.returns(false) await addTallyResultsBatch( - fundingRoundAsCoordinator, + fundingRound.connect(coordinator) as Contract, tallyTreeDepth, smallTallyTestData, 3 ) - await finalizeRound(fundingRound, totalSpent, totalSpentSalt) + await fundingRound.finalize( + totalSpent, + totalSpentSalt, + newResultCommitment, + perVOSpentVoiceCreditsHash + ) const claimData = getRecipientClaimData( recipientIndex, tallyTreeDepth, smallTallyTestData ) + + await tally.mock.verifyPerVOSpentVoiceCredits.returns(false) await expect( fundingRoundAsRecipient.claimFunds(...claimData) - ).to.be.revertedWith('IncorrectSpentVoiceCredits') + ).to.be.revertedWithCustomError( + fundingRound, + 'IncorrectPerVOSpentVoiceCredits' + ) }) }) describe('finalizing with alpha', function () { this.timeout(2 * 60 * 1000) const treeDepth = 2 - const totalSpentSalt = genRandomSalt().toString() - beforeEach(async () => { - maci = await deployMaciMock() - poll = await deployMockContractByName('Poll') - tally = await deployMockContractByName('Tally') - pollId = BigInt(0) - await tally.mock.verifyTallyResult.returns(true) - await tally.mock.verifySpentVoiceCredits.returns(true) - await poll.mock.treeDepths.returns(1, 1, 1, treeDepth) - await maci.mock.getPoll.returns(poll.target) - - // round.isTallied() = tallyBatchSize * tallyBatchNum >= numSignups - await poll.mock.numSignUpsAndMessages.returns(numSignUps, 1) - await poll.mock.batchSizes.returns(1, tallyBatchSize, 1) - await tally.mock.verifyPerVOSpentVoiceCredits.returns(true) - await tally.mock.tallyBatchNum.returns(tallyBatchNum) - - // round.isVotingOver() - const deployTime = await time.latest() - await poll.mock.getDeployTimeAndDuration.returns(deployTime, pollDuration) - await recipientRegistry.mock.getRecipientAddress.returns( - recipient.address - ) - - await fundingRound.setMaci(maci.target) - await fundingRound.setPoll(pollId) - await fundingRoundAsCoordinator.setTally(tally.target) await recipientRegistry.mock.getRecipientAddress.returns( recipient.address ) await token.transfer(fundingRound.target, budget) + + const fundingRoundAsCoordinator = fundingRound.connect( + coordinator + ) as Contract await fundingRoundAsCoordinator.publishTallyHash(tallyHash) - await time.increase(pollDuration) + await time.increase(roundDuration) }) it('adds and verifies tally results', async function () { this.timeout(2 * 60 * 1000) await addTallyResultsBatch( - fundingRoundAsCoordinator, + fundingRound.connect(coordinator) as Contract, treeDepth, smallTallyTestData, 5 ) const totalResults = await fundingRound.totalTallyResults() - expect(totalResults.toNumber()).to.eq(25, 'total verified mismatch') + expect(toNumber(totalResults)).to.eq(25, 'total verified mismatch') const totalSquares = await fundingRound.totalVotesSquares() expect(totalSquares.toString()).to.eq( @@ -1336,7 +1234,7 @@ describe('Funding Round', () => { it('calculates alpha correctly', async function () { this.timeout(2 * 60 * 1000) await addTallyResultsBatch( - fundingRoundAsCoordinator, + fundingRound.connect(coordinator) as Contract, treeDepth, smallTallyTestData, 5 @@ -1354,12 +1252,18 @@ describe('Funding Round', () => { it('finalizes successfully', async function () { await addTallyResultsBatch( - fundingRoundAsCoordinator, + fundingRound.connect(coordinator) as Contract, treeDepth, smallTallyTestData, 3 ) - await finalizeRound(fundingRound, totalSpent, totalSpentSalt) + const { spent, salt } = smallTallyTestData.totalSpentVoiceCredits + await fundingRound.finalize( + spent, + salt, + newResultCommitment, + perVOSpentVoiceCreditsHash + ) const alpha = await fundingRound.alpha() expect(alpha.toString()).to.eq( @@ -1368,16 +1272,17 @@ describe('Funding Round', () => { ) }) - it('fails to finalize if no project has more than 1 vote', async function () { - const tallyTreeDepth = 1 - await poll.mock.treeDepths.returns(1, 1, 1, tallyTreeDepth) + it('fails to finalize if all projects only have 1 contributor', async function () { const tallyWith1Contributor = { newTallyCommitment: - '0x2a7a1fe8c2773fdba262033741655070ba52fea7c1333049ec87c2c248e600bb', + '0xae3fc926f8347c17f9787eded70bc60e32a175cb46c58c03ffe2f4372cd736', results: { commitment: '0x2f44c97ce649078012fd686eaf996fc6b8d817e11ab574f0d0a0d750ee1ec101', - tally: [0, 200, 200, 0, 0], + tally: [ + 0, 200, 200, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, + ], salt: '0xa1f71f9e48a5f2ec55020051a190f079ca43d66457879972554c3c2e8a07ea0', }, totalSpentVoiceCredits: { @@ -1389,38 +1294,78 @@ describe('Funding Round', () => { perVOSpentVoiceCredits: { commitment: '0x26e6ae35c82006eff6408b713d477307b2da16c7a1ff15fb46c0762ee308e88a', - tally: ['0', '40000', '40000', '0', '0'], - salt: '0x2013aa4e350542684f78adbf3e716c3bcf96e12c64b8e8ef3d962e3568132778', + tally: [ + '0', + '40000', + '40000', + '0', + '0', + '0', + '0', + '0', + '0', + '0', + '0', + '0', + '0', + '0', + '0', + '0', + '0', + '0', + '0', + '0', + '0', + '0', + '0', + '0', + '0', + ], + salt: '0x63c80f2b0319790c19b3b17ecd7b00fc1dc7398198601d0dfb30253306ecb34', }, - salt: '0x63c80f2b0319790c19b3b17ecd7b00fc1dc7398198601d0dfb30253306ecb34', } const batchSize = 3 await addTallyResultsBatch( - fundingRoundAsCoordinator, + fundingRound.connect(coordinator) as Contract, tallyTreeDepth, tallyWith1Contributor, batchSize ) - + const { spent, salt } = smallTallyTestData.totalSpentVoiceCredits await expect( - finalizeRound(fundingRound, totalSpent, totalSpentSalt) - ).to.be.revertedWith('NoProjectHasMoreThanOneVote') + fundingRound.finalize( + spent, + salt, + newResultCommitment, + perVOSpentVoiceCreditsHash + ) + ).to.be.revertedWithCustomError( + fundingRound, + 'NoProjectHasMoreThanOneVote' + ) }) it('calculates claim funds correctly', async function () { await addTallyResultsBatch( - fundingRoundAsCoordinator, + fundingRound.connect(coordinator) as Contract, treeDepth, smallTallyTestData, 20 ) - await finalizeRound(fundingRound, totalSpent, totalSpentSalt) + const { spent, salt } = smallTallyTestData.totalSpentVoiceCredits + await fundingRound.finalize( + spent, + salt, + newResultCommitment, + perVOSpentVoiceCreditsHash + ) + await tally.mock.verifyPerVOSpentVoiceCredits.returns(true) - const { tally } = smallTallyTestData.results + const { tally: tallyResults } = smallTallyTestData.results const { tally: spents } = smallTallyTestData.perVOSpentVoiceCredits - for (let i = 0; i < tally.length; i++) { - const tallyResult = tally[i] + for (let i = 0; i < tallyResults.length; i++) { + const tallyResult = tallyResults[i] if (tallyResult !== '0') { const amount = await fundingRound.getAllocatedAmount( tallyResult, @@ -1445,22 +1390,22 @@ describe('Funding Round', () => { } }) - it.skip('prevents finalize if tally results not completely received', async function () { - // increase the number of signup to simulate incomplete tallying - await poll.mock.numSignUpsAndMessages.returns(numSignUps * 2, 1) - await addTallyResultsBatch( - fundingRoundAsCoordinator, - tallyTreeDepth, - smallTallyTestData, - tallyBatchSize - ) - + it('prevents finalize if tally results not completely received', async function () { + const { spent, salt } = smallTallyTestData.totalSpentVoiceCredits await expect( - finalizeRound(fundingRound, totalSpent, totalSpentSalt) - ).to.be.revertedWith('FundingRound: Incomplete tally results') + fundingRound.finalize( + spent, + salt, + newResultCommitment, + perVOSpentVoiceCreditsHash + ) + ).to.be.revertedWithCustomError(fundingRound, 'IncompleteTallyResults') }) it('allows only coordinator to add tally results', async function () { + const fundingRoundAsContributor = fundingRound.connect( + contributor + ) as Contract await expect( addTallyResultsBatch( fundingRoundAsContributor, @@ -1468,10 +1413,13 @@ describe('Funding Round', () => { smallTallyTestData, 5 ) - ).to.be.revertedWith('NotCoordinator') + ).to.be.revertedWithCustomError(fundingRound, 'NotCoordinator') }) it('allows only coordinator to add tally results in batches', async function () { + const fundingRoundAsContributor = fundingRound.connect( + contributor + ) as Contract await expect( addTallyResultsBatch( fundingRoundAsContributor, @@ -1479,59 +1427,62 @@ describe('Funding Round', () => { smallTallyTestData, 5 ) - ).to.be.revertedWith('NotCoordinator') + ).to.be.revertedWithCustomError(fundingRound, 'NotCoordinator') }) it('prevents adding tally results if maci has not completed tallying', async function () { - // increase the number of signup to simulate incomplete tallying - await poll.mock.numSignUpsAndMessages.returns(numSignUps * 2, 1) - + await tally.mock.tallyBatchNum.returns(0) await expect( addTallyResultsBatch( - fundingRoundAsCoordinator, + fundingRound.connect(coordinator) as Contract, tallyTreeDepth, smallTallyTestData, 5 ) - ).to.be.revertedWith('VotesNotTallied') + ).to.be.revertedWithCustomError(fundingRound, 'VotesNotTallied') }) it('prevents adding batches of tally results if maci has not completed tallying', async function () { - // increase the number of signup to simulate incomplete tallying - await poll.mock.numSignUpsAndMessages.returns(numSignUps * 2, 1) - + await tally.mock.tallyBatchNum.returns(0) await expect( addTallyResultsBatch( - fundingRoundAsCoordinator, + fundingRound.connect(coordinator) as Contract, tallyTreeDepth, smallTallyTestData, 5 ) - ).to.be.revertedWith('VotesNotTallied') + ).to.be.revertedWithCustomError(fundingRound, 'VotesNotTallied') }) it('prevent adding more tally results if already finalized', async () => { - //await maci.mock.treeDepths.returns(10, 10, tallyTreeDepth) - await addTallyResultsBatch( - fundingRoundAsCoordinator, + fundingRound.connect(coordinator) as Contract, tallyTreeDepth, smallTallyTestData, 5 ) - await finalizeRound(fundingRound, totalSpent, totalSpentSalt) + const { spent, salt } = smallTallyTestData.totalSpentVoiceCredits + await fundingRound.finalize( + spent, + salt, + newResultCommitment, + perVOSpentVoiceCreditsHash + ) await expect( addTallyResultsBatch( - fundingRoundAsCoordinator, + fundingRound.connect(coordinator) as Contract, tallyTreeDepth, smallTallyTestData, 5 ) - ).to.be.revertedWith('RoundAlreadyFinalized') + ).to.be.revertedWithCustomError(fundingRound, 'RoundAlreadyFinalized') }) it('prevents adding tally results that were already verified', async function () { + const fundingRoundAsCoordinator = fundingRound.connect( + coordinator + ) as Contract await addTallyResultsBatch( fundingRoundAsCoordinator, tallyTreeDepth, @@ -1545,7 +1496,7 @@ describe('Funding Round', () => { smallTallyTestData, 5 ) - ).to.revertedWith('VoteResultsAlreadyVerified') + ).to.revertedWithCustomError(fundingRound, 'VoteResultsAlreadyVerified') }) it('returns correct proccessed count in the callback for processing tally results', async () => { @@ -1555,7 +1506,7 @@ describe('Funding Round', () => { const total = smallTallyTestData.results.tally.length const lastBatch = Math.ceil(total / batchSize) await addTallyResultsBatch( - fundingRoundAsCoordinator, + fundingRound.connect(coordinator) as Contract, tallyTreeDepth, smallTallyTestData, batchSize, @@ -1621,16 +1572,19 @@ describe('Funding Round', () => { const totalSpent = 100 await expect( fundingRound.calcAlpha(totalBudget, totalVotesSquares, totalSpent) - ).to.be.revertedWith('InvalidBudget') + ).to.be.revertedWithCustomError(fundingRound, 'InvalidBudget') }) - it('fails alpha calculation if no project has more than 1 vote', async function () { + it('fails alpha calculation if total votes square less than total spent', async function () { const totalBudget = parseEther('200') const totalVotesSquares = 88 const totalSpent = 100 await expect( fundingRound.calcAlpha(totalBudget, totalVotesSquares, totalSpent) - ).to.be.revertedWith('NoProjectHasMoreThanOneVote') + ).to.be.revertedWithCustomError( + fundingRound, + 'NoProjectHasMoreThanOneVote' + ) }) }) }) diff --git a/contracts/tsconfig.json b/contracts/tsconfig.json index 785bef695..4565eb4ff 100644 --- a/contracts/tsconfig.json +++ b/contracts/tsconfig.json @@ -8,6 +8,6 @@ "skipLibCheck": true, "resolveJsonModule": true }, - "include": ["./scripts", "./tests", "./cli", "./e2e"], + "include": ["./scripts", "./tests", "./cli", "./e2e", "./utils"], "files": ["./hardhat.config.ts"] } diff --git a/contracts/utils/deployment.ts b/contracts/utils/deployment.ts index ed3b0035e..2b227b46a 100644 --- a/contracts/utils/deployment.ts +++ b/contracts/utils/deployment.ts @@ -1,7 +1,7 @@ import { Signer, Contract, - ContractTransaction, + ContractTransactionResponse, encodeBytes32String, } from 'ethers' import path from 'path' @@ -312,69 +312,6 @@ export async function deployPollFactory({ }) } -/** - * Deploy the contracts needed to run the proveOnChain script. - * If the poseidon contracts are not provided, it will create them - * using the byte codes in the artifactsPath - * - * libraries - poseidon libraries - * artifactsPath - path of artifact containing the poseidon abi and bytecode - * @returns the MessageProcessor and Tally contracts - */ -export async function deployMessageProcesorAndTally({ - artifactsPath, - libraries, - ethers, - signer, -}: { - libraries?: Libraries - artifactsPath?: string - signer?: Signer - ethers: HardhatEthersHelpers -}): Promise<{ - mpContract: Contract - tallyContract: Contract -}> { - if (!libraries) { - if (!artifactsPath) { - throw Error('Need the artifacts path to create the poseidon contracts') - } - libraries = await deployPoseidonLibraries({ - artifactsPath, - ethers, - signer, - }) - } - - const verifierContract = await deployContract({ - name: 'Verifier', - signer, - ethers, - }) - - const tallyContract = await deployContract({ - name: 'Tally', - contractArgs: [verifierContract.target], - libraries, - ethers, - signer, - }) - - // deploy the message processing contract - const mpContract = await deployContract({ - name: 'MessageProcessor', - contractArgs: [verifierContract.target], - signer, - libraries, - ethers, - }) - - return { - mpContract, - tallyContract, - } -} - /** * Deploy an instance of MACI factory * libraries - poseidon contracts @@ -457,7 +394,7 @@ export async function setMaciParameters( maciFactory: Contract, directory: string, circuit = DEFAULT_CIRCUIT -): Promise { +): Promise { if (!isPathExist(directory)) { throw new Error(`Path ${directory} does not exists`) } @@ -484,7 +421,7 @@ export async function setCoordinator({ clrfundContract: Contract coordinatorAddress: string coordinatorMacisk?: string -}): Promise { +}): Promise { // Generate or use the passed in coordinator key const privKey = coordinatorMacisk ? PrivKey.deserialize(coordinatorMacisk) diff --git a/contracts/utils/maci.ts b/contracts/utils/maci.ts index d354af874..e5d454dee 100644 --- a/contracts/utils/maci.ts +++ b/contracts/utils/maci.ts @@ -305,17 +305,8 @@ export async function mergeMaciSubtrees( pollId: string, numOperations: number ) { - await mergeMessages({ - contract: maciAddress, - poll_id: pollId, - num_queue_ops: numOperations, - }) - - await mergeSignups({ - contract: maciAddress, - poll_id: pollId, - num_queue_ops: numOperations, - }) + await mergeMessages(Number(pollId), maciAddress, numOperations.toString()) + await mergeSignups(Number(pollId), maciAddress, numOperations.toString()) } /** diff --git a/contracts/utils/maciParameters.ts b/contracts/utils/maciParameters.ts index 0f493bde3..a16010f2d 100644 --- a/contracts/utils/maciParameters.ts +++ b/contracts/utils/maciParameters.ts @@ -14,6 +14,18 @@ export interface ZkFiles { tallyWasm: string } +type TreeDepths = { + intStateTreeDepth: number + messageTreeSubDepth: number + messageTreeDepth: number + voteOptionTreeDepth: number +} + +type MaxValues = { + maxMessages: number + maxVoteOptions: number +} + /** * Get the zkey file path * @param name zkey file name @@ -42,6 +54,8 @@ export class MaciParameters { messageBatchSize: number processVk: VerifyingKey tallyVk: VerifyingKey + treeDepths: TreeDepths + maxValues: MaxValues constructor(parameters: { [name: string]: any } = {}) { this.stateTreeDepth = parameters.stateTreeDepth @@ -54,6 +68,16 @@ export class MaciParameters { this.messageBatchSize = parameters.messageBatchSize this.processVk = parameters.processVk this.tallyVk = parameters.tallyVk + this.treeDepths = { + intStateTreeDepth: this.intStateTreeDepth, + messageTreeSubDepth: this.messageTreeSubDepth, + messageTreeDepth: this.messageTreeDepth, + voteOptionTreeDepth: this.voteOptionTreeDepth, + } + this.maxValues = { + maxMessages: this.maxMessages, + maxVoteOptions: this.maxVoteOptions, + } } asContractParam(): any[] { @@ -117,7 +141,7 @@ export class MaciParameters { }) } - static mock(circuit: string): MaciParameters { + static mock(): MaciParameters { const processVk = VerifyingKey.fromObj({ protocol: 1, curve: 1, @@ -138,7 +162,20 @@ export class MaciParameters { vk_alphabeta_12: [[[1, 2, 3]]], IC: [[1, 2]], }) - const params = CIRCUITS[circuit] + + // use smaller voteOptionTreeDepth for testing + const params = { + maxValues: { maxMessages: 390625, maxVoteOptions: 25 }, + treeDepths: { + stateTreeDepth: 6, + intStateTreeDepth: 2, + messageTreeSubDepth: 2, + messageTreeDepth: 8, + voteOptionTreeDepth: 2, + }, + batchSizes: { messageBatchSize: 25 }, + } + return new MaciParameters({ ...params.maxValues, ...params.treeDepths, diff --git a/contracts/utils/testutils.ts b/contracts/utils/testutils.ts new file mode 100644 index 000000000..dff6b4ca5 --- /dev/null +++ b/contracts/utils/testutils.ts @@ -0,0 +1,159 @@ +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' + +/** + * Deploy a mock contract with the given contract name + * @param signer signer of the mock contract deployment + * @param name name of the contract to mock + * @returns a mock contract + */ +export async function deployMockContractByName( + name: string, + signer: Signer +): Promise { + const ContractArtifacts = await artifacts.readArtifact(name) + return deployMockContract(signer, ContractArtifacts.abi) +} + +/** + * Deploy an instance of the maciFactory contracct and returns the factory contracts + * @param signer signer of the contract deployment + * @param libraries poseidon libraries + * @returns the contract factories in the maciFactory contract + */ +export async function deployFactories( + signer: Signer, + libraries: { [name: string]: string } +) { + const maciFactory = await deployMaciFactory({ libraries, ethers, signer }) + const factories = await maciFactory.factories() + return { + pollFactory: factories.pollFactory, + tallyFactory: factories.tallyFactory, + subsidyFactory: factories.subsidyFactory, + messageProcessorFactory: factories.messageProcessorFactory, + } +} + +/** + * Output from the deployTestFundingRound() function + */ +export type DeployTestFundingRoundOutput = { + token: Contract + fundingRound: Contract + mockUserRegistry: MockContract + mockRecipientRegistry: MockContract + mockVerifier: MockContract + mockTally: MockContract +} + +/** + * Deploy an instance of funding round contract for testing + * @param tokenSupply initial supply for the native token + * @param coordinatorAddress the coordinator wallet address + * @param deployer singer for the contract deployment + * @returns all the deployed objects in DeployTestFundingRoundOutput + */ +export async function deployTestFundingRound( + tokenSupply: bigint, + coordinatorAddress: string, + coordinatorPubKey: PubKey, + roundDuration: number, + deployer: Signer +): Promise { + const token = await ethers.deployContract( + 'AnyOldERC20Token', + [tokenSupply], + deployer + ) + + const mockUserRegistry = await deployMockContractByName( + 'IUserRegistry', + deployer + ) + const mockRecipientRegistry = await deployMockContractByName( + 'IRecipientRegistry', + deployer + ) + + const fundingRound = await ethers.deployContract( + 'FundingRound', + [ + token.target, + mockUserRegistry.target, + mockRecipientRegistry.target, + coordinatorAddress, + ], + deployer + ) + + const libraries = await deployPoseidonLibraries({ + signer: deployer, + ethers, + artifactsPath: config.paths.artifacts, + }) + + const maciParameters = MaciParameters.mock() + const factories = await deployFactories(deployer, libraries) + 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 maciInstance = await ethers.deployContract( + 'MACI', + [ + factories.pollFactory, + factories.messageProcessorFactory, + factories.tallyFactory, + factories.subsidyFactory, + fundingRound.target, + fundingRound.target, + topupToken.target, + maciParameters.stateTreeDepth, + ], + { + signer: deployer, + libraries, + } + ) + const deployPollTx = await maciInstance.deployPoll( + roundDuration, + maciParameters.maxValues, + maciParameters.treeDepths, + coordinatorPubKey.asContractParam(), + mockVerifier.target, + vkRegistry.target, + // pass false to not deploy the subsidy contract + false + ) + const pollAddr = await getEventArg( + deployPollTx, + maciInstance, + 'DeployPoll', + 'pollAddr' + ) + + // swap out the tally with mock tally for testing + const pollContracts = { + tally: mockTally.target, + poll: pollAddr.poll, + messageProcessor: pollAddr.messageProcessor, + subsidy: pollAddr.subsidy, + } + await fundingRound.setMaci(maciInstance.target, pollContracts, factories) + + return { + token, + fundingRound, + mockRecipientRegistry, + mockUserRegistry, + mockVerifier, + mockTally, + } +} diff --git a/yarn.lock b/yarn.lock index b818f39fb..b4fb44a73 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7866,9 +7866,9 @@ circomlib@^2.0.5: resolved "https://registry.yarnpkg.com/circomlib/-/circomlib-2.0.5.tgz#183c703e53ed7d011811842dbeeeb9819f4cc1d6" integrity sha512-O7NQ8OS+J4eshBuoy36z/TwQU0YHw8W3zxZcs4hVwpEll3e4hDm3mgkIPqItN8FDeLEKZFK3YeT/+k8TiLF3/A== -"circomlib@https://github.com/weijiekoh/circomlib#ac85e82c1914d47789e2032fb11ceb2cfdd38a2b": +"circomlib@git+https://github.com/weijiekoh/circomlib.git#ac85e82c1914d47789e2032fb11ceb2cfdd38a2b": version "1.0.0" - resolved "https://github.com/weijiekoh/circomlib#ac85e82c1914d47789e2032fb11ceb2cfdd38a2b" + resolved "git+https://github.com/weijiekoh/circomlib.git#ac85e82c1914d47789e2032fb11ceb2cfdd38a2b" circomlibjs@0.0.8: version "0.0.8" @@ -15328,20 +15328,20 @@ luxon@^3.1.1, luxon@^3.2.1: resolved "https://registry.yarnpkg.com/luxon/-/luxon-3.4.4.tgz#cf20dc27dc532ba41a169c43fdcc0063601577af" integrity sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA== -maci-circuits@0.0.0-ci.2e62774: - version "0.0.0-ci.2e62774" - resolved "https://registry.yarnpkg.com/maci-circuits/-/maci-circuits-0.0.0-ci.2e62774.tgz#04157c79d8493e120142ce59b4d63f9e6e5b0dff" - integrity sha512-tcrksLX8Eo/f8TDA0dV1cwrnd0zIAtxS9KSP1Gy2jhaecAgepmqOEeOsPhfCta0IA9wlp1ZZUyIiGqgea2NP7g== +maci-circuits@0.0.0-ci.ae1f52e: + version "0.0.0-ci.ae1f52e" + resolved "https://registry.yarnpkg.com/maci-circuits/-/maci-circuits-0.0.0-ci.ae1f52e.tgz#d34f485ce8a2ffe71229858d1c46a269e5e0f1ec" + integrity sha512-y2FdkKVLHi/Ogc7tci80NrbXqIV0MCuQr3H1XlGHJIBAkx0WswX4N0rS4WPocnQKgmrLPhVIxOyIEwqkJ0lx5Q== dependencies: "@zk-kit/circuits" "^0.3.0" circomkit "^0.0.20" circomlib "^2.0.5" - maci-core "^0.0.0-ci.2e62774" - maci-crypto "^0.0.0-ci.2e62774" - maci-domainobjs "^0.0.0-ci.2e62774" - snarkjs "^0.7.2" + maci-core "^0.0.0-ci.ae1f52e" + maci-crypto "^0.0.0-ci.ae1f52e" + maci-domainobjs "^0.0.0-ci.ae1f52e" + snarkjs "^0.7.3" -maci-circuits@^0.0.0-ci.2e62774, maci-circuits@^0.0.0-ci.fb8c927: +maci-circuits@^0.0.0-ci.ae1f52e, maci-circuits@^0.0.0-ci.fb8c927: version "0.0.0-ci.fb8c927" resolved "https://registry.yarnpkg.com/maci-circuits/-/maci-circuits-0.0.0-ci.fb8c927.tgz#7a474689b9312bc09392ebb06a21ef591f97f9dc" integrity sha512-ai+C4y9vi17Cr420wCB75oI1wemOLFqXqfeiwBqvhpARVie5gX2hvaKet1w/rUOK2iSGheDKFpmkiM4v5bXXcQ== @@ -15352,43 +15352,43 @@ maci-circuits@^0.0.0-ci.2e62774, maci-circuits@^0.0.0-ci.fb8c927: maci-domainobjs "^0.0.0-ci.fb8c927" snarkjs "^0.7.2" -maci-cli@0.0.0-ci.2e62774: - version "0.0.0-ci.2e62774" - resolved "https://registry.yarnpkg.com/maci-cli/-/maci-cli-0.0.0-ci.2e62774.tgz#7dad01346533c2fa1b19e38daa9c921186e1b3d1" - integrity sha512-/hqvBKMcl9lFMKppKs5/lLeoX8CKM5qszgQvwZGMtxhi+J2uJCBhMccfn+R3/0iHIhkWAwJEZ/b9BYuefrs+AQ== +maci-cli@0.0.0-ci.ae1f52e: + version "0.0.0-ci.ae1f52e" + resolved "https://registry.yarnpkg.com/maci-cli/-/maci-cli-0.0.0-ci.ae1f52e.tgz#3bd445330412ac51e7d971c233fb7feb423175b3" + integrity sha512-4bMI7wSIYZlZGiKnqqa51wXdBmyTjQ0gDm/XhLqFEjfBfkHgew1r6Zsl+/pJhSZrXgrcp4a1l+HRaAlt6cqBAA== dependencies: "@commander-js/extra-typings" "^11.1.0" "@nomicfoundation/hardhat-toolbox" "^4.0.0" - circomlib "^2.0.5" + big-integer "^1.6.52" commander "^11.1.0" dotenv "^16.3.1" - ethers "^6.9.2" + ethers "^6.10.0" hardhat "^2.19.2" - maci-circuits "^0.0.0-ci.2e62774" - maci-contracts "^0.0.0-ci.2e62774" - maci-core "^0.0.0-ci.2e62774" - maci-crypto "^0.0.0-ci.2e62774" - maci-domainobjs "^0.0.0-ci.2e62774" + maci-circuits "^0.0.0-ci.ae1f52e" + maci-contracts "^0.0.0-ci.ae1f52e" + maci-core "^0.0.0-ci.ae1f52e" + maci-crypto "^0.0.0-ci.ae1f52e" + maci-domainobjs "^0.0.0-ci.ae1f52e" prompt "^1.3.0" -maci-contracts@0.0.0-ci.2e62774: - version "0.0.0-ci.2e62774" - resolved "https://registry.yarnpkg.com/maci-contracts/-/maci-contracts-0.0.0-ci.2e62774.tgz#9c287317ae7f7c6755d454950f880e009fc4759c" - integrity sha512-BXfa/ZmpIKwzaGl6i2br4zs+apzTQbUkL5+mW3/lfylrL+j4Qhvu998VsMUacwv3xhTg5vcg1+5KBXt/JVzrhg== +maci-contracts@0.0.0-ci.ae1f52e: + version "0.0.0-ci.ae1f52e" + resolved "https://registry.yarnpkg.com/maci-contracts/-/maci-contracts-0.0.0-ci.ae1f52e.tgz#1af2e3834449c217673ac8cacb5acd3b57d76416" + integrity sha512-QM4t575mAGxvoR6qSWQjZ1Lgf+CxIBZST7DSThZpRFxxvzTHdUa9aoq1XRD4O6WrcIB9Ip6mO6gObV0tuYKLSg== dependencies: "@nomicfoundation/hardhat-ethers" "^3.0.5" "@nomicfoundation/hardhat-toolbox" "^4.0.0" "@openzeppelin/contracts" "^4.8.0" circomlibjs "^0.1.7" - ethers "^6.9.2" + ethers "^6.10.0" hardhat "^2.19.4" - maci-circuits "^0.0.0-ci.2e62774" - maci-core "^0.0.0-ci.2e62774" - maci-crypto "^0.0.0-ci.2e62774" - maci-domainobjs "^0.0.0-ci.2e62774" + maci-circuits "^0.0.0-ci.ae1f52e" + maci-core "^0.0.0-ci.ae1f52e" + maci-crypto "^0.0.0-ci.ae1f52e" + maci-domainobjs "^0.0.0-ci.ae1f52e" solidity-docgen "^0.6.0-beta.36" -maci-contracts@^0.0.0-ci.2e62774: +maci-contracts@^0.0.0-ci.ae1f52e: version "0.0.0-ci.fb8c927" resolved "https://registry.yarnpkg.com/maci-contracts/-/maci-contracts-0.0.0-ci.fb8c927.tgz#1b4aec71962fb6d2ca4bdb07808b301eb639b43c" integrity sha512-mZTRp9rDuYfh/2p7nuG0X0yitYIfW53Vw1h2KToyCJ//KJ9AUHzBbW5ZYIVE/wjgbnUjvK3L61SSbSgYOPcLVg== @@ -15404,7 +15404,7 @@ maci-contracts@^0.0.0-ci.2e62774: maci-domainobjs "^0.0.0-ci.fb8c927" solidity-docgen "^0.6.0-beta.36" -maci-core@^0.0.0-ci.2e62774, maci-core@^0.0.0-ci.fb8c927: +maci-core@^0.0.0-ci.ae1f52e, maci-core@^0.0.0-ci.fb8c927: version "0.0.0-ci.fb8c927" resolved "https://registry.yarnpkg.com/maci-core/-/maci-core-0.0.0-ci.fb8c927.tgz#10dcc8c54507bb81d2d5560859e165e20b974ba3" integrity sha512-+bUdlz/sTRiY0i8B7Ai3cydSnM5wpdHrdsMX2SwAsM4k61hVAu9GakTqvJtI8XnDPdHFVhvAyob9hKTUIbfu0A== @@ -15412,18 +15412,19 @@ maci-core@^0.0.0-ci.2e62774, maci-core@^0.0.0-ci.fb8c927: maci-crypto "^0.0.0-ci.fb8c927" maci-domainobjs "^0.0.0-ci.fb8c927" -maci-crypto@0.0.0-ci.2e62774: - version "0.0.0-ci.2e62774" - resolved "https://registry.yarnpkg.com/maci-crypto/-/maci-crypto-0.0.0-ci.2e62774.tgz#4d85a19f1c6bb059cd8d7ec4f572cab56c03f915" - integrity sha512-iYhNHFlI5UVR6/8JYsQUx0hOT4iddXG8q57d4r/BdQjTbtTmCwfUqFltyWI6+hdW0Z9ty0c+x6O5WFTceSuctw== +maci-crypto@0.0.0-ci.ae1f52e: + version "0.0.0-ci.ae1f52e" + resolved "https://registry.yarnpkg.com/maci-crypto/-/maci-crypto-0.0.0-ci.ae1f52e.tgz#cb468991585955c1b511444efada78f545780129" + integrity sha512-bJYYdXgI6Ztho0pw0eb7QOyD3nsrs8TtY0f77Vvj3iOc+mLOEtoqsAZykF2rbUqsQOdl7xuNuMlU1Ytky6EWnQ== dependencies: "@zk-kit/baby-jubjub" "^0.1.1" "@zk-kit/eddsa-poseidon" "^0.5.1" "@zk-kit/poseidon-cipher" "^0.1.1" - ethers "^6.9.2" + big-integer "^1.6.52" + ethers "^6.10.0" optimisedmt "^0.0.9" -maci-crypto@^0.0.0-ci.2e62774, maci-crypto@^0.0.0-ci.fb8c927: +maci-crypto@^0.0.0-ci.ae1f52e, maci-crypto@^0.0.0-ci.fb8c927: version "0.0.0-ci.fb8c927" resolved "https://registry.yarnpkg.com/maci-crypto/-/maci-crypto-0.0.0-ci.fb8c927.tgz#6d01aaabbe32033efbefb320fdd11c528e16c855" integrity sha512-Up5a/llu67BhR6bDsqDMnLH1jmMAMnSAx8Hdt2cH0rlS8iHQr2d8SLGS35FN2OIYqHN7zPUxdFHedFCJAsuPoQ== @@ -15434,14 +15435,14 @@ maci-crypto@^0.0.0-ci.2e62774, maci-crypto@^0.0.0-ci.fb8c927: ethers "^6.9.2" optimisedmt "^0.0.9" -maci-domainobjs@0.0.0-ci.2e62774: - version "0.0.0-ci.2e62774" - resolved "https://registry.yarnpkg.com/maci-domainobjs/-/maci-domainobjs-0.0.0-ci.2e62774.tgz#3f171aa80bb86929c53656ea288ceda19455e114" - integrity sha512-1/9pvvs8l1ftyFu7EQLImRfboiR9qlVPI8pEp9iRP44qi4u2qRW1mErq/vRB9XRlTXQssiOtptqF3e5fuJUjBA== +maci-domainobjs@0.0.0-ci.ae1f52e: + version "0.0.0-ci.ae1f52e" + resolved "https://registry.yarnpkg.com/maci-domainobjs/-/maci-domainobjs-0.0.0-ci.ae1f52e.tgz#bf7b925560cd45d461a85a66f148e90571e17f57" + integrity sha512-0GE/712GROq7bja/imFwzs4w1wekXa0IzL042I8GaqdumHAnHXrSO7S4D6bbPdeERMdqvEi+KM8rYtfpZOJvpg== dependencies: - maci-crypto "^0.0.0-ci.2e62774" + maci-crypto "^0.0.0-ci.ae1f52e" -maci-domainobjs@^0.0.0-ci.2e62774, maci-domainobjs@^0.0.0-ci.fb8c927: +maci-domainobjs@^0.0.0-ci.ae1f52e, maci-domainobjs@^0.0.0-ci.fb8c927: version "0.0.0-ci.fb8c927" resolved "https://registry.yarnpkg.com/maci-domainobjs/-/maci-domainobjs-0.0.0-ci.fb8c927.tgz#0c2a89e3ca94a84854e8a7831d5a7c7ed0761425" integrity sha512-k7TC1W2t7epaLcoMw54112nZVQk9/16BTcd4x9/WtJtug54zqwBc25J6N8KFVeLpqAwek50+fhBKw85WcBD22w== @@ -19532,7 +19533,7 @@ snarkjs@0.5.0: logplease "^1.2.15" r1csfile "0.0.41" -snarkjs@^0.7.0, snarkjs@^0.7.2: +snarkjs@^0.7.0, snarkjs@^0.7.2, snarkjs@^0.7.3: version "0.7.3" resolved "https://registry.yarnpkg.com/snarkjs/-/snarkjs-0.7.3.tgz#7f703d05b810235255f2d0a70d8a9b8b3ea916e5" integrity sha512-cDLpWqdqEJSCQNc+cXYX1XTKdUZBtYEisuOsgmXf/HUsN5WmGN+FO7HfCS+cMQT1Nzbm1a9gAEpKH6KRtDtS1Q==