diff --git a/.github/workflows/create-version.yml b/.github/workflows/create-version.yml index 9cccd61a7..997f82322 100644 --- a/.github/workflows/create-version.yml +++ b/.github/workflows/create-version.yml @@ -31,18 +31,14 @@ jobs: node-version: ${{ env.NODE_VERSION }} - name: Checkout source code uses: actions/checkout@v3 - - name: Install dependencies + - name: Create new version run: | # use https to avoid error: unable to connect to github.com git config --global url."https://".insteadOf git:// - yarn && yarn build - - name: setup git config - run: | # setup the username and email. I tend to use 'GitHub Actions Bot' with no email by default git config user.name "GitHub Actions Bot" git config user.email "<>" - - name: Create new version - run: | + yarn && yarn build echo "Version: ${{ github.event.inputs.version }}" cd contracts npm version ${{ github.event.inputs.version }} diff --git a/common/package.json b/common/package.json index ee496d949..9e3d72dc2 100644 --- a/common/package.json +++ b/common/package.json @@ -22,9 +22,9 @@ }, "dependencies": { "@openzeppelin/merkle-tree": "^1.0.5", - "ethers": "^6.11.1", - "maci-crypto": "^1.2.0", - "maci-domainobjs": "^1.2.0" + "ethers": "^6.12.1", + "maci-crypto": "1.2.2", + "maci-domainobjs": "1.2.2" }, "repository": { "type": "git", diff --git a/common/src/__tests__/keypair.spec.ts b/common/src/__tests__/keypair.spec.ts new file mode 100644 index 000000000..7dbc6e195 --- /dev/null +++ b/common/src/__tests__/keypair.spec.ts @@ -0,0 +1,21 @@ +import { expect } from 'chai' +import { Keypair, PubKey } from '../keypair' +import { Wallet, sha256, randomBytes } from 'ethers' + +describe('keypair', function () { + for (let i = 0; i < 10; i++) { + it(`should generate key ${i} from seed successfully`, function () { + const wallet = Wallet.createRandom() + const signature = wallet.signMessageSync(randomBytes(32).toString()) + const seed = sha256(signature) + const keypair = Keypair.createFromSeed(seed) + expect(keypair.pubKey.serialize()).to.match(/^macipk./) + }) + } + + it('should throw if pubKey is invalid', () => { + expect(() => { + new PubKey([1n, 1n]) + }).to.throw('PubKey not on curve') + }) +}) diff --git a/common/src/keypair.ts b/common/src/keypair.ts index 8e02ab52f..c6c9ea165 100644 --- a/common/src/keypair.ts +++ b/common/src/keypair.ts @@ -1,37 +1,31 @@ import { keccak256, isBytesLike, concat, toBeArray } from 'ethers' import { Keypair as MaciKeypair, PrivKey, PubKey } from 'maci-domainobjs' -const SNARK_FIELD_SIZE = BigInt( - '21888242871839275222246405745257275088548364400416034343698204186575808495617' -) - /** - * Returns a BabyJub-compatible value. This function is modified from - * the MACI's genRandomBabyJubValue(). Instead of returning random value - * for the private key, it derives the private key from the users - * signature hash + * Derives the MACI private key from the users signature hash * @param hash - user's signature hash + * @return The MACI private key */ function genPrivKey(hash: string): PrivKey { - // Prevent modulo bias - //const lim = BigInt('0x10000000000000000000000000000000000000000000000000000000000000000') - //const min = (lim - SNARK_FIELD_SIZE) % SNARK_FIELD_SIZE - const min = BigInt( - '6350874878119819312338956282401532410528162663560392320966563075034087161851' - ) - if (!isBytesLike(hash)) { - throw new Error(`Hash must be a hex string: ${hash}`) + throw new Error(`genPrivKey() error. Hash must be a hex string: ${hash}`) } - let hashBN = BigInt(hash) - // don't think we'll enter the for loop below, but, just in case - for (let counter = 1; hashBN < min; counter++) { - const data = concat([toBeArray(hashBN), toBeArray(counter)]) - hashBN = BigInt(keccak256(data)) + let rawPrivKey = BigInt(hash) + let pubKey: PubKey | null = null + + for (let counter = 1; pubKey === null; counter++) { + try { + const privKey = new PrivKey(rawPrivKey) + // this will throw 'Invalid public key' if key is not on the Baby Jubjub elliptic curve + const keypair = new Keypair(privKey) + pubKey = keypair.pubKey + } catch { + const data = concat([toBeArray(rawPrivKey), toBeArray(counter)]) + rawPrivKey = BigInt(keccak256(data)) + } } - const rawPrivKey = hashBN % SNARK_FIELD_SIZE return new PrivKey(rawPrivKey) } 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/contracts/AnyOldERC20Token.sol b/contracts/contracts/AnyOldERC20Token.sol index 3d88c9508..be5ba8656 100644 --- a/contracts/contracts/AnyOldERC20Token.sol +++ b/contracts/contracts/AnyOldERC20Token.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.10; +pragma solidity 0.8.20; import '@openzeppelin/contracts/token/ERC20/ERC20.sol'; diff --git a/contracts/contracts/CloneFactory.sol b/contracts/contracts/CloneFactory.sol index 99247f723..fcdf5d9b6 100644 --- a/contracts/contracts/CloneFactory.sol +++ b/contracts/contracts/CloneFactory.sol @@ -21,7 +21,7 @@ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -pragma solidity 0.8.10; +pragma solidity 0.8.20; contract CloneFactory { // implementation of eip-1167 - see https://eips.ethereum.org/EIPS/eip-1167 function createClone(address target) internal returns (address result) { diff --git a/contracts/contracts/ClrFund.sol b/contracts/contracts/ClrFund.sol index c9ad77389..499994383 100644 --- a/contracts/contracts/ClrFund.sol +++ b/contracts/contracts/ClrFund.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity 0.8.10; +pragma solidity 0.8.20; import '@openzeppelin/contracts/token/ERC20/ERC20.sol'; import '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol'; @@ -60,8 +60,14 @@ contract ClrFund is OwnableUpgradeable, DomainObjs, Params { error InvalidFundingRoundFactory(); error InvalidMaciFactory(); error RecipientRegistryNotSet(); + error MaxRecipientsNotSet(); error NotInitialized(); - error VoteOptionTreeDepthNotSet(); + + modifier maciFactoryInitialized() { + if (address(maciFactory) == address(0)) revert InvalidMaciFactory(); + if (maciFactory.maxRecipients() == 0) revert MaxRecipientsNotSet(); + _; + } /** @@ -128,20 +134,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. @@ -162,10 +154,13 @@ contract ClrFund is OwnableUpgradeable, DomainObjs, Params { function setRecipientRegistry(IRecipientRegistry _recipientRegistry) external onlyOwner + maciFactoryInitialized { + recipientRegistry = _recipientRegistry; - uint256 maxRecipients = getMaxRecipients(); - recipientRegistry.setMaxRecipients(maxRecipients); + + // Make sure that the max number of recipients is set correctly + recipientRegistry.setMaxRecipients(maciFactory.maxRecipients()); emit RecipientRegistryChanged(address(_recipientRegistry)); } @@ -220,6 +215,7 @@ contract ClrFund is OwnableUpgradeable, DomainObjs, Params { ) external onlyOwner + maciFactoryInitialized { IFundingRound currentRound = getCurrentRound(); if (address(currentRound) != address(0) && !currentRound.isFinalized()) { @@ -229,8 +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 - uint256 maxRecipients = getMaxRecipients(); - recipientRegistry.setMaxRecipients(maxRecipients); + recipientRegistry.setMaxRecipients(maciFactory.maxRecipients()); // Deploy funding round and MACI contracts address newRound = roundFactory.deploy(duration, address(this)); diff --git a/contracts/contracts/ClrFundDeployer.sol b/contracts/contracts/ClrFundDeployer.sol index 2487723ff..4f2b279d5 100644 --- a/contracts/contracts/ClrFundDeployer.sol +++ b/contracts/contracts/ClrFundDeployer.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity 0.8.10; +pragma solidity 0.8.20; import {MACIFactory} from './MACIFactory.sol'; import {ClrFund} from './ClrFund.sol'; @@ -9,7 +9,7 @@ import {SignUpGatekeeper} from "maci-contracts/contracts/gatekeepers/SignUpGatek import {InitialVoiceCreditProxy} from "maci-contracts/contracts/initialVoiceCreditProxy/InitialVoiceCreditProxy.sol"; import {Ownable} from '@openzeppelin/contracts/access/Ownable.sol'; -contract ClrFundDeployer is CloneFactory, Ownable { +contract ClrFundDeployer is CloneFactory, Ownable(msg.sender) { address public clrfundTemplate; address public maciFactory; address public roundFactory; diff --git a/contracts/contracts/ExternalContacts.sol b/contracts/contracts/ExternalContacts.sol index 51706fb5f..d2f1d989e 100644 --- a/contracts/contracts/ExternalContacts.sol +++ b/contracts/contracts/ExternalContacts.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.10; +pragma solidity 0.8.20; /* * These imports are just for hardhat to find the contracts for deployment @@ -9,5 +9,4 @@ pragma solidity ^0.8.10; import {Poll} from 'maci-contracts/contracts/Poll.sol'; import {PollFactory} from 'maci-contracts/contracts/PollFactory.sol'; import {TallyFactory} from 'maci-contracts/contracts/TallyFactory.sol'; -import {SubsidyFactory} from 'maci-contracts/contracts/SubsidyFactory.sol'; import {MessageProcessorFactory} from 'maci-contracts/contracts/MessageProcessorFactory.sol'; diff --git a/contracts/contracts/FundingRound.sol b/contracts/contracts/FundingRound.sol index 38bb4f1ee..701872a8f 100644 --- a/contracts/contracts/FundingRound.sol +++ b/contracts/contracts/FundingRound.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity 0.8.10; +pragma solidity 0.8.20; import '@openzeppelin/contracts/access/Ownable.sol'; import '@openzeppelin/contracts/token/ERC20/ERC20.sol'; @@ -15,7 +15,7 @@ import {SignUpGatekeeper} from 'maci-contracts/contracts/gatekeepers/SignUpGatek import {InitialVoiceCreditProxy} from 'maci-contracts/contracts/initialVoiceCreditProxy/InitialVoiceCreditProxy.sol'; import {CommonUtilities} from 'maci-contracts/contracts/utilities/CommonUtilities.sol'; import {SnarkCommon} from 'maci-contracts/contracts/crypto/SnarkCommon.sol'; -import {ITallySubsidyFactory} from 'maci-contracts/contracts/interfaces/ITallySubsidyFactory.sol'; +import {ITallyFactory} from 'maci-contracts/contracts/interfaces/ITallyFactory.sol'; import {IMessageProcessorFactory} from 'maci-contracts/contracts/interfaces/IMPFactory.sol'; import {IClrFund} from './interfaces/IClrFund.sol'; import {IMACIFactory} from './interfaces/IMACIFactory.sol'; @@ -25,7 +25,7 @@ import './userRegistry/IUserRegistry.sol'; import './recipientRegistry/IRecipientRegistry.sol'; contract FundingRound is - Ownable, + Ownable(msg.sender), SignUpGatekeeper, InitialVoiceCreditProxy, DomainObjs, @@ -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 @@ -221,10 +209,10 @@ contract FundingRound is address vkRegistry = address(tally.vkRegistry()); IMessageProcessorFactory messageProcessorFactory = maci.messageProcessorFactory(); - ITallySubsidyFactory tallyFactory = maci.tallyFactory(); + ITallyFactory tallyFactory = maci.tallyFactory(); - address mp = messageProcessorFactory.deploy(verifier, vkRegistry, address(poll), coordinator); - address newTally = tallyFactory.deploy(verifier, vkRegistry, address(poll), mp, coordinator); + address mp = messageProcessorFactory.deploy(verifier, vkRegistry, address(poll), coordinator, Mode.QV); + address newTally = tallyFactory.deploy(verifier, vkRegistry, address(poll), mp, coordinator, Mode.QV); _setTally(newTally); } @@ -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/FundingRoundFactory.sol b/contracts/contracts/FundingRoundFactory.sol index 9a35c1077..06d0453e5 100644 --- a/contracts/contracts/FundingRoundFactory.sol +++ b/contracts/contracts/FundingRoundFactory.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.10; +pragma solidity 0.8.20; import {FundingRound} from './FundingRound.sol'; import {IClrFund} from './interfaces/IClrFund.sol'; diff --git a/contracts/contracts/MACICommon.sol b/contracts/contracts/MACICommon.sol index 3a73b4110..01224230b 100644 --- a/contracts/contracts/MACICommon.sol +++ b/contracts/contracts/MACICommon.sol @@ -1,14 +1,11 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.10; +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. @@ -16,9 +13,6 @@ contract MACICommon { struct Factories { address pollFactory; address tallyFactory; - // subsidyFactory is not currently used, it's just a place holder here - address subsidyFactory; address messageProcessorFactory; } - } \ No newline at end of file diff --git a/contracts/contracts/MACIFactory.sol b/contracts/contracts/MACIFactory.sol index 1fb0b800b..f3952280d 100644 --- a/contracts/contracts/MACIFactory.sol +++ b/contracts/contracts/MACIFactory.sol @@ -1,10 +1,10 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.10; +pragma solidity 0.8.20; import {MACI} from 'maci-contracts/contracts/MACI.sol'; import {IPollFactory} from 'maci-contracts/contracts/interfaces/IPollFactory.sol'; -import {ITallySubsidyFactory} from 'maci-contracts/contracts/interfaces/ITallySubsidyFactory.sol'; +import {ITallyFactory} from 'maci-contracts/contracts/interfaces/ITallyFactory.sol'; import {IMessageProcessorFactory} from 'maci-contracts/contracts/interfaces/IMPFactory.sol'; import {SignUpGatekeeper} from 'maci-contracts/contracts/gatekeepers/SignUpGatekeeper.sol'; import {InitialVoiceCreditProxy} from 'maci-contracts/contracts/initialVoiceCreditProxy/InitialVoiceCreditProxy.sol'; @@ -17,7 +17,7 @@ import {Params} from 'maci-contracts/contracts/utilities/Params.sol'; import {DomainObjs} from 'maci-contracts/contracts/utilities/DomainObjs.sol'; import {MACICommon} from './MACICommon.sol'; -contract MACIFactory is Ownable, Params, SnarkCommon, DomainObjs, MACICommon { +contract MACIFactory is Ownable(msg.sender), Params, SnarkCommon, DomainObjs, MACICommon { // Verifying Key Registry containing circuit parameters VkRegistry public vkRegistry; @@ -29,6 +29,8 @@ contract MACIFactory is Ownable, Params, SnarkCommon, DomainObjs, MACICommon { // circuit parameters uint8 public stateTreeDepth; TreeDepths public treeDepths; + uint256 public messageBatchSize; + uint256 public maxRecipients; // Events event MaciParametersChanged(); @@ -38,12 +40,15 @@ contract MACIFactory is Ownable, Params, SnarkCommon, DomainObjs, MACICommon { error NotInitialized(); error ProcessVkNotSet(); error TallyVkNotSet(); + error VoteOptionTreeDepthNotSet(); error InvalidVkRegistry(); error InvalidPollFactory(); error InvalidTallyFactory(); - error InvalidSubsidyFactory(); error InvalidMessageProcessorFactory(); error InvalidVerifier(); + error InvalidMaxRecipients(); + error InvalidMessageBatchSize(); + error InvalidVoteOptionTreeDepth(); constructor( address _vkRegistry, @@ -61,14 +66,6 @@ contract MACIFactory is Ownable, Params, SnarkCommon, DomainObjs, MACICommon { 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 */ @@ -123,19 +120,26 @@ contract MACIFactory is Ownable, Params, SnarkCommon, DomainObjs, MACICommon { */ 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, _treeDepths.messageTreeDepth, _treeDepths.voteOptionTreeDepth, - messageBatchSize) + messageBatchSize, + Mode.QV) ) { revert ProcessVkNotSet(); } @@ -143,7 +147,8 @@ contract MACIFactory is Ownable, Params, SnarkCommon, DomainObjs, MACICommon { if (!vkRegistry.hasTallyVk( _stateTreeDepth, _treeDepths.intStateTreeDepth, - _treeDepths.voteOptionTreeDepth) + _treeDepths.voteOptionTreeDepth, + Mode.QV) ) { revert TallyVkNotSet(); } @@ -154,6 +159,7 @@ contract MACIFactory is Ownable, Params, SnarkCommon, DomainObjs, MACICommon { emit MaciParametersChanged(); } + /** * @dev Deploy new MACI instance. */ @@ -169,13 +175,12 @@ contract MACIFactory is Ownable, Params, SnarkCommon, DomainObjs, MACICommon { external returns (MACI _maci, MACI.PollContracts memory _pollContracts) { - uint256 messageBatchSize = getMessageBatchSize(treeDepths.messageTreeSubDepth); - if (!vkRegistry.hasProcessVk( stateTreeDepth, treeDepths.messageTreeDepth, treeDepths.voteOptionTreeDepth, - messageBatchSize) + messageBatchSize, + Mode.QV) ) { revert ProcessVkNotSet(); } @@ -183,7 +188,8 @@ contract MACIFactory is Ownable, Params, SnarkCommon, DomainObjs, MACICommon { if (!vkRegistry.hasTallyVk( stateTreeDepth, treeDepths.intStateTreeDepth, - treeDepths.voteOptionTreeDepth) + treeDepths.voteOptionTreeDepth, + Mode.QV) ) { revert TallyVkNotSet(); } @@ -191,8 +197,7 @@ contract MACIFactory is Ownable, Params, SnarkCommon, DomainObjs, MACICommon { _maci = new MACI( IPollFactory(factories.pollFactory), IMessageProcessorFactory(factories.messageProcessorFactory), - ITallySubsidyFactory(factories.tallyFactory), - ITallySubsidyFactory(factories.subsidyFactory), + ITallyFactory(factories.tallyFactory), signUpGatekeeper, initialVoiceCreditProxy, TopupCredit(topupCredit), @@ -205,8 +210,7 @@ contract MACIFactory is Ownable, Params, SnarkCommon, DomainObjs, MACICommon { coordinatorPubKey, address(verifier), address(vkRegistry), - // pass false to not deploy the subsidy contract - false + Mode.QV ); // transfer ownership to coordinator to run the tally scripts diff --git a/contracts/contracts/OwnableUpgradeable.sol b/contracts/contracts/OwnableUpgradeable.sol index 3826e125a..152178fe4 100644 --- a/contracts/contracts/OwnableUpgradeable.sol +++ b/contracts/contracts/OwnableUpgradeable.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; +pragma solidity 0.8.20; // NOTE: had to copy contracts over since OZ uses a higher pragma than we do in the one's they maintain. diff --git a/contracts/contracts/TopupToken.sol b/contracts/contracts/TopupToken.sol index c70068ae7..8cafce9e1 100644 --- a/contracts/contracts/TopupToken.sol +++ b/contracts/contracts/TopupToken.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.10; +pragma solidity 0.8.20; import {ERC20} from '@openzeppelin/contracts/token/ERC20/ERC20.sol'; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; @@ -9,7 +9,7 @@ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; * TopupToken is used by MACI Poll contract to validate the topup credits of a user * In clrfund, this is only used as gateway to pass the topup amount to the Poll contract */ -contract TopupToken is ERC20, Ownable { +contract TopupToken is ERC20, Ownable(msg.sender) { constructor() ERC20("TopupCredit", "TopupCredit") {} function airdrop(uint256 amount) public onlyOwner { diff --git a/contracts/contracts/interfaces/IClrFund.sol b/contracts/contracts/interfaces/IClrFund.sol index 5da1b8b45..2f77f992d 100644 --- a/contracts/contracts/interfaces/IClrFund.sol +++ b/contracts/contracts/interfaces/IClrFund.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity 0.8.10; +pragma solidity 0.8.20; import {ERC20} from '@openzeppelin/contracts/token/ERC20/ERC20.sol'; import {IUserRegistry} from '../userRegistry/IUserRegistry.sol'; diff --git a/contracts/contracts/interfaces/IFundingRound.sol b/contracts/contracts/interfaces/IFundingRound.sol index 026f40c0a..65daf3ea6 100644 --- a/contracts/contracts/interfaces/IFundingRound.sol +++ b/contracts/contracts/interfaces/IFundingRound.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.10; +pragma solidity 0.8.20; import {ERC20} from '@openzeppelin/contracts/token/ERC20/ERC20.sol'; diff --git a/contracts/contracts/interfaces/IFundingRoundFactory.sol b/contracts/contracts/interfaces/IFundingRoundFactory.sol index 9ca5806f1..45ec956fa 100644 --- a/contracts/contracts/interfaces/IFundingRoundFactory.sol +++ b/contracts/contracts/interfaces/IFundingRoundFactory.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.10; +pragma solidity 0.8.20; import {ERC20} from '@openzeppelin/contracts/token/ERC20/ERC20.sol'; diff --git a/contracts/contracts/interfaces/IMACIFactory.sol b/contracts/contracts/interfaces/IMACIFactory.sol index d5d0bf3b2..7e207afe8 100644 --- a/contracts/contracts/interfaces/IMACIFactory.sol +++ b/contracts/contracts/interfaces/IMACIFactory.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.10; +pragma solidity 0.8.20; import {IVkRegistry} from 'maci-contracts/contracts/interfaces/IVkRegistry.sol'; import {IVerifier} from 'maci-contracts/contracts/interfaces/IVerifier.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/BaseRecipientRegistry.sol b/contracts/contracts/recipientRegistry/BaseRecipientRegistry.sol index 8215fce92..d8c656d21 100644 --- a/contracts/contracts/recipientRegistry/BaseRecipientRegistry.sol +++ b/contracts/contracts/recipientRegistry/BaseRecipientRegistry.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.10; +pragma solidity 0.8.20; import './IRecipientRegistry.sol'; diff --git a/contracts/contracts/recipientRegistry/IKlerosGTCR.sol b/contracts/contracts/recipientRegistry/IKlerosGTCR.sol index 6eda6f7cb..2326e70ba 100644 --- a/contracts/contracts/recipientRegistry/IKlerosGTCR.sol +++ b/contracts/contracts/recipientRegistry/IKlerosGTCR.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.10; +pragma solidity 0.8.20; /** * @dev Interface for Kleros Generalized TCR. diff --git a/contracts/contracts/recipientRegistry/IRecipientRegistry.sol b/contracts/contracts/recipientRegistry/IRecipientRegistry.sol index 3f139948e..edfbc5029 100644 --- a/contracts/contracts/recipientRegistry/IRecipientRegistry.sol +++ b/contracts/contracts/recipientRegistry/IRecipientRegistry.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.10; +pragma solidity 0.8.20; /** * @dev Interface of the recipient registry. @@ -17,6 +17,7 @@ pragma solidity ^0.8.10; */ 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/contracts/recipientRegistry/KlerosGTCRAdapter.sol b/contracts/contracts/recipientRegistry/KlerosGTCRAdapter.sol index 2bf70bd6d..621e6dd45 100644 --- a/contracts/contracts/recipientRegistry/KlerosGTCRAdapter.sol +++ b/contracts/contracts/recipientRegistry/KlerosGTCRAdapter.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.10; +pragma solidity 0.8.20; import 'solidity-rlp/contracts/RLPReader.sol'; diff --git a/contracts/contracts/recipientRegistry/KlerosGTCRMock.sol b/contracts/contracts/recipientRegistry/KlerosGTCRMock.sol index abbc9bd16..e1988cd4e 100644 --- a/contracts/contracts/recipientRegistry/KlerosGTCRMock.sol +++ b/contracts/contracts/recipientRegistry/KlerosGTCRMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; +pragma solidity 0.8.20; import '@openzeppelin/contracts/access/Ownable.sol'; @@ -9,7 +9,7 @@ import '@openzeppelin/contracts/access/Ownable.sol'; * This contract is a curated registry for any types of items. Just like a TCR contract it features the request-challenge protocol and appeal fees crowdfunding. * Adapted from https://github.com/kleros/tcr/blob/v2.0.0/contracts/GeneralizedTCR.sol */ -contract KlerosGTCRMock is Ownable { +contract KlerosGTCRMock is Ownable(msg.sender) { enum Status { Absent, // The item is not in the registry. diff --git a/contracts/contracts/recipientRegistry/OptimisticRecipientRegistry.sol b/contracts/contracts/recipientRegistry/OptimisticRecipientRegistry.sol index 8bc331dbb..57ac016f5 100644 --- a/contracts/contracts/recipientRegistry/OptimisticRecipientRegistry.sol +++ b/contracts/contracts/recipientRegistry/OptimisticRecipientRegistry.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.10; +pragma solidity 0.8.20; import '@openzeppelin/contracts/access/Ownable.sol'; @@ -9,7 +9,7 @@ import './BaseRecipientRegistry.sol'; /** * @dev Recipient registry with optimistic execution of registrations and removals. */ -contract OptimisticRecipientRegistry is Ownable, BaseRecipientRegistry { +contract OptimisticRecipientRegistry is Ownable(msg.sender), BaseRecipientRegistry { // Enums enum RequestType { diff --git a/contracts/contracts/recipientRegistry/PermissionedRecipientRegistry.sol b/contracts/contracts/recipientRegistry/PermissionedRecipientRegistry.sol index 3833f3f77..fe8e16829 100644 --- a/contracts/contracts/recipientRegistry/PermissionedRecipientRegistry.sol +++ b/contracts/contracts/recipientRegistry/PermissionedRecipientRegistry.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.10; +pragma solidity 0.8.20; import '@openzeppelin/contracts/access/Ownable.sol'; @@ -9,7 +9,7 @@ import './BaseRecipientRegistry.sol'; /** * @dev Recipient registry with permissioned execution of registrations and removals. */ -contract PermissionedRecipientRegistry is Ownable, BaseRecipientRegistry { +contract PermissionedRecipientRegistry is Ownable(msg.sender), BaseRecipientRegistry { // Enums enum RequestType { diff --git a/contracts/contracts/recipientRegistry/SimpleRecipientRegistry.sol b/contracts/contracts/recipientRegistry/SimpleRecipientRegistry.sol index bd273cea1..11c2b2014 100644 --- a/contracts/contracts/recipientRegistry/SimpleRecipientRegistry.sol +++ b/contracts/contracts/recipientRegistry/SimpleRecipientRegistry.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.10; +pragma solidity 0.8.20; import '@openzeppelin/contracts/access/Ownable.sol'; @@ -9,7 +9,7 @@ import './BaseRecipientRegistry.sol'; /** * @dev A simple recipient registry managed by a trusted entity. */ -contract SimpleRecipientRegistry is Ownable, BaseRecipientRegistry { +contract SimpleRecipientRegistry is Ownable(msg.sender), BaseRecipientRegistry { // Events event RecipientAdded( diff --git a/contracts/contracts/userRegistry/BrightIdSponsor.sol b/contracts/contracts/userRegistry/BrightIdSponsor.sol index 302a80b13..d27487d2a 100644 --- a/contracts/contracts/userRegistry/BrightIdSponsor.sol +++ b/contracts/contracts/userRegistry/BrightIdSponsor.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.10; +pragma solidity 0.8.20; contract BrightIdSponsor { event Sponsor(address indexed addr); diff --git a/contracts/contracts/userRegistry/BrightIdUserRegistry.sol b/contracts/contracts/userRegistry/BrightIdUserRegistry.sol index 09557af3a..49033f0bc 100644 --- a/contracts/contracts/userRegistry/BrightIdUserRegistry.sol +++ b/contracts/contracts/userRegistry/BrightIdUserRegistry.sol @@ -1,12 +1,12 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.10; +pragma solidity 0.8.20; import './IUserRegistry.sol'; import './BrightIdSponsor.sol'; import '@openzeppelin/contracts/access/Ownable.sol'; -contract BrightIdUserRegistry is Ownable, IUserRegistry { +contract BrightIdUserRegistry is Ownable(msg.sender), IUserRegistry { string private constant ERROR_NEWER_VERIFICATION = 'NEWER VERIFICATION REGISTERED BEFORE'; string private constant ERROR_NOT_AUTHORIZED = 'NOT AUTHORIZED'; string private constant ERROR_INVALID_VERIFIER = 'INVALID VERIFIER'; diff --git a/contracts/contracts/userRegistry/IUserRegistry.sol b/contracts/contracts/userRegistry/IUserRegistry.sol index cce90ae42..2bd12bddf 100644 --- a/contracts/contracts/userRegistry/IUserRegistry.sol +++ b/contracts/contracts/userRegistry/IUserRegistry.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.10; +pragma solidity 0.8.20; /** * @dev Interface of the registry of verified users. diff --git a/contracts/contracts/userRegistry/MerkleUserRegistry.sol b/contracts/contracts/userRegistry/MerkleUserRegistry.sol index e6ff85c0f..0c6c39303 100644 --- a/contracts/contracts/userRegistry/MerkleUserRegistry.sol +++ b/contracts/contracts/userRegistry/MerkleUserRegistry.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.10; +pragma solidity 0.8.20; import '@openzeppelin/contracts/access/Ownable.sol'; @@ -13,7 +13,7 @@ import {MerkleProof} from '../utils/cryptography/MerkleProof.sol'; * a successful verification against the merkle root set by * the funding round coordinator. */ -contract MerkleUserRegistry is Ownable, IUserRegistry { +contract MerkleUserRegistry is Ownable(msg.sender), IUserRegistry { // verified users grouped by merkleRoot // merkleRoot -> user -> status diff --git a/contracts/contracts/userRegistry/SemaphoreUserRegistry.sol b/contracts/contracts/userRegistry/SemaphoreUserRegistry.sol index a141e422e..9be159b9b 100644 --- a/contracts/contracts/userRegistry/SemaphoreUserRegistry.sol +++ b/contracts/contracts/userRegistry/SemaphoreUserRegistry.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.10; +pragma solidity 0.8.20; import '@openzeppelin/contracts/access/Ownable.sol'; @@ -9,7 +9,7 @@ import './IUserRegistry.sol'; /** * @dev A simple semaphore user registry managed by a trusted entity. */ -contract SemaphoreUserRegistry is Ownable, IUserRegistry { +contract SemaphoreUserRegistry is Ownable(msg.sender), IUserRegistry { mapping(address => bool) private users; mapping(uint256 => bool) private semaphoreIds; diff --git a/contracts/contracts/userRegistry/SimpleUserRegistry.sol b/contracts/contracts/userRegistry/SimpleUserRegistry.sol index 4f4a7ff00..21fcaa38a 100644 --- a/contracts/contracts/userRegistry/SimpleUserRegistry.sol +++ b/contracts/contracts/userRegistry/SimpleUserRegistry.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.10; +pragma solidity 0.8.20; import '@openzeppelin/contracts/access/Ownable.sol'; @@ -9,7 +9,7 @@ import './IUserRegistry.sol'; /** * @dev A simple user registry managed by a trusted entity. */ -contract SimpleUserRegistry is Ownable, IUserRegistry { +contract SimpleUserRegistry is Ownable(msg.sender), IUserRegistry { mapping(address => bool) private users; diff --git a/contracts/contracts/userRegistry/SnapshotUserRegistry.sol b/contracts/contracts/userRegistry/SnapshotUserRegistry.sol index 754bf869c..3d7ecdd18 100644 --- a/contracts/contracts/userRegistry/SnapshotUserRegistry.sol +++ b/contracts/contracts/userRegistry/SnapshotUserRegistry.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.10; +pragma solidity 0.8.20; import '@openzeppelin/contracts/access/Ownable.sol'; @@ -14,7 +14,7 @@ import {StateProofVerifier} from '../utils/cryptography/StateProofVerifier.sol'; * @dev A user registry that verifies users based on ownership of a token * at a specific block snapshot */ -contract SnapshotUserRegistry is Ownable, IUserRegistry { +contract SnapshotUserRegistry is Ownable(msg.sender), IUserRegistry { using RLPReader for RLPReader.RLPItem; using RLPReader for bytes; diff --git a/contracts/contracts/utils/cryptography/MerklePatriciaProofVerifier.sol b/contracts/contracts/utils/cryptography/MerklePatriciaProofVerifier.sol index b0a6df1d3..a24404e9e 100644 --- a/contracts/contracts/utils/cryptography/MerklePatriciaProofVerifier.sol +++ b/contracts/contracts/utils/cryptography/MerklePatriciaProofVerifier.sol @@ -4,7 +4,7 @@ * Modified from https://github.com/lidofinance/curve-merkle-oracle/blob/main/contracts/MerklePatriciaProofVerifier.sol * git commit hash 1033b3e84142317ffd8f366b52e489d5eb49c73f */ -pragma solidity ^0.8.10; +pragma solidity 0.8.20; import {RLPReader} from 'solidity-rlp/contracts/RLPReader.sol'; diff --git a/contracts/contracts/utils/cryptography/MerkleProof.sol b/contracts/contracts/utils/cryptography/MerkleProof.sol index 08c4a87ed..dd2c0aa99 100644 --- a/contracts/contracts/utils/cryptography/MerkleProof.sol +++ b/contracts/contracts/utils/cryptography/MerkleProof.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // Modified from OpenZeppelin Contracts (last updated v4.9.2) (utils/cryptography/MerkleProof.sol) -pragma solidity ^0.8.10; +pragma solidity 0.8.20; /** * @dev These functions deal with verification of Merkle Tree proofs. diff --git a/contracts/contracts/utils/cryptography/StateProofVerifier.sol b/contracts/contracts/utils/cryptography/StateProofVerifier.sol index 407ccd0bc..9425345b1 100644 --- a/contracts/contracts/utils/cryptography/StateProofVerifier.sol +++ b/contracts/contracts/utils/cryptography/StateProofVerifier.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; +pragma solidity 0.8.20; import {RLPReader} from 'solidity-rlp/contracts/RLPReader.sol'; import {MerklePatriciaProofVerifier} from './MerklePatriciaProofVerifier.sol'; diff --git a/contracts/deploy-config-example.json b/contracts/deploy-config-example.json index 77032cfed..4fbcc8441 100644 --- a/contracts/deploy-config-example.json +++ b/contracts/deploy-config-example.json @@ -16,11 +16,6 @@ "OptimisticRecipientRegistry": { "challengePeriodSeconds": 9007199254740990, "deposit": "0.001" - }, - "BrightIdUserRegistry": { - "deploy": false, - "context": "clrfund-arbitrum-goerli", - "verifier": "0xdbf0b2ee9887fe11934789644096028ed3febe9c" } }, "arbitrum-sepolia": { @@ -43,8 +38,7 @@ }, "BrightIdUserRegistry": { "context": "clrfund-arbitrum-goerli", - "verifier": "0xdbf0b2ee9887fe11934789644096028ed3febe9c", - "sponsor": "0xC7c81634Dac2de4E7f2Ba407B638ff003ce4534C" + "verifier": "0xdbf0b2ee9887fe11934789644096028ed3febe9c" } } } diff --git a/contracts/e2e/index.ts b/contracts/e2e/index.ts index 76e21525c..52d949abe 100644 --- a/contracts/e2e/index.ts +++ b/contracts/e2e/index.ts @@ -16,7 +16,7 @@ import { DEFAULT_GET_LOG_BATCH_SIZE, DEFAULT_SR_QUEUE_OPS, } from '../utils/constants' -import { getEventArg } from '../utils/contracts' +import { getContractAt, getEventArg } from '../utils/contracts' import { deployPoseidonLibraries, deployMaciFactory } from '../utils/testutils' import { getIpfsHash } from '../utils/ipfs' import { @@ -270,7 +270,11 @@ describe('End-to-end Tests', function () { pollId = await fundingRound.pollId() const pollAddress = await fundingRound.poll() - pollContract = await ethers.getContractAt(EContracts.Poll, pollAddress) + pollContract = await getContractAt( + EContracts.Poll, + pollAddress, + ethers + ) await mine() }) @@ -390,7 +394,6 @@ describe('End-to-end Tests', function () { await proveOnChain({ pollId, proofDir: genProofArgs.outputDir, - subsidyEnabled: false, maciAddress, messageProcessorAddress, tallyAddress, @@ -410,7 +413,6 @@ describe('End-to-end Tests', function () { await proveOnChain({ pollId, proofDir: genProofArgs.outputDir, - subsidyEnabled: false, maciAddress, messageProcessorAddress, tallyAddress, diff --git a/contracts/hardhat.config.ts b/contracts/hardhat.config.ts index e9fcb6931..7837be09c 100644 --- a/contracts/hardhat.config.ts +++ b/contracts/hardhat.config.ts @@ -39,7 +39,7 @@ export default { url: 'http://127.0.0.1:8555', gasLimit: GAS_LIMIT, } as any, - goerli: { + sepolia: { url: process.env.JSONRPC_HTTP_URL || 'http://127.0.0.1:8545', accounts, }, @@ -67,10 +67,6 @@ export default { url: process.env.JSONRPC_HTTP_URL || 'https://sepolia.optimism.io', accounts, }, - sepolia: { - url: process.env.JSONRPC_HTTP_URL || 'http://127.0.0.1:8545', - accounts, - }, 'mantle-testnet': { url: process.env.JSONRPC_HTTP_URL || 'https://rpc.testnet.mantle.xyz', accounts, @@ -89,6 +85,7 @@ export default { process.env.OPTIMISMSCAN_API_KEY || 'YOUR_OPTIMISMSCAN_API_KEY', 'optimism-sepolia': process.env.OPTIMISMSCAN_API_KEY || 'YOUR_OPTIMISMSCAN_API_KEY', + sepolia: process.env.ETHERSCAN_API_KEY || 'YOUR_ETHERSCAN_API_KEY', }, customChains: [ { @@ -122,7 +119,7 @@ export default { disambiguatePaths: false, }, solidity: { - version: '0.8.10', + version: '0.8.20', settings: { optimizer: { enabled: true, @@ -131,7 +128,6 @@ export default { }, overrides: { 'contracts/FundingRoundFactory.sol': { - version: '0.8.10', settings: { optimizer: { enabled: true, @@ -140,7 +136,6 @@ export default { }, }, 'contracts/FundingRound.sol': { - version: '0.8.10', settings: { optimizer: { enabled: true, @@ -149,7 +144,6 @@ export default { }, }, 'contracts/recipientRegistry/OptimisticRecipientRegistry.sol': { - version: '0.8.10', settings: { optimizer: { enabled: true, @@ -158,7 +152,6 @@ export default { }, }, 'contracts/userRegistry/SimpleUserRegistry.sol': { - version: '0.8.10', settings: { optimizer: { enabled: true, @@ -167,7 +160,6 @@ export default { }, }, 'contracts/userRegistry/BrightIdUserRegistry.sol': { - version: '0.8.10', settings: { optimizer: { enabled: true, diff --git a/contracts/package.json b/contracts/package.json index 94bb91a88..126d0a1ec 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -1,6 +1,6 @@ { "name": "@clrfund/contracts", - "version": "5.1.1", + "version": "6.0.0", "license": "GPL-3.0", "scripts": { "hardhat": "hardhat", @@ -15,18 +15,18 @@ "clean": "rm -rf cache && rm -rf build" }, "dependencies": { - "@openzeppelin/contracts": "4.9.0", + "@openzeppelin/contracts": "5.0.2", "@pinata/sdk": "^2.1.0", "dotenv": "^8.2.0", - "maci-contracts": "^1.2.0", + "maci-contracts": "1.2.2", "solidity-rlp": "2.0.8" }, "devDependencies": { "@clrfund/common": "^0.0.1", - "@clrfund/waffle-mock-contract": "^0.0.4", + "@clrfund/waffle-mock-contract": "^0.0.11", "@kleros/gtcr-encoder": "^1.4.0", - "@nomicfoundation/hardhat-chai-matchers": "^2.0.3", - "@nomicfoundation/hardhat-ethers": "^3.0.5", + "@nomicfoundation/hardhat-chai-matchers": "^2.0.6", + "@nomicfoundation/hardhat-ethers": "^3.0.6", "@nomicfoundation/hardhat-network-helpers": "^1.0.10", "@nomicfoundation/hardhat-toolbox": "^4.0.0", "@nomicfoundation/hardhat-verify": "^2.0.3", @@ -34,14 +34,15 @@ "@typechain/ethers-v6": "^0.5.1", "@typechain/hardhat": "^9.1.0", "@types/mocha": "^10.0.6", - "ethers": "^6.11.1", - "hardhat": "^2.19.4", + "chai": "4", + "ethers": "^6.12.1", + "hardhat": "^2.22.3", "hardhat-contract-sizer": "^2.10.0", "hardhat-gas-reporter": "^1.0.8", "ipfs-only-hash": "^2.0.1", - "maci-circuits": "^1.2.0", - "maci-cli": "^1.2.0", - "maci-domainobjs": "^1.2.0", + "maci-circuits": "1.2.2", + "maci-cli": "1.2.2", + "maci-domainobjs": "1.2.2", "mocha": "^10.2.0", "solidity-coverage": "^0.8.1", "ts-node": "^10.9.2", diff --git a/contracts/sh/deployLocal.sh b/contracts/sh/deployLocal.sh index d0c1aac73..0c48ffe01 100755 --- a/contracts/sh/deployLocal.sh +++ b/contracts/sh/deployLocal.sh @@ -26,4 +26,4 @@ export COORDINATOR_MACISK=$(echo "${MACI_KEYPAIR}" | grep -o "macisk.*$") yarn hardhat new-clrfund --network ${NETWORK} # deploy a new funding round -yarn hardhat new-round --network ${NETWORK} +yarn hardhat new-round --network ${NETWORK} --round-duration 3600 diff --git a/contracts/tasks/helpers/ConstructorArguments.ts b/contracts/tasks/helpers/ConstructorArguments.ts index 6f9f3d5b4..9997e9683 100644 --- a/contracts/tasks/helpers/ConstructorArguments.ts +++ b/contracts/tasks/helpers/ConstructorArguments.ts @@ -12,6 +12,7 @@ import { Poll, Tally, } from '../../typechain-types' +import { getContractAt, getQualifiedContractName } from '../../utils/contracts' /** A list of functions to get contract constructor arguments from the contract */ const ConstructorArgumentsGetters: Record< @@ -69,7 +70,6 @@ async function getMaciConstructorArguments( maci.pollFactory(), maci.messageProcessorFactory(), maci.tallyFactory(), - maci.subsidyFactory(), maci.signUpGatekeeper(), maci.initialVoiceCreditProxy(), maci.topupCredit(), @@ -89,10 +89,11 @@ async function getPollConstructorArguments( address: string, ethers: HardhatEthersHelpers ): Promise> { - const pollContract = (await ethers.getContractAt( + const pollContract = await getContractAt( EContracts.Poll, - address - )) as BaseContract as Poll + address, + ethers + ) const [, duration] = await pollContract.getDeployTimeAndDuration() const [maxValues, treeDepths, coordinatorPubKey, extContracts] = @@ -149,6 +150,7 @@ async function getTallyConstructorArguments( tallyContract.vkRegistry(), tallyContract.poll(), tallyContract.messageProcessor(), + tallyContract.mode(), ]) return args @@ -173,6 +175,7 @@ async function getMessageProcessorConstructorArguments( messageProcesor.verifier(), messageProcesor.vkRegistry(), messageProcesor.poll(), + messageProcesor.mode(), ]) return args @@ -310,7 +313,8 @@ export class ConstructorArguments { address: string, ethers: HardhatEthersHelpers ): Promise> { - const contractArtifact = this.hre.artifacts.readArtifactSync(name) + const qualifiedName = getQualifiedContractName(name) + const contractArtifact = this.hre.artifacts.readArtifactSync(qualifiedName) const contractInterface = new Interface(contractArtifact.abi) if (contractInterface.deploy.inputs.length === 0) { // no argument diff --git a/contracts/tasks/helpers/Subtask.ts b/contracts/tasks/helpers/Subtask.ts index 089cf06d6..06eed8015 100644 --- a/contracts/tasks/helpers/Subtask.ts +++ b/contracts/tasks/helpers/Subtask.ts @@ -18,7 +18,7 @@ import type { HardhatRuntimeEnvironment, } from 'hardhat/types' -import { deployContract } from '../../utils/contracts' +import { deployContract, getQualifiedContractName } from '../../utils/contracts' import { EContracts } from '../../utils/types' import { ContractStorage } from './ContractStorage' import { @@ -527,7 +527,8 @@ export class Subtask { const contractAddress = address || this.storage.mustGetAddress(name, this.hre.network.name) - const { abi } = await this.hre.artifacts.readArtifact(name.toString()) + const qualifiedName = getQualifiedContractName(name) + const { abi } = await this.hre.artifacts.readArtifact(qualifiedName) return new BaseContract(contractAddress, abi, deployer) as T } diff --git a/contracts/tasks/index.ts b/contracts/tasks/index.ts index 59f1c9699..98c143c27 100644 --- a/contracts/tasks/index.ts +++ b/contracts/tasks/index.ts @@ -15,6 +15,7 @@ import './runners/finalize' import './runners/claim' import './runners/cancel' import './runners/exportRound' +import './runners/exportImages' import './runners/mergeAllocation' import './runners/loadSimpleUsers' import './runners/loadMerkleUsers' @@ -28,3 +29,5 @@ import './runners/genProofs' import './runners/proveOnChain' import './runners/publishTallyResults' import './runners/resetTally' +import './runners/maciPubkey' +import './runners/simulateContribute' diff --git a/contracts/tasks/runners/claim.ts b/contracts/tasks/runners/claim.ts index e5135aa24..0f27a2e95 100644 --- a/contracts/tasks/runners/claim.ts +++ b/contracts/tasks/runners/claim.ts @@ -5,7 +5,7 @@ * yarn hardhat claim --recipient --network */ -import { getEventArg } from '../../utils/contracts' +import { getContractAt, getEventArg } from '../../utils/contracts' import { getRecipientClaimData } from '@clrfund/common' import { JSONFile } from '../../utils/JSONFile' import { @@ -17,6 +17,7 @@ import { getNumber } from 'ethers' import { task, types } from 'hardhat/config' import { EContracts } from '../../utils/types' import { ContractStorage } from '../helpers/ContractStorage' +import { Poll } from 'maci-contracts/build/typechain-types' task('claim', 'Claim funnds for test recipients') .addOptionalParam('roundAddress', 'Funding round contract address') @@ -64,7 +65,11 @@ task('claim', 'Claim funnds for test recipients') const pollAddress = await fundingRoundContract.poll() console.log('pollAddress', pollAddress) - const poll = await ethers.getContractAt(EContracts.Poll, pollAddress) + const poll = await getContractAt( + EContracts.Poll, + pollAddress, + ethers + ) const treeDepths = await poll.treeDepths() const recipientTreeDepth = getNumber(treeDepths.voteOptionTreeDepth) diff --git a/contracts/tasks/runners/contribute.ts b/contracts/tasks/runners/contribute.ts index 6060c17a1..41cb7d610 100644 --- a/contracts/tasks/runners/contribute.ts +++ b/contracts/tasks/runners/contribute.ts @@ -11,7 +11,7 @@ import { Keypair, createMessage, Message, PubKey } from '@clrfund/common' import { UNIT } from '../../utils/constants' -import { getEventArg } from '../../utils/contracts' +import { getContractAt, getEventArg } from '../../utils/contracts' import type { FundingRound, ERC20, Poll } from '../../typechain-types' import { task } from 'hardhat/config' import { EContracts } from '../../utils/types' @@ -98,9 +98,10 @@ task('contribute', 'Contribute to a funding round').setAction( const pollId = await fundingRound.pollId() const pollAddress = await fundingRound.poll() - const pollContract = await ethers.getContractAt( + const pollContract = await getContractAt( EContracts.Poll, - pollAddress + pollAddress, + ethers ) const rawCoordinatorPubKey = await pollContract.coordinatorPubKey() diff --git a/contracts/tasks/runners/exportImages.ts b/contracts/tasks/runners/exportImages.ts new file mode 100644 index 000000000..46a782362 --- /dev/null +++ b/contracts/tasks/runners/exportImages.ts @@ -0,0 +1,74 @@ +/** + * Export the project logo images in a ClrFund round. + * + * Sample usage: + * yarn hardhat export-images \ + * --output-dir ../vue-apps/public/ipfs + * --gateway https://ipfs.io + * --round-file ../vue-app/src/rounds/arbitrum/0x4A2d90844EB9C815eF10dB0371726F0ceb2848B0.json + * + * Notes: + * 1) This script assumes the round has been exported using the `export-round` hardhat task + */ + +import { task } from 'hardhat/config' +import { isPathExist, makeDirectory } from '../../utils/misc' +import { getIpfsContent } from '@clrfund/common' +import fs from 'fs' + +/** + * Download the IPFS file with the ipfsHash to the output directory + * @param gateway IPFS gateway url + * @param ipfsHash IPFS hash of the file to download + * @param outputDir The directory to store the downloaded file + */ +async function download({ + gateway, + ipfsHash, + outputDir, +}: { + gateway: string + ipfsHash: string + outputDir: string +}) { + if (!ipfsHash) return + + const res = await getIpfsContent(ipfsHash, gateway) + if (res.hasBody()) { + console.log('Downloaded', ipfsHash) + fs.writeFileSync(`${outputDir}/${ipfsHash}`, res.body) + } +} + +task('export-images', 'Export project logo images') + .addParam('outputDir', 'The output directory') + .addParam('roundFile', 'The exported funding round file path') + .addParam('gateway', 'The IPFS gateway url') + .setAction(async ({ outputDir, roundFile, gateway }) => { + console.log('Starting to download from ipfs') + + if (!isPathExist(outputDir)) { + makeDirectory(outputDir) + } + + const data = fs.readFileSync(roundFile, { encoding: 'utf-8' }) + const round = JSON.parse(data) + const projects = round.projects + const images = projects.map((project: any) => { + const { bannerImageHash, thumbnailImageHash } = project.metadata + return { bannerImageHash, thumbnailImageHash } + }) + + for (let i = 0; i < images.length; i++) { + await download({ + gateway, + ipfsHash: images[i].bannerImageHash, + outputDir, + }) + await download({ + gateway, + ipfsHash: images[i].thumbnailImageHash, + outputDir, + }) + } + }) diff --git a/contracts/tasks/runners/exportRound.ts b/contracts/tasks/runners/exportRound.ts index fa3e332b1..42c3638f2 100644 --- a/contracts/tasks/runners/exportRound.ts +++ b/contracts/tasks/runners/exportRound.ts @@ -14,12 +14,14 @@ import { task, types } from 'hardhat/config' import { Contract, formatUnits, getNumber } from 'ethers' import { Ipfs } from '../../utils/ipfs' -import { Project, Round, RoundFileContent } from '../../utils/types' +import { EContracts, Project, Round, RoundFileContent } from '../../utils/types' import { RecipientRegistryLogProcessor } from '../../utils/RecipientRegistryLogProcessor' import { getRecipientAddressAbi, MaciV0Abi } from '../../utils/abi' import { JSONFile } from '../../utils/JSONFile' import path from 'path' import fs from 'fs' +import { getContractAt } from '../../utils/contracts' +import { Poll } from '../../typechain-types' type RoundListEntry = { network: string @@ -216,12 +218,15 @@ async function getRoundInfo( try { if (pollAddress) { - const pollContract = await ethers.getContractAt('Poll', pollAddress) + const pollContract = await getContractAt( + EContracts.Poll, + pollAddress, + ethers + ) const [roundStartTime, roundDuration] = await pollContract.getDeployTimeAndDuration() startTime = getNumber(roundStartTime) signUpDuration = roundDuration - votingDuration = roundDuration endTime = startTime + getNumber(roundDuration) pollId = await roundContract.pollId() diff --git a/contracts/tasks/runners/finalize.ts b/contracts/tasks/runners/finalize.ts index e48a6c2b6..74afe85e1 100644 --- a/contracts/tasks/runners/finalize.ts +++ b/contracts/tasks/runners/finalize.ts @@ -17,6 +17,8 @@ import { EContracts } from '../../utils/types' import { ContractStorage } from '../helpers/ContractStorage' import { Subtask } from '../helpers/Subtask' import { getProofDirForRound, getTalyFilePath } from '../../utils/misc' +import { getContractAt } from '../../utils/contracts' +import { Poll } from 'maci-contracts/build/typechain-types' task('finalize', 'Finalize a funding round') .addOptionalParam('clrfund', 'The ClrFund contract address') @@ -47,9 +49,10 @@ task('finalize', 'Finalize a funding round') console.log('Current round', fundingRound.target) const pollAddress = await fundingRound.poll() - const pollContract = await ethers.getContractAt( + const pollContract = await getContractAt( EContracts.Poll, - pollAddress + pollAddress, + ethers ) console.log('Poll', pollAddress) diff --git a/contracts/tasks/runners/genProofs.ts b/contracts/tasks/runners/genProofs.ts index ed3061edb..708573980 100644 --- a/contracts/tasks/runners/genProofs.ts +++ b/contracts/tasks/runners/genProofs.ts @@ -187,7 +187,7 @@ task('gen-proofs', 'Generate MACI proofs offchain') quiet, outputPath: maciStateFile, pollId, - maciContractAddress: maciAddress, + maciAddress, coordinatorPrivateKey: coordinatorMacisk, ethereumProvider: providerUrl, transactionHash: maciTxHash, diff --git a/contracts/tasks/runners/maciPubkey.ts b/contracts/tasks/runners/maciPubkey.ts index c69ff002a..dad72f66e 100644 --- a/contracts/tasks/runners/maciPubkey.ts +++ b/contracts/tasks/runners/maciPubkey.ts @@ -4,7 +4,6 @@ * * Usage: hardhat maci-pubkey --macisk */ -import { id } from 'ethers' import { task } from 'hardhat/config' import { PubKey, PrivKey, Keypair } from '@clrfund/common' @@ -26,8 +25,5 @@ task('maci-pubkey', 'Get the serialized MACI public key') } const pubKey = new PubKey([BigInt(x), BigInt(y)]) console.log(`Public Key: ${pubKey.serialize()}`) - - const subgraphId = id(x + '.' + y) - console.log(`Subgraph id: ${subgraphId}`) } }) diff --git a/contracts/tasks/runners/proveOnChain.ts b/contracts/tasks/runners/proveOnChain.ts index af777ed12..40946ba62 100644 --- a/contracts/tasks/runners/proveOnChain.ts +++ b/contracts/tasks/runners/proveOnChain.ts @@ -90,7 +90,6 @@ task('prove-on-chain', 'Prove on chain with the MACI proofs') await proveOnChain({ pollId, proofDir, - subsidyEnabled: false, maciAddress, messageProcessorAddress, tallyAddress, diff --git a/contracts/tasks/runners/publishTallyResults.ts b/contracts/tasks/runners/publishTallyResults.ts index 5651ac353..b9db3404f 100644 --- a/contracts/tasks/runners/publishTallyResults.ts +++ b/contracts/tasks/runners/publishTallyResults.ts @@ -24,10 +24,12 @@ import { FundingRound, Poll } from '../../typechain-types' import { HardhatEthersHelpers } from '@nomicfoundation/hardhat-ethers/types' import { EContracts } from '../../utils/types' import { Subtask } from '../helpers/Subtask' -import { getCurrentFundingRoundContract } from '../../utils/contracts' +import { + getContractAt, + getCurrentFundingRoundContract, +} from '../../utils/contracts' import { getTalyFilePath } from '../../utils/misc' import { ContractStorage } from '../helpers/ContractStorage' -import { PINATA_PINNING_URL } from '../../utils/constants' /** * Publish the tally IPFS hash on chain if it's not already published @@ -92,7 +94,11 @@ async function getRecipientTreeDepth( ethers: HardhatEthersHelpers ): Promise { const pollAddress = await fundingRoundContract.poll() - const pollContract = await ethers.getContractAt(EContracts.Poll, pollAddress) + const pollContract = await getContractAt( + EContracts.Poll, + pollAddress, + ethers + ) const treeDepths = await (pollContract as BaseContract as Poll).treeDepths() const voteOptionTreeDepth = treeDepths.voteOptionTreeDepth return getNumber(voteOptionTreeDepth) 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/tasks/runners/verifyAll.ts b/contracts/tasks/runners/verifyAll.ts index c010df8ff..7aa910b72 100644 --- a/contracts/tasks/runners/verifyAll.ts +++ b/contracts/tasks/runners/verifyAll.ts @@ -16,6 +16,7 @@ import { BaseContract } from 'ethers' import { HardhatEthersHelpers } from '@nomicfoundation/hardhat-ethers/types' import { ZERO_ADDRESS } from '../../utils/constants' import { ConstructorArguments } from '../helpers/ConstructorArguments' +import { getContractAt } from '../../utils/contracts' type ContractInfo = { name: string @@ -138,6 +139,8 @@ async function getContractList( clrfund: string, ethers: HardhatEthersHelpers ): Promise { + const userRegistries = new Set() + const recipientRegistries = new Set() const contractList: ContractInfo[] = [ { name: EContracts.ClrFund, @@ -145,10 +148,11 @@ async function getContractList( }, ] - const clrfundContract = (await ethers.getContractAt( + const clrfundContract = await getContractAt( EContracts.ClrFund, - clrfund - )) as BaseContract as ClrFund + clrfund, + ethers + ) const fundingRoundFactoryAddress = await clrfundContract.roundFactory() if (fundingRoundFactoryAddress !== ZERO_ADDRESS) { @@ -192,6 +196,16 @@ async function getContractList( }) } + const userRegistryAddress = await clrfundContract.userRegistry() + if (userRegistryAddress !== ZERO_ADDRESS) { + userRegistries.add(userRegistryAddress) + } + + const recipientRegistryAddress = await clrfundContract.recipientRegistry() + if (recipientRegistryAddress !== ZERO_ADDRESS) { + recipientRegistries.add(recipientRegistryAddress) + } + const fundingRoundAddress = await clrfundContract.getCurrentRound() if (fundingRoundAddress !== ZERO_ADDRESS) { contractList.push({ @@ -255,27 +269,32 @@ async function getContractList( // User Registry const userRegistryAddress = await fundingRound.userRegistry() if (userRegistryAddress !== ZERO_ADDRESS) { - const name = await getUserRegistryName(userRegistryAddress, ethers) - contractList.push({ - name, - address: userRegistryAddress, - }) + userRegistries.add(userRegistryAddress) } // Recipient Registry const recipientRegistryAddress = await fundingRound.recipientRegistry() if (recipientRegistryAddress !== ZERO_ADDRESS) { - const name = await getRecipientRegistryName( - recipientRegistryAddress, - ethers - ) - contractList.push({ - name, - address: recipientRegistryAddress, - }) + recipientRegistries.add(recipientRegistryAddress) } } + for (const address of userRegistries) { + const name = await getUserRegistryName(address, ethers) + contractList.push({ + name, + address, + }) + } + + for (const address of recipientRegistries) { + const name = await getRecipientRegistryName(address, ethers) + contractList.push({ + name, + address, + }) + } + return contractList } @@ -286,7 +305,7 @@ task('verify-all', 'Verify contracts listed in storage') .addOptionalParam('clrfund', 'The ClrFund contract address') .addFlag('force', 'Ignore verified status') .setAction(async ({ clrfund }, hre) => { - const { ethers, config, network } = hre + const { ethers, network } = hre const storage = ContractStorage.getInstance() const clrfundContractAddress = diff --git a/contracts/tasks/subtasks/clrfund/03-setVkRegsitry.ts b/contracts/tasks/subtasks/clrfund/03-setVkRegsitry.ts index a3fefb412..fcbe9b1bb 100644 --- a/contracts/tasks/subtasks/clrfund/03-setVkRegsitry.ts +++ b/contracts/tasks/subtasks/clrfund/03-setVkRegsitry.ts @@ -3,6 +3,7 @@ import { setVerifyingKeys } from '../../../utils/contracts' import { MaciParameters } from '../../../utils/maciParameters' import { Subtask } from '../../helpers/Subtask' import { EContracts, ISubtaskParams } from '../../helpers/types' +import { EMode } from 'maci-contracts' const subtask = Subtask.getInstance() @@ -39,13 +40,15 @@ subtask stateTreeDepth, messageTreeDepth, voteOptionTreeDepth, - messageBatchSize + messageBatchSize, + EMode.QV ) const hasTallyVk = await vkRegistryContract.hasTallyVk( stateTreeDepth, intStateTreeDepth, - voteOptionTreeDepth + voteOptionTreeDepth, + EMode.QV ) if (incremental) { diff --git a/contracts/tasks/subtasks/clrfund/08-maciFactory.ts b/contracts/tasks/subtasks/clrfund/08-maciFactory.ts index cfaf146a6..c21c41817 100644 --- a/contracts/tasks/subtasks/clrfund/08-maciFactory.ts +++ b/contracts/tasks/subtasks/clrfund/08-maciFactory.ts @@ -48,8 +48,6 @@ subtask const factories = { pollFactory: pollFactoryContractAddress, tallyFactory: tallyFactoryContractAddress, - // subsidy is not currently used - subsidyFactory: ZERO_ADDRESS, messageProcessorFactory: messageProcessorFactoryContractAddress, } diff --git a/contracts/tasks/subtasks/coordinator/01-coordinator.ts b/contracts/tasks/subtasks/coordinator/01-coordinator.ts index 3b2f0b767..10685b5a7 100644 --- a/contracts/tasks/subtasks/coordinator/01-coordinator.ts +++ b/contracts/tasks/subtasks/coordinator/01-coordinator.ts @@ -44,19 +44,30 @@ subtask clrfundContract.coordinatorPubKey(), ]) - const currentPubKey = new PubKey([coordinatorPubKey.x, coordinatorPubKey.y]) + // if the coordinator has not been set in the clrfund contract + // use allowInvalid option to prevent it from throwing in new PubKey() + const allowInvalid = true + const currentPubKey = new PubKey( + [coordinatorPubKey.x, coordinatorPubKey.y], + allowInvalid + ) const newPrivKey = PrivKey.deserialize(coordinatorMacisk) const newKeypair = new Keypair(newPrivKey) const normalizedCurrentCoordinator = getAddress(currentCoordinator) const normalizedNewCoordinator = getAddress(coordinatorAddress) - console.log('Current coordinator', normalizedCurrentCoordinator) - console.log(' New coordinator', normalizedNewCoordinator) + console.log('Current coordinator:', normalizedCurrentCoordinator) + console.log(' New coordinator:', normalizedNewCoordinator) - const serializedCurrentPubKey = currentPubKey.serialize() + let serializedCurrentPubKey = 'Not set' + try { + serializedCurrentPubKey = currentPubKey.serialize() + } catch { + // if the public key was not set, serialize will throw. + } const serializedNewPubKey = newKeypair.pubKey.serialize() - console.log('Current MACI key', serializedCurrentPubKey) - console.log(' New MACI key', serializedNewPubKey) + console.log('Current MACI key:', serializedCurrentPubKey) + console.log(' New MACI key:', serializedNewPubKey) console.log() if ( diff --git a/contracts/tasks/subtasks/round/02-deploy-round.ts b/contracts/tasks/subtasks/round/02-deploy-round.ts index 812bf75b7..707216d2c 100644 --- a/contracts/tasks/subtasks/round/02-deploy-round.ts +++ b/contracts/tasks/subtasks/round/02-deploy-round.ts @@ -15,6 +15,7 @@ import { } from '../../../typechain-types' import { ContractTransactionResponse } from 'ethers' import { ISubtaskParams } from '../../helpers/types' +import { EMode } from 'maci-contracts' const subtask = Subtask.getInstance() const storage = ContractStorage.getInstance() @@ -69,7 +70,6 @@ async function registerMaci( maciContract.pollFactory(), maciContract.messageProcessorFactory(), maciContract.tallyFactory(), - maciContract.subsidyFactory(), maciContract.signUpGatekeeper(), maciContract.initialVoiceCreditProxy(), maciContract.topupCredit(), @@ -180,7 +180,7 @@ async function registerTallyAndMessageProcessor( tallyContract.vkRegistry(), ]) - let args = [verifier, vkRegistry, poll, mp] + let args = [verifier, vkRegistry, poll, mp, EMode.QV] await storage.register({ id: EContracts.Tally, contract: tallyContract, @@ -189,7 +189,7 @@ async function registerTallyAndMessageProcessor( tx, }) - args = [verifier, vkRegistry, poll] + args = [verifier, vkRegistry, poll, EMode.QV] await storage.register({ id: EContracts.MessageProcessor, contract: messageProcessorContract, diff --git a/contracts/tasks/subtasks/user/03-brightidSponsor.ts b/contracts/tasks/subtasks/user/03-brightidSponsor.ts deleted file mode 100644 index 9482a449e..000000000 --- a/contracts/tasks/subtasks/user/03-brightidSponsor.ts +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Deploy an instance of the BrightID sponsor contract - * - */ -import { ContractStorage } from '../../helpers/ContractStorage' -import { Subtask } from '../../helpers/Subtask' -import { EContracts } from '../../../utils/types' - -const subtask = Subtask.getInstance() -const storage = ContractStorage.getInstance() - -/** - * Deploy step registration and task itself - */ -subtask - .addTask('user:deploy-brightid-sponsor', 'Deploy BrightID sponsor contract') - .setAction(async (_, hre) => { - subtask.setHre(hre) - const deployer = await subtask.getDeployer() - - const userRegistryName = subtask.getConfigField( - EContracts.ClrFund, - 'userRegistry' - ) - - if (userRegistryName !== EContracts.BrightIdUserRegistry) { - return - } - - let brightidSponsorContractAddress = subtask.tryGetConfigField( - EContracts.BrightIdUserRegistry, - 'sponsor' - ) - - if (brightidSponsorContractAddress) { - console.log( - `Skip BrightIdSponsor deployment, use ${brightidSponsorContractAddress}` - ) - return - } - - brightidSponsorContractAddress = storage.getAddress( - EContracts.BrightIdSponsor, - hre.network.name - ) - - if (brightidSponsorContractAddress) { - return - } - - const BrightIdSponsorContract = await subtask.deployContract( - EContracts.BrightIdSponsor, - { signer: deployer } - ) - - await storage.register({ - id: EContracts.BrightIdSponsor, - contract: BrightIdSponsorContract, - args: [], - network: hre.network.name, - }) - }) diff --git a/contracts/tasks/subtasks/user/04-brightidUserRegistry.ts b/contracts/tasks/subtasks/user/04-brightidUserRegistry.ts index 6db98dc2c..73f1ce259 100644 --- a/contracts/tasks/subtasks/user/04-brightidUserRegistry.ts +++ b/contracts/tasks/subtasks/user/04-brightidUserRegistry.ts @@ -10,6 +10,10 @@ import { EContracts } from '../../../utils/types' const subtask = Subtask.getInstance() const storage = ContractStorage.getInstance() +// @TODO remove this on the next major release when the sponsor contract is removed +// Hardcode with a dummy address for now +const BRIGHTID_SPONSOR = '0xC7c81634Dac2de4E7f2Ba407B638ff003ce4534C' + /** * Deploy step registration and task itself */ @@ -58,15 +62,7 @@ subtask 'verifier' ) - let sponsor = subtask.tryGetConfigField( - EContracts.BrightIdUserRegistry, - 'sponsor' - ) - if (!sponsor) { - sponsor = storage.mustGetAddress(EContracts.BrightIdSponsor, network) - } - - const args = [encodeBytes32String(context), verifier, sponsor] + const args = [encodeBytes32String(context), verifier, BRIGHTID_SPONSOR] const brightidUserRegistryContract = await subtask.deployContract( EContracts.BrightIdUserRegistry, { signer: deployer, args } diff --git a/contracts/tests/deployer.ts b/contracts/tests/deployer.ts index f21353557..3eace1611 100644 --- a/contracts/tests/deployer.ts +++ b/contracts/tests/deployer.ts @@ -1,20 +1,27 @@ -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 { getGasUsage, getEventArg, deployContract } from '../utils/contracts' +import { ZERO_ADDRESS, UNIT } from '../utils/constants' +import { + getGasUsage, + getEventArg, + deployContract, + getContractAt, +} from '../utils/contracts' import { deployPoseidonLibraries, deployMaciFactory } from '../utils/testutils' import { MaciParameters } from '../utils/maciParameters' import { HardhatEthersSigner } from '@nomicfoundation/hardhat-ethers/signers' import { ClrFund, ClrFundDeployer, + FundingRound, FundingRoundFactory, MACIFactory, + Poll, } from '../typechain-types' import { EContracts } from '../utils/types' @@ -193,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) ) }) @@ -318,7 +326,11 @@ describe('Clr fund deployer', async () => { 'pollAddr' ) - const poll = await ethers.getContractAt('Poll', pollAddress.poll) + const poll = await getContractAt( + EContracts.Poll, + pollAddress.poll, + ethers + ) const roundCoordinatorPubKey = await poll.coordinatorPubKey() expect(roundCoordinatorPubKey.x).to.equal(coordinatorPubKey.x) expect(roundCoordinatorPubKey.y).to.equal(coordinatorPubKey.y) @@ -443,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( @@ -451,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( @@ -464,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 () => { @@ -475,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, @@ -483,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 () => { @@ -493,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, @@ -501,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/maciFactory.ts b/contracts/tests/maciFactory.ts index 3f23d58ad..fcaf8d483 100644 --- a/contracts/tests/maciFactory.ts +++ b/contracts/tests/maciFactory.ts @@ -89,7 +89,10 @@ describe('MACI factory', () => { coordinatorMaciFactory.setMaciParameters( ...maciParameters.asContractParam() ) - ).to.be.revertedWith('Ownable: caller is not the owner') + ).to.be.revertedWithCustomError( + coordinatorMaciFactory, + 'OwnableUnauthorizedAccount' + ) }) it('deploys MACI', async () => { diff --git a/contracts/tests/recipientRegistry.ts b/contracts/tests/recipientRegistry.ts index ea5932993..1e0124376 100644 --- a/contracts/tests/recipientRegistry.ts +++ b/contracts/tests/recipientRegistry.ts @@ -154,7 +154,10 @@ describe('Simple Recipient Registry', async () => { const registryAsRecipient = registry.connect(recipient) as Contract await expect( registryAsRecipient.addRecipient(recipientAddress, metadata) - ).to.be.revertedWith('Ownable: caller is not the owner') + ).to.be.revertedWithCustomError( + registryAsRecipient, + 'OwnableUnauthorizedAccount' + ) }) it('should not accept zero-address as recipient address', async () => { @@ -218,7 +221,10 @@ describe('Simple Recipient Registry', async () => { const registryAsRecipient = registry.connect(recipient) as Contract await expect( registryAsRecipient.removeRecipient(recipientId) - ).to.be.revertedWith('Ownable: caller is not the owner') + ).to.be.revertedWithCustomError( + registryAsRecipient, + 'OwnableUnauthorizedAccount' + ) }) it('reverts if recipient is not in registry', async () => { @@ -762,7 +768,7 @@ describe('Optimistic recipient registry', () => { recipientId, requester.address ) - ).to.be.revertedWith('Ownable: caller is not the owner') + ).to.be.revertedWithCustomError(registry, 'OwnableUnauthorizedAccount') }) it('should not allow to challenge resolved request', async () => { diff --git a/contracts/tests/round.ts b/contracts/tests/round.ts index c0fa4f813..fd6e8095b 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 { @@ -21,7 +23,7 @@ import { VOICE_CREDIT_FACTOR, ALPHA_PRECISION, } from '../utils/constants' -import { getEventArg, getGasUsage } from '../utils/contracts' +import { getContractAt, getEventArg, getGasUsage } from '../utils/contracts' import { bnSqrt, createMessage, @@ -29,11 +31,15 @@ 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)) const perVOSpentVoiceCreditsHash = hexlify(randomBytes(32)) @@ -65,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 @@ -100,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 @@ -114,12 +147,13 @@ 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) 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( @@ -136,7 +170,12 @@ describe('Funding Round', () => { maciAddress = await fundingRound.maci() maci = await ethers.getContractAt('MACI', maciAddress) const pollAddress = await fundingRound.poll() - poll = await ethers.getContractAt('Poll', pollAddress, deployer) + poll = await getContractAt( + EContracts.Poll, + pollAddress, + ethers, + deployer + ) pollId = await fundingRound.pollId() const treeDepths = await poll.treeDepths() @@ -199,8 +238,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 () => { @@ -243,7 +308,7 @@ describe('Funding Round', () => { it('requires approval', async () => { await expect( fundingRoundAsContributor.contribute(userPubKey, contributionAmount) - ).to.be.revertedWith('ERC20: insufficient allowance') + ).to.be.revertedWithCustomError(token, 'ERC20InsufficientAllowance') }) it('rejects contributions from unverified users', async () => { @@ -631,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 }) @@ -744,7 +810,10 @@ describe('Funding Round', () => { newResultCommitment, perVOSpentVoiceCreditsHash ) - ).to.be.revertedWith('Ownable: caller is not the owner') + ).to.be.revertedWithCustomError( + fundingRoundAsCoordinator, + 'OwnableUnauthorizedAccount' + ) }) }) @@ -805,8 +874,11 @@ describe('Funding Round', () => { const fundingRoundAsCoordinator = fundingRound.connect( coordinator ) as Contract - await expect(fundingRoundAsCoordinator.cancel()).to.be.revertedWith( - 'Ownable: caller is not the owner' + await expect( + fundingRoundAsCoordinator.cancel() + ).to.be.revertedWithCustomError( + fundingRoundAsCoordinator, + 'OwnableUnauthorizedAccount' ) }) }) @@ -1424,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, @@ -1436,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/tests/userRegistry.ts b/contracts/tests/userRegistry.ts index 63d05e76f..7d0c9e7bc 100644 --- a/contracts/tests/userRegistry.ts +++ b/contracts/tests/userRegistry.ts @@ -44,8 +44,11 @@ describe('Simple User Registry', () => { it('allows only owner to add users', async () => { const registryAsUser = registry.connect(user) as Contract - await expect(registryAsUser.addUser(user.address)).to.be.revertedWith( - 'Ownable: caller is not the owner' + await expect( + registryAsUser.addUser(user.address) + ).to.be.revertedWithCustomError( + registryAsUser, + 'OwnableUnauthorizedAccount' ) }) @@ -66,8 +69,11 @@ describe('Simple User Registry', () => { it('allows only owner to remove users', async () => { await registry.addUser(user.address) const registryAsUser = registry.connect(user) as Contract - await expect(registryAsUser.removeUser(user.address)).to.be.revertedWith( - 'Ownable: caller is not the owner' + await expect( + registryAsUser.removeUser(user.address) + ).to.be.revertedWithCustomError( + registryAsUser, + 'OwnableUnauthorizedAccount' ) }) }) diff --git a/contracts/tests/userRegistryMerkle.ts b/contracts/tests/userRegistryMerkle.ts index 87c021a55..8761e33dc 100644 --- a/contracts/tests/userRegistryMerkle.ts +++ b/contracts/tests/userRegistryMerkle.ts @@ -39,7 +39,10 @@ describe('Merkle User Registry', () => { const registryAsUser = registry.connect(signers[user1.address]) as Contract await expect( registryAsUser.setMerkleRoot(randomBytes(32), 'non owner') - ).to.be.revertedWith('Ownable: caller is not the owner') + ).to.be.revertedWithCustomError( + registryAsUser, + 'OwnableUnauthorizedAccount' + ) }) describe('registration', () => { diff --git a/contracts/tests/userRegistrySemaphore.ts b/contracts/tests/userRegistrySemaphore.ts index 95a71c4ef..af500261f 100644 --- a/contracts/tests/userRegistrySemaphore.ts +++ b/contracts/tests/userRegistrySemaphore.ts @@ -66,7 +66,10 @@ describe('Semaphore User Registry', () => { const registryAsUser = registry.connect(user) as Contract await expect( registryAsUser.addUser(user.address, semaphoreId) - ).to.be.revertedWith('Ownable: caller is not the owner') + ).to.be.revertedWithCustomError( + registryAsUser, + 'OwnableUnauthorizedAccount' + ) }) it('allows owner to remove user', async () => { @@ -88,8 +91,11 @@ describe('Semaphore User Registry', () => { const semaphoreId = 1 await registry.addUser(user.address, semaphoreId) const registryAsUser = registry.connect(user) as Contract - await expect(registryAsUser.removeUser(user.address)).to.be.revertedWith( - 'Ownable: caller is not the owner' + await expect( + registryAsUser.removeUser(user.address) + ).to.be.revertedWithCustomError( + registryAsUser, + 'OwnableUnauthorizedAccount' ) }) 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/contracts.ts b/contracts/utils/contracts.ts index 45250e3fb..3c26cf5d8 100644 --- a/contracts/utils/contracts.ts +++ b/contracts/utils/contracts.ts @@ -12,7 +12,7 @@ import { } from '@nomicfoundation/hardhat-ethers/types' import { VkRegistry, FundingRound } from '../typechain-types' import { MaciParameters } from './maciParameters' -import { IVerifyingKeyStruct } from 'maci-contracts' +import { EMode, IVerifyingKeyStruct } from 'maci-contracts' /** * Deploy a contract @@ -27,7 +27,7 @@ export async function deployContract( options?: DeployContractOptions & { args?: unknown[]; quiet?: boolean } ): Promise { const args = options?.args || [] - const contractName = String(name).includes('Poseidon') ? ':' + name : name + const contractName = getQualifiedContractName(name) const contract = await ethers.deployContract(contractName, args, options) await contract.waitForDeployment() @@ -54,6 +54,7 @@ export async function setVerifyingKeys( params.treeDepths.messageTreeDepth, params.treeDepths.voteOptionTreeDepth, messageBatchSize, + EMode.QV, params.processVk.asContractParam() as IVerifyingKeyStruct, params.tallyVk.asContractParam() as IVerifyingKeyStruct ) @@ -116,4 +117,40 @@ export async function getCurrentFundingRoundContract( return fundingRoundContract as BaseContract as FundingRound } + +/** + * Return the fully qualified contract name for Poll and PollFactory + * since there is a local copy and another in the maci-contracts + * otherwise return the name of the contract + * @param name The contract name + * @returns The qualified contract name + */ +export function getQualifiedContractName(name: EContracts | string): string { + let contractName = String(name) + if (contractName.includes('Poseidon')) { + contractName = `:${name}` + } + return contractName +} + +/** + * Get a contract + * @param name Name of the contract + * @param address The contract address + * @param ethers Hardhat ethers handle + * @param signers The signer + * @returns contract + */ +export async function getContractAt( + name: EContracts, + address: string, + ethers: HardhatEthersHelpers, + signer?: Signer +): Promise { + const contractName = getQualifiedContractName(name) + const contract = await ethers.getContractAt(contractName, address, signer) + + return contract as BaseContract as T +} + export { getEventArg } diff --git a/contracts/utils/maci.ts b/contracts/utils/maci.ts index bb3e3bb0b..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, @@ -23,7 +23,7 @@ import { verify, } from 'maci-cli' -import { getTalyFilePath, isPathExist } from './misc' +import { isPathExist } from './misc' import { getCircuitFiles } from './circuits' import { FundingRound } from '../typechain-types' @@ -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) { @@ -303,7 +303,7 @@ export async function mergeMaciSubtrees({ await mergeMessages({ pollId, - maciContractAddress: maciAddress, + maciAddress, numQueueOps, signer, quiet, @@ -311,7 +311,7 @@ export async function mergeMaciSubtrees({ await mergeSignups({ pollId, - maciContractAddress: maciAddress, + maciAddress, numQueueOps, signer, quiet, 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/contracts/utils/providers/EtherscanProvider.ts b/contracts/utils/providers/EtherscanProvider.ts index 8534156ae..047ccdbea 100644 --- a/contracts/utils/providers/EtherscanProvider.ts +++ b/contracts/utils/providers/EtherscanProvider.ts @@ -3,6 +3,7 @@ import { FetchRequest } from 'ethers' import { HardhatConfig } from 'hardhat/types' const EtherscanApiUrl: Record = { + sepolia: 'https://api-sepolia.etherscan.io', xdai: 'https://api.gnosisscan.io', arbitrum: 'https://api.arbiscan.io', 'arbitrum-goerli': 'https://api-goerli.arbiscan.io', diff --git a/contracts/utils/testutils.ts b/contracts/utils/testutils.ts index e5ace7da7..6d80b7a71 100644 --- a/contracts/utils/testutils.ts +++ b/contracts/utils/testutils.ts @@ -9,6 +9,7 @@ import { EContracts } from './types' import { Libraries } from 'hardhat/types' import { MACIFactory, VkRegistry } from '../typechain-types' import { ZERO_ADDRESS } from './constants' +import { EMode } from 'maci-contracts' /** * Deploy a mock contract with the given contract name @@ -127,8 +128,6 @@ export async function deployMaciFactory({ const factories = { pollFactory: pollFactory.target, tallyFactory: tallyFactory.target, - // subsidy is not currently used - subsidyFactory: ZERO_ADDRESS, messageProcessorFactory: messageProcessorFactory.target, } @@ -170,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], @@ -209,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, @@ -236,7 +249,6 @@ export async function deployTestFundingRound( factories.pollFactory, factories.messageProcessorFactory, factories.tallyFactory, - factories.subsidyFactory, fundingRound.target, fundingRound.target, topupToken.target, @@ -254,8 +266,7 @@ export async function deployTestFundingRound( coordinatorPubKey.asContractParam(), mockVerifier.target, vkRegistry.target, - // pass false to not deploy the subsidy contract - false + EMode.QV ) const pollAddr = await getEventArg( deployPollTx, diff --git a/docs/brightid.md b/docs/brightid.md index efd01636d..5c84d799e 100644 --- a/docs/brightid.md +++ b/docs/brightid.md @@ -18,13 +18,13 @@ USER_REGISTRY_TYPE=brightid Available envs: -| Network/Env | Context | Sponsor Contract | -| ----------- | ------- | ---------------- | -| arbitrum | clrfund-arbitrum |0x669A55Dd17a2f9F4dacC37C7eeB5Ed3e13f474f9| -| arbitrum rinkeby | clrfund-arbitrum-rinkeby | 0xC7c81634Dac2de4E7f2Ba407B638ff003ce4534C | -| arbitrum sepolia | clrfund-arbitrum-goerli | 0xF1ef516dEa7e6Dd334996726D58029Ee9bAD081D | -| goerli | clrfund-goerli | 0xF045234A776C87060DEEc5689056455A24a59c08 | -| xdai | clrfund-gnosischain |0x669A55Dd17a2f9F4dacC37C7eeB5Ed3e13f474f9| +| Network/Env | Context | +| ----------- | ------- | +| arbitrum | clrfund-arbitrum | +| arbitrum rinkeby | clrfund-arbitrum-rinkeby | +| arbitrum sepolia | clrfund-arbitrum-goerli | +| goerli | clrfund-goerli | +| xdai | clrfund-gnosischain | ```.sh # /vue-app/.env @@ -35,7 +35,6 @@ BRIGHTID_CONTEXT={CONTEXT} ``` Note: the BrightID context is specific to the BrightID network - it's independent from the Ethereum network you choose to run the app on. It refers to the BrightID app context where you want to burn sponsorship tokens. -The `Sponsor Contract` is the contract set up in the BrightID node to track the sponsorship event. The BrightID context can be found here: https://apps.brightid.org/#nodes @@ -55,38 +54,20 @@ BRIGHTID_VERIFIER_ADDR=0xdbf0b2ee9887fe11934789644096028ed3febe9c By default, the clrfund app will connect to the BrightId node run by clrfund, https://brightid.clr.fund. -**#4 configure the BrightID sponsorship page** -By default, the clrfund app will sponsor the BrightId users using the smart contract event logging method. The `Sponsor` contract is listed in the step #2 above. - -Alternatively, you can configure the clrfund app to use the BrightId sponsorship api to submit the sponsorship request directly by setting the following environment variables. Only one of VITE_BRIGHTID_SPONSOR_KEY_FOR_NETLIFY or VITE_BRIGHTID_SPONSOR_KEY needs to be set. If VITE_BRIGHTID_SPONSOR_KEY_FOR_NETLIFY is set, the clrfund app must be deployed to the netlify platform as it will use the netlify serverless function. The netlify option is used if you want to protect the BrightId sponsor key. - -The BrightId sponsor key can be generated using the random Nacl keypair at [https://tweetnacl.js.org/#/sign](https://tweetnacl.js.org/#/sign). Give the public key part to the BrightId folks to setup the context and put the private key part in VITE_BRIGHTID_SPONSOR_KEY or VITE_BRIGHTID_SPONSOR_KEY_FOR_NETLIFY. - -```.sh -# /vue-app/.env -VITE_BRIGHTID_SPONSOR_API_URL=https://brightid.clr.fund/brightid/v6/operations -VITE_BRIGHTID_SPONSOR_KEY_FOR_NETLIFY= -VITE_BRIGHTID_SPONSOR_KEY= -``` - -**#5 netlify function setup** -See the [deployment guide](./deploymnet.md) for special setup to use the sponsor netlify function. - ## Troubleshooting linking failure -### Sponsorship timeout -1. check for sponsorship status https://app.brightid.org/node/v6/sponsorships/WALLET_ADDRESS -2. check for sponsorship status from clrfund's brightid node: +### General linking error +1. check for sponsorship status from clrfund's brightid node: - https://brightid.clr.fund/brightid/v6/sponsorships/WALLET_ADDRESS -3. check the clrfund's brightid node docker logs +2. check the clrfund's brightid node docker logs - look for sponsorship event listening error ```.sh docker logs --since 1440m brightid-node-docker-db-1 ``` -4. check for sponsorship token balance +3. check for sponsorship token balance - for clrfund-arbitrum context: https://brightid.clr.fund/brightid/v6/apps/clrfund-arbitrum ### Signature is not valid 1. Check that the verifier address is correct, it is the `ethSigningAddress` from https://brightid.clr.fund -2. You can update the verifier address using `sponsorContract.setSettings()` +2. You can update the verifier address using `BrightIdUserRegistryContract.setSettings()` ## Resources diff --git a/docs/deployment.md b/docs/deployment.md index 5dd55a9a4..4d292238e 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -24,13 +24,6 @@ Note: select the `hex address (ONLY)` option to identify users and the `meet` ve Once the app is registered, you will get an appId which will be set to `BRIGHTID_CONTEXT` when deploying the contracts in later steps. -### Setup BrightID sponsorship keys - -1. Generate sponsorship signing keys here: https://tweetnacl.js.org/#/sign -2. Provide the public key to BrightID support through their discord channel: https://discord.gg/QW7ThZ5K4V -3. Save the private key for setting up the clrfund user interface in environment variable: `VITE_BRIGHTID_SPONSOR_KEY` - - ## Deploy Contracts Goto the `contracts` folder. @@ -161,8 +154,6 @@ VITE_SUBGRAPH_URL= VITE_CLRFUND_ADDRESS= VITE_USER_REGISTRY_TYPE= VITE_BRIGHTID_CONTEXT= -VITE_BRIGHTID_SPONSOR_KEY= -VITE_BRIGHTID_SPONSOR_API_URL=https://brightid.clr.fund/brightid/v6/operations VITE_RECIPIENT_REGISTRY_TYPE= # see google-sheets.md for instruction on how to set these @@ -179,10 +170,6 @@ Note: if VITE_SUBGRAPH_URL is not set, the app will try to get the round informa See [How to set netlify function directory](https://docs.netlify.com/functions/optional-configuration/?fn-language=ts) -2. Set environment variable: `AWS_LAMBDA_JS_RUNTIME=nodejs18.x` - -This environment variable is needed for the `sponsor.js` function. If not set, it will throw error `fetch not found`. - #### Deploy on IPFS diff --git a/docs/tally-verify.md b/docs/tally-verify.md index 9ce6bc7dd..080d219c1 100644 --- a/docs/tally-verify.md +++ b/docs/tally-verify.md @@ -87,6 +87,4 @@ Also, lack of memory can also cause `core dump`, try to work around it by settin export NODE_OPTIONS=--max-old-space-size=4096 ``` -If you notice `Error at message index 0 - failed decryption due to either wrong encryption public key or corrupted ciphertext` while running the tally script, don't worry, it's just a warning. This issue is tracked [here](https://github.com/privacy-scaling-explorations/maci/issues/1134) - `Error at message index n - invalid nonce` is also a warning, not an error. This error occurs when users reallocated their contribution. diff --git a/package.json b/package.json index 072d15e02..0ae9648b6 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "test:contracts": "yarn workspace @clrfund/contracts run test", "test:e2e": "yarn workspace @clrfund/contracts run e2e", "test:web": "yarn workspace @clrfund/vue-app run test", + "test:common": "yarn workspace @clrfund/common run test", "test:format": "yarn prettier --check", "test:lint-i18n": "echo yarn workspace @clrfund/vue-app run test:lint-i18n --ci", "deploy:subgraph": "yarn workspace @clrfund/subgraph run deploy", 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/MACI.json b/subgraph/abis/MACI.json index 4a09120f9..ba7b1c96a 100644 --- a/subgraph/abis/MACI.json +++ b/subgraph/abis/MACI.json @@ -12,15 +12,10 @@ "type": "address" }, { - "internalType": "contract ITallySubsidyFactory", + "internalType": "contract ITallyFactory", "name": "_tallyFactory", "type": "address" }, - { - "internalType": "contract ITallySubsidyFactory", - "name": "_subsidyFactory", - "type": "address" - }, { "internalType": "contract SignUpGatekeeper", "name": "_signUpGatekeeper", @@ -63,7 +58,29 @@ }, { "inputs": [], - "name": "MaciPubKeyLargerThanSnarkFieldSize", + "name": "InvalidPubKey", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "OwnableInvalidOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "OwnableUnauthorizedAccount", "type": "error" }, { @@ -140,11 +157,6 @@ "internalType": "address", "name": "tally", "type": "address" - }, - { - "internalType": "address", - "name": "subsidy", - "type": "address" } ], "indexed": false, @@ -287,9 +299,9 @@ "type": "address" }, { - "internalType": "bool", - "name": "useSubsidy", - "type": "bool" + "internalType": "enum DomainObjs.Mode", + "name": "_mode", + "type": "uint8" } ], "name": "deployPoll", @@ -310,11 +322,6 @@ "internalType": "address", "name": "tally", "type": "address" - }, - { - "internalType": "address", - "name": "subsidy", - "type": "address" } ], "internalType": "struct MACI.PollContracts", @@ -845,19 +852,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [], - "name": "subsidyFactory", - "outputs": [ - { - "internalType": "contract ITallySubsidyFactory", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, { "inputs": [], "name": "subtreesMerged", @@ -876,7 +870,7 @@ "name": "tallyFactory", "outputs": [ { - "internalType": "contract ITallySubsidyFactory", + "internalType": "contract ITallyFactory", "name": "", "type": "address" } diff --git a/subgraph/abis/MACIFactory.json b/subgraph/abis/MACIFactory.json index c8ab362b8..3488fff8d 100644 --- a/subgraph/abis/MACIFactory.json +++ b/subgraph/abis/MACIFactory.json @@ -18,11 +18,6 @@ "name": "tallyFactory", "type": "address" }, - { - "internalType": "address", - "name": "subsidyFactory", - "type": "address" - }, { "internalType": "address", "name": "messageProcessorFactory", @@ -44,17 +39,22 @@ }, { "inputs": [], - "name": "InvalidMessageProcessorFactory", + "name": "InvalidMaxRecipients", "type": "error" }, { "inputs": [], - "name": "InvalidPollFactory", + "name": "InvalidMessageBatchSize", "type": "error" }, { "inputs": [], - "name": "InvalidSubsidyFactory", + "name": "InvalidMessageProcessorFactory", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidPollFactory", "type": "error" }, { @@ -72,11 +72,38 @@ "name": "InvalidVkRegistry", "type": "error" }, + { + "inputs": [], + "name": "InvalidVoteOptionTreeDepth", + "type": "error" + }, { "inputs": [], "name": "NotInitialized", "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": "ProcessVkNotSet", @@ -87,6 +114,11 @@ "name": "TallyVkNotSet", "type": "error" }, + { + "inputs": [], + "name": "VoteOptionTreeDepthNotSet", + "type": "error" + }, { "anonymous": false, "inputs": [ @@ -138,19 +170,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [], - "name": "TREE_ARITY", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, { "inputs": [ { @@ -224,11 +243,6 @@ "internalType": "address", "name": "tally", "type": "address" - }, - { - "internalType": "address", - "name": "subsidy", - "type": "address" } ], "internalType": "struct MACI.PollContracts", @@ -253,11 +267,6 @@ "name": "tallyFactory", "type": "address" }, - { - "internalType": "address", - "name": "subsidyFactory", - "type": "address" - }, { "internalType": "address", "name": "messageProcessorFactory", @@ -268,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" }, { @@ -313,6 +329,16 @@ "name": "_stateTreeDepth", "type": "uint8" }, + { + "internalType": "uint256", + "name": "_messageBatchSize", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_maxRecipients", + "type": "uint256" + }, { "components": [ { diff --git a/subgraph/abis/Poll.json b/subgraph/abis/Poll.json index 671177ba2..e31d07163 100644 --- a/subgraph/abis/Poll.json +++ b/subgraph/abis/Poll.json @@ -105,7 +105,29 @@ }, { "inputs": [], - "name": "MaciPubKeyLargerThanSnarkFieldSize", + "name": "InvalidPubKey", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "OwnableInvalidOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "OwnableUnauthorizedAccount", "type": "error" }, { @@ -142,13 +164,13 @@ "anonymous": false, "inputs": [ { - "indexed": false, + "indexed": true, "internalType": "uint256", "name": "_stateRoot", "type": "uint256" }, { - "indexed": false, + "indexed": true, "internalType": "uint256", "name": "_numSignups", "type": "uint256" @@ -161,7 +183,7 @@ "anonymous": false, "inputs": [ { - "indexed": false, + "indexed": true, "internalType": "uint256", "name": "_numSrQueueOps", "type": "uint256" @@ -174,7 +196,7 @@ "anonymous": false, "inputs": [ { - "indexed": false, + "indexed": true, "internalType": "uint256", "name": "_messageRoot", "type": "uint256" @@ -187,7 +209,7 @@ "anonymous": false, "inputs": [ { - "indexed": false, + "indexed": true, "internalType": "uint256", "name": "_numSrQueueOps", "type": "uint256" 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/MACI.ts b/subgraph/generated/ClrFund/MACI.ts index b1b8238e1..a770b955e 100644 --- a/subgraph/generated/ClrFund/MACI.ts +++ b/subgraph/generated/ClrFund/MACI.ts @@ -54,10 +54,6 @@ export class DeployPollPollAddrStruct extends ethereum.Tuple { get tally(): Address { return this[2].toAddress(); } - - get subsidy(): Address { - return this[3].toAddress(); - } } export class OwnershipTransferred extends ethereum.Event { @@ -128,10 +124,6 @@ export class MACI__deployPollResultPollAddrStruct extends ethereum.Tuple { get tally(): Address { return this[2].toAddress(); } - - get subsidy(): Address { - return this[3].toAddress(); - } } export class MACI__deployPollInput_treeDepthsStruct extends ethereum.Tuple { @@ -298,18 +290,18 @@ export class MACI extends ethereum.SmartContract { _coordinatorPubKey: MACI__deployPollInput_coordinatorPubKeyStruct, _verifier: Address, _vkRegistry: Address, - useSubsidy: boolean, + _mode: i32, ): MACI__deployPollResultPollAddrStruct { let result = super.call( "deployPoll", - "deployPoll(uint256,(uint8,uint8,uint8,uint8),(uint256,uint256),address,address,bool):((address,address,address,address))", + "deployPoll(uint256,(uint8,uint8,uint8,uint8),(uint256,uint256),address,address,uint8):((address,address,address))", [ ethereum.Value.fromUnsignedBigInt(_duration), ethereum.Value.fromTuple(_treeDepths), ethereum.Value.fromTuple(_coordinatorPubKey), ethereum.Value.fromAddress(_verifier), ethereum.Value.fromAddress(_vkRegistry), - ethereum.Value.fromBoolean(useSubsidy), + ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(_mode)), ], ); @@ -324,18 +316,18 @@ export class MACI extends ethereum.SmartContract { _coordinatorPubKey: MACI__deployPollInput_coordinatorPubKeyStruct, _verifier: Address, _vkRegistry: Address, - useSubsidy: boolean, + _mode: i32, ): ethereum.CallResult { let result = super.tryCall( "deployPoll", - "deployPoll(uint256,(uint8,uint8,uint8,uint8),(uint256,uint256),address,address,bool):((address,address,address,address))", + "deployPoll(uint256,(uint8,uint8,uint8,uint8),(uint256,uint256),address,address,uint8):((address,address,address))", [ ethereum.Value.fromUnsignedBigInt(_duration), ethereum.Value.fromTuple(_treeDepths), ethereum.Value.fromTuple(_coordinatorPubKey), ethereum.Value.fromAddress(_verifier), ethereum.Value.fromAddress(_vkRegistry), - ethereum.Value.fromBoolean(useSubsidy), + ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(_mode)), ], ); if (result.reverted) { @@ -831,25 +823,6 @@ export class MACI extends ethereum.SmartContract { return ethereum.CallResult.fromValue(value[0].toI32()); } - subsidyFactory(): Address { - let result = super.call("subsidyFactory", "subsidyFactory():(address)", []); - - return result[0].toAddress(); - } - - try_subsidyFactory(): ethereum.CallResult
{ - let result = super.tryCall( - "subsidyFactory", - "subsidyFactory():(address)", - [], - ); - if (result.reverted) { - return new ethereum.CallResult(); - } - let value = result.value; - return ethereum.CallResult.fromValue(value[0].toAddress()); - } - subtreesMerged(): boolean { let result = super.call("subtreesMerged", "subtreesMerged():(bool)", []); @@ -925,24 +898,20 @@ export class ConstructorCall__Inputs { return this._call.inputValues[2].value.toAddress(); } - get _subsidyFactory(): Address { - return this._call.inputValues[3].value.toAddress(); - } - get _signUpGatekeeper(): Address { - return this._call.inputValues[4].value.toAddress(); + return this._call.inputValues[3].value.toAddress(); } get _initialVoiceCreditProxy(): Address { - return this._call.inputValues[5].value.toAddress(); + return this._call.inputValues[4].value.toAddress(); } get _topupCredit(): Address { - return this._call.inputValues[6].value.toAddress(); + return this._call.inputValues[5].value.toAddress(); } get _stateTreeDepth(): i32 { - return this._call.inputValues[7].value.toI32(); + return this._call.inputValues[6].value.toI32(); } } @@ -995,8 +964,8 @@ export class DeployPollCall__Inputs { return this._call.inputValues[4].value.toAddress(); } - get useSubsidy(): boolean { - return this._call.inputValues[5].value.toBoolean(); + get _mode(): i32 { + return this._call.inputValues[5].value.toI32(); } } @@ -1054,10 +1023,6 @@ export class DeployPollCallPollAddrStruct extends ethereum.Tuple { get tally(): Address { return this[2].toAddress(); } - - get subsidy(): Address { - return this[3].toAddress(); - } } export class MergeStateAqCall extends ethereum.Call { diff --git a/subgraph/generated/ClrFund/MACIFactory.ts b/subgraph/generated/ClrFund/MACIFactory.ts index c9f330074..0f6f79fc3 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; } } @@ -243,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, @@ -269,7 +238,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 +269,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 +297,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 +305,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,28 +323,44 @@ export class MACIFactory extends ethereum.SmartContract { value[0].toAddress(), value[1].toAddress(), value[2].toAddress(), - value[3].toAddress(), ), ); } - 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(); @@ -534,12 +518,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 +611,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 { @@ -684,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/schema.ts b/subgraph/generated/schema.ts index 97e62f0d3..50ce4aecf 100644 --- a/subgraph/generated/schema.ts +++ b/subgraph/generated/schema.ts @@ -551,21 +551,17 @@ export class PublicKey extends Entity { this.set("id", Value.fromString(value)); } - get fundingRound(): string | null { - let value = this.get("fundingRound"); + get maci(): string { + let value = this.get("maci"); if (!value || value.kind == ValueKind.NULL) { - return null; + throw new Error("Cannot return null for a required field."); } else { return value.toString(); } } - set fundingRound(value: string | null) { - if (!value) { - this.unset("fundingRound"); - } else { - this.set("fundingRound", Value.fromString(value)); - } + set maci(value: string) { + this.set("maci", Value.fromString(value)); } get messages(): MessageLoader { @@ -1020,6 +1016,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/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/MACI/MACI.ts b/subgraph/generated/templates/MACI/MACI.ts index b1b8238e1..a770b955e 100644 --- a/subgraph/generated/templates/MACI/MACI.ts +++ b/subgraph/generated/templates/MACI/MACI.ts @@ -54,10 +54,6 @@ export class DeployPollPollAddrStruct extends ethereum.Tuple { get tally(): Address { return this[2].toAddress(); } - - get subsidy(): Address { - return this[3].toAddress(); - } } export class OwnershipTransferred extends ethereum.Event { @@ -128,10 +124,6 @@ export class MACI__deployPollResultPollAddrStruct extends ethereum.Tuple { get tally(): Address { return this[2].toAddress(); } - - get subsidy(): Address { - return this[3].toAddress(); - } } export class MACI__deployPollInput_treeDepthsStruct extends ethereum.Tuple { @@ -298,18 +290,18 @@ export class MACI extends ethereum.SmartContract { _coordinatorPubKey: MACI__deployPollInput_coordinatorPubKeyStruct, _verifier: Address, _vkRegistry: Address, - useSubsidy: boolean, + _mode: i32, ): MACI__deployPollResultPollAddrStruct { let result = super.call( "deployPoll", - "deployPoll(uint256,(uint8,uint8,uint8,uint8),(uint256,uint256),address,address,bool):((address,address,address,address))", + "deployPoll(uint256,(uint8,uint8,uint8,uint8),(uint256,uint256),address,address,uint8):((address,address,address))", [ ethereum.Value.fromUnsignedBigInt(_duration), ethereum.Value.fromTuple(_treeDepths), ethereum.Value.fromTuple(_coordinatorPubKey), ethereum.Value.fromAddress(_verifier), ethereum.Value.fromAddress(_vkRegistry), - ethereum.Value.fromBoolean(useSubsidy), + ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(_mode)), ], ); @@ -324,18 +316,18 @@ export class MACI extends ethereum.SmartContract { _coordinatorPubKey: MACI__deployPollInput_coordinatorPubKeyStruct, _verifier: Address, _vkRegistry: Address, - useSubsidy: boolean, + _mode: i32, ): ethereum.CallResult { let result = super.tryCall( "deployPoll", - "deployPoll(uint256,(uint8,uint8,uint8,uint8),(uint256,uint256),address,address,bool):((address,address,address,address))", + "deployPoll(uint256,(uint8,uint8,uint8,uint8),(uint256,uint256),address,address,uint8):((address,address,address))", [ ethereum.Value.fromUnsignedBigInt(_duration), ethereum.Value.fromTuple(_treeDepths), ethereum.Value.fromTuple(_coordinatorPubKey), ethereum.Value.fromAddress(_verifier), ethereum.Value.fromAddress(_vkRegistry), - ethereum.Value.fromBoolean(useSubsidy), + ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(_mode)), ], ); if (result.reverted) { @@ -831,25 +823,6 @@ export class MACI extends ethereum.SmartContract { return ethereum.CallResult.fromValue(value[0].toI32()); } - subsidyFactory(): Address { - let result = super.call("subsidyFactory", "subsidyFactory():(address)", []); - - return result[0].toAddress(); - } - - try_subsidyFactory(): ethereum.CallResult
{ - let result = super.tryCall( - "subsidyFactory", - "subsidyFactory():(address)", - [], - ); - if (result.reverted) { - return new ethereum.CallResult(); - } - let value = result.value; - return ethereum.CallResult.fromValue(value[0].toAddress()); - } - subtreesMerged(): boolean { let result = super.call("subtreesMerged", "subtreesMerged():(bool)", []); @@ -925,24 +898,20 @@ export class ConstructorCall__Inputs { return this._call.inputValues[2].value.toAddress(); } - get _subsidyFactory(): Address { - return this._call.inputValues[3].value.toAddress(); - } - get _signUpGatekeeper(): Address { - return this._call.inputValues[4].value.toAddress(); + return this._call.inputValues[3].value.toAddress(); } get _initialVoiceCreditProxy(): Address { - return this._call.inputValues[5].value.toAddress(); + return this._call.inputValues[4].value.toAddress(); } get _topupCredit(): Address { - return this._call.inputValues[6].value.toAddress(); + return this._call.inputValues[5].value.toAddress(); } get _stateTreeDepth(): i32 { - return this._call.inputValues[7].value.toI32(); + return this._call.inputValues[6].value.toI32(); } } @@ -995,8 +964,8 @@ export class DeployPollCall__Inputs { return this._call.inputValues[4].value.toAddress(); } - get useSubsidy(): boolean { - return this._call.inputValues[5].value.toBoolean(); + get _mode(): i32 { + return this._call.inputValues[5].value.toI32(); } } @@ -1054,10 +1023,6 @@ export class DeployPollCallPollAddrStruct extends ethereum.Tuple { get tally(): Address { return this[2].toAddress(); } - - get subsidy(): Address { - return this[3].toAddress(); - } } export class MergeStateAqCall 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/package.json b/subgraph/package.json index e764fefc9..3da61b629 100644 --- a/subgraph/package.json +++ b/subgraph/package.json @@ -1,6 +1,6 @@ { "name": "@clrfund/subgraph", - "version": "5.1.1", + "version": "6.0.0", "repository": "https://github.com/clrfund/monorepo/subgraph", "keywords": [ "clr.fund", diff --git a/subgraph/schema.graphql b/subgraph/schema.graphql index 147b3225f..2d779393a 100644 --- a/subgraph/schema.graphql +++ b/subgraph/schema.graphql @@ -37,7 +37,7 @@ type Message @entity { type PublicKey @entity { id: ID! - fundingRound: FundingRound + maci: String! messages: [Message!] @derivedFrom(field: "publicKey") x: BigInt! y: BigInt! @@ -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..e8f1db50d 100644 --- a/subgraph/schema.template.graphql +++ b/subgraph/schema.template.graphql @@ -49,7 +49,7 @@ type Message @entity { type PublicKey @entity { id: ID! - fundingRound: FundingRound + maci: String! messages: [Message!] @derivedFrom(field: "publicKey") x: BigInt! y: BigInt! @@ -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/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/ClrFundMapping.ts b/subgraph/src/ClrFundMapping.ts index f26e32586..3e97056c8 100644 --- a/subgraph/src/ClrFundMapping.ts +++ b/subgraph/src/ClrFundMapping.ts @@ -132,8 +132,10 @@ function createOrUpdateClrFund( let recipientRegistryId = recipientRegistryAddress.toHexString() let recipientRegistry = RecipientRegistry.load(recipientRegistryId) if (!recipientRegistry) { - createRecipientRegistry(clrFundId, recipientRegistryAddress) + recipientRegistry = createRecipientRegistry(recipientRegistryAddress) } + recipientRegistry.clrFund = clrFundId + recipientRegistry.save() let contributorRegistryAddress = clrFundContract.userRegistry() let contributorRegistryId = contributorRegistryAddress.toHexString() @@ -289,6 +291,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/subgraph/src/MACIMapping.ts b/subgraph/src/MACIMapping.ts index 75da4929b..667822f48 100644 --- a/subgraph/src/MACIMapping.ts +++ b/subgraph/src/MACIMapping.ts @@ -21,11 +21,11 @@ import { makePublicKeyId } from './PublicKey' // - contract.verifier(...) export function handleSignUp(event: SignUp): void { - let fundingRoundAddress = event.transaction.to! - let fundingRoundId = fundingRoundAddress.toHex() + let maciAddress = event.address + let maciId = maciAddress.toHex() let publicKeyId = makePublicKeyId( - fundingRoundId, + maciId, event.params._userPubKeyX, event.params._userPubKeyY ) @@ -39,14 +39,7 @@ export function handleSignUp(event: SignUp): void { publicKey.y = event.params._userPubKeyY publicKey.stateIndex = event.params._stateIndex publicKey.voiceCreditBalance = event.params._voiceCreditBalance - - let fundingRound = FundingRound.load(fundingRoundId) - if (fundingRound == null) { - log.error('Error: handleSignUp failed, fundingRound not registered', []) - return - } - - publicKey.fundingRound = fundingRoundId + publicKey.maci = maciId publicKey.save() log.info('SignUp', []) diff --git a/subgraph/src/PollMapping.ts b/subgraph/src/PollMapping.ts index 99aa537fb..e566813e6 100644 --- a/subgraph/src/PollMapping.ts +++ b/subgraph/src/PollMapping.ts @@ -5,15 +5,7 @@ import { FundingRound, 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 {}', [ @@ -25,12 +17,24 @@ export function handlePublishMessage(event: PublishMessage): void { let fundingRoundId = poll.fundingRound if (!fundingRoundId) { log.error( - 'Error: handlePublishMessage failed poll {} missing funding round', + 'Error: handlePublishMessage failed poll {} missing funding round id', [pollEntityId] ) return } + let fundingRound = FundingRound.load(fundingRoundId) + if (!fundingRound) { + log.error( + 'Error: handlePublishMessage failed poll {} missing funding round entity', + [pollEntityId] + ) + return + } + + let maci = fundingRound.maci + let maciId = maci ? maci.toHex() : '' + let messageID = event.transaction.hash.toHexString() + '-' + @@ -45,7 +49,7 @@ export function handlePublishMessage(event: PublishMessage): void { message.submittedBy = event.transaction.from let publicKeyId = makePublicKeyId( - fundingRoundId, + maciId, event.params._encPubKey.x, event.params._encPubKey.y ) @@ -56,8 +60,7 @@ export function handlePublishMessage(event: PublishMessage): void { let publicKey = new PublicKey(publicKeyId) publicKey.x = event.params._encPubKey.x publicKey.y = event.params._encPubKey.y - publicKey.fundingRound = fundingRoundId - + publicKey.maci = maciId publicKey.save() } diff --git a/subgraph/src/PublicKey.ts b/subgraph/src/PublicKey.ts index d34f33706..d3edacbc0 100644 --- a/subgraph/src/PublicKey.ts +++ b/subgraph/src/PublicKey.ts @@ -2,15 +2,11 @@ import { ByteArray, crypto, BigInt } from '@graphprotocol/graph-ts' // Create the PublicKey entity id used in subgraph // using MACI public key x and y values -export function makePublicKeyId( - fundingRoundId: string, - x: BigInt, - y: BigInt -): string { +export function makePublicKeyId(maciId: string, x: BigInt, y: BigInt): string { let publicKeyX = x.toString() let publicKeyY = y.toString() let publicKeyXY = ByteArray.fromUTF8( - fundingRoundId + '.' + publicKeyX + '.' + publicKeyY + maciId + '.' + publicKeyX + '.' + publicKeyY ) let publicKeyId = crypto.keccak256(publicKeyXY).toHexString() return publicKeyId 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 diff --git a/vue-app/.env.example b/vue-app/.env.example index b7a9357e4..a1da8a51b 100644 --- a/vue-app/.env.example +++ b/vue-app/.env.example @@ -21,27 +21,18 @@ VITE_SUBGRAPH_URL=http://localhost:8000/subgraphs/name/clrfund/clrfund VITE_CLRFUND_ADDRESS=0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82 -# Supported values: simple, brightid, snapshot, merkle +# Supported values: simple, brightid, snapshot, merkle, semaphore VITE_USER_REGISTRY_TYPE=simple # clr.fund (prod) or CLRFundTest (testing) # Learn more about BrightID and context in /docs/brightid.md VITE_BRIGHTID_CONTEXT=clrfund-goerli -# These are for interacting with the BrightID api. When the SPONSOR_API_URL and one of the -# SPONSOR_KEY is set, a sponsor request will be sent to the BrightID node when the QR code -# to link users wallet address to BrightID is displayed. SPONSOR_KEY is used to sign the -# sponsor request. The SPONSOR_KEY_FOR_NETLIFY will trigger the netlify serverless function -# to send the sponsor request. The SPONSOR_KEY alone will send the request directly from -# the web app without using the netlify function. # VITE_BRIGHTID_NODE_URL is the BrightID node used to query BrightID status. It needs to # match the BRIGHTID_VERIFIER_ADDR defined in the contract .env file. This address is used # to verify the signature returned from the BrightID verification status for user registration. # The BRIGHTID_VERIFIER_ADDR value is the ethSigningAddress from the node url, # e.g. https://brightid.clr.fund -#VITE_BRIGHTID_SPONSOR_KEY_FOR_NETLIFY= -#VITE_BRIGHTID_SPONSOR_KEY= -#VITE_BRIGHTID_SPONSOR_API_URL=https://brightid.clr.fund/brightid/v6/operations # BrightId node one url, default to clrfund node at https://brightid.clr.fund/brightid/v6 #VITE_BRIGHTID_NODE_URL=https://app.brightid.org/node/v6 @@ -75,9 +66,6 @@ GOOGLE_SHEET_NAME= # the number of record to export in a pending submissions JSON file. Default 60. VITE_EXPORT_BATCH_SIZE= -# This is only used for netlify function, sponsor.js, to avoid getting the 'fetch not found' error -AWS_LAMBDA_JS_RUNTIME=nodejs18.x - # walletconnect project id VITE_WALLET_CONNECT_PROJECT_ID=walletconnect_project_id diff --git a/vue-app/package.json b/vue-app/package.json index 1c7267fbd..ccd226c26 100644 --- a/vue-app/package.json +++ b/vue-app/package.json @@ -1,6 +1,6 @@ { "name": "@clrfund/vue-app", - "version": "5.1.1", + "version": "6.0.0", "private": true, "license": "GPL-3.0", "type": "module", @@ -34,7 +34,7 @@ "@walletconnect/modal": "^2.6.0", "crypto-js": "^4.1.1", "ethereum-blockies-base64": "^1.0.2", - "ethers": "^6.11.1", + "ethers": "^6.12.1", "floating-vue": "^2.0.0-beta.20", "google-spreadsheet": "^3.3.0", "graphql": "^16.6.0", diff --git a/vue-app/src/api/bright-id.ts b/vue-app/src/api/bright-id.ts index 0aba9a8ad..10330c6fe 100644 --- a/vue-app/src/api/bright-id.ts +++ b/vue-app/src/api/bright-id.ts @@ -2,7 +2,7 @@ import { Contract, encodeBytes32String, toUtf8Bytes, decodeBase64, encodeBase64 import type { TransactionResponse, Signer } from 'ethers' import { BrightIdUserRegistry } from './abi' -import { brightIdSponsorKey, brightIdNodeUrl } from './core' +import { brightIdNodeUrl } from './core' import nacl from 'tweetnacl' const BRIGHTID_APP_URL = 'https://app.brightid.org' @@ -44,55 +44,6 @@ export interface Verification { app: string } -export interface Sponsorship { - timestamp: number - app: string - appHasAuthorized: boolean - spendRequested: boolean -} - -type AppData = { - id: string - name: string - context?: string - verification: string - verifications?: string[] - verificationsUrl: string - logo?: string - url?: string - assignedSponsorships?: number - unusedSponsorships?: number - testing?: boolean - idAsHex?: boolean - usingBlindSig?: boolean - verificationExpirationLength?: number - sponsorPublicKey?: string - nodeUrl?: string - soulbound: boolean - callbackUrl?: string -} - -type SponsorOperation = { - name: string - app: string - appUserId: string - timestamp: number - v: number - sig?: string -} - -type SponsorData = { - hash?: string - error?: string -} - -export async function selfSponsor(registryAddress: string, signer: Signer): Promise { - const registry = new Contract(registryAddress, BrightIdUserRegistry, signer) - const userAddress = await signer.getAddress() - const transaction = await registry.sponsor(userAddress) - return transaction -} - // This link is for generating QR code export function getBrightIdLink(userAddress: string): string { const deepLink = `brightid://link-verification/${CONTEXT}/${userAddress}` @@ -166,129 +117,3 @@ export async function getBrightId(contextId: string): Promise { } return brightId } - -/** - * Get the unused sponsorship amount - * @param context - the context to retrieve unused sponsorships for - * - * @returns Returns the number of sponsorships available to the specified `context` - */ -async function unusedSponsorships(context: string): Promise { - const endpoint = `${NODE_URL}/apps/${context}` - const response = await fetch(endpoint) - const json = await response.json() - - if (json['errorMessage']) { - throw new Error(JSON.stringify(json)) - } - - const data = json['data'] as AppData - return data.unusedSponsorships || 0 -} - -/** - * Call the BrightID sponsor operation endpoint to put a sponsorship request for the user - * @param userAddress user wallet address - * @returns sponsporship result or error - */ -export async function brightIdSponsor(userAddress: string): Promise { - const endpoint = `${NODE_URL}/operations` - - if (!brightIdSponsorKey) { - return { error: 'BrightId sponsor key not set' } - } - - const sponsorships = await unusedSponsorships(CONTEXT) - if (typeof sponsorships === 'number' && sponsorships < 1) { - return { error: 'BrightID sponsorships not available' } - } - - if (typeof sponsorships !== 'number') { - return { error: 'Invalid BrightID sponsorship' } - } - - const timestamp = Date.now() - - // these fields must be in alphabetical because - // BrightID nodes use 'fast-json-stable-stringify' that sorts fields - const op: SponsorOperation = { - app: CONTEXT, - appUserId: userAddress, - name: 'Sponsor', - timestamp, - v: 6, - } - - const message = JSON.stringify(op) - const arrayedMessage = toUtf8Bytes(message) - const arrayedKey = decodeBase64(brightIdSponsorKey) - const signature = nacl.sign.detached(arrayedMessage, arrayedKey) - op.sig = encodeBase64(signature) - - const res = await fetch(endpoint, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(op), - }) - const json = await res.json() - - if (json['error']) { - if (canIgnoreError(json.errorNum)) { - // sponsorship already sent recently, ignore this error - return { hash: '0x0' } - } - return { error: json['errorMessage'] } - } else { - return json['data'] - } -} - -/** - * Call the netlify function to invoke the BrightId sponsor api - * @param userAddress user wallet address - * @returns sponsorship data or error - */ -async function netlifySponsor(userAddress: string): Promise { - const res = await fetch('/.netlify/functions/sponsor', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ userAddress }), - }) - - const json = await res.json() - if (res.status === 200) { - return json - } - - if (res.status === 400 && canIgnoreError(json.errorNum)) { - return { hash: '0x0' } - } - - // return the error - return json -} - -/** - * Sponsor a BrightID user using the sponsorship api - * @param userAddress user wallet address - * @returns sponsporship result or error - */ -export async function sponsorUser(userAddress: string): Promise { - if (brightIdSponsorKey) { - return brightIdSponsor(userAddress) - } - - try { - return await netlifySponsor(userAddress) - } catch (err) { - if (err instanceof Error) { - return { error: (err as Error).message } - } else { - return { error: 'Unknown sponsorhip error' } - } - } -} diff --git a/vue-app/src/api/cart.ts b/vue-app/src/api/cart.ts index c95993530..fd5c3ca62 100644 --- a/vue-app/src/api/cart.ts +++ b/vue-app/src/api/cart.ts @@ -17,15 +17,14 @@ export async function getCommittedCart( encryptionKey: string, contributorAddress: string, ): Promise { - const { coordinatorPubKey, fundingRoundAddress, voiceCreditFactor, nativeTokenDecimals, recipientRegistryAddress } = - round + const { coordinatorPubKey, maciAddress, voiceCreditFactor, nativeTokenDecimals, recipientRegistryAddress } = round const encKeypair = await Keypair.createFromSeed(encryptionKey) const sharedKey = Keypair.genEcdhSharedKey(encKeypair.privKey, coordinatorPubKey) const messages = await getContributorMessages({ - fundingRoundAddress, + maciAddress, contributorKey: encKeypair, coordinatorPubKey, contributorAddress, diff --git a/vue-app/src/api/contributions.ts b/vue-app/src/api/contributions.ts index 5e0618447..a6aed99ef 100644 --- a/vue-app/src/api/contributions.ts +++ b/vue-app/src/api/contributions.ts @@ -27,13 +27,13 @@ export interface Contributor { /** * get the id of the subgraph public key entity from the pubKey value - * @param fundingRoundAddress funding round address + * @param maciAddress MACI address * @param pubKey MACI public key * @returns the id for the subgraph public key entity */ -function getPubKeyId(fundingRoundAddress = '', pubKey: PubKey): string { +function getPubKeyId(maciAddress = '', pubKey: PubKey): string { const pubKeyPair = pubKey.asContractParam() - return id(fundingRoundAddress.toLowerCase() + '.' + pubKeyPair.x + '.' + pubKeyPair.y) + return id(maciAddress.toLowerCase() + '.' + pubKeyPair.x + '.' + pubKeyPair.y) } export function getCartStorageKey(roundAddress: string): string { @@ -141,17 +141,16 @@ export function isContributionAmountValid(value: string, currentRound: RoundInfo /** * Get the MACI contributor state index - * @param fundingRoundAddress Funding round contract address + * @param maciAddress MACI contract address * @param pubKey Contributor public key * @returns Contributor stateIndex returned from MACI */ -export async function getContributorIndex(fundingRoundAddress: string, pubKey: PubKey): Promise { - if (!fundingRoundAddress) { +export async function getContributorIndex(maciAddress: string, pubKey: PubKey): Promise { + if (!maciAddress) { return null } - const id = getPubKeyId(fundingRoundAddress, pubKey) + const id = getPubKeyId(maciAddress, pubKey) const data = await sdk.GetContributorIndex({ - fundingRoundAddress: fundingRoundAddress.toLowerCase(), publicKeyId: id, }) @@ -178,29 +177,28 @@ function getMaciMessage(type: any, data: any[] | null): Message { /** * Get the latest set of vote messages submitted by contributor - * @param fundingRoundAddress Funding round contract address + * @param maciAddress MACI contract address * @param contributorKey Contributor key used to encrypt messages * @param coordinatorPubKey Coordinator public key * @returns MACI messages */ export async function getContributorMessages({ - fundingRoundAddress, + maciAddress, contributorKey, coordinatorPubKey, contributorAddress, }: { - fundingRoundAddress: string + maciAddress: string contributorKey: Keypair coordinatorPubKey: PubKey contributorAddress: string }): Promise { - if (!fundingRoundAddress) { + if (!maciAddress) { return [] } - const key = getPubKeyId(fundingRoundAddress, contributorKey.pubKey) + const key = getPubKeyId(maciAddress, contributorKey.pubKey) const result = await sdk.GetContributorMessages({ - fundingRoundAddress: fundingRoundAddress.toLowerCase(), pubKey: key, contributorAddress: contributorAddress.toLowerCase(), }) diff --git a/vue-app/src/api/core.ts b/vue-app/src/api/core.ts index 19a8a283b..0de7568ae 100644 --- a/vue-app/src/api/core.ts +++ b/vue-app/src/api/core.ts @@ -45,6 +45,7 @@ export enum UserRegistryType { SIMPLE = 'simple', SNAPSHOT = 'snapshot', MERKLE = 'merkle', + SEMAPHORE = 'semaphore', } if (!Object.values(UserRegistryType).includes(userRegistryType as UserRegistryType)) { @@ -74,10 +75,8 @@ export const maxDecimals = Number(import.meta.env.VUE_APP_MAX_DECIMAL || 1) // the number of records per batch in the `pending submissions` export file export const exportBatchSize = Number(import.meta.env.VITE_EXPORT_BATCH_SIZE) || 60 -// BrightId sponsorhip stuff, set these parameters to automatically sponsor user using the brightId URL -export const brightIdSponsorKey = import.meta.env.VITE_BRIGHTID_SPONSOR_KEY +// BrightId url for querying user verification export const brightIdNodeUrl = import.meta.env.VITE_BRIGHTID_NODE_URL || 'https://brightid.clr.fund/brightid/v6' -export const brightIdSponsorUrl = import.meta.env.VITE_BRIGHTID_SPONSOR_API_URL // wait for data to sync with the subgraph export const MAX_WAIT_DEPTH = Number(import.meta.env.VITE_MAX_WAIT_DEPTH) || 15 @@ -94,7 +93,11 @@ export const showComplianceRequirement = /^yes$/i.test(import.meta.env.VITE_SHOW export const isBrightIdRequired = userRegistryType === 'brightid' export const isOptimisticRecipientRegistry = recipientRegistryType === 'optimistic' -export const isUserRegistrationRequired = userRegistryType !== UserRegistryType.SIMPLE +export const isUserRegistrationRequired = [ + UserRegistryType.BRIGHT_ID, + UserRegistryType.MERKLE, + UserRegistryType.SNAPSHOT, +].includes(userRegistryType) // Try to get the next scheduled start date const nextStartDate = import.meta.env.VITE_NEXT_ROUND_START_DATE diff --git a/vue-app/src/api/projects.ts b/vue-app/src/api/projects.ts index 2795ad82e..4df82be0b 100644 --- a/vue-app/src/api/projects.ts +++ b/vue-app/src/api/projects.ts @@ -14,9 +14,8 @@ export interface LeaderboardProject { id: string // Address or another ID depending on registry implementation name: string index: number - bannerImageUrl?: string - thumbnailImageUrl?: string - imageUrl?: string + bannerImageHash?: string + thumbnailImageHash?: string allocatedAmount: bigint votes: bigint donation: bigint @@ -39,9 +38,8 @@ export interface Project { websiteUrl?: string twitterUrl?: string discordUrl?: string - bannerImageUrl?: string - thumbnailImageUrl?: string - imageUrl?: string // TODO remove + bannerImageHash?: string + thumbnailImageHash?: string index: number isHidden: boolean // Hidden from the list (does not participate in round) isLocked: boolean // Visible, but contributions are not allowed @@ -134,17 +132,13 @@ export async function getProjectByIndex( metadata = {} } - const thumbnailImageUrl = metadata.thumbnailImageHash - ? `${ipfsGatewayUrl}/ipfs/${metadata.thumbnailImageHash}` - : `${ipfsGatewayUrl}/ipfs/${metadata.imageUrl}` - return { id: recipient.id, address: recipient.recipientAddress || '', name: metadata.name, description: metadata.description, tagline: metadata.tagline, - thumbnailImageUrl, + thumbnailImageHash: metadata.thumbnailImageHash || metadata.imageHash, index: recipient.recipientIndex, } } @@ -182,12 +176,12 @@ export async function getRecipientIdByHash(transactionHash: string): Promise project.id === projectId) const metadata = project.metadata - const thumbnailHash = metadata.thumbnailImageHash || metadata.imageHash - const thumbnailImageUrl = thumbnailHash ? `${ipfsGatewayUrl}/ipfs/${thumbnailHash}` : undefined - const bannerHash = metadata.bannerImageHash || metadata.imageHash - const bannerImageUrl = bannerHash ? `${ipfsGatewayUrl}/ipfs/${bannerHash}` : undefined + const thumbnailImageHash = metadata.thumbnailImageHash || metadata.imageHash + const bannerImageHash = metadata.bannerImageHash || metadata.imageHash return { id: project.id, @@ -228,8 +220,8 @@ export async function getLeaderboardProject( websiteUrl: metadata.websiteUrl, twitterUrl: metadata.twitterUrl, discordUrl: metadata.discordUrl, - thumbnailImageUrl, - bannerImageUrl, + thumbnailImageHash, + bannerImageHash, index: project.recipientIndex, isHidden: false, // always show leaderboard project isLocked: true, // Visible, but contributions are not allowed @@ -254,8 +246,8 @@ export function formToProjectInterface(data: RecipientApplicationData): Project websiteUrl: links.website, twitterUrl: links.twitter, discordUrl: links.discord, - bannerImageUrl: `${ipfsGatewayUrl}/ipfs/${image.bannerHash}`, - thumbnailImageUrl: `${ipfsGatewayUrl}/ipfs/${image.thumbnailHash}`, + bannerImageHash: image.bannerHash, + thumbnailImageHash: image.thumbnailHash, index: 0, isHidden: false, isLocked: true, @@ -284,8 +276,8 @@ export function staticDataToProjectInterface(project: any): Project { websiteUrl: project.metadata.websiteUrl, twitterUrl: project.metadata.twitterUrl, discordUrl: project.discordUrl, - bannerImageUrl: `${ipfsGatewayUrl}/ipfs/${project.metadata.bannerImageHash}`, - thumbnailImageUrl: `${ipfsGatewayUrl}/ipfs/${project.metadata.thumbnailImageHash}`, + bannerImageHash: project.metadata.bannerImageHash, + thumbnailImageHash: project.metadata.thumbnailImageHash, index: project.recipientIndex, isHidden: project.state !== 'Accepted', isLocked: false, diff --git a/vue-app/src/api/recipient-registry-kleros.ts b/vue-app/src/api/recipient-registry-kleros.ts index 7ab229938..943a6e149 100644 --- a/vue-app/src/api/recipient-registry-kleros.ts +++ b/vue-app/src/api/recipient-registry-kleros.ts @@ -39,7 +39,7 @@ function decodeTcrItemData( address: string name: string description: string - imageUrl: string + imageHash: string } { // Disable console.error to ignore parser errors /* eslint-disable no-console */ @@ -52,7 +52,7 @@ function decodeTcrItemData( address: decodedMetadata[1] as string, name: decodedMetadata[0] as string, description: decodedMetadata[3] as string, - imageUrl: `${ipfsGatewayUrl}${decodedMetadata[2]}`, + imageHash: decodedMetadata[2] as string, } } diff --git a/vue-app/src/api/recipient-registry-optimistic.ts b/vue-app/src/api/recipient-registry-optimistic.ts index 1baf6610f..70cb01bad 100644 --- a/vue-app/src/api/recipient-registry-optimistic.ts +++ b/vue-app/src/api/recipient-registry-optimistic.ts @@ -70,8 +70,8 @@ export enum RequestStatus { interface RecipientMetadata { name: string description: string - imageUrl: string - thumbnailImageUrl: string + imageHash: string + thumbnailImageHash: string } export interface Request { @@ -155,10 +155,8 @@ export async function getRequests(registryInfo: RegistryInfo, registryAddress: s metadata = { name, description, - imageUrl: `${ipfsGatewayUrl}/ipfs/${imageHash}`, - thumbnailImageUrl: thumbnailImageHash - ? `${ipfsGatewayUrl}/ipfs/${thumbnailImageHash}` - : `${ipfsGatewayUrl}/ipfs/${imageHash}`, + imageHash: imageHash, + thumbnailImageHash: thumbnailImageHash, } } @@ -213,9 +211,6 @@ function decodeProject(recipient: Partial): Project { const metadata = JSON.parse(recipient.recipientMetadata || '') - // imageUrl is the legacy form property - fall back to this if bannerImageHash or thumbnailImageHash don't exist - const imageUrl = `${ipfsGatewayUrl}/ipfs/${metadata.imageHash}` - let requester if (recipient.requester) { requester = recipient.requester @@ -227,7 +222,6 @@ function decodeProject(recipient: Partial): Project { requester, name: metadata.name, description: metadata.description, - imageUrl, // Only unregistered project can have invalid index 0 index: 0, isHidden: false, @@ -246,8 +240,8 @@ function decodeProject(recipient: Partial): Project { websiteUrl: metadata.websiteUrl, twitterUrl: metadata.twitterUrl, discordUrl: metadata.discordUrl, - bannerImageUrl: metadata.bannerImageHash ? `${ipfsGatewayUrl}/ipfs/${metadata.bannerImageHash}` : imageUrl, - thumbnailImageUrl: metadata.thumbnailImageHash ? `${ipfsGatewayUrl}/ipfs/${metadata.thumbnailImageHash}` : imageUrl, + bannerImageHash: metadata.bannerImageHash || metadata.imageHash, + thumbnailImageHash: metadata.thumbnailImageHash || metadata.imageHash, } } diff --git a/vue-app/src/api/recipient-registry-simple.ts b/vue-app/src/api/recipient-registry-simple.ts index 852e85f4c..fd988e92a 100644 --- a/vue-app/src/api/recipient-registry-simple.ts +++ b/vue-app/src/api/recipient-registry-simple.ts @@ -6,6 +6,7 @@ import { provider, ipfsGatewayUrl } from './core' import type { Project } from './projects' import type { RegistryInfo, RecipientApplicationData } from './types' import { formToRecipientData } from './recipient' +import { getNumber } from 'ethers' function decodeRecipientAdded(event: EventLog): Project { const args = event.args as any @@ -26,9 +27,9 @@ function decodeRecipientAdded(event: EventLog): Project { websiteUrl: metadata.websiteUrl, twitterUrl: metadata.twitterUrl, discordUrl: metadata.discordUrl, - bannerImageUrl: `${ipfsGatewayUrl}/ipfs/${metadata.bannerImageHash}`, - thumbnailImageUrl: `${ipfsGatewayUrl}/ipfs/${metadata.thumbnailImageHash}`, - index: args._index.toNumber(), + bannerImageHash: metadata.bannerImageHash, + thumbnailImageHash: metadata.thumbnailImageHash, + index: getNumber(args._index), isHidden: false, isLocked: false, } 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/api/tally.ts b/vue-app/src/api/tally.ts index 1280796dd..fa1c3b156 100644 --- a/vue-app/src/api/tally.ts +++ b/vue-app/src/api/tally.ts @@ -1,16 +1,29 @@ import { Contract } from 'ethers' import { FundingRound } from './abi' -import { provider, ipfsGatewayUrl } from './core' +import { provider, chain } from './core' // https://github.com/webpack/webpack/issues/7378#issuecomment-683891615 import type { Tally } from '@clrfund/common' +import { getIpfsUrl } from '@/utils/url' +import { getLeaderboardRoundInfo } from './round' export { Tally } export async function getTally(fundingRoundAddress: string): Promise { const fundingRound = new Contract(fundingRoundAddress, FundingRound, provider) const tallyHash = await fundingRound.tallyHash() - const response = await fetch(`${ipfsGatewayUrl}/ipfs/${tallyHash}`) - return await response.json() + + try { + // try to get the tally file statically first, if not found, try the IPFS gateway + const round = await getLeaderboardRoundInfo(fundingRoundAddress, chain.name) + if (round?.tally) { + return round.tally + } else { + throw new Error('No tally data, get from IPFS gateway') + } + } catch { + const response = await fetch(getIpfsUrl(tallyHash) || '') + return await response.json() + } } diff --git a/vue-app/src/components.d.ts b/vue-app/src/components.d.ts index 5f037b3de..49476f1ad 100644 --- a/vue-app/src/components.d.ts +++ b/vue-app/src/components.d.ts @@ -36,6 +36,7 @@ declare module '@vue/runtime-core' { Info: typeof import('./components/Info.vue')['default'] InputButton: typeof import('./components/InputButton.vue')['default'] IpfsCopyWidget: typeof import('./components/IpfsCopyWidget.vue')['default'] + IpfsImage: typeof import('./components/IpfsImage.vue')['default'] IpfsImageUpload: typeof import('./components/IpfsImageUpload.vue')['default'] LayoutSteps: typeof import('./components/LayoutSteps.vue')['default'] LeaderboardDetailView: typeof import('./components/LeaderboardDetailView.vue')['default'] diff --git a/vue-app/src/components/CallToActionCard.vue b/vue-app/src/components/CallToActionCard.vue index 7d094a1a6..2d29121d7 100644 --- a/vue-app/src/components/CallToActionCard.vue +++ b/vue-app/src/components/CallToActionCard.vue @@ -55,7 +55,7 @@ import { computed } from 'vue' import BrightIdWidget from '@/components/BrightIdWidget.vue' import Links from '@/components/Links.vue' -import { userRegistryType, UserRegistryType, isBrightIdRequired } from '@/api/core' +import { isUserRegistrationRequired, isBrightIdRequired } from '@/api/core' import { useAppStore, useUserStore } from '@/stores' import { storeToRefs } from 'pinia' @@ -69,7 +69,7 @@ const hasStartedVerification = computed( ) const showUserVerification = computed(() => { return ( - userRegistryType !== UserRegistryType.SIMPLE && + isUserRegistrationRequired && currentRound.value && currentUser.value?.isRegistered !== undefined && !currentUser.value.isRegistered diff --git a/vue-app/src/components/Cart.vue b/vue-app/src/components/Cart.vue index 0f0e7de4f..09d8bb3b4 100644 --- a/vue-app/src/components/Cart.vue +++ b/vue-app/src/components/Cart.vue @@ -226,7 +226,7 @@ import CartItems from '@/components/CartItems.vue' import Links from '@/components/Links.vue' import TimeLeft from '@/components/TimeLeft.vue' import { MAX_CONTRIBUTION_AMOUNT, MAX_CART_SIZE, type CartItem, isContributionAmountValid } from '@/api/contributions' -import { userRegistryType, UserRegistryType, operator } from '@/api/core' +import { userRegistryType, UserRegistryType, operator, isUserRegistrationRequired } from '@/api/core' import { RoundStatus } from '@/api/round' import { formatAmount as _formatAmount } from '@/utils/amounts' import FundsNeededWarning from '@/components/FundsNeededWarning.vue' @@ -466,18 +466,16 @@ const isBrightIdRequired = computed( () => userRegistryType === UserRegistryType.BRIGHT_ID && !currentUser.value?.isRegistered, ) -const isRegistrationRequired = computed( - () => userRegistryType !== UserRegistryType.SIMPLE && !currentUser.value?.isRegistered, -) +const isRegistrationRequired = computed(() => isUserRegistrationRequired && !currentUser.value?.isRegistered) const errorMessage = computed(() => { if (isMessageLimitReached.value) return t('dynamic.cart.error.reached_contribution_limit') if (!currentUser.value) return t('dynamic.cart.error.connect_wallet') if (isBrightIdRequired.value) return t('dynamic.cart.error.need_to_setup_brightid') if (!currentUser.value.isRegistered) { - return userRegistryType === UserRegistryType.SIMPLE - ? t('dynamic.cart.error.user_not_registered', { operator }) - : t('dynamic.cart.error.need_to_register') + return isUserRegistrationRequired + ? t('dynamic.cart.error.need_to_register') + : t('dynamic.cart.error.user_not_registered', { operator }) } if (!isFormValid()) return t('dynamic.cart.error.invalid_contribution_amount') if (cart.value.length > MAX_CART_SIZE) diff --git a/vue-app/src/components/CartItems.vue b/vue-app/src/components/CartItems.vue index 915a09f58..e2081542e 100644 --- a/vue-app/src/components/CartItems.vue +++ b/vue-app/src/components/CartItems.vue @@ -10,7 +10,7 @@ >
- + {{ item.name }} diff --git a/vue-app/src/components/IpfsImage.vue b/vue-app/src/components/IpfsImage.vue new file mode 100644 index 000000000..54cb3b5c8 --- /dev/null +++ b/vue-app/src/components/IpfsImage.vue @@ -0,0 +1,25 @@ + + + diff --git a/vue-app/src/components/LeaderboardSimpleView.vue b/vue-app/src/components/LeaderboardSimpleView.vue index 5ffe65266..6b58894f1 100644 --- a/vue-app/src/components/LeaderboardSimpleView.vue +++ b/vue-app/src/components/LeaderboardSimpleView.vue @@ -25,7 +25,7 @@
- +
{{ project.name }} @@ -79,14 +79,6 @@ function formatAllocationAmount(amount?: bigint): string { return amount ? formatAmount(amount, tokenDecimals, null, 0) : '0' } -const projectImageUrl = computed(() => { - if (typeof props.project.imageUrl !== 'undefined') { - return props.project.imageUrl - } - - return null -}) - const tokenSymbol = computed(() => { return props.round.nativeTokenSymbol }) diff --git a/vue-app/src/components/ProjectListItem.vue b/vue-app/src/components/ProjectListItem.vue index 009502493..04282b1da 100644 --- a/vue-app/src/components/ProjectListItem.vue +++ b/vue-app/src/components/ProjectListItem.vue @@ -3,7 +3,7 @@
- +
{{ $t(categoryLocaleKey(project.category)) }}
@@ -37,6 +37,7 @@ import { useRoute, type RouteLocationRaw } from 'vue-router' import { useAppStore } from '@/stores' import { storeToRefs } from 'pinia' import { isActiveApp } from '@/api/core' +import IpfsImage from './IpfsImage.vue' const route = useRoute() const appStore = useAppStore() @@ -55,16 +56,6 @@ const descriptionHtml = computed(() => { return markdown.renderInline(props.project.description) }) -const projectImageUrl = computed(() => { - if (typeof props.project.bannerImageUrl !== 'undefined') { - return props.project.bannerImageUrl - } - if (typeof props.project.imageUrl !== 'undefined') { - return props.project.imageUrl - } - return null -}) - const inCart = computed(() => { const index = appStore.cart.findIndex((item: CartItem) => { // Ignore cleared items diff --git a/vue-app/src/components/ProjectProfile.vue b/vue-app/src/components/ProjectProfile.vue index 1bdc167ed..eaa233143 100644 --- a/vue-app/src/components/ProjectProfile.vue +++ b/vue-app/src/components/ProjectProfile.vue @@ -1,7 +1,7 @@