From 30a38a3c856fe044bc63a8cd2d072766f703beb5 Mon Sep 17 00:00:00 2001 From: yuetloo Date: Mon, 20 May 2024 22:10:24 -0400 Subject: [PATCH 1/7] fix incorrect maxContributors, maxMessages and maxVoteOptions --- common/src/utils.ts | 16 +++- contracts/tests/round.ts | 80 ++++++++++++++++--- contracts/utils/maci.ts | 4 +- contracts/utils/testutils.ts | 26 ++++-- subgraph/generated/ClrFund/MACIFactory.ts | 40 ++-------- subgraph/generated/schema.ts | 34 ++++++++ subgraph/schema.graphql | 3 + subgraph/schema.template.graphql | 3 + subgraph/src/ClrFundMapping.ts | 6 ++ vue-app/src/api/round.ts | 22 +++-- vue-app/src/graphql/API.ts | 37 ++++++++- .../src/graphql/queries/GetRoundInfo.graphql | 2 + 12 files changed, 209 insertions(+), 64 deletions(-) diff --git a/common/src/utils.ts b/common/src/utils.ts index d0bd5cdcb..aee9aba9c 100644 --- a/common/src/utils.ts +++ b/common/src/utils.ts @@ -12,7 +12,9 @@ import { Keypair } from './keypair' import { Tally } from './tally' import { bnSqrt } from './math' -const LEAVES_PER_NODE = 5 +// This has to match the MACI TREE_ARITY at: +// github.com/privacy-scaling-explorations/maci/blob/0c18913d4c84bfa9fbfd66dc017e338df9fdda96/contracts/contracts/MACI.sol#L31 +export const MACI_TREE_ARITY = 5 export function createMessage( userStateIndex: number, @@ -65,7 +67,7 @@ export function getRecipientClaimData( const spentTree = new IncrementalQuinTree( recipientTreeDepth, BigInt(0), - LEAVES_PER_NODE, + MACI_TREE_ARITY, hash5 ) for (const leaf of tally.perVOSpentVoiceCredits.tally) { @@ -94,6 +96,15 @@ export function getRecipientClaimData( ] } +/** + * Returns the maximum MACI users allowed by the state tree + * @param stateTreeDepth MACI state tree depth + * @returns the maximum number of contributors allowed by MACI circuit + */ +export function getMaxContributors(stateTreeDepth: number): number { + return MACI_TREE_ARITY ** stateTreeDepth - 1 +} + export { genTallyResultCommitment, Message, @@ -103,5 +114,4 @@ export { hash2, hash3, hashLeftRight, - LEAVES_PER_NODE, } diff --git a/contracts/tests/round.ts b/contracts/tests/round.ts index 887e9224a..52f0bf12a 100644 --- a/contracts/tests/round.ts +++ b/contracts/tests/round.ts @@ -10,9 +10,11 @@ import { randomBytes, hexlify, toNumber, + Wallet, + TransactionResponse, } from 'ethers' import { genRandomSalt } from 'maci-crypto' -import { Keypair } from '@clrfund/common' +import { getMaxContributors, Keypair, MACI_TREE_ARITY } from '@clrfund/common' import { time } from '@nomicfoundation/hardhat-network-helpers' import { @@ -29,11 +31,14 @@ import { getRecipientClaimData, mergeMaciSubtrees, } from '../utils/maci' -import { deployTestFundingRound } from '../utils/testutils' +import { + deployTestFundingRound, + DeployTestFundingRoundOutput, +} from '../utils/testutils' // ethStaker test vectors for Quadratic Funding with alpha import smallTallyTestData from './data/testTallySmall.json' -import { FundingRound } from '../typechain-types' +import { AnyOldERC20Token, FundingRound } from '../typechain-types' import { EContracts } from '../utils/types' const newResultCommitment = hexlify(randomBytes(32)) @@ -66,6 +71,33 @@ function calcAllocationAmount(tally: string, voiceCredit: string): bigint { return allocation / ALPHA_PRECISION } +/** + * Simulate contribution by a random user + * @param contracts list of contracts returned from the deployTestFundingRound function + * @param deployer the account that owns the contracts + * @returns contribute transaction response + */ +async function contributeByRandomUser( + contracts: DeployTestFundingRoundOutput, + deployer: HardhatEthersSigner +): Promise { + const amount = ethers.parseEther('0.1') + const keypair = new Keypair() + const user = Wallet.createRandom(ethers.provider) + await contracts.token.transfer(user.address, amount) + await deployer.sendTransaction({ to: user.address, value: amount }) + const tokenAsUser = contracts.token.connect(user) as AnyOldERC20Token + await tokenAsUser.approve(contracts.fundingRound.target, amount) + const fundingRoundAsUser = contracts.fundingRound.connect( + user + ) as FundingRound + const tx = await fundingRoundAsUser.contribute( + keypair.pubKey.asContractParam(), + amount + ) + return tx +} + describe('Funding Round', () => { const coordinatorPubKey = new Keypair().pubKey const roundDuration = 86400 * 7 @@ -101,13 +133,13 @@ describe('Funding Round', () => { beforeEach(async () => { const tokenInitialSupply = UNIT * BigInt(1000000) - const deployed = await deployTestFundingRound( - tokenInitialSupply + budget, - coordinator.address, - coordinatorPubKey, + const deployed = await deployTestFundingRound({ + tokenSupply: tokenInitialSupply + budget, + coordinatorAddress: coordinator.address, + coordinatorPubKey: coordinatorPubKey, roundDuration, - deployer - ) + deployer, + }) token = deployed.token fundingRound = deployed.fundingRound userRegistry = deployed.mockUserRegistry @@ -115,7 +147,7 @@ describe('Funding Round', () => { tally = deployed.mockTally const mockVerifier = deployed.mockVerifier - // make the verifier to alwasy returns true + // make the verifier to always returns true await mockVerifier.mock.verify.returns(true) await userRegistry.mock.isVerifiedUser.returns(true) await tally.mock.tallyBatchNum.returns(1) @@ -205,8 +237,34 @@ describe('Funding Round', () => { ).to.equal(expectedVoiceCredits) }) + it('calculates max contributors correctly', async () => { + const stateTreeDepth = toNumber(await maci.stateTreeDepth()) + const maxUsers = MACI_TREE_ARITY ** stateTreeDepth - 1 + expect(getMaxContributors(stateTreeDepth)).to.eq(maxUsers) + }) + it('limits the number of contributors', async () => { - // TODO: add test later + // use a smaller stateTreeDepth to run the test faster + const stateTreeDepth = 1 + const contracts = await deployTestFundingRound({ + stateTreeDepth, + tokenSupply: UNIT * BigInt(1000000), + coordinatorAddress: coordinator.address, + coordinatorPubKey, + roundDuration, + deployer, + }) + await contracts.mockUserRegistry.mock.isVerifiedUser.returns(true) + + const maxUsers = getMaxContributors(stateTreeDepth) + for (let i = 0; i < maxUsers; i++) { + await contributeByRandomUser(contracts, deployer) + } + + // this should throw TooManySignups + await expect( + contributeByRandomUser(contracts, deployer) + ).to.be.revertedWithCustomError(maci, 'TooManySignups') }) it('rejects contributions if funding round has been finalized', async () => { diff --git a/contracts/utils/maci.ts b/contracts/utils/maci.ts index caa8467ba..340134752 100644 --- a/contracts/utils/maci.ts +++ b/contracts/utils/maci.ts @@ -7,7 +7,7 @@ import { hash5, hash3, hashLeftRight, - LEAVES_PER_NODE, + MACI_TREE_ARITY, genTallyResultCommitment, Keypair, Tally as TallyData, @@ -50,7 +50,7 @@ export function getTallyResultProof( const resultTree = new IncrementalQuinTree( recipientTreeDepth, BigInt(0), - LEAVES_PER_NODE, + MACI_TREE_ARITY, hash5 ) for (const leaf of tally.results.tally) { diff --git a/contracts/utils/testutils.ts b/contracts/utils/testutils.ts index 8a306cf41..6d80b7a71 100644 --- a/contracts/utils/testutils.ts +++ b/contracts/utils/testutils.ts @@ -169,13 +169,21 @@ export type DeployTestFundingRoundOutput = { * @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, +export async function deployTestFundingRound({ + stateTreeDepth, + tokenSupply, + coordinatorAddress, + coordinatorPubKey, + roundDuration, + deployer, +}: { + stateTreeDepth?: number + tokenSupply: bigint + coordinatorAddress: string + coordinatorPubKey: PubKey + roundDuration: number deployer: Signer -): Promise { +}): Promise { const token = await ethers.deployContract( EContracts.AnyOldERC20Token, [tokenSupply], @@ -208,6 +216,12 @@ export async function deployTestFundingRound( }) const maciParameters = MaciParameters.mock() + + // use the stateTreeDepth from input + if (stateTreeDepth != undefined) { + maciParameters.stateTreeDepth = stateTreeDepth + } + const maciFactory = await deployMaciFactory({ libraries, ethers, diff --git a/subgraph/generated/ClrFund/MACIFactory.ts b/subgraph/generated/ClrFund/MACIFactory.ts index c9f330074..7c1fbe71b 100644 --- a/subgraph/generated/ClrFund/MACIFactory.ts +++ b/subgraph/generated/ClrFund/MACIFactory.ts @@ -76,10 +76,6 @@ export class MACIFactory__deployMaciResult_pollContractsStruct extends ethereum. get tally(): Address { return this[2].toAddress(); } - - get subsidy(): Address { - return this[3].toAddress(); - } } export class MACIFactory__deployMaciResult { @@ -124,18 +120,11 @@ export class MACIFactory__factoriesResult { value0: Address; value1: Address; value2: Address; - value3: Address; - constructor( - value0: Address, - value1: Address, - value2: Address, - value3: Address, - ) { + constructor(value0: Address, value1: Address, value2: Address) { this.value0 = value0; this.value1 = value1; this.value2 = value2; - this.value3 = value3; } toMap(): TypedMap { @@ -143,7 +132,6 @@ export class MACIFactory__factoriesResult { map.set("value0", ethereum.Value.fromAddress(this.value0)); map.set("value1", ethereum.Value.fromAddress(this.value1)); map.set("value2", ethereum.Value.fromAddress(this.value2)); - map.set("value3", ethereum.Value.fromAddress(this.value3)); return map; } @@ -155,12 +143,8 @@ export class MACIFactory__factoriesResult { return this.value1; } - getSubsidyFactory(): Address { - return this.value2; - } - getMessageProcessorFactory(): Address { - return this.value3; + return this.value2; } } @@ -269,7 +253,7 @@ export class MACIFactory extends ethereum.SmartContract { ): MACIFactory__deployMaciResult { let result = super.call( "deployMaci", - "deployMaci(address,address,address,uint256,address,(uint256,uint256),address):(address,(address,address,address,address))", + "deployMaci(address,address,address,uint256,address,(uint256,uint256),address):(address,(address,address,address))", [ ethereum.Value.fromAddress(signUpGatekeeper), ethereum.Value.fromAddress(initialVoiceCreditProxy), @@ -300,7 +284,7 @@ export class MACIFactory extends ethereum.SmartContract { ): ethereum.CallResult { let result = super.tryCall( "deployMaci", - "deployMaci(address,address,address,uint256,address,(uint256,uint256),address):(address,(address,address,address,address))", + "deployMaci(address,address,address,uint256,address,(uint256,uint256),address):(address,(address,address,address))", [ ethereum.Value.fromAddress(signUpGatekeeper), ethereum.Value.fromAddress(initialVoiceCreditProxy), @@ -328,7 +312,7 @@ export class MACIFactory extends ethereum.SmartContract { factories(): MACIFactory__factoriesResult { let result = super.call( "factories", - "factories():(address,address,address,address)", + "factories():(address,address,address)", [], ); @@ -336,14 +320,13 @@ export class MACIFactory extends ethereum.SmartContract { result[0].toAddress(), result[1].toAddress(), result[2].toAddress(), - result[3].toAddress(), ); } try_factories(): ethereum.CallResult { let result = super.tryCall( "factories", - "factories():(address,address,address,address)", + "factories():(address,address,address)", [], ); if (result.reverted) { @@ -355,7 +338,6 @@ export class MACIFactory extends ethereum.SmartContract { value[0].toAddress(), value[1].toAddress(), value[2].toAddress(), - value[3].toAddress(), ), ); } @@ -534,12 +516,8 @@ export class ConstructorCall_factoriesStruct extends ethereum.Tuple { return this[1].toAddress(); } - get subsidyFactory(): Address { - return this[2].toAddress(); - } - get messageProcessorFactory(): Address { - return this[3].toAddress(); + return this[2].toAddress(); } } @@ -631,10 +609,6 @@ export class DeployMaciCall_pollContractsStruct extends ethereum.Tuple { get tally(): Address { return this[2].toAddress(); } - - get subsidy(): Address { - return this[3].toAddress(); - } } export class RenounceOwnershipCall extends ethereum.Call { diff --git a/subgraph/generated/schema.ts b/subgraph/generated/schema.ts index 97e62f0d3..b3f92c01b 100644 --- a/subgraph/generated/schema.ts +++ b/subgraph/generated/schema.ts @@ -1020,6 +1020,40 @@ export class FundingRound extends Entity { this.set("voteOptionTreeDepth", Value.fromI32(value)); } + get maxMessages(): BigInt | null { + let value = this.get("maxMessages"); + if (!value || value.kind == ValueKind.NULL) { + return null; + } else { + return value.toBigInt(); + } + } + + set maxMessages(value: BigInt | null) { + if (!value) { + this.unset("maxMessages"); + } else { + this.set("maxMessages", Value.fromBigInt(value)); + } + } + + get maxVoteOptions(): BigInt | null { + let value = this.get("maxVoteOptions"); + if (!value || value.kind == ValueKind.NULL) { + return null; + } else { + return value.toBigInt(); + } + } + + set maxVoteOptions(value: BigInt | null) { + if (!value) { + this.unset("maxVoteOptions"); + } else { + this.set("maxVoteOptions", Value.fromBigInt(value)); + } + } + get coordinatorPubKeyX(): BigInt | null { let value = this.get("coordinatorPubKeyX"); if (!value || value.kind == ValueKind.NULL) { diff --git a/subgraph/schema.graphql b/subgraph/schema.graphql index 147b3225f..3d9b3610a 100644 --- a/subgraph/schema.graphql +++ b/subgraph/schema.graphql @@ -73,6 +73,9 @@ type FundingRound @entity { messageTreeDepth: Int voteOptionTreeDepth: Int + maxMessages: BigInt + maxVoteOptions: BigInt + coordinatorPubKeyX: BigInt coordinatorPubKeyY: BigInt coordinator: Bytes diff --git a/subgraph/schema.template.graphql b/subgraph/schema.template.graphql index af4e1689d..44b3d2127 100644 --- a/subgraph/schema.template.graphql +++ b/subgraph/schema.template.graphql @@ -85,6 +85,9 @@ type FundingRound @entity { messageTreeDepth: Int voteOptionTreeDepth: Int + maxMessages: BigInt + maxVoteOptions: BigInt + coordinatorPubKeyX: BigInt coordinatorPubKeyY: BigInt coordinator: Bytes diff --git a/subgraph/src/ClrFundMapping.ts b/subgraph/src/ClrFundMapping.ts index f26e32586..85cbe3170 100644 --- a/subgraph/src/ClrFundMapping.ts +++ b/subgraph/src/ClrFundMapping.ts @@ -289,6 +289,12 @@ export function handleRoundStarted(event: RoundStarted): void { fundingRound.voteOptionTreeDepth = treeDepths.value.value3 } + let maxValues = pollContract.try_maxValues() + if (!maxValues.reverted) { + fundingRound.maxMessages = maxValues.value.value0 + fundingRound.maxVoteOptions = maxValues.value.value1 + } + let coordinatorPubKey = pollContract.try_coordinatorPubKey() if (!coordinatorPubKey.reverted) { fundingRound.coordinatorPubKeyX = coordinatorPubKey.value.value0 diff --git a/vue-app/src/api/round.ts b/vue-app/src/api/round.ts index bc724117c..219dbecad 100644 --- a/vue-app/src/api/round.ts +++ b/vue-app/src/api/round.ts @@ -1,6 +1,6 @@ -import { Contract, toNumber, getAddress, hexlify, randomBytes } from 'ethers' +import { Contract, getAddress, hexlify, randomBytes, getNumber } from 'ethers' import { DateTime } from 'luxon' -import { PubKey, type Tally } from '@clrfund/common' +import { PubKey, type Tally, getMaxContributors } from '@clrfund/common' import { FundingRound, Poll } from './abi' import { provider, clrFundContract, isActiveApp } from './core' @@ -174,8 +174,9 @@ export async function getRoundInfo( isFinalized, isCancelled, stateTreeDepth, - messageTreeDepth, voteOptionTreeDepth, + maxMessages: maxMessagesBigInt, + maxVoteOptions: maxVoteOptionsBigInt, startTime: startTimeInSeconds, signUpDeadline: signUpDeadlineInSeconds, votingDeadline: votingDeadlineInSeconds, @@ -193,8 +194,8 @@ export async function getRoundInfo( const nativeTokenSymbol = data.fundingRound.nativeTokenInfo?.symbol || '' const nativeTokenDecimals = Number(data.fundingRound.nativeTokenInfo?.decimals || '') - const maxContributors = stateTreeDepth ? 2 ** stateTreeDepth - 1 : 0 - const maxMessages = messageTreeDepth ? 2 ** messageTreeDepth - 1 : 0 + const maxContributors = getMaxContributors(stateTreeDepth || 0) + const maxMessages = getNumber(maxMessagesBigInt) || 0 const now = DateTime.local() const startTime = DateTime.fromSeconds(Number(startTimeInSeconds || 0)) const signUpDeadline = DateTime.fromSeconds(Number(signUpDeadlineInSeconds || 0)) @@ -217,9 +218,10 @@ export async function getRoundInfo( contributions = contributionsInfo.amount matchingPool = await clrFundContract.getMatchingFunds(nativeTokenAddress) } else { - if (now < signUpDeadline && contributors < maxContributors) { + if (now < votingDeadline && contributors < maxContributors) { status = RoundStatus.Contributing } else if (now < votingDeadline) { + // Too many contributors, do not allow new contributors, allow reallocation only status = RoundStatus.Reallocating } else { status = RoundStatus.Tallying @@ -231,6 +233,10 @@ export async function getRoundInfo( const totalFunds = matchingPool + contributions + // recipient 0 is reserved, so maxRecipients is 1 fewer than the maxVoteOptions + const maxVoteOptions = getNumber(maxVoteOptionsBigInt) + const maxRecipients = maxVoteOptions > 0 ? maxVoteOptions - 1 : 0 + return { fundingRoundAddress, recipientRegistryAddress: getAddress(recipientRegistryAddress), @@ -239,7 +245,7 @@ export async function getRoundInfo( pollId: BigInt(pollId || 0), recipientTreeDepth: voteOptionTreeDepth || 1, maxContributors, - maxRecipients: voteOptionTreeDepth ? 5 ** voteOptionTreeDepth - 1 : 0, + maxRecipients, maxMessages, coordinatorPubKey, nativeTokenAddress: getAddress(nativeTokenAddress), @@ -254,6 +260,6 @@ export async function getRoundInfo( matchingPool, contributions, contributors, - messages: toNumber(messages), + messages: getNumber(messages), } } diff --git a/vue-app/src/graphql/API.ts b/vue-app/src/graphql/API.ts index fc61e9d4f..72b5be6c3 100644 --- a/vue-app/src/graphql/API.ts +++ b/vue-app/src/graphql/API.ts @@ -17,6 +17,7 @@ export type Scalars = { BigInt: any; Bytes: any; Int8: any; + Timestamp: any; }; export enum Aggregation_Interval { @@ -334,6 +335,8 @@ export enum ClrFund_OrderBy { CurrentRoundMaci = 'currentRound__maci', CurrentRoundMaciTxHash = 'currentRound__maciTxHash', CurrentRoundMatchingPoolSize = 'currentRound__matchingPoolSize', + CurrentRoundMaxMessages = 'currentRound__maxMessages', + CurrentRoundMaxVoteOptions = 'currentRound__maxVoteOptions', CurrentRoundMessageTreeDepth = 'currentRound__messageTreeDepth', CurrentRoundNativeToken = 'currentRound__nativeToken', CurrentRoundPollAddress = 'currentRound__pollAddress', @@ -503,6 +506,8 @@ export enum Contribution_OrderBy { FundingRoundMaci = 'fundingRound__maci', FundingRoundMaciTxHash = 'fundingRound__maciTxHash', FundingRoundMatchingPoolSize = 'fundingRound__matchingPoolSize', + FundingRoundMaxMessages = 'fundingRound__maxMessages', + FundingRoundMaxVoteOptions = 'fundingRound__maxVoteOptions', FundingRoundMessageTreeDepth = 'fundingRound__messageTreeDepth', FundingRoundNativeToken = 'fundingRound__nativeToken', FundingRoundPollAddress = 'fundingRound__pollAddress', @@ -1031,6 +1036,8 @@ export enum Donation_OrderBy { FundingRoundMaci = 'fundingRound__maci', FundingRoundMaciTxHash = 'fundingRound__maciTxHash', FundingRoundMatchingPoolSize = 'fundingRound__matchingPoolSize', + FundingRoundMaxMessages = 'fundingRound__maxMessages', + FundingRoundMaxVoteOptions = 'fundingRound__maxVoteOptions', FundingRoundMessageTreeDepth = 'fundingRound__messageTreeDepth', FundingRoundNativeToken = 'fundingRound__nativeToken', FundingRoundPollAddress = 'fundingRound__pollAddress', @@ -1070,6 +1077,8 @@ export type FundingRound = { maci: Maybe; maciTxHash: Maybe; matchingPoolSize: Maybe; + maxMessages: Maybe; + maxVoteOptions: Maybe; messageTreeDepth: Maybe; messages: Maybe>; nativeToken: Maybe; @@ -1303,6 +1312,22 @@ export type FundingRound_Filter = { matchingPoolSize_lte: InputMaybe; matchingPoolSize_not: InputMaybe; matchingPoolSize_not_in: InputMaybe>; + maxMessages: InputMaybe; + maxMessages_gt: InputMaybe; + maxMessages_gte: InputMaybe; + maxMessages_in: InputMaybe>; + maxMessages_lt: InputMaybe; + maxMessages_lte: InputMaybe; + maxMessages_not: InputMaybe; + maxMessages_not_in: InputMaybe>; + maxVoteOptions: InputMaybe; + maxVoteOptions_gt: InputMaybe; + maxVoteOptions_gte: InputMaybe; + maxVoteOptions_in: InputMaybe>; + maxVoteOptions_lt: InputMaybe; + maxVoteOptions_lte: InputMaybe; + maxVoteOptions_not: InputMaybe; + maxVoteOptions_not_in: InputMaybe>; messageTreeDepth: InputMaybe; messageTreeDepth_gt: InputMaybe; messageTreeDepth_gte: InputMaybe; @@ -1524,6 +1549,8 @@ export enum FundingRound_OrderBy { Maci = 'maci', MaciTxHash = 'maciTxHash', MatchingPoolSize = 'matchingPoolSize', + MaxMessages = 'maxMessages', + MaxVoteOptions = 'maxVoteOptions', MessageTreeDepth = 'messageTreeDepth', Messages = 'messages', NativeToken = 'nativeToken', @@ -1728,6 +1755,8 @@ export enum Message_OrderBy { FundingRoundMaci = 'fundingRound__maci', FundingRoundMaciTxHash = 'fundingRound__maciTxHash', FundingRoundMatchingPoolSize = 'fundingRound__matchingPoolSize', + FundingRoundMaxMessages = 'fundingRound__maxMessages', + FundingRoundMaxVoteOptions = 'fundingRound__maxVoteOptions', FundingRoundMessageTreeDepth = 'fundingRound__messageTreeDepth', FundingRoundNativeToken = 'fundingRound__nativeToken', FundingRoundPollAddress = 'fundingRound__pollAddress', @@ -1832,6 +1861,8 @@ export enum Poll_OrderBy { FundingRoundMaci = 'fundingRound__maci', FundingRoundMaciTxHash = 'fundingRound__maciTxHash', FundingRoundMatchingPoolSize = 'fundingRound__matchingPoolSize', + FundingRoundMaxMessages = 'fundingRound__maxMessages', + FundingRoundMaxVoteOptions = 'fundingRound__maxVoteOptions', FundingRoundMessageTreeDepth = 'fundingRound__messageTreeDepth', FundingRoundNativeToken = 'fundingRound__nativeToken', FundingRoundPollAddress = 'fundingRound__pollAddress', @@ -1955,6 +1986,8 @@ export enum PublicKey_OrderBy { FundingRoundMaci = 'fundingRound__maci', FundingRoundMaciTxHash = 'fundingRound__maciTxHash', FundingRoundMatchingPoolSize = 'fundingRound__matchingPoolSize', + FundingRoundMaxMessages = 'fundingRound__maxMessages', + FundingRoundMaxVoteOptions = 'fundingRound__maxVoteOptions', FundingRoundMessageTreeDepth = 'fundingRound__messageTreeDepth', FundingRoundNativeToken = 'fundingRound__nativeToken', FundingRoundPollAddress = 'fundingRound__pollAddress', @@ -3223,7 +3256,7 @@ export type GetRoundInfoQueryVariables = Exact<{ }>; -export type GetRoundInfoQuery = { __typename?: 'Query', fundingRound: { __typename?: 'FundingRound', id: string, maci: any | null, pollId: any | null, pollAddress: any | null, recipientRegistryAddress: any | null, contributorRegistryAddress: any | null, voiceCreditFactor: any | null, isFinalized: boolean | null, isCancelled: boolean | null, contributorCount: any, totalSpent: any | null, matchingPoolSize: any | null, startTime: any | null, signUpDeadline: any | null, votingDeadline: any | null, coordinatorPubKeyX: any | null, coordinatorPubKeyY: any | null, stateTreeDepth: number | null, messageTreeDepth: number | null, voteOptionTreeDepth: number | null, nativeTokenInfo: { __typename?: 'Token', tokenAddress: any | null, symbol: string | null, decimals: any | null } | null } | null }; +export type GetRoundInfoQuery = { __typename?: 'Query', fundingRound: { __typename?: 'FundingRound', id: string, maci: any | null, pollId: any | null, pollAddress: any | null, recipientRegistryAddress: any | null, contributorRegistryAddress: any | null, voiceCreditFactor: any | null, isFinalized: boolean | null, isCancelled: boolean | null, contributorCount: any, totalSpent: any | null, matchingPoolSize: any | null, startTime: any | null, signUpDeadline: any | null, votingDeadline: any | null, coordinatorPubKeyX: any | null, coordinatorPubKeyY: any | null, maxMessages: any | null, maxVoteOptions: any | null, stateTreeDepth: number | null, messageTreeDepth: number | null, voteOptionTreeDepth: number | null, nativeTokenInfo: { __typename?: 'Token', tokenAddress: any | null, symbol: string | null, decimals: any | null } | null } | null }; export type GetRoundsQueryVariables = Exact<{ clrFundAddress: Scalars['String']; @@ -3436,6 +3469,8 @@ export const GetRoundInfoDocument = gql` votingDeadline coordinatorPubKeyX coordinatorPubKeyY + maxMessages + maxVoteOptions stateTreeDepth messageTreeDepth voteOptionTreeDepth diff --git a/vue-app/src/graphql/queries/GetRoundInfo.graphql b/vue-app/src/graphql/queries/GetRoundInfo.graphql index d5995c8ff..73f2cb9fb 100644 --- a/vue-app/src/graphql/queries/GetRoundInfo.graphql +++ b/vue-app/src/graphql/queries/GetRoundInfo.graphql @@ -22,6 +22,8 @@ query GetRoundInfo($fundingRoundAddress: ID!) { votingDeadline coordinatorPubKeyX coordinatorPubKeyY + maxMessages + maxVoteOptions stateTreeDepth messageTreeDepth voteOptionTreeDepth From 465a116bf3dbca77b39c76a7a9532e7290aff822 Mon Sep 17 00:00:00 2001 From: yuetloo Date: Tue, 21 May 2024 19:52:32 -0400 Subject: [PATCH 2/7] votingDuration is obsolete post MACI v1 --- contracts/tasks/runners/exportRound.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/tasks/runners/exportRound.ts b/contracts/tasks/runners/exportRound.ts index 27be1e51b..42c3638f2 100644 --- a/contracts/tasks/runners/exportRound.ts +++ b/contracts/tasks/runners/exportRound.ts @@ -227,7 +227,6 @@ async function getRoundInfo( await pollContract.getDeployTimeAndDuration() startTime = getNumber(roundStartTime) signUpDuration = roundDuration - votingDuration = roundDuration endTime = startTime + getNumber(roundDuration) pollId = await roundContract.pollId() From f9d90abc55db015d719293cffddaa6f35387018e Mon Sep 17 00:00:00 2001 From: yuetloo Date: Tue, 21 May 2024 20:01:13 -0400 Subject: [PATCH 3/7] remove hardcoding of TREE_ARITY as its value can change between MACI versions --- contracts/contracts/ClrFund.sol | 32 ++- contracts/contracts/FundingRound.sol | 33 ++- contracts/contracts/MACICommon.sol | 3 - contracts/contracts/MACIFactory.sol | 25 +- .../contracts/interfaces/IMACIFactory.sol | 5 +- .../recipientRegistry/IRecipientRegistry.sol | 1 + contracts/tasks/index.ts | 1 + contracts/tasks/runners/simulateContribute.ts | 214 ++++++++++++++++++ contracts/tests/deployer.ts | 50 +++- contracts/tests/round.ts | 6 +- contracts/utils/circuits.ts | 10 +- contracts/utils/constants.ts | 1 - contracts/utils/maciParameters.ts | 17 +- subgraph/abis/ClrFund.json | 55 +++-- subgraph/abis/ClrFundDeployer.json | 22 ++ subgraph/abis/FundingRound.json | 83 +++++-- subgraph/abis/MACIFactory.json | 64 ++++-- subgraph/generated/ClrFund/ClrFund.ts | 23 -- subgraph/generated/ClrFund/FundingRound.ts | 19 -- subgraph/generated/ClrFund/MACIFactory.ts | 62 ++--- .../templates/FundingRound/FundingRound.ts | 19 -- .../generated/templates/MACI/FundingRound.ts | 19 -- .../generated/templates/Poll/FundingRound.ts | 19 -- subgraph/src/ClrFundMapping.ts | 2 +- subgraph/src/RecipientRegistry.ts | 19 +- 25 files changed, 531 insertions(+), 273 deletions(-) create mode 100644 contracts/tasks/runners/simulateContribute.ts diff --git a/contracts/contracts/ClrFund.sol b/contracts/contracts/ClrFund.sol index 4f5ad30bc..3927ab2c1 100644 --- a/contracts/contracts/ClrFund.sol +++ b/contracts/contracts/ClrFund.sol @@ -60,8 +60,8 @@ contract ClrFund is OwnableUpgradeable, DomainObjs, Params { error InvalidFundingRoundFactory(); error InvalidMaciFactory(); error RecipientRegistryNotSet(); + error MaxRecipientsNotSet(); error NotInitialized(); - error VoteOptionTreeDepthNotSet(); /** @@ -128,20 +128,6 @@ contract ClrFund is OwnableUpgradeable, DomainObjs, Params { _setFundingRoundFactory(_roundFactory); } - /** - * @dev Get the maximum recipients allowed in the recipient registry - */ - function getMaxRecipients() public view returns (uint256 _maxRecipients) { - TreeDepths memory treeDepths = maciFactory.treeDepths(); - if (treeDepths.voteOptionTreeDepth == 0) revert VoteOptionTreeDepthNotSet(); - - uint256 maxVoteOption = maciFactory.TREE_ARITY() ** treeDepths.voteOptionTreeDepth; - - // -1 because the first slot of the recipients array is not used - // and maxRecipients is used to generate 0 based index to the array - _maxRecipients = maxVoteOption - 1; - } - /** * @dev Set registry of verified users. * @param _userRegistry Address of a user registry. @@ -155,6 +141,15 @@ contract ClrFund is OwnableUpgradeable, DomainObjs, Params { emit UserRegistryChanged(address(_userRegistry)); } + /** + * @dev Set the max recipients in the recipient registry + */ + function _setMaxRecipients() private { + if (address(maciFactory) == address(0)) revert NotInitialized(); + if (maciFactory.maxRecipients() == 0) revert MaxRecipientsNotSet(); + recipientRegistry.setMaxRecipients(maciFactory.maxRecipients()); + } + /** * @dev Set recipient registry. * @param _recipientRegistry Address of a recipient registry. @@ -163,9 +158,9 @@ contract ClrFund is OwnableUpgradeable, DomainObjs, Params { external onlyOwner { + recipientRegistry = _recipientRegistry; - uint256 maxRecipients = getMaxRecipients(); - recipientRegistry.setMaxRecipients(maxRecipients); + _setMaxRecipients(); emit RecipientRegistryChanged(address(_recipientRegistry)); } @@ -229,8 +224,7 @@ contract ClrFund is OwnableUpgradeable, DomainObjs, Params { if (address(recipientRegistry) == address(0)) revert RecipientRegistryNotSet(); // Make sure that the max number of recipients is set correctly - uint256 maxRecipients = getMaxRecipients(); - recipientRegistry.setMaxRecipients(maxRecipients); + _setMaxRecipients(); // Deploy funding round and MACI contracts address newRound = roundFactory.deploy(duration, address(this)); diff --git a/contracts/contracts/FundingRound.sol b/contracts/contracts/FundingRound.sol index 9f8fe143b..701872a8f 100644 --- a/contracts/contracts/FundingRound.sol +++ b/contracts/contracts/FundingRound.sol @@ -66,6 +66,7 @@ contract FundingRound is error TallyHashNotPublished(); error IncompleteTallyResults(uint256 total, uint256 actual); error NoVotes(); + error NoSignUps(); error MaciNotSet(); error PollNotSet(); error InvalidMaci(); @@ -175,19 +176,6 @@ contract FundingRound is return (addressValue == address(0)); } - /** - * @dev Have the votes been tallied - */ - function isTallied() private view returns (bool) { - (uint256 numSignUps, ) = poll.numSignUpsAndMessages(); - (uint8 intStateTreeDepth, , , ) = poll.treeDepths(); - uint256 tallyBatchSize = TREE_ARITY ** uint256(intStateTreeDepth); - uint256 tallyBatchNum = tally.tallyBatchNum(); - uint256 totalTallied = tallyBatchNum * tallyBatchSize; - - return numSignUps > 0 && totalTallied >= numSignUps; - } - /** * @dev Set the tally contract * @param _tally The tally contract address @@ -470,18 +458,18 @@ contract FundingRound is _votingPeriodOver(poll); - if (!isTallied()) { + if (!tally.isTallied()) { revert VotesNotTallied(); } + if (bytes(tallyHash).length == 0) { revert TallyHashNotPublished(); } // make sure we have received all the tally results - (,,, uint8 voteOptionTreeDepth) = poll.treeDepths(); - uint256 totalResults = uint256(TREE_ARITY) ** uint256(voteOptionTreeDepth); - if ( totalTallyResults != totalResults ) { - revert IncompleteTallyResults(totalResults, totalTallyResults); + (, uint256 maxVoteOptions) = poll.maxValues(); + if (totalTallyResults != maxVoteOptions) { + revert IncompleteTallyResults(maxVoteOptions, totalTallyResults); } // If nobody voted, the round should be cancelled to avoid locking of matching funds @@ -494,7 +482,6 @@ contract FundingRound is revert IncorrectSpentVoiceCredits(); } - totalSpent = _totalSpent; // Total amount of spent voice credits is the size of the pool of direct rewards. // Everything else, including unspent voice credits and downscaling error, @@ -675,9 +662,15 @@ contract FundingRound is { if (isAddressZero(address(maci))) revert MaciNotSet(); - if (!isTallied()) { + if (maci.numSignUps() == 0) { + // no sign ups, so no tally results + revert NoSignUps(); + } + + if (!tally.isTallied()) { revert VotesNotTallied(); } + if (isFinalized) { revert RoundAlreadyFinalized(); } diff --git a/contracts/contracts/MACICommon.sol b/contracts/contracts/MACICommon.sol index f41843794..01224230b 100644 --- a/contracts/contracts/MACICommon.sol +++ b/contracts/contracts/MACICommon.sol @@ -6,9 +6,6 @@ pragma solidity 0.8.20; * @dev a contract that holds common MACI structures */ contract MACICommon { - // MACI tree arity - uint256 public constant TREE_ARITY = 5; - /** * @dev These are contract factories used to deploy MACI poll processing contracts * when creating a new ClrFund funding round. diff --git a/contracts/contracts/MACIFactory.sol b/contracts/contracts/MACIFactory.sol index 16a6f6e48..f3952280d 100644 --- a/contracts/contracts/MACIFactory.sol +++ b/contracts/contracts/MACIFactory.sol @@ -29,6 +29,8 @@ contract MACIFactory is Ownable(msg.sender), Params, SnarkCommon, DomainObjs, MA // circuit parameters uint8 public stateTreeDepth; TreeDepths public treeDepths; + uint256 public messageBatchSize; + uint256 public maxRecipients; // Events event MaciParametersChanged(); @@ -38,11 +40,15 @@ contract MACIFactory is Ownable(msg.sender), Params, SnarkCommon, DomainObjs, MA error NotInitialized(); error ProcessVkNotSet(); error TallyVkNotSet(); + error VoteOptionTreeDepthNotSet(); error InvalidVkRegistry(); error InvalidPollFactory(); error InvalidTallyFactory(); error InvalidMessageProcessorFactory(); error InvalidVerifier(); + error InvalidMaxRecipients(); + error InvalidMessageBatchSize(); + error InvalidVoteOptionTreeDepth(); constructor( address _vkRegistry, @@ -60,14 +66,6 @@ contract MACIFactory is Ownable(msg.sender), Params, SnarkCommon, DomainObjs, MA verifier = Verifier(_verifier); } - /** - * @dev calculate the message batch size - */ - function getMessageBatchSize(uint8 messageTreeSubDepth) public pure - returns(uint256 _messageBatchSize) { - _messageBatchSize = TREE_ARITY ** messageTreeSubDepth; - } - /** * @dev set vk registry */ @@ -122,13 +120,19 @@ contract MACIFactory is Ownable(msg.sender), Params, SnarkCommon, DomainObjs, MA */ function setMaciParameters( uint8 _stateTreeDepth, + uint256 _messageBatchSize, + uint256 _maxRecipients, TreeDepths calldata _treeDepths ) public onlyOwner { + if (_treeDepths.voteOptionTreeDepth == 0) revert InvalidVoteOptionTreeDepth(); + if (_maxRecipients == 0) revert InvalidMaxRecipients(); + if (_messageBatchSize == 0) revert InvalidMessageBatchSize(); - uint256 messageBatchSize = getMessageBatchSize(_treeDepths.messageTreeSubDepth); + messageBatchSize = _messageBatchSize; + maxRecipients = _maxRecipients; if (!vkRegistry.hasProcessVk( _stateTreeDepth, @@ -155,6 +159,7 @@ contract MACIFactory is Ownable(msg.sender), Params, SnarkCommon, DomainObjs, MA emit MaciParametersChanged(); } + /** * @dev Deploy new MACI instance. */ @@ -170,8 +175,6 @@ contract MACIFactory is Ownable(msg.sender), Params, SnarkCommon, DomainObjs, MA external returns (MACI _maci, MACI.PollContracts memory _pollContracts) { - uint256 messageBatchSize = getMessageBatchSize(treeDepths.messageTreeSubDepth); - if (!vkRegistry.hasProcessVk( stateTreeDepth, treeDepths.messageTreeDepth, diff --git a/contracts/contracts/interfaces/IMACIFactory.sol b/contracts/contracts/interfaces/IMACIFactory.sol index eacfd7df2..7e207afe8 100644 --- a/contracts/contracts/interfaces/IMACIFactory.sol +++ b/contracts/contracts/interfaces/IMACIFactory.sol @@ -28,10 +28,7 @@ interface IMACIFactory { function stateTreeDepth() external view returns (uint8); function treeDepths() external view returns (Params.TreeDepths memory); - function getMessageBatchSize(uint8 _messageTreeSubDepth) external pure - returns(uint256 _messageBatchSize); - - function TREE_ARITY() external pure returns (uint256); + function maxRecipients() external view returns (uint256); function deployMaci( SignUpGatekeeper signUpGatekeeper, diff --git a/contracts/contracts/recipientRegistry/IRecipientRegistry.sol b/contracts/contracts/recipientRegistry/IRecipientRegistry.sol index 82fc0c4a9..edfbc5029 100644 --- a/contracts/contracts/recipientRegistry/IRecipientRegistry.sol +++ b/contracts/contracts/recipientRegistry/IRecipientRegistry.sol @@ -17,6 +17,7 @@ pragma solidity 0.8.20; */ interface IRecipientRegistry { + function maxRecipients() external returns (uint256); function setMaxRecipients(uint256 _maxRecipients) external returns (bool); function getRecipientAddress(uint256 _index, uint256 _startBlock, uint256 _endBlock) external view returns (address); diff --git a/contracts/tasks/index.ts b/contracts/tasks/index.ts index b25d56890..19050a901 100644 --- a/contracts/tasks/index.ts +++ b/contracts/tasks/index.ts @@ -29,3 +29,4 @@ import './runners/proveOnChain' import './runners/publishTallyResults' import './runners/resetTally' import './runners/maciPubkey' +import './runners/simulateContribute' diff --git a/contracts/tasks/runners/simulateContribute.ts b/contracts/tasks/runners/simulateContribute.ts new file mode 100644 index 000000000..dcfc2cb1b --- /dev/null +++ b/contracts/tasks/runners/simulateContribute.ts @@ -0,0 +1,214 @@ +/** + * Simulate contributions to a funding round. This script is mainly used for testing. + * + * Sample usage: + * yarn hardhat simulate-contribute --count \ + * --fund --network + * + * Make sure deployed-contracts.json exists with the funding round address + * Make sure to use a token with mint() function like 0x65bc8dd04808d99cf8aa6749f128d55c2051edde + */ + +import { Keypair, createMessage, Message, PubKey } from '@clrfund/common' + +import { UNIT } from '../../utils/constants' +import { getContractAt, getEventArg } from '../../utils/contracts' +import type { FundingRound, ERC20, Poll } from '../../typechain-types' +import { task, types } from 'hardhat/config' +import { EContracts } from '../../utils/types' +import { ContractStorage } from '../helpers/ContractStorage' +import { parseEther, Wallet } from 'ethers' + +const tokenAbi = [ + 'function mint(address,uint256)', + 'function transfer(address,uint256)', + 'function approve(address,uint256)', +] +/** + * Cast a vote by the contributor + * + * @param stateIndex The contributor stateIndex + * @param pollId The pollId + * @param contributorKeyPair The contributor MACI key pair + * @param coordinatorPubKey The coordinator MACI public key + * @param voiceCredits The total voice credits the contributor can use + * @param pollContract The poll contract with the vote function + */ +async function vote( + stateIndex: number, + pollId: bigint, + contributorKeyPair: Keypair, + coordinatorPubKey: PubKey, + voiceCredits: bigint, + pollContract: Poll +) { + const messages: Message[] = [] + const encPubKeys: PubKey[] = [] + let nonce = 1 + // Change key + const newContributorKeypair = new Keypair() + const [message, encPubKey] = createMessage( + stateIndex, + contributorKeyPair, + newContributorKeypair, + coordinatorPubKey, + null, + null, + nonce, + pollId + ) + messages.push(message) + encPubKeys.push(encPubKey) + nonce += 1 + // Vote + for (const recipientIndex of [1, 2]) { + const votes = BigInt(voiceCredits) / BigInt(2) + const [message, encPubKey] = createMessage( + stateIndex, + newContributorKeypair, + null, + coordinatorPubKey, + recipientIndex, + votes, + nonce, + pollId + ) + messages.push(message) + encPubKeys.push(encPubKey) + nonce += 1 + } + + const tx = await pollContract.publishMessageBatch( + messages.reverse().map((msg) => msg.asContractParam()), + encPubKeys.reverse().map((key) => key.asContractParam()) + ) + const receipt = await tx.wait() + if (receipt?.status !== 1) { + throw new Error(`Contributor ${stateIndex} failed to vote`) + } +} + +task('simulate-contribute', 'Contribute to a funding round') + .addParam('count', 'Number of contributors to simulate', 70, types.int) + .addParam('fund', 'Number of contributors to simulate', '0.01') + .setAction(async ({ count, fund }, { ethers, network }) => { + // gas for transactions + const value = parseEther(fund) + const contributionAmount = UNIT + + const [deployer] = await ethers.getSigners() + const storage = ContractStorage.getInstance() + const fundingRoundContractAddress = storage.mustGetAddress( + EContracts.FundingRound, + network.name + ) + const fundingRound = await ethers.getContractAt( + EContracts.FundingRound, + fundingRoundContractAddress + ) + + const pollId = await fundingRound.pollId() + const pollAddress = await fundingRound.poll() + const pollContract = await getContractAt( + EContracts.Poll, + pollAddress, + ethers + ) + + const rawCoordinatorPubKey = await pollContract.coordinatorPubKey() + const coordinatorPubKey = new PubKey([ + BigInt(rawCoordinatorPubKey.x), + BigInt(rawCoordinatorPubKey.y), + ]) + + const tokenAddress = await fundingRound.nativeToken() + const token = await ethers.getContractAt(tokenAbi, tokenAddress) + + const maciAddress = await fundingRound.maci() + const maci = await ethers.getContractAt(EContracts.MACI, maciAddress) + + const userRegistryAddress = await fundingRound.userRegistry() + const userRegistry = await ethers.getContractAt( + EContracts.SimpleUserRegistry, + userRegistryAddress + ) + + for (let i = 0; i < count; i++) { + const contributor = Wallet.createRandom(ethers.provider) + + let tx = await userRegistry.addUser(contributor.address) + let receipt = await tx.wait() + if (receipt.status !== 1) { + throw new Error(`Failed to add user to the user registry`) + } + + // transfer token to contributor first + tx = await token.mint(contributor.address, contributionAmount) + receipt = await tx.wait() + if (receipt.status !== 1) { + throw new Error(`Failed to mint token for ${contributor.address}`) + } + + tx = await deployer.sendTransaction({ value, to: contributor.address }) + receipt = await tx.wait() + if (receipt.status !== 1) { + throw new Error(`Failed to fund ${contributor.address}`) + } + + const contributorKeypair = new Keypair() + const tokenAsContributor = token.connect(contributor) as ERC20 + tx = await tokenAsContributor.approve( + fundingRound.target, + contributionAmount + ) + receipt = await tx.wait() + if (receipt.status !== 1) { + throw new Error('Failed to approve token') + } + + const fundingRoundAsContributor = fundingRound.connect( + contributor + ) as FundingRound + const contributionTx = await fundingRoundAsContributor.contribute( + contributorKeypair.pubKey.asContractParam(), + contributionAmount + ) + receipt = await contributionTx.wait() + if (receipt.status !== 1) { + throw new Error('Failed to contribute') + } + + const stateIndex = await getEventArg( + contributionTx, + maci, + 'SignUp', + '_stateIndex' + ) + const voiceCredits = await getEventArg( + contributionTx, + maci, + 'SignUp', + '_voiceCreditBalance' + ) + + console.log( + `Contributor ${ + contributor.address + } registered. State index: ${stateIndex}. Voice credits: ${voiceCredits.toString()}.` + ) + + const pollContractAsContributor = pollContract.connect( + contributor + ) as Poll + + await vote( + stateIndex, + pollId, + contributorKeypair, + coordinatorPubKey, + voiceCredits, + pollContractAsContributor + ) + console.log(`Contributor ${contributor.address} voted.`) + } + }) diff --git a/contracts/tests/deployer.ts b/contracts/tests/deployer.ts index bbe8c027a..3eace1611 100644 --- a/contracts/tests/deployer.ts +++ b/contracts/tests/deployer.ts @@ -1,11 +1,11 @@ -import { ethers, config, artifacts } from 'hardhat' +import { ethers, artifacts } from 'hardhat' import { time } from '@nomicfoundation/hardhat-network-helpers' import { expect } from 'chai' import { BaseContract, Contract } from 'ethers' import { genRandomSalt } from 'maci-crypto' -import { Keypair } from '@clrfund/common' +import { Keypair, MACI_TREE_ARITY } from '@clrfund/common' -import { TREE_ARITY, ZERO_ADDRESS, UNIT } from '../utils/constants' +import { ZERO_ADDRESS, UNIT } from '../utils/constants' import { getGasUsage, getEventArg, @@ -18,6 +18,7 @@ import { HardhatEthersSigner } from '@nomicfoundation/hardhat-ethers/signers' import { ClrFund, ClrFundDeployer, + FundingRound, FundingRoundFactory, MACIFactory, Poll, @@ -199,7 +200,8 @@ describe('Clr fund deployer', async () => { expect(await recipientRegistry.controller()).to.equal(clrfund.target) const params = MaciParameters.mock() expect(await recipientRegistry.maxRecipients()).to.equal( - BigInt(TREE_ARITY) ** BigInt(params.treeDepths.voteOptionTreeDepth) - + BigInt(MACI_TREE_ARITY) ** + BigInt(params.treeDepths.voteOptionTreeDepth) - BigInt(1) ) }) @@ -453,6 +455,14 @@ describe('Clr fund deployer', async () => { contributionAmount ) await clrfund.deployNewRound(roundDuration) + const roundAddress = await clrfund.getCurrentRound() + const roundContract = (await getContractAt( + EContracts.FundingRound, + roundAddress, + ethers, + coordinator + )) as FundingRound + await roundContract.publishTallyHash('xxx') await time.increase(roundDuration) await expect( clrfund.transferMatchingFunds( @@ -461,11 +471,19 @@ describe('Clr fund deployer', async () => { resultsCommitment, perVOVoiceCreditCommitment ) - ).to.be.revertedWithCustomError(roundInterface, 'VotesNotTallied') + ).to.be.revertedWithCustomError(roundInterface, 'IncompleteTallyResults') }) it('allows owner to finalize round even without matching funds', async () => { await clrfund.deployNewRound(roundDuration) + const roundAddress = await clrfund.getCurrentRound() + const roundContract = (await getContractAt( + EContracts.FundingRound, + roundAddress, + ethers, + coordinator + )) as FundingRound + await roundContract.publishTallyHash('xxx') await time.increase(roundDuration) await expect( clrfund.transferMatchingFunds( @@ -474,7 +492,7 @@ describe('Clr fund deployer', async () => { resultsCommitment, perVOVoiceCreditCommitment ) - ).to.be.revertedWithCustomError(roundInterface, 'VotesNotTallied') + ).to.be.revertedWithCustomError(roundInterface, 'IncompleteTallyResults') }) it('pulls funds from funding source', async () => { @@ -485,7 +503,15 @@ describe('Clr fund deployer', async () => { ) await clrfund.addFundingSource(deployer.address) // Doesn't have tokens await clrfund.deployNewRound(roundDuration) + const roundAddress = await clrfund.getCurrentRound() + const roundContract = (await getContractAt( + EContracts.FundingRound, + roundAddress, + ethers, + coordinator + )) as FundingRound await time.increase(roundDuration) + await roundContract.publishTallyHash('xxx') await expect( clrfund.transferMatchingFunds( totalSpent, @@ -493,7 +519,7 @@ describe('Clr fund deployer', async () => { resultsCommitment, perVOVoiceCreditCommitment ) - ).to.be.revertedWithCustomError(roundInterface, 'VotesNotTallied') + ).to.be.revertedWithCustomError(roundInterface, 'IncompleteTallyResults') }) it('pulls funds from funding source if allowance is greater than balance', async () => { @@ -503,7 +529,15 @@ describe('Clr fund deployer', async () => { contributionAmount * 2n ) await clrfund.deployNewRound(roundDuration) + const roundAddress = await clrfund.getCurrentRound() + const roundContract = (await getContractAt( + EContracts.FundingRound, + roundAddress, + ethers, + coordinator + )) as FundingRound await time.increase(roundDuration) + await roundContract.publishTallyHash('xxx') await expect( clrfund.transferMatchingFunds( totalSpent, @@ -511,7 +545,7 @@ describe('Clr fund deployer', async () => { resultsCommitment, perVOVoiceCreditCommitment ) - ).to.be.revertedWithCustomError(roundInterface, 'VotesNotTallied') + ).to.be.revertedWithCustomError(roundInterface, 'IncompleteTallyResults') }) it('allows only owner to finalize round', async () => { diff --git a/contracts/tests/round.ts b/contracts/tests/round.ts index 52f0bf12a..fd6e8095b 100644 --- a/contracts/tests/round.ts +++ b/contracts/tests/round.ts @@ -153,6 +153,7 @@ describe('Funding Round', () => { await tally.mock.tallyBatchNum.returns(1) await tally.mock.verifyTallyResult.returns(true) await tally.mock.verifySpentVoiceCredits.returns(true) + await tally.mock.isTallied.returns(true) tokenAsContributor = token.connect(contributor) as Contract fundingRoundAsCoordinator = fundingRound.connect( @@ -695,6 +696,7 @@ describe('Funding Round', () => { userKeypair.pubKey.asContractParam(), totalContributions ) + await tally.mock.isTallied.returns(false) await time.increase(roundDuration) await mergeMaciSubtrees({ maciAddress, pollId, signer: deployer }) @@ -1494,7 +1496,7 @@ describe('Funding Round', () => { }) it('prevents adding tally results if maci has not completed tallying', async function () { - await tally.mock.tallyBatchNum.returns(0) + await tally.mock.isTallied.returns(false) await expect( addTallyResultsBatch( fundingRoundAsCoordinator, @@ -1506,7 +1508,7 @@ describe('Funding Round', () => { }) it('prevents adding batches of tally results if maci has not completed tallying', async function () { - await tally.mock.tallyBatchNum.returns(0) + await tally.mock.isTallied.returns(false) await expect( addTallyResultsBatch( fundingRoundAsCoordinator, diff --git a/contracts/utils/circuits.ts b/contracts/utils/circuits.ts index f7cba55e6..acc73fca7 100644 --- a/contracts/utils/circuits.ts +++ b/contracts/utils/circuits.ts @@ -4,9 +4,7 @@ // the EmptyBallotRoots.sol published in MACI npm package is hardcoded for stateTreeDepth = 6 import path from 'path' - -// This should match MACI.TREE_ARITY in the contract -const TREE_ARITY = 5 +import { MACI_TREE_ARITY } from '@clrfund/common' export const DEFAULT_CIRCUIT = 'micro' @@ -65,11 +63,11 @@ export const CIRCUITS: { [name: string]: CircuitInfo } = { // maxMessages and maxVoteOptions are calculated using treeArity = 5 as seen in the following code: // https://github.com/privacy-scaling-explorations/maci/blob/master/contracts/contracts/Poll.sol#L115 // treeArity ** messageTreeDepth - maxMessages: BigInt(TREE_ARITY ** 9), + maxMessages: BigInt(MACI_TREE_ARITY ** 9), // treeArity ** voteOptionTreeDepth - maxVoteOptions: BigInt(TREE_ARITY ** 3), + maxVoteOptions: BigInt(MACI_TREE_ARITY ** 3), }, - messageBatchSize: BigInt(TREE_ARITY ** 2), + messageBatchSize: BigInt(MACI_TREE_ARITY ** 2), }, } diff --git a/contracts/utils/constants.ts b/contracts/utils/constants.ts index dfdbcd011..70687b10a 100644 --- a/contracts/utils/constants.ts +++ b/contracts/utils/constants.ts @@ -5,7 +5,6 @@ export const VOICE_CREDIT_FACTOR = 10n ** BigInt(4 + 18 - 9) export const ALPHA_PRECISION = 10n ** 18n export const DEFAULT_SR_QUEUE_OPS = '4' export const DEFAULT_GET_LOG_BATCH_SIZE = 20000 -export const TREE_ARITY = 5 // brightid.clr.fund node uses this to sign messages // see the ethSigningAddress in https://brightid.clr.fund diff --git a/contracts/utils/maciParameters.ts b/contracts/utils/maciParameters.ts index e498a15e9..0e04d25ce 100644 --- a/contracts/utils/maciParameters.ts +++ b/contracts/utils/maciParameters.ts @@ -3,7 +3,7 @@ import { Contract, BigNumberish } from 'ethers' import { VerifyingKey } from 'maci-domainobjs' import { extractVk } from 'maci-circuits' import { CIRCUITS, getCircuitFiles } from './circuits' -import { TREE_ARITY } from './constants' +import { MACI_TREE_ARITY } from '@clrfund/common' import { Params } from '../typechain-types/contracts/MACIFactory' type TreeDepths = { @@ -36,15 +36,28 @@ export class MaciParameters { * @returns message batch size */ getMessageBatchSize(): number { - return TREE_ARITY ** this.treeDepths.messageTreeSubDepth + return MACI_TREE_ARITY ** this.treeDepths.messageTreeSubDepth + } + + /** + * Calculate the maximum recipients allowed by the MACI circuit + * @returns maximum recipient count + */ + getMaxRecipients(): number { + // -1 because recipients is 0 index based and the 0th slot is reserved + return MACI_TREE_ARITY ** this.treeDepths.voteOptionTreeDepth - 1 } asContractParam(): [ _stateTreeDepth: BigNumberish, + _messageBatchSize: BigNumberish, + _maxRecipients: BigNumberish, _treeDepths: Params.TreeDepthsStruct, ] { return [ this.stateTreeDepth, + this.getMessageBatchSize(), + this.getMaxRecipients(), { intStateTreeDepth: this.treeDepths.intStateTreeDepth, messageTreeSubDepth: this.treeDepths.messageTreeSubDepth, diff --git a/subgraph/abis/ClrFund.json b/subgraph/abis/ClrFund.json index 30e4badb0..1146484de 100644 --- a/subgraph/abis/ClrFund.json +++ b/subgraph/abis/ClrFund.json @@ -1,9 +1,36 @@ [ + { + "inputs": [ + { + "internalType": "address", + "name": "target", + "type": "address" + } + ], + "name": "AddressEmptyCode", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "AddressInsufficientBalance", + "type": "error" + }, { "inputs": [], "name": "AlreadyFinalized", "type": "error" }, + { + "inputs": [], + "name": "FailedInnerCall", + "type": "error" + }, { "inputs": [], "name": "FundingSourceAlreadyAdded", @@ -24,6 +51,11 @@ "name": "InvalidMaciFactory", "type": "error" }, + { + "inputs": [], + "name": "MaxRecipientsNotSet", + "type": "error" + }, { "inputs": [], "name": "NoCurrentRound", @@ -55,8 +87,14 @@ "type": "error" }, { - "inputs": [], - "name": "VoteOptionTreeDepthNotSet", + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "SafeERC20FailedOperation", "type": "error" }, { @@ -343,19 +381,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [], - "name": "getMaxRecipients", - "outputs": [ - { - "internalType": "uint256", - "name": "_maxRecipients", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, { "inputs": [ { diff --git a/subgraph/abis/ClrFundDeployer.json b/subgraph/abis/ClrFundDeployer.json index 68b32d168..3dd329580 100644 --- a/subgraph/abis/ClrFundDeployer.json +++ b/subgraph/abis/ClrFundDeployer.json @@ -40,6 +40,28 @@ "name": "InvalidMaciFactory", "type": "error" }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "OwnableInvalidOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "OwnableUnauthorizedAccount", + "type": "error" + }, { "anonymous": false, "inputs": [ diff --git a/subgraph/abis/FundingRound.json b/subgraph/abis/FundingRound.json index 7bfd15417..9d7aa994b 100644 --- a/subgraph/abis/FundingRound.json +++ b/subgraph/abis/FundingRound.json @@ -25,6 +25,28 @@ "stateMutability": "nonpayable", "type": "constructor" }, + { + "inputs": [ + { + "internalType": "address", + "name": "target", + "type": "address" + } + ], + "name": "AddressEmptyCode", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "AddressInsufficientBalance", + "type": "error" + }, { "inputs": [], "name": "AlreadyContributed", @@ -45,6 +67,11 @@ "name": "EmptyTallyHash", "type": "error" }, + { + "inputs": [], + "name": "FailedInnerCall", + "type": "error" + }, { "inputs": [], "name": "FundsAlreadyClaimed", @@ -141,6 +168,11 @@ "name": "NoProjectHasMoreThanOneVote", "type": "error" }, + { + "inputs": [], + "name": "NoSignUps", + "type": "error" + }, { "inputs": [], "name": "NoVoiceCredits", @@ -166,6 +198,28 @@ "name": "OnlyMaciCanRegisterVoters", "type": "error" }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "OwnableInvalidOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "OwnableUnauthorizedAccount", + "type": "error" + }, { "inputs": [], "name": "PollNotSet", @@ -191,6 +245,17 @@ "name": "RoundNotFinalized", "type": "error" }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "SafeERC20FailedOperation", + "type": "error" + }, { "inputs": [], "name": "TallyHashNotPublished", @@ -402,19 +467,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [], - "name": "TREE_ARITY", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, { "inputs": [], "name": "alpha", @@ -891,11 +943,6 @@ "internalType": "address", "name": "tally", "type": "address" - }, - { - "internalType": "address", - "name": "subsidy", - "type": "address" } ], "internalType": "struct MACI.PollContracts", diff --git a/subgraph/abis/MACIFactory.json b/subgraph/abis/MACIFactory.json index 7f3ae7c07..3488fff8d 100644 --- a/subgraph/abis/MACIFactory.json +++ b/subgraph/abis/MACIFactory.json @@ -37,6 +37,16 @@ "stateMutability": "nonpayable", "type": "constructor" }, + { + "inputs": [], + "name": "InvalidMaxRecipients", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidMessageBatchSize", + "type": "error" + }, { "inputs": [], "name": "InvalidMessageProcessorFactory", @@ -62,6 +72,11 @@ "name": "InvalidVkRegistry", "type": "error" }, + { + "inputs": [], + "name": "InvalidVoteOptionTreeDepth", + "type": "error" + }, { "inputs": [], "name": "NotInitialized", @@ -99,6 +114,11 @@ "name": "TallyVkNotSet", "type": "error" }, + { + "inputs": [], + "name": "VoteOptionTreeDepthNotSet", + "type": "error" + }, { "anonymous": false, "inputs": [ @@ -150,19 +170,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [], - "name": "TREE_ARITY", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, { "inputs": [ { @@ -270,22 +277,29 @@ "type": "function" }, { - "inputs": [ + "inputs": [], + "name": "maxRecipients", + "outputs": [ { - "internalType": "uint8", - "name": "messageTreeSubDepth", - "type": "uint8" + "internalType": "uint256", + "name": "", + "type": "uint256" } ], - "name": "getMessageBatchSize", + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "messageBatchSize", "outputs": [ { "internalType": "uint256", - "name": "_messageBatchSize", + "name": "", "type": "uint256" } ], - "stateMutability": "pure", + "stateMutability": "view", "type": "function" }, { @@ -315,6 +329,16 @@ "name": "_stateTreeDepth", "type": "uint8" }, + { + "internalType": "uint256", + "name": "_messageBatchSize", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_maxRecipients", + "type": "uint256" + }, { "components": [ { diff --git a/subgraph/generated/ClrFund/ClrFund.ts b/subgraph/generated/ClrFund/ClrFund.ts index 3446a9e7b..5810bf338 100644 --- a/subgraph/generated/ClrFund/ClrFund.ts +++ b/subgraph/generated/ClrFund/ClrFund.ts @@ -389,29 +389,6 @@ export class ClrFund extends ethereum.SmartContract { return ethereum.CallResult.fromValue(value[0].toBigInt()); } - getMaxRecipients(): BigInt { - let result = super.call( - "getMaxRecipients", - "getMaxRecipients():(uint256)", - [], - ); - - return result[0].toBigInt(); - } - - try_getMaxRecipients(): ethereum.CallResult { - let result = super.tryCall( - "getMaxRecipients", - "getMaxRecipients():(uint256)", - [], - ); - if (result.reverted) { - return new ethereum.CallResult(); - } - let value = result.value; - return ethereum.CallResult.fromValue(value[0].toBigInt()); - } - maciFactory(): Address { let result = super.call("maciFactory", "maciFactory():(address)", []); diff --git a/subgraph/generated/ClrFund/FundingRound.ts b/subgraph/generated/ClrFund/FundingRound.ts index 32be22df9..a2660d7ad 100644 --- a/subgraph/generated/ClrFund/FundingRound.ts +++ b/subgraph/generated/ClrFund/FundingRound.ts @@ -277,21 +277,6 @@ export class FundingRound extends ethereum.SmartContract { return ethereum.CallResult.fromValue(value[0].toI32()); } - TREE_ARITY(): BigInt { - let result = super.call("TREE_ARITY", "TREE_ARITY():(uint256)", []); - - return result[0].toBigInt(); - } - - try_TREE_ARITY(): ethereum.CallResult { - let result = super.tryCall("TREE_ARITY", "TREE_ARITY():(uint256)", []); - if (result.reverted) { - return new ethereum.CallResult(); - } - let value = result.value; - return ethereum.CallResult.fromValue(value[0].toBigInt()); - } - alpha(): BigInt { let result = super.call("alpha", "alpha():(uint256)", []); @@ -1237,10 +1222,6 @@ export class SetMaciCall_pollContractsStruct extends ethereum.Tuple { get tally(): Address { return this[2].toAddress(); } - - get subsidy(): Address { - return this[3].toAddress(); - } } export class SetMaciInstanceCall extends ethereum.Call { diff --git a/subgraph/generated/ClrFund/MACIFactory.ts b/subgraph/generated/ClrFund/MACIFactory.ts index 7c1fbe71b..0f6f79fc3 100644 --- a/subgraph/generated/ClrFund/MACIFactory.ts +++ b/subgraph/generated/ClrFund/MACIFactory.ts @@ -227,21 +227,6 @@ export class MACIFactory extends ethereum.SmartContract { return ethereum.CallResult.fromValue(value[0].toI32()); } - TREE_ARITY(): BigInt { - let result = super.call("TREE_ARITY", "TREE_ARITY():(uint256)", []); - - return result[0].toBigInt(); - } - - try_TREE_ARITY(): ethereum.CallResult { - let result = super.tryCall("TREE_ARITY", "TREE_ARITY():(uint256)", []); - if (result.reverted) { - return new ethereum.CallResult(); - } - let value = result.value; - return ethereum.CallResult.fromValue(value[0].toBigInt()); - } - deployMaci( signUpGatekeeper: Address, initialVoiceCreditProxy: Address, @@ -342,23 +327,40 @@ export class MACIFactory extends ethereum.SmartContract { ); } - getMessageBatchSize(messageTreeSubDepth: i32): BigInt { + maxRecipients(): BigInt { + let result = super.call("maxRecipients", "maxRecipients():(uint256)", []); + + return result[0].toBigInt(); + } + + try_maxRecipients(): ethereum.CallResult { + let result = super.tryCall( + "maxRecipients", + "maxRecipients():(uint256)", + [], + ); + if (result.reverted) { + return new ethereum.CallResult(); + } + let value = result.value; + return ethereum.CallResult.fromValue(value[0].toBigInt()); + } + + messageBatchSize(): BigInt { let result = super.call( - "getMessageBatchSize", - "getMessageBatchSize(uint8):(uint256)", - [ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(messageTreeSubDepth))], + "messageBatchSize", + "messageBatchSize():(uint256)", + [], ); return result[0].toBigInt(); } - try_getMessageBatchSize( - messageTreeSubDepth: i32, - ): ethereum.CallResult { + try_messageBatchSize(): ethereum.CallResult { let result = super.tryCall( - "getMessageBatchSize", - "getMessageBatchSize(uint8):(uint256)", - [ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(messageTreeSubDepth))], + "messageBatchSize", + "messageBatchSize():(uint256)", + [], ); if (result.reverted) { return new ethereum.CallResult(); @@ -658,9 +660,17 @@ export class SetMaciParametersCall__Inputs { return this._call.inputValues[0].value.toI32(); } + get _messageBatchSize(): BigInt { + return this._call.inputValues[1].value.toBigInt(); + } + + get _maxRecipients(): BigInt { + return this._call.inputValues[2].value.toBigInt(); + } + get _treeDepths(): SetMaciParametersCall_treeDepthsStruct { return changetype( - this._call.inputValues[1].value.toTuple(), + this._call.inputValues[3].value.toTuple(), ); } } diff --git a/subgraph/generated/templates/FundingRound/FundingRound.ts b/subgraph/generated/templates/FundingRound/FundingRound.ts index 32be22df9..a2660d7ad 100644 --- a/subgraph/generated/templates/FundingRound/FundingRound.ts +++ b/subgraph/generated/templates/FundingRound/FundingRound.ts @@ -277,21 +277,6 @@ export class FundingRound extends ethereum.SmartContract { return ethereum.CallResult.fromValue(value[0].toI32()); } - TREE_ARITY(): BigInt { - let result = super.call("TREE_ARITY", "TREE_ARITY():(uint256)", []); - - return result[0].toBigInt(); - } - - try_TREE_ARITY(): ethereum.CallResult { - let result = super.tryCall("TREE_ARITY", "TREE_ARITY():(uint256)", []); - if (result.reverted) { - return new ethereum.CallResult(); - } - let value = result.value; - return ethereum.CallResult.fromValue(value[0].toBigInt()); - } - alpha(): BigInt { let result = super.call("alpha", "alpha():(uint256)", []); @@ -1237,10 +1222,6 @@ export class SetMaciCall_pollContractsStruct extends ethereum.Tuple { get tally(): Address { return this[2].toAddress(); } - - get subsidy(): Address { - return this[3].toAddress(); - } } export class SetMaciInstanceCall extends ethereum.Call { diff --git a/subgraph/generated/templates/MACI/FundingRound.ts b/subgraph/generated/templates/MACI/FundingRound.ts index 32be22df9..a2660d7ad 100644 --- a/subgraph/generated/templates/MACI/FundingRound.ts +++ b/subgraph/generated/templates/MACI/FundingRound.ts @@ -277,21 +277,6 @@ export class FundingRound extends ethereum.SmartContract { return ethereum.CallResult.fromValue(value[0].toI32()); } - TREE_ARITY(): BigInt { - let result = super.call("TREE_ARITY", "TREE_ARITY():(uint256)", []); - - return result[0].toBigInt(); - } - - try_TREE_ARITY(): ethereum.CallResult { - let result = super.tryCall("TREE_ARITY", "TREE_ARITY():(uint256)", []); - if (result.reverted) { - return new ethereum.CallResult(); - } - let value = result.value; - return ethereum.CallResult.fromValue(value[0].toBigInt()); - } - alpha(): BigInt { let result = super.call("alpha", "alpha():(uint256)", []); @@ -1237,10 +1222,6 @@ export class SetMaciCall_pollContractsStruct extends ethereum.Tuple { get tally(): Address { return this[2].toAddress(); } - - get subsidy(): Address { - return this[3].toAddress(); - } } export class SetMaciInstanceCall extends ethereum.Call { diff --git a/subgraph/generated/templates/Poll/FundingRound.ts b/subgraph/generated/templates/Poll/FundingRound.ts index 32be22df9..a2660d7ad 100644 --- a/subgraph/generated/templates/Poll/FundingRound.ts +++ b/subgraph/generated/templates/Poll/FundingRound.ts @@ -277,21 +277,6 @@ export class FundingRound extends ethereum.SmartContract { return ethereum.CallResult.fromValue(value[0].toI32()); } - TREE_ARITY(): BigInt { - let result = super.call("TREE_ARITY", "TREE_ARITY():(uint256)", []); - - return result[0].toBigInt(); - } - - try_TREE_ARITY(): ethereum.CallResult { - let result = super.tryCall("TREE_ARITY", "TREE_ARITY():(uint256)", []); - if (result.reverted) { - return new ethereum.CallResult(); - } - let value = result.value; - return ethereum.CallResult.fromValue(value[0].toBigInt()); - } - alpha(): BigInt { let result = super.call("alpha", "alpha():(uint256)", []); @@ -1237,10 +1222,6 @@ export class SetMaciCall_pollContractsStruct extends ethereum.Tuple { get tally(): Address { return this[2].toAddress(); } - - get subsidy(): Address { - return this[3].toAddress(); - } } export class SetMaciInstanceCall extends ethereum.Call { diff --git a/subgraph/src/ClrFundMapping.ts b/subgraph/src/ClrFundMapping.ts index 85cbe3170..02c978eb3 100644 --- a/subgraph/src/ClrFundMapping.ts +++ b/subgraph/src/ClrFundMapping.ts @@ -132,7 +132,7 @@ function createOrUpdateClrFund( let recipientRegistryId = recipientRegistryAddress.toHexString() let recipientRegistry = RecipientRegistry.load(recipientRegistryId) if (!recipientRegistry) { - createRecipientRegistry(clrFundId, recipientRegistryAddress) + createRecipientRegistry(recipientRegistryAddress) } let contributorRegistryAddress = clrFundContract.userRegistry() diff --git a/subgraph/src/RecipientRegistry.ts b/subgraph/src/RecipientRegistry.ts index a9af36950..751a30e45 100644 --- a/subgraph/src/RecipientRegistry.ts +++ b/subgraph/src/RecipientRegistry.ts @@ -8,7 +8,6 @@ import { OptimisticRecipientRegistry as RecipientRegistryTemplate } from '../gen * Create the recipient registry entity */ export function createRecipientRegistry( - clrFundId: string, recipientRegistryAddress: Address ): RecipientRegistry { let recipientRegistryId = recipientRegistryAddress.toHexString() @@ -41,7 +40,6 @@ export function createRecipientRegistry( if (!owner.reverted) { recipientRegistry.owner = owner.value } - recipientRegistry.clrFund = clrFundId recipientRegistry.save() return recipientRegistry @@ -56,22 +54,7 @@ export function loadRecipientRegistry( let recipientRegistryId = address.toHexString() let recipientRegistry = RecipientRegistry.load(recipientRegistryId) if (!recipientRegistry) { - let recipientRegistryContract = RecipientRegistryContract.bind(address) - let controller = recipientRegistryContract.try_controller() - if (!controller.reverted) { - // Recipient registry's controller must be the ClrFund contract - let clrFundId = controller.value.toHexString() - let clrFund = ClrFund.load(clrFundId) - if (clrFund) { - /* This is our registry, create it */ - recipientRegistry = createRecipientRegistry(clrFund.id, address) - - // update factory - clrFund.recipientRegistry = recipientRegistryId - clrFund.recipientRegistryAddress = address - clrFund.save() - } - } + recipientRegistry = createRecipientRegistry(address) } return recipientRegistry From dd028f90cfd12bc28f1c70cf9471b6b2474088e4 Mon Sep 17 00:00:00 2001 From: yuetloo Date: Tue, 21 May 2024 20:27:04 -0400 Subject: [PATCH 4/7] fix incorrect entity id mapping when contract was called from another contract --- subgraph/src/ClrFundDeployerMapping.ts | 2 +- subgraph/src/MACIMapping.ts | 2 +- subgraph/src/PollMapping.ts | 12 ++---------- 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/subgraph/src/ClrFundDeployerMapping.ts b/subgraph/src/ClrFundDeployerMapping.ts index c04392dac..840ccf8c8 100644 --- a/subgraph/src/ClrFundDeployerMapping.ts +++ b/subgraph/src/ClrFundDeployerMapping.ts @@ -5,7 +5,7 @@ import { ClrFundDeployer, ClrFund } from '../generated/schema' import { ClrFund as ClrFundTemplate } from '../generated/templates' export function handleNewInstance(event: NewInstance): void { - let clrfundDeployerAddress = event.transaction.to! + let clrfundDeployerAddress = event.address let clrfundDeployerId = clrfundDeployerAddress.toHex() let clrFundDeployer = ClrFundDeployer.load(clrfundDeployerId) diff --git a/subgraph/src/MACIMapping.ts b/subgraph/src/MACIMapping.ts index 75da4929b..e1cbdb28f 100644 --- a/subgraph/src/MACIMapping.ts +++ b/subgraph/src/MACIMapping.ts @@ -21,7 +21,7 @@ import { makePublicKeyId } from './PublicKey' // - contract.verifier(...) export function handleSignUp(event: SignUp): void { - let fundingRoundAddress = event.transaction.to! + let fundingRoundAddress = event.address let fundingRoundId = fundingRoundAddress.toHex() let publicKeyId = makePublicKeyId( diff --git a/subgraph/src/PollMapping.ts b/subgraph/src/PollMapping.ts index 99aa537fb..e3c6fe043 100644 --- a/subgraph/src/PollMapping.ts +++ b/subgraph/src/PollMapping.ts @@ -1,19 +1,11 @@ import { log } from '@graphprotocol/graph-ts' import { PublishMessage } from '../generated/templates/Poll/Poll' -import { FundingRound, Poll, Message, PublicKey } from '../generated/schema' +import { Poll, Message, PublicKey } from '../generated/schema' import { makePublicKeyId } from './PublicKey' export function handlePublishMessage(event: PublishMessage): void { - if (!event.transaction.to) { - log.error( - 'Error: handlePublishMessage failed fundingRound not registered', - [] - ) - return - } - - let pollEntityId = event.transaction.to!.toHex() + let pollEntityId = event.address.toHex() let poll = Poll.load(pollEntityId) if (poll == null) { log.error('Error: handlePublishMessage failed poll not found {}', [ From c71e354526fcd549a55437db5686069495365ae0 Mon Sep 17 00:00:00 2001 From: yuetloo Date: Tue, 21 May 2024 21:11:27 -0400 Subject: [PATCH 5/7] update recipient registry address --- subgraph/src/ClrFundMapping.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/subgraph/src/ClrFundMapping.ts b/subgraph/src/ClrFundMapping.ts index 02c978eb3..92f408fdd 100644 --- a/subgraph/src/ClrFundMapping.ts +++ b/subgraph/src/ClrFundMapping.ts @@ -131,7 +131,10 @@ function createOrUpdateClrFund( let recipientRegistryAddress = clrFundContract.recipientRegistry() let recipientRegistryId = recipientRegistryAddress.toHexString() let recipientRegistry = RecipientRegistry.load(recipientRegistryId) - if (!recipientRegistry) { + if (recipientRegistry) { + recipientRegistry.clrFund = clrFundId + recipientRegistry.save() + } else { createRecipientRegistry(recipientRegistryAddress) } From 3d47629493a1a619f1b7d0eba6954c9d39e91a30 Mon Sep 17 00:00:00 2001 From: yuetloo Date: Tue, 21 May 2024 21:30:00 -0400 Subject: [PATCH 6/7] update clrfund with recipient registry address --- subgraph/src/ClrFundMapping.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/subgraph/src/ClrFundMapping.ts b/subgraph/src/ClrFundMapping.ts index 92f408fdd..3e97056c8 100644 --- a/subgraph/src/ClrFundMapping.ts +++ b/subgraph/src/ClrFundMapping.ts @@ -131,12 +131,11 @@ function createOrUpdateClrFund( let recipientRegistryAddress = clrFundContract.recipientRegistry() let recipientRegistryId = recipientRegistryAddress.toHexString() let recipientRegistry = RecipientRegistry.load(recipientRegistryId) - if (recipientRegistry) { - recipientRegistry.clrFund = clrFundId - recipientRegistry.save() - } else { - createRecipientRegistry(recipientRegistryAddress) + if (!recipientRegistry) { + recipientRegistry = createRecipientRegistry(recipientRegistryAddress) } + recipientRegistry.clrFund = clrFundId + recipientRegistry.save() let contributorRegistryAddress = clrFundContract.userRegistry() let contributorRegistryId = contributorRegistryAddress.toHexString() From 729eadcc193df0dc00f7cd975b3cec24d5bf19c3 Mon Sep 17 00:00:00 2001 From: yuetloo Date: Wed, 22 May 2024 11:21:24 -0300 Subject: [PATCH 7/7] refactor code for readability --- contracts/contracts/ClrFund.sol | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/contracts/contracts/ClrFund.sol b/contracts/contracts/ClrFund.sol index 3927ab2c1..499994383 100644 --- a/contracts/contracts/ClrFund.sol +++ b/contracts/contracts/ClrFund.sol @@ -63,6 +63,12 @@ contract ClrFund is OwnableUpgradeable, DomainObjs, Params { error MaxRecipientsNotSet(); error NotInitialized(); + modifier maciFactoryInitialized() { + if (address(maciFactory) == address(0)) revert InvalidMaciFactory(); + if (maciFactory.maxRecipients() == 0) revert MaxRecipientsNotSet(); + _; + } + /** * @dev Initialize clrfund instance with MACI factory and round factory @@ -141,15 +147,6 @@ contract ClrFund is OwnableUpgradeable, DomainObjs, Params { emit UserRegistryChanged(address(_userRegistry)); } - /** - * @dev Set the max recipients in the recipient registry - */ - function _setMaxRecipients() private { - if (address(maciFactory) == address(0)) revert NotInitialized(); - if (maciFactory.maxRecipients() == 0) revert MaxRecipientsNotSet(); - recipientRegistry.setMaxRecipients(maciFactory.maxRecipients()); - } - /** * @dev Set recipient registry. * @param _recipientRegistry Address of a recipient registry. @@ -157,10 +154,13 @@ contract ClrFund is OwnableUpgradeable, DomainObjs, Params { function setRecipientRegistry(IRecipientRegistry _recipientRegistry) external onlyOwner + maciFactoryInitialized { recipientRegistry = _recipientRegistry; - _setMaxRecipients(); + + // Make sure that the max number of recipients is set correctly + recipientRegistry.setMaxRecipients(maciFactory.maxRecipients()); emit RecipientRegistryChanged(address(_recipientRegistry)); } @@ -215,6 +215,7 @@ contract ClrFund is OwnableUpgradeable, DomainObjs, Params { ) external onlyOwner + maciFactoryInitialized { IFundingRound currentRound = getCurrentRound(); if (address(currentRound) != address(0) && !currentRound.isFinalized()) { @@ -224,7 +225,7 @@ contract ClrFund is OwnableUpgradeable, DomainObjs, Params { if (address(recipientRegistry) == address(0)) revert RecipientRegistryNotSet(); // Make sure that the max number of recipients is set correctly - _setMaxRecipients(); + recipientRegistry.setMaxRecipients(maciFactory.maxRecipients()); // Deploy funding round and MACI contracts address newRound = roundFactory.deploy(duration, address(this));