diff --git a/contracts/contracts/ClrFund.sol b/contracts/contracts/ClrFund.sol index 36fd7e85b..4b99f2c8c 100644 --- a/contracts/contracts/ClrFund.sol +++ b/contracts/contracts/ClrFund.sol @@ -6,15 +6,9 @@ import '@openzeppelin/contracts/token/ERC20/ERC20.sol'; import '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol'; import '@openzeppelin/contracts/utils/structs/EnumerableSet.sol'; -// Import ownable from OpenZeppelin contracts - -import {MACI} from 'maci-contracts/contracts/MACI.sol'; -import {SnarkCommon} from 'maci-contracts/contracts/crypto/SnarkCommon.sol'; import {SignUpGatekeeper} from "maci-contracts/contracts/gatekeepers/SignUpGatekeeper.sol"; import {InitialVoiceCreditProxy} from "maci-contracts/contracts/initialVoiceCreditProxy/InitialVoiceCreditProxy.sol"; -import {Params} from 'maci-contracts/contracts/Params.sol'; import {PollFactory} from 'maci-contracts/contracts/Poll.sol'; -import {DomainObjs} from 'maci-contracts/contracts/DomainObjs.sol'; import './userRegistry/IUserRegistry.sol'; import './recipientRegistry/IRecipientRegistry.sol'; @@ -22,7 +16,7 @@ import './MACIFactory.sol'; import './FundingRound.sol'; import './OwnableUpgradeable.sol'; -contract ClrFund is OwnableUpgradeable, SnarkCommon, Params, DomainObjs { +contract ClrFund is OwnableUpgradeable, IPubKey, SnarkCommon { using EnumerableSet for EnumerableSet.AddressSet; using SafeERC20 for ERC20; @@ -46,6 +40,19 @@ contract ClrFund is OwnableUpgradeable, SnarkCommon, Params, DomainObjs { event TokenChanged(address _token); event CoordinatorChanged(address _coordinator); + // errors + error FundingSourceAlreadyAdded(); + error FundingSourceNotFound(); + error AlreadyFinalized(); + error NotFinalized(); + error NotAuthorized(); + error NoCurrentRound(); + error NoCoordinator(); + error NoToken(); + error NoRecipientRegistry(); + error NoUserRegistry(); + error NotOwnerOfMaciFactory(); + function init( MACIFactory _maciFactory ) @@ -88,7 +95,9 @@ contract ClrFund is OwnableUpgradeable, SnarkCommon, Params, DomainObjs { onlyOwner { bool result = fundingSources.add(_source); - require(result, 'Factory: Funding source already added'); + if (!result) { + revert FundingSourceAlreadyAdded(); + } emit FundingSourceAdded(_source); } @@ -101,7 +110,9 @@ contract ClrFund is OwnableUpgradeable, SnarkCommon, Params, DomainObjs { onlyOwner { bool result = fundingSources.remove(_source); - require(result, 'Factory: Funding source not found'); + if (!result) { + revert FundingSourceNotFound(); + } emit FundingSourceRemoved(_source); } @@ -117,39 +128,55 @@ contract ClrFund is OwnableUpgradeable, SnarkCommon, Params, DomainObjs { } function setMaciParameters( - uint8 _messageTreeDepth, - uint8 _voteOptionTreeDepth - ) + uint8 stateTreeDepth, + uint8 intStateTreeDepth, + uint8 messageTreeSubDepth, + uint8 messageTreeDepth, + uint8 voteOptionTreeDepth, + uint256 maxMessages, + uint256 maxVoteOptions, + uint256 messageBatchSize, + VerifyingKey calldata processVk, + VerifyingKey calldata tallyVk + ) external - onlyOwner + onlyCoordinator { maciFactory.setMaciParameters( - _messageTreeDepth, - _voteOptionTreeDepth + stateTreeDepth, + intStateTreeDepth, + messageTreeSubDepth, + messageTreeDepth, + voteOptionTreeDepth, + maxMessages, + maxVoteOptions, + messageBatchSize, + processVk, + tallyVk ); } - /** + /** * @dev Deploy new funding round. * @param duration The poll duration in seconds - * @param vkRegistry The VkRegistry contract address */ - function deployNewRound(uint256 duration, address vkRegistry, address pollFactory, TreeDepths calldata treeDepths) + function deployNewRound(uint256 duration) external onlyOwner + requireToken + requireCoordinator + requireRecipientRegistry + requireUserRegistry { - require(maciFactory.owner() == address(this), 'Factory: MACI factory is not owned by FR factory'); - require(address(userRegistry) != address(0), 'Factory: User registry is not set'); - require(address(recipientRegistry) != address(0), 'Factory: Recipient registry is not set'); - require(address(nativeToken) != address(0), 'Factory: Native token is not set'); - require(coordinator != address(0), 'Factory: No coordinator'); + if (maciFactory.owner() != address(this)) { + revert NotOwnerOfMaciFactory(); + } FundingRound currentRound = getCurrentRound(); - require( - address(currentRound) == address(0) || currentRound.isFinalized(), - 'Factory: Current round is not finalized' - ); + if (address(currentRound) != address(0) && !currentRound.isFinalized()) { + revert NotFinalized(); + } // Make sure that the max number of recipients is set correctly - (uint256 maxMessages, uint256 maxVoteOptions) = maciFactory.maxValues(); + (, uint256 maxVoteOptions) = maciFactory.maxValues(); recipientRegistry.setMaxRecipients(maxVoteOptions); // Deploy funding round and MACI contracts FundingRound newRound = new FundingRound( @@ -159,18 +186,16 @@ contract ClrFund is OwnableUpgradeable, SnarkCommon, Params, DomainObjs { coordinator ); rounds.push(newRound); + MACI maci = maciFactory.deployMaci( SignUpGatekeeper(newRound), InitialVoiceCreditProxy(newRound), - vkRegistry, - pollFactory, - address(nativeToken) + address(nativeToken), + duration, + coordinatorPubKey ); - MaxValues memory maxValues = MaxValues(maxMessages, maxVoteOptions); - maci.deployPoll(duration, maxValues, treeDepths, coordinatorPubKey); newRound.setMaci(maci); - emit RoundStarted(address(newRound)); } @@ -206,7 +231,8 @@ contract ClrFund is OwnableUpgradeable, SnarkCommon, Params, DomainObjs { onlyOwner { FundingRound currentRound = getCurrentRound(); - require(address(currentRound) != address(0), 'Factory: Funding round has not been deployed'); + requireCurrentRound(currentRound); + ERC20 roundToken = currentRound.nativeToken(); // Factory contract is the default funding source uint256 matchingPoolSize = roundToken.balanceOf(address(this)); @@ -235,8 +261,12 @@ contract ClrFund is OwnableUpgradeable, SnarkCommon, Params, DomainObjs { onlyOwner { FundingRound currentRound = getCurrentRound(); - require(address(currentRound) != address(0), 'Factory: Funding round has not been deployed'); - require(!currentRound.isFinalized(), 'Factory: Current round is finalized'); + requireCurrentRound(currentRound); + + if (currentRound.isFinalized()) { + revert AlreadyFinalized(); + } + currentRound.cancel(); emit RoundFinalized(address(currentRound)); } @@ -287,7 +317,43 @@ contract ClrFund is OwnableUpgradeable, SnarkCommon, Params, DomainObjs { } modifier onlyCoordinator() { - require(msg.sender == coordinator, 'Factory: Sender is not the coordinator'); + if (msg.sender != coordinator) { + revert NotAuthorized(); + } + _; + } + + function requireCurrentRound(FundingRound currentRound) private { + if (address(currentRound) == address(0)) { + revert NoCurrentRound(); + } + } + + modifier requireToken() { + if (address(nativeToken) == address(0)) { + revert NoToken(); + } + _; + } + + modifier requireCoordinator() { + if (coordinator == address(0)) { + revert NoCoordinator(); + } + _; + } + + modifier requireUserRegistry() { + if (address(userRegistry) == address(0)) { + revert NoUserRegistry(); + } + _; + } + + modifier requireRecipientRegistry() { + if (address(recipientRegistry) == address(0)) { + revert NoRecipientRegistry(); + } _; } } diff --git a/contracts/contracts/FundingRoundFactory.sol b/contracts/contracts/FundingRoundFactory.sol index ad82c1f2f..9cc55ef64 100644 --- a/contracts/contracts/FundingRoundFactory.sol +++ b/contracts/contracts/FundingRoundFactory.sol @@ -8,21 +8,33 @@ import '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol'; import '@openzeppelin/contracts/utils/structs/EnumerableSet.sol'; import {MACI} from 'maci-contracts/contracts/MACI.sol'; -import {SignUpGatekeeper} from "maci-contracts/contracts/gatekeepers/SignUpGatekeeper.sol"; -import {InitialVoiceCreditProxy} from "maci-contracts/contracts/initialVoiceCreditProxy/InitialVoiceCreditProxy.sol"; +import {SignUpGatekeeper} from 'maci-contracts/contracts/gatekeepers/SignUpGatekeeper.sol'; +import {InitialVoiceCreditProxy} from 'maci-contracts/contracts/initialVoiceCreditProxy/InitialVoiceCreditProxy.sol'; +import {IPubKey} from 'maci-contracts/contracts/DomainObjs.sol'; import {SnarkCommon} from 'maci-contracts/contracts/crypto/SnarkCommon.sol'; -import {Params} from 'maci-contracts/contracts/Params.sol'; -import {DomainObjs} from 'maci-contracts/contracts/DomainObjs.sol'; import './userRegistry/IUserRegistry.sol'; import './recipientRegistry/IRecipientRegistry.sol'; import './MACIFactory.sol'; import './FundingRound.sol'; -contract FundingRoundFactory is Ownable, SnarkCommon, Params, DomainObjs { +contract FundingRoundFactory is Ownable, IPubKey, SnarkCommon { using EnumerableSet for EnumerableSet.AddressSet; using SafeERC20 for ERC20; + // errors + error FundingSourceAlreadyAdded(); + error FundingSourceNotFound(); + error AlreadyFinalized(); + error NotFinalized(); + error NotAuthorized(); + error NoCurrentRound(); + error NoCoordinator(); + error NoToken(); + error NoRecipientRegistry(); + error NoUserRegistry(); + error NotOwnerOfMaciFactory(); + // State address public coordinator; @@ -83,7 +95,9 @@ contract FundingRoundFactory is Ownable, SnarkCommon, Params, DomainObjs { onlyOwner { bool result = fundingSources.add(_source); - require(result, 'Factory: Funding source already added'); + if (!result) { + revert FundingSourceAlreadyAdded(); + } emit FundingSourceAdded(_source); } @@ -96,7 +110,9 @@ contract FundingRoundFactory is Ownable, SnarkCommon, Params, DomainObjs { onlyOwner { bool result = fundingSources.remove(_source); - require(result, 'Factory: Funding source not found'); + if (!result) { + revert FundingSourceNotFound(); + } emit FundingSourceRemoved(_source); } @@ -112,39 +128,55 @@ contract FundingRoundFactory is Ownable, SnarkCommon, Params, DomainObjs { } function setMaciParameters( - uint8 _messageTreeDepth, - uint8 _voteOptionTreeDepth + uint8 stateTreeDepth, + uint8 intStateTreeDepth, + uint8 messageTreeSubDepth, + uint8 messageTreeDepth, + uint8 voteOptionTreeDepth, + uint256 maxMessages, + uint256 maxVoteOptions, + uint256 messageBatchSize, + VerifyingKey calldata processVk, + VerifyingKey calldata tallyVk ) external - onlyOwner + onlyCoordinator { maciFactory.setMaciParameters( - _messageTreeDepth, - _voteOptionTreeDepth + stateTreeDepth, + intStateTreeDepth, + messageTreeSubDepth, + messageTreeDepth, + voteOptionTreeDepth, + maxMessages, + maxVoteOptions, + messageBatchSize, + processVk, + tallyVk ); } /** * @dev Deploy new funding round. * @param duration The poll duration in seconds - * @param vkRegistry The VkRegistry contract address */ - function deployNewRound(uint256 duration, address vkRegistry, address pollFactory, TreeDepths calldata treeDepths) + function deployNewRound(uint256 duration) external onlyOwner + requireToken + requireCoordinator + requireRecipientRegistry + requireUserRegistry { - require(maciFactory.owner() == address(this), 'Factory: MACI factory is not owned by FR factory'); - require(address(userRegistry) != address(0), 'Factory: User registry is not set'); - require(address(recipientRegistry) != address(0), 'Factory: Recipient registry is not set'); - require(address(nativeToken) != address(0), 'Factory: Native token is not set'); - require(coordinator != address(0), 'Factory: No coordinator'); + if (maciFactory.owner() != address(this)) { + revert NotOwnerOfMaciFactory(); + } FundingRound currentRound = getCurrentRound(); - require( - address(currentRound) == address(0) || currentRound.isFinalized(), - 'Factory: Current round is not finalized' - ); + if (address(currentRound) != address(0) && !currentRound.isFinalized()) { + revert NotFinalized(); + } // Make sure that the max number of recipients is set correctly - (uint256 maxMessages, uint256 maxVoteOptions) = maciFactory.maxValues(); + (, uint256 maxVoteOptions) = maciFactory.maxValues(); recipientRegistry.setMaxRecipients(maxVoteOptions); // Deploy funding round and MACI contracts FundingRound newRound = new FundingRound( @@ -158,14 +190,11 @@ contract FundingRoundFactory is Ownable, SnarkCommon, Params, DomainObjs { MACI maci = maciFactory.deployMaci( SignUpGatekeeper(newRound), InitialVoiceCreditProxy(newRound), - vkRegistry, - pollFactory, - address(nativeToken) + address(nativeToken), + duration, + coordinatorPubKey ); - MaxValues memory maxValues = MaxValues(maxMessages, maxVoteOptions); - maci.deployPoll(duration, maxValues, treeDepths, coordinatorPubKey); - newRound.setMaci(maci); emit RoundStarted(address(newRound)); } @@ -202,7 +231,8 @@ contract FundingRoundFactory is Ownable, SnarkCommon, Params, DomainObjs { onlyOwner { FundingRound currentRound = getCurrentRound(); - require(address(currentRound) != address(0), 'Factory: Funding round has not been deployed'); + requireCurrentRound(currentRound); + ERC20 roundToken = currentRound.nativeToken(); // Factory contract is the default funding source uint256 matchingPoolSize = roundToken.balanceOf(address(this)); @@ -231,8 +261,12 @@ contract FundingRoundFactory is Ownable, SnarkCommon, Params, DomainObjs { onlyOwner { FundingRound currentRound = getCurrentRound(); - require(address(currentRound) != address(0), 'Factory: Funding round has not been deployed'); - require(!currentRound.isFinalized(), 'Factory: Current round is finalized'); + requireCurrentRound(currentRound); + + if (currentRound.isFinalized()) { + revert AlreadyFinalized(); + } + currentRound.cancel(); emit RoundFinalized(address(currentRound)); } @@ -283,7 +317,43 @@ contract FundingRoundFactory is Ownable, SnarkCommon, Params, DomainObjs { } modifier onlyCoordinator() { - require(msg.sender == coordinator, 'Factory: Sender is not the coordinator'); + if (msg.sender != coordinator) { + revert NotAuthorized(); + } + _; + } + + function requireCurrentRound(FundingRound currentRound) private { + if (address(currentRound) == address(0)) { + revert NoCurrentRound(); + } + } + + modifier requireToken() { + if (address(nativeToken) == address(0)) { + revert NoToken(); + } + _; + } + + modifier requireCoordinator() { + if (coordinator == address(0)) { + revert NoCoordinator(); + } + _; + } + + modifier requireUserRegistry() { + if (address(userRegistry) == address(0)) { + revert NoUserRegistry(); + } + _; + } + + modifier requireRecipientRegistry() { + if (address(recipientRegistry) == address(0)) { + revert NoRecipientRegistry(); + } _; } } diff --git a/contracts/contracts/MACIFactory.sol b/contracts/contracts/MACIFactory.sol index a13e6cba7..de886d310 100644 --- a/contracts/contracts/MACIFactory.sol +++ b/contracts/contracts/MACIFactory.sol @@ -10,31 +10,111 @@ import {TopupCredit} from 'maci-contracts/contracts/TopupCredit.sol'; import {VkRegistry} from 'maci-contracts/contracts/VkRegistry.sol'; import {SnarkCommon} from 'maci-contracts/contracts/crypto/SnarkCommon.sol'; import {Ownable} from '@openzeppelin/contracts/access/Ownable.sol'; -import {Params} from "maci-contracts/contracts/Params.sol"; - -contract MACIFactory is Ownable, Params, SnarkCommon { - // Constants - uint256 private constant MESSAGE_TREE_BASE = 2; - uint256 private constant VOTE_OPTION_TREE_BASE = 5; +import {Params} from 'maci-contracts/contracts/Params.sol'; +import {PollFactoryCreator} from './PollFactoryCreator.sol'; +import {VkRegistryCreator} from './VkRegistryCreator.sol'; +import {IPubKey} from 'maci-contracts/contracts/DomainObjs.sol'; +contract MACIFactory is Ownable, Params, SnarkCommon, IPubKey { // States + bool public initialized = false; + uint8 public stateTreeDepth; + + // treeDepths + TreeDepths public treeDepths; + + VerifyingKey public processVk; + VerifyingKey public tallyVk; + + // max values MaxValues public maxValues; + uint256 public messageBatchSize; // Events event MaciParametersChanged(); event MaciDeployed(address _maci); + // errors + error NotInitialized(); + + function init( + uint8 _stateTreeDepth, + uint8 _intStateTreeDepth, + uint8 _messageTreeSubDepth, + uint8 _messageTreeDepth, + uint8 _voteOptionTreeDepth, + uint256 _maxMessages, + uint256 _maxVoteOptions, + uint256 _messageBatchSize, + VerifyingKey calldata _processVk, + VerifyingKey calldata _tallyVk + ) + external + { + _setMaciParameters( + _stateTreeDepth, + _intStateTreeDepth, + _messageTreeSubDepth, + _messageTreeDepth, + _voteOptionTreeDepth, + _maxMessages, + _maxVoteOptions, + _messageBatchSize, + _processVk, + _tallyVk + ); + initialized = true; + } + function setMaciParameters( + uint8 _stateTreeDepth, + uint8 _intStateTreeDepth, + uint8 _messageTreeSubDepth, uint8 _messageTreeDepth, - uint8 _voteOptionTreeDepth + uint8 _voteOptionTreeDepth, + uint256 _maxMessages, + uint256 _maxVoteOptions, + uint256 _messageBatchSize, + VerifyingKey calldata _processVk, + VerifyingKey calldata _tallyVk ) external - onlyOwner { - maxValues = MaxValues( - MESSAGE_TREE_BASE ** _messageTreeDepth - 1, - VOTE_OPTION_TREE_BASE ** _voteOptionTreeDepth - 1 + _setMaciParameters( + _stateTreeDepth, + _intStateTreeDepth, + _messageTreeSubDepth, + _messageTreeDepth, + _voteOptionTreeDepth, + _maxMessages, + _maxVoteOptions, + _messageBatchSize, + _processVk, + _tallyVk ); + } + + function _setMaciParameters( + uint8 _stateTreeDepth, + uint8 _intStateTreeDepth, + uint8 _messageTreeSubDepth, + uint8 _messageTreeDepth, + uint8 _voteOptionTreeDepth, + uint256 _maxMessages, + uint256 _maxVoteOptions, + uint256 _messageBatchSize, + VerifyingKey calldata _processVk, + VerifyingKey calldata _tallyVk + ) + internal + onlyOwner + { + stateTreeDepth = _stateTreeDepth; + treeDepths = TreeDepths(_intStateTreeDepth, _messageTreeSubDepth, _messageTreeDepth, _voteOptionTreeDepth); + maxValues = MaxValues(_maxMessages, _maxVoteOptions); + messageBatchSize = _messageBatchSize; + processVk = _processVk; + tallyVk = _tallyVk; emit MaciParametersChanged(); } @@ -45,17 +125,19 @@ contract MACIFactory is Ownable, Params, SnarkCommon { function deployMaci( SignUpGatekeeper signUpGatekeeper, InitialVoiceCreditProxy initialVoiceCreditProxy, - address vkRegistry, - address pollFactoryAddress, - address topupCredit + address topupCredit, + uint256 duration, + PubKey calldata coordinatorPubKey ) external onlyOwner returns (MACI _maci) { - require(topupCredit != address(0), 'MACIFactory: topupCredit address cannot be zero'); + if (!initialized ) { + revert NotInitialized(); + } - PollFactory pollFactory = PollFactory(pollFactoryAddress); + PollFactory pollFactory = PollFactoryCreator.create(); _maci = new MACI( pollFactory, signUpGatekeeper, @@ -64,8 +146,21 @@ contract MACIFactory is Ownable, Params, SnarkCommon { pollFactory.transferOwnership(address(_maci)); + VkRegistry vkRegistry = VkRegistryCreator.create(); + vkRegistry.setVerifyingKeys( + stateTreeDepth, + treeDepths.intStateTreeDepth, + treeDepths.messageTreeDepth, + treeDepths.voteOptionTreeDepth, + messageBatchSize, + processVk, + tallyVk + ); + MessageAqFactory messageAqFactory = pollFactory.messageAqFactory(); - _maci.init(VkRegistry(vkRegistry), messageAqFactory, TopupCredit(topupCredit)); + _maci.init(vkRegistry, messageAqFactory, TopupCredit(topupCredit)); + + _maci.deployPoll(duration, maxValues, treeDepths, coordinatorPubKey); emit MaciDeployed(address(_maci)); } diff --git a/contracts/contracts/PollFactoryCreator.sol b/contracts/contracts/PollFactoryCreator.sol new file mode 100644 index 000000000..91923e304 --- /dev/null +++ b/contracts/contracts/PollFactoryCreator.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.10; + +import {PollFactory} from 'maci-contracts/contracts/Poll.sol'; + +library PollFactoryCreator { + function create() external returns (PollFactory pollFactory) { + pollFactory = new PollFactory(); + } +} \ No newline at end of file diff --git a/contracts/contracts/VkRegistryCreator.sol b/contracts/contracts/VkRegistryCreator.sol new file mode 100644 index 000000000..1e74242c8 --- /dev/null +++ b/contracts/contracts/VkRegistryCreator.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.10; + +import {VkRegistry} from 'maci-contracts/contracts/VkRegistry.sol'; + +library VkRegistryCreator { + function create() public returns (VkRegistry vkRegistry) { + vkRegistry = new VkRegistry(); + } +} \ No newline at end of file