From 23c2c957f1f8bdefae7c18c1c5170fd54254e7d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Tue, 24 Sep 2024 15:57:24 +0200 Subject: [PATCH 01/14] forge install: ens-contracts v1.1.6 --- .gitmodules | 3 +++ lib/ens-contracts | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/ens-contracts diff --git a/.gitmodules b/.gitmodules index 72ef667..8b58daf 100644 --- a/.gitmodules +++ b/.gitmodules @@ -19,3 +19,6 @@ branch = v4.9.2 [submodule "lib/solmate"] path = lib/solmate url = https://github.com/transmissions11/solmate +[submodule "lib/ens-contracts"] + path = lib/ens-contracts + url = https://github.com/ensdomains/ens-contracts diff --git a/lib/ens-contracts b/lib/ens-contracts new file mode 160000 index 0000000..a6139f0 --- /dev/null +++ b/lib/ens-contracts @@ -0,0 +1 @@ +Subproject commit a6139f0381ce6bcc584cbce36933c5ea95837212 From 944e1b55bb6abe95f14af2fac5bd55bf82bdfd6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Tue, 24 Sep 2024 17:49:15 +0200 Subject: [PATCH 02/14] Mode DAO Factory work in progress --- .env.example | 30 ++++ script/Deploy.s.sol | 149 ++++++++++++++++ script/multisig-members.json | 4 + src/factory/ModeDaoFactory.sol | 312 +++++++++++++++++++++++++++++++++ 4 files changed, 495 insertions(+) create mode 100644 .env.example create mode 100644 script/Deploy.s.sol create mode 100644 script/multisig-members.json create mode 100644 src/factory/ModeDaoFactory.sol diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..dab619b --- /dev/null +++ b/.env.example @@ -0,0 +1,30 @@ +# NETWORK AND DEPLOYMENT WALLET +DEPLOYMENT_PRIVATE_KEY="..." +ALCHEMY_API_KEY="..." +ETHERSCAN_API_KEY="..." +NETWORK="holesky" +DEPLOY_AS_PRODUCTION=true # With false, the script will deploy mock helpers + +# GOVERNANCE PARAMETERS +MIN_PROPOSAL_DURATION="864000" # in seconds (10 days) +MIN_APPROVALS="5" # How many multisig approvals are required +MULTISIG_PROPOSAL_EXPIRATION_PERIOD="864000" # How long until a pending proposal expires (10 days) + +# PARAMETERS +MODE_TOKEN_ADDRESS="0x..." +BPT_TOKEN_ADDRESS="0x..." + +# OSx IMPLEMENTATIONS ADDRESSES (network dependent, see active_contracts.json on lib/osx) +# HOLESKY +DAO_FACTORY="0xE640Da5AD169630555A86D9b6b9C145B4961b1EB" +PLUGIN_SETUP_PROCESSOR="0xCe0B4124dea6105bfB85fB4461c4D39f360E9ef3" +PLUGIN_REPO_FACTORY="0x95D563382BeD5AcB458759EE05b27DF2CB019Cc7" +GOVERNANCE_ERC20_BASE="0xC24188a73dc09aA7C721f96Ad8857B469C01dC9f" +GOVERNANCE_WRAPPED_ERC20_BASE="0x7a62da7B56fB3bfCdF70E900787010Bc4c9Ca42e" + +# PLUGIN REPO PARAMETERS (per-network) +# HOLESKY +MULTISIG_PLUGIN_REPO_ADDRESS="0x..." +MULTISIG_PLUGIN_RELEASE="1" +MULTISIG_PLUGIN_BUILD="2" +SIMPLE_GAUGE_VOTER_ENS_DOMAIN="mode-simple-gauge-voter-0" diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol new file mode 100644 index 0000000..3aa8b13 --- /dev/null +++ b/script/Deploy.s.sol @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +import {Script, console} from "forge-std/Script.sol"; +import {DAO} from "@aragon/osx/core/dao/DAO.sol"; +import {ModeDaoFactory} from "../src/factory/ModeDaoFactory.sol"; +import {MultisigSetup} from "@aragon/osx/plugins/governance/multisig/MultisigSetup.sol"; +import {SimpleGaugeVoterSetup} from "../src/voting/SimpleGaugeVoterSetup.sol"; +import {PluginRepoFactory} from "@aragon/osx/framework/plugin/repo/PluginRepoFactory.sol"; +import {PluginSetupProcessor} from "@aragon/osx/framework/plugin/setup/PluginSetupProcessor.sol"; + +contract Deploy is Script { + MultisigPluginSetup multisigPluginSetup; + SimpleGaugeVoterSetup simpleGaugeVoterSetup; + + modifier broadcast() { + vm.startBroadcast(vm.envUint("DEPLOYMENT_PRIVATE_KEY")); + console.log("Deploying from:", vm.addr(vm.envUint("DEPLOYMENT_PRIVATE_KEY"))); + _; + vm.stopBroadcast(); + } + + function run() public broadcast { + // NOTE: + // Deploying the plugin setup's separately because of the code size limit + + // Resolve the multisig plugin repo address + + // Deploy the main plugin setup + + // TODO + + ModeDaoFactory.DeploymentSettings memory settings; + if (vm.envBool("DEPLOY_AS_PRODUCTION")) { + settings = getProductionSettings(); + } else { + settings = getInternalTestingSettings(); + } + + console.log(""); + + // Create the DAO + ModeDaoFactory factory = new ModeDaoFactory(settings); + factory.deployOnce(); + + // Done + printDeploymentSummary(factory); + } + + function getProductionSettings() + internal + view + returns (ModeDaoFactory.DeploymentSettings memory settings) + { + console.log("Using production settings"); + + settings = ModeDaoFactory.DeploymentSettings({ + // Mode contract settings + tokenAddress: IVotesUpgradeable(vm.envAddress("TOKEN_ADDRESS")), + // Voting settings + minVetoRatio: uint32(vm.envUint("MIN_VETO_RATIO")), + minStdProposalDuration: uint64(vm.envUint("MIN_STD_PROPOSAL_DURATION")), + minStdApprovals: uint16(vm.envUint("MIN_STD_APPROVALS")), + // OSx contracts + osxDaoFactory: vm.envAddress("DAO_FACTORY"), + pluginSetupProcessor: PluginSetupProcessor(vm.envAddress("PLUGIN_SETUP_PROCESSOR")), + pluginRepoFactory: PluginRepoFactory(vm.envAddress("PLUGIN_REPO_FACTORY")), + // Plugin setup's + multisigPluginSetup: MultisigPluginSetup(multisigPluginSetup), + optimisticTokenVotingPluginSetup: OptimisticTokenVotingPluginSetup( + optimisticTokenVotingPluginSetup + ), + // Multisig members + multisigMembers: readMultisigMembers(), + multisigExpirationPeriod: uint64(vm.envUint("MULTISIG_PROPOSAL_EXPIRATION_PERIOD")), + // ENS + stdMultisigEnsDomain: vm.envString("STD_MULTISIG_ENS_DOMAIN"), + optimisticTokenVotingEnsDomain: vm.envString("OPTIMISTIC_TOKEN_VOTING_ENS_DOMAIN") + }); + } + + function getInternalTestingSettings() + internal + returns (ModeDaoFactory.DeploymentSettings memory settings) + { + console.log("Using internal testing settings"); + + address[] memory multisigMembers = readMultisigMembers(); + // address votingToken = createTestToken(multisigMembers, tokenAddress); + + settings = ModeDaoFactory.DeploymentSettings({ + // Mode contract settings + tokenAddress: IVotesUpgradeable(votingToken), + // Voting settings + minVetoRatio: uint32(vm.envUint("MIN_VETO_RATIO")), + minStdProposalDuration: uint64(vm.envUint("MIN_STD_PROPOSAL_DURATION")), + minStdApprovals: uint16(vm.envUint("MIN_STD_APPROVALS")), + // OSx contracts + osxDaoFactory: vm.envAddress("DAO_FACTORY"), + pluginSetupProcessor: PluginSetupProcessor(vm.envAddress("PLUGIN_SETUP_PROCESSOR")), + pluginRepoFactory: PluginRepoFactory(vm.envAddress("PLUGIN_REPO_FACTORY")), + // Plugin setup's + multisigPluginSetup: MultisigPluginSetup(multisigPluginSetup), + optimisticTokenVotingPluginSetup: OptimisticTokenVotingPluginSetup( + optimisticTokenVotingPluginSetup + ), + // Multisig members + multisigMembers: multisigMembers, + multisigExpirationPeriod: uint64(vm.envUint("MULTISIG_PROPOSAL_EXPIRATION_PERIOD")), + // ENS + stdMultisigEnsDomain: vm.envString("STD_MULTISIG_ENS_DOMAIN"), + optimisticTokenVotingEnsDomain: vm.envString("OPTIMISTIC_TOKEN_VOTING_ENS_DOMAIN") + }); + } + + function readMultisigMembers() internal view returns (address[] memory) { + // JSON list of members + string memory path = string.concat(vm.projectRoot(), "/script/multisig-members.json"); + string memory json = vm.readFile(path); + return vm.parseJsonAddressArray(json, "$.members"); + } + + function printDeploymentSummary(address factory) internal { + console.log("Factory:", address(factory)); + console.log("Chain ID:", block.chainid); + console.log(""); + console.log("DAO:", address(daoDeployment.dao)); + console.log("Voting token:", address(settings.tokenAddress)); + console.log(""); + + console.log("Plugins"); + console.log("- Multisig plugin:", address(daoDeployment.multisigPlugin)); + console.log( + "- Token voting plugin:", + address(daoDeployment.optimisticTokenVotingPlugin) + ); + console.log(""); + + console.log("Plugin repositories"); + console.log("- Multisig plugin repository:", address(daoDeployment.multisigPluginRepo)); + console.log( + "- Token voting plugin repository:", + address(daoDeployment.optimisticTokenVotingPluginRepo) + ); + console.log(""); + + console.log("Helpers"); + } +} diff --git a/script/multisig-members.json b/script/multisig-members.json new file mode 100644 index 0000000..3cc928f --- /dev/null +++ b/script/multisig-members.json @@ -0,0 +1,4 @@ +{ + "members": [ + ] +} \ No newline at end of file diff --git a/src/factory/ModeDaoFactory.sol b/src/factory/ModeDaoFactory.sol new file mode 100644 index 0000000..b75db45 --- /dev/null +++ b/src/factory/ModeDaoFactory.sol @@ -0,0 +1,312 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +import {DAO} from "@aragon/osx/core/dao/DAO.sol"; +import {DAOFactory} from "@aragon/osx/framework/dao/DAOFactory.sol"; +import {Clock} from "@clock/Clock.sol"; +import {IEscrowCurveUserStorage} from "@escrow-interfaces/IEscrowCurveIncreasing.sol"; +import {IWithdrawalQueueErrors} from "src/escrow/increasing/interfaces/IVotingEscrowIncreasing.sol"; +import {IGaugeVote} from "src/voting/ISimpleGaugeVoter.sol"; +import {VotingEscrow, Lock, QuadraticIncreasingEscrow, ExitQueue, SimpleGaugeVoter, SimpleGaugeVoterSetup, ISimpleGaugeVoterSetupParams} from "src/voting/SimpleGaugeVoterSetup.sol"; +import {Addresslist} from "@aragon/osx/plugins/utils/Addresslist.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {PluginSetupProcessor} from "@aragon/osx/framework/plugin/setup/PluginSetupProcessor.sol"; +import {hashHelpers, PluginSetupRef} from "@aragon/osx/framework/plugin/setup/PluginSetupProcessorHelpers.sol"; +import {PluginRepoFactory} from "@aragon/osx/framework/plugin/repo/PluginRepoFactory.sol"; +import {PluginRepo} from "@aragon/osx/framework/plugin/repo/PluginRepo.sol"; +import {IPluginSetup} from "@aragon/osx/framework/plugin/setup/IPluginSetup.sol"; +import {Multisig} from "@aragon/osx/plugins/governance/multisig/Multisig.sol"; +import {MultisigSetup} from "@aragon/osx/plugins/governance/multisig/MultisigSetup.sol"; +import {IVotesUpgradeable} from "@openzeppelin/contracts-upgradeable/governance/utils/IVotesUpgradeable.sol"; +import {GovernanceERC20} from "@aragon/osx/token/ERC20/governance/GovernanceERC20.sol"; +import {createERC1967Proxy} from "@aragon/osx/utils/Proxy.sol"; +import {PermissionLib} from "@aragon/osx/core/permission/PermissionLib.sol"; + +contract ModeDaoFactory { + /// @notice The struct containing all the parameters to deploy the DAO + /// @param modeTokenAddress The address of the IVotes compatible ERC20 token contract to use for the voting power + /// @param bptTokenAddress The address of the IVotes compatible ERC20 token contract to use for the voting power + /// @param minApprovals The amount of approvals required for the multisig to be able to execute a proposal on the DAO + // OSx + /// @param osxDaoFactory The address of the OSx DAO factory contract, used to retrieve the DAO implementation address + /// @param pluginSetupProcessor The address of the OSx PluginSetupProcessor contract on the target chain + /// @param pluginRepoFactory The address of the OSx PluginRepoFactory contract on the target chain + // Plugins + /// @param multisigPluginSetup The address of the already deployed plugin setup for the standard multisig + /// @param votingPluginSetup The address of the already deployed plugin setup for the optimistic voting plugin + /// @param multisigMembers The list of addresses to be defined as the initial multisig signers + /// @param multisigExpirationPeriod How many seconds until a pending multisig proposal expires + /// @param multisigEnsDomain The subdomain to use as the ENS for the standard mulsitig plugin setup. Note: it must be unique and available. + /// @param votingEnsDomain The subdomain to use as the ENS for the optimistic voting plugin setup. Note: it must be unique and available. + struct DeploymentSettings { + // Mode plugin settings + IVotesUpgradeable tokenAddress; + uint16 minApprovals; + address[] multisigMembers; + uint64 multisigExpirationPeriod; + // OSx contracts + address osxDaoFactory; + PluginSetupProcessor pluginSetupProcessor; + PluginRepoFactory pluginRepoFactory; + // Main plugin setup + SimpleGaugeVoterSetup voterPluginSetup; + // ENS + PluginRepo multisigPluginRepo; + uint8 multisigPluginRepoRelease; + uint16 multisigPluginRepoBuild; + string voterEnsDomain; + } + + struct Deployment { + DAO dao; + // Plugins + Multisig multisigPlugin; + SimpleGaugeVoter modeVoterPlugin; + SimpleGaugeVoter bptVoterPlugin; + // Plugin repo's + PluginRepo voterPluginRepo; + } + + /// @notice Thrown when attempting to call deployOnce() when the DAO is already deployed. + error AlreadyDeployed(); + + DeploymentSettings settings; + Deployment deployment; + + /// @notice Initializes the factory and performs the full deployment. Values become read-only after that. + /// @param _settings The settings of the one-time deployment. + constructor(DeploymentSettings memory _settings) { + settings = _settings; + } + + function deployOnce() public { + if (address(deployment.dao) != address(0)) revert AlreadyDeployed(); + + IPluginSetup.PreparedSetupData memory preparedMultisigSetupData; + IPluginSetup.PreparedSetupData memory preparedVoterSetupData; + + // DEPLOY THE DAO (The factory is the interim owner) + DAO dao = prepareDao(); + deployment.dao = dao; + + // DEPLOY THE PLUGINS + ( + deployment.multisigPlugin, + deployment.multisigPluginRepo, + preparedMultisigSetupData + ) = prepareMultisig(dao); + + ( + deployment.voterPlugin, + deployment.voterPluginRepo, + preparedVoterSetupData + ) = prepareSimpleGaugeVoterPlugin(dao); + + // APPLY THE INSTALLATIONS + grantApplyInstallationPermissions(dao); + + applyPluginInstallation( + dao, + address(deployment.multisigPlugin), + deployment.multisigPluginRepo, + preparedMultisigSetupData + ); + applyPluginInstallation( + dao, + address(deployment.voterPlugin), + deployment.voterPluginRepo, + preparedVoterSetupData + ); + + revokeApplyInstallationPermissions(dao); + + // REMOVE THIS CONTRACT AS OWNER + revokeOwnerPermission(deployment.dao); + + // DEPLOY OTHER CONTRACTS + deployment.publicKeyRegistry = deployPublicKeyRegistry(); + } + + function prepareDao() internal returns (DAO dao) { + address daoBase = DAOFactory(settings.osxDaoFactory).daoBase(); + + dao = DAO( + payable( + createERC1967Proxy( + address(daoBase), + abi.encodeCall( + DAO.initialize, + ( + "", // Metadata URI + address(this), // initialOwner + address(0x0), // Trusted forwarder + "" // DAO URI + ) + ) + ) + ) + ); + + // Grant DAO all the needed permissions on itself + PermissionLib.SingleTargetPermission[] + memory items = new PermissionLib.SingleTargetPermission[](3); + items[0] = PermissionLib.SingleTargetPermission( + PermissionLib.Operation.Grant, + address(dao), + dao.ROOT_PERMISSION_ID() + ); + items[1] = PermissionLib.SingleTargetPermission( + PermissionLib.Operation.Grant, + address(dao), + dao.UPGRADE_DAO_PERMISSION_ID() + ); + items[2] = PermissionLib.SingleTargetPermission( + PermissionLib.Operation.Grant, + address(dao), + dao.REGISTER_STANDARD_CALLBACK_PERMISSION_ID() + ); + + dao.applySingleTargetPermissions(address(dao), items); + } + + function prepareMultisig( + DAO dao + ) internal returns (Multisig, PluginRepo, IPluginSetup.PreparedSetupData memory) { + // Publish repo + PluginRepo pluginRepo = PluginRepoFactory(settings.pluginRepoFactory) + .createPluginRepoWithFirstVersion( + settings.stdMultisigEnsDomain, + address(settings.multisigPluginSetup), + msg.sender, + " ", + " " + ); + + bytes memory settingsData = settings.multisigPluginSetup.encodeInstallationParameters( + settings.multisigMembers, + Multisig.MultisigSettings( + true, // onlyListed + settings.minApprovals, + settings.multisigExpirationPeriod + ) + ); + + (address plugin, IPluginSetup.PreparedSetupData memory preparedSetupData) = settings + .pluginSetupProcessor + .prepareInstallation( + address(dao), + PluginSetupProcessor.PrepareInstallationParams( + PluginSetupRef(PluginRepo.Tag(1, 1), PluginRepo(pluginRepo)), + settingsData + ) + ); + + return (Multisig(plugin), pluginRepo, preparedSetupData); + } + + function prepareSimpleGaugeVoterPlugin( + DAO dao, + address stdProposer, + address emergencyProposer + ) internal returns (SimpleGaugeVoter, PluginRepo, IPluginSetup.PreparedSetupData memory) { + // Plugin settings + bytes memory settingsData; + { + SimpleGaugeVoterSettings memory voterSettings = SimpleGaugeVoterSettings(); + // TODO + + SimpleGaugeVoterSetup.TokenSettings memory existingTokenSettings = SimpleGaugeVoterSetup + .TokenSettings(address(settings.tokenAddress), "Mode", "TKO"); + GovernanceERC20.MintSettings memory unusedMintSettings = GovernanceERC20.MintSettings( + new address[](0), + new uint256[](0) + ); + + settingsData = settings.optimisticTokenVotingPluginSetup.encodeInstallationParams( + SimpleGaugeVoterSetup.InstallationParameters( + voterSettings, + existingTokenSettings, + unusedMintSettings, + settings.modeL1ContractAddress, + settings.modeBridgeAddress, + settings.minStdProposalDuration, + stdProposer, + emergencyProposer + ) + ); + } + + (address plugin, IPluginSetup.PreparedSetupData memory preparedSetupData) = settings + .pluginSetupProcessor + .prepareInstallation( + address(dao), + PluginSetupProcessor.PrepareInstallationParams( + PluginSetupRef( + PluginRepo.Tag( + settings.multisigPluginRepoRelease, + settings.multisigPluginRepoBuild, + 2 + ), + settings.multisigPluginRepo + ), + settingsData + ) + ); + return (SimpleGaugeVoter(plugin), pluginRepo, preparedSetupData); + } + + function applyPluginInstallation( + DAO dao, + address plugin, + PluginRepo pluginRepo, + IPluginSetup.PreparedSetupData memory preparedSetupData + ) internal { + settings.pluginSetupProcessor.applyInstallation( + address(dao), + PluginSetupProcessor.ApplyInstallationParams( + PluginSetupRef(PluginRepo.Tag(1, 1), pluginRepo), + plugin, + preparedSetupData.permissions, + hashHelpers(preparedSetupData.helpers) + ) + ); + } + + function grantApplyInstallationPermissions(DAO dao) internal { + // The PSP can manage permissions on the new DAO + dao.grant(address(dao), address(settings.pluginSetupProcessor), dao.ROOT_PERMISSION_ID()); + + // This factory can call applyInstallation() on the PSP + dao.grant( + address(settings.pluginSetupProcessor), + address(this), + settings.pluginSetupProcessor.APPLY_INSTALLATION_PERMISSION_ID() + ); + } + + function revokeApplyInstallationPermissions(DAO dao) internal { + // Revoking the permission for the factory to call applyInstallation() on the PSP + dao.revoke( + address(settings.pluginSetupProcessor), + address(this), + settings.pluginSetupProcessor.APPLY_INSTALLATION_PERMISSION_ID() + ); + + // Revoke the PSP permission to manage permissions on the new DAO + dao.revoke(address(dao), address(settings.pluginSetupProcessor), dao.ROOT_PERMISSION_ID()); + } + + function revokeOwnerPermission(DAO dao) internal { + dao.revoke(address(dao), address(this), dao.ROOT_PERMISSION_ID()); + } + + // Getters + + function getSettings() public view returns (DeploymentSettings memory) { + return settings; + } + + function getDeployment() public view returns (Deployment memory) { + return deployment; + } +} From 941a7885f8b2baaad4de47c84cdaa219a30a8b90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Wed, 25 Sep 2024 17:50:31 +0100 Subject: [PATCH 03/14] Factory work in progress --- .env.example | 51 +++-- script/Deploy.s.sol | 208 +++++++++++--------- src/factory/GaugesDaoFactory.sol | 328 +++++++++++++++++++++++++++++++ src/factory/ModeDaoFactory.sol | 312 ----------------------------- 4 files changed, 480 insertions(+), 419 deletions(-) create mode 100644 src/factory/GaugesDaoFactory.sol delete mode 100644 src/factory/ModeDaoFactory.sol diff --git a/.env.example b/.env.example index dab619b..04b761d 100644 --- a/.env.example +++ b/.env.example @@ -3,28 +3,53 @@ DEPLOYMENT_PRIVATE_KEY="..." ALCHEMY_API_KEY="..." ETHERSCAN_API_KEY="..." NETWORK="holesky" -DEPLOY_AS_PRODUCTION=true # With false, the script will deploy mock helpers -# GOVERNANCE PARAMETERS +# With false, the script will deploy mock helpers +DEPLOY_AS_PRODUCTION=true +MULTISIG_MEMBERS_JSON_FILE_NAME="/script/multisig-members.json" + +# MULTISIG PARAMETERS MIN_PROPOSAL_DURATION="864000" # in seconds (10 days) MIN_APPROVALS="5" # How many multisig approvals are required MULTISIG_PROPOSAL_EXPIRATION_PERIOD="864000" # How long until a pending proposal expires (10 days) -# PARAMETERS -MODE_TOKEN_ADDRESS="0x..." -BPT_TOKEN_ADDRESS="0x..." +# GAUGE VOTER PARAMETERS +# The token to be used for the escrow +TOKEN1_ADDRESS="0xdc518215FCbeB2b641073F4387895E64d65D51fB" +VE_TOKEN1_NAME="Voting Escrow Token 1" +VE_TOKEN1_SYMBOL="veTK1" -# OSx IMPLEMENTATIONS ADDRESSES (network dependent, see active_contracts.json on lib/osx) -# HOLESKY -DAO_FACTORY="0xE640Da5AD169630555A86D9b6b9C145B4961b1EB" -PLUGIN_SETUP_PROCESSOR="0xCe0B4124dea6105bfB85fB4461c4D39f360E9ef3" -PLUGIN_REPO_FACTORY="0x95D563382BeD5AcB458759EE05b27DF2CB019Cc7" -GOVERNANCE_ERC20_BASE="0xC24188a73dc09aA7C721f96Ad8857B469C01dC9f" -GOVERNANCE_WRAPPED_ERC20_BASE="0x7a62da7B56fB3bfCdF70E900787010Bc4c9Ca42e" +# Additional tokens +TOKEN2_ADDRESS="0x..." # Ignored if 0x0 +VE_TOKEN2_NAME="Voting Escrow Token 2" +VE_TOKEN2_SYMBOL="veTK2" + +# 1 ether = 100%, represents the fee taken on withdrawals +FEE_PERCENT_WEI="0" + +# Min seconds after depositing before voting is possible +WARMUP_PERIOD="259200" # 3 days + +# Min seconds after queuing an exit before withdrawing is possible +COOLDOWN_PERIOD="259200" # 3 days + +# Min seconds a user must have locked in escrow before they can queue an exit +MIN_LOCK_DURATION="4838400" # 8 weeks + +# Prevent voting until manually activated by the multisig +VOTING_PAUSED=true # PLUGIN REPO PARAMETERS (per-network) # HOLESKY MULTISIG_PLUGIN_REPO_ADDRESS="0x..." MULTISIG_PLUGIN_RELEASE="1" MULTISIG_PLUGIN_BUILD="2" -SIMPLE_GAUGE_VOTER_ENS_DOMAIN="mode-simple-gauge-voter-0" +SIMPLE_GAUGE_VOTER_REPO_ENS_SUBDOMAIN="my-simple-gauge-voter-0" + +# OSx IMPLEMENTATIONS ADDRESSES (network dependent, see active_contracts.json on lib/osx) +# HOLESKY +DAO_FACTORY="0xE640Da5AD169630555A86D9b6b9C145B4961b1EB" +PLUGIN_SETUP_PROCESSOR="0xCe0B4124dea6105bfB85fB4461c4D39f360E9ef3" +PLUGIN_REPO_FACTORY="0x95D563382BeD5AcB458759EE05b27DF2CB019Cc7" + +# GOVERNANCE_ERC20_BASE="0xC24188a73dc09aA7C721f96Ad8857B469C01dC9f" diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index 3aa8b13..a99fee1 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -3,20 +3,26 @@ pragma solidity ^0.8.17; import {Script, console} from "forge-std/Script.sol"; import {DAO} from "@aragon/osx/core/dao/DAO.sol"; -import {ModeDaoFactory} from "../src/factory/ModeDaoFactory.sol"; -import {MultisigSetup} from "@aragon/osx/plugins/governance/multisig/MultisigSetup.sol"; -import {SimpleGaugeVoterSetup} from "../src/voting/SimpleGaugeVoterSetup.sol"; +import {GaugesDaoFactory, DeploymentParameters, Deployment, TokenParameters} from "../src/factory/GaugesDaoFactory.sol"; +import {MultisigSetup as MultisigPluginSetup} from "@aragon/osx/plugins/governance/multisig/MultisigSetup.sol"; +import {VotingEscrow, Clock, Lock, QuadraticIncreasingEscrow, ExitQueue, SimpleGaugeVoter, SimpleGaugeVoterSetup, ISimpleGaugeVoterSetupParams} from "src/voting/SimpleGaugeVoterSetup.sol"; +import {PluginRepo} from "@aragon/osx/framework/plugin/repo/PluginRepo.sol"; import {PluginRepoFactory} from "@aragon/osx/framework/plugin/repo/PluginRepoFactory.sol"; import {PluginSetupProcessor} from "@aragon/osx/framework/plugin/setup/PluginSetupProcessor.sol"; contract Deploy is Script { - MultisigPluginSetup multisigPluginSetup; SimpleGaugeVoterSetup simpleGaugeVoterSetup; + /// @dev Thrown when attempting to create a DAO with an empty multisig + error EmptyMultisig(); + modifier broadcast() { - vm.startBroadcast(vm.envUint("DEPLOYMENT_PRIVATE_KEY")); - console.log("Deploying from:", vm.addr(vm.envUint("DEPLOYMENT_PRIVATE_KEY"))); + uint256 privKey = vm.envUint("DEPLOYMENT_PRIVATE_KEY"); + vm.startBroadcast(privKey); + console.log("Deploying from:", vm.addr(privKey)); + _; + vm.stopBroadcast(); } @@ -24,124 +30,138 @@ contract Deploy is Script { // NOTE: // Deploying the plugin setup's separately because of the code size limit - // Resolve the multisig plugin repo address - - // Deploy the main plugin setup + // Note: Multisig is already deployed, not redeploying - // TODO + // Deploy the voter plugin setup + // TODO: - ModeDaoFactory.DeploymentSettings memory settings; - if (vm.envBool("DEPLOY_AS_PRODUCTION")) { - settings = getProductionSettings(); - } else { - settings = getInternalTestingSettings(); - } - - console.log(""); + DeploymentParameters memory parameters = getDeploymentParameters( + vm.envBool("DEPLOY_AS_PRODUCTION") + ); // Create the DAO - ModeDaoFactory factory = new ModeDaoFactory(settings); + GaugesDaoFactory factory = new GaugesDaoFactory(parameters); factory.deployOnce(); // Done printDeploymentSummary(factory); } - function getProductionSettings() - internal - view - returns (ModeDaoFactory.DeploymentSettings memory settings) - { - console.log("Using production settings"); - - settings = ModeDaoFactory.DeploymentSettings({ - // Mode contract settings - tokenAddress: IVotesUpgradeable(vm.envAddress("TOKEN_ADDRESS")), - // Voting settings - minVetoRatio: uint32(vm.envUint("MIN_VETO_RATIO")), - minStdProposalDuration: uint64(vm.envUint("MIN_STD_PROPOSAL_DURATION")), - minStdApprovals: uint16(vm.envUint("MIN_STD_APPROVALS")), - // OSx contracts - osxDaoFactory: vm.envAddress("DAO_FACTORY"), - pluginSetupProcessor: PluginSetupProcessor(vm.envAddress("PLUGIN_SETUP_PROCESSOR")), - pluginRepoFactory: PluginRepoFactory(vm.envAddress("PLUGIN_REPO_FACTORY")), - // Plugin setup's - multisigPluginSetup: MultisigPluginSetup(multisigPluginSetup), - optimisticTokenVotingPluginSetup: OptimisticTokenVotingPluginSetup( - optimisticTokenVotingPluginSetup - ), - // Multisig members + function getDeploymentParameters( + bool isProduction + ) internal returns (DeploymentParameters memory parameters) { + parameters = DeploymentParameters({ + // Multisig settings + minProposalDuration: uint64(vm.envUint("MIN_PROPOSAL_DURATION")), + minApprovals: uint8(vm.envUint("MIN_APPROVALS")), multisigMembers: readMultisigMembers(), - multisigExpirationPeriod: uint64(vm.envUint("MULTISIG_PROPOSAL_EXPIRATION_PERIOD")), - // ENS - stdMultisigEnsDomain: vm.envString("STD_MULTISIG_ENS_DOMAIN"), - optimisticTokenVotingEnsDomain: vm.envString("OPTIMISTIC_TOKEN_VOTING_ENS_DOMAIN") - }); - } - - function getInternalTestingSettings() - internal - returns (ModeDaoFactory.DeploymentSettings memory settings) - { - console.log("Using internal testing settings"); - - address[] memory multisigMembers = readMultisigMembers(); - // address votingToken = createTestToken(multisigMembers, tokenAddress); - - settings = ModeDaoFactory.DeploymentSettings({ - // Mode contract settings - tokenAddress: IVotesUpgradeable(votingToken), - // Voting settings - minVetoRatio: uint32(vm.envUint("MIN_VETO_RATIO")), - minStdProposalDuration: uint64(vm.envUint("MIN_STD_PROPOSAL_DURATION")), - minStdApprovals: uint16(vm.envUint("MIN_STD_APPROVALS")), - // OSx contracts + // Gauge Voter + tokenParameters: getTokenParameters(isProduction), + feePercent: vm.envUint("FEE_PERCENT_WEI"), + warmupPeriod: uint64(vm.envUint("WARMUP_PERIOD")), + cooldownPeriod: uint64(vm.envUint("COOLDOWN_PERIOD")), + minLockDuration: uint64(vm.envUint("MIN_LOCK_DURATION")), + votingPaused: vm.envBool("VOTING_PAUSED"), + // Standard multisig repo + multisigPluginRepo: PluginRepo(vm.envAddress("MULTISIG_PLUGIN_REPO_ADDRESS")), + multisigPluginRelease: uint8(vm.envUint("MULTISIG_PLUGIN_RELEASE")), + multisigPluginBuild: uint16(vm.envUint("MULTISIG_PLUGIN_BUILD")), + // Voter plugin setup and ENS + voterPluginSetup: deploySimpleGaugeVoterPluginSetup(), + voterEnsSubdomain: vm.envString("SIMPLE_GAUGE_VOTER_REPO_ENS_SUBDOMAIN"), + // OSx addresses osxDaoFactory: vm.envAddress("DAO_FACTORY"), pluginSetupProcessor: PluginSetupProcessor(vm.envAddress("PLUGIN_SETUP_PROCESSOR")), - pluginRepoFactory: PluginRepoFactory(vm.envAddress("PLUGIN_REPO_FACTORY")), - // Plugin setup's - multisigPluginSetup: MultisigPluginSetup(multisigPluginSetup), - optimisticTokenVotingPluginSetup: OptimisticTokenVotingPluginSetup( - optimisticTokenVotingPluginSetup - ), - // Multisig members - multisigMembers: multisigMembers, - multisigExpirationPeriod: uint64(vm.envUint("MULTISIG_PROPOSAL_EXPIRATION_PERIOD")), - // ENS - stdMultisigEnsDomain: vm.envString("STD_MULTISIG_ENS_DOMAIN"), - optimisticTokenVotingEnsDomain: vm.envString("OPTIMISTIC_TOKEN_VOTING_ENS_DOMAIN") + pluginRepoFactory: PluginRepoFactory(vm.envAddress("PLUGIN_REPO_FACTORY")) }); } - function readMultisigMembers() internal view returns (address[] memory) { + function readMultisigMembers() internal view returns (address[] memory result) { // JSON list of members - string memory path = string.concat(vm.projectRoot(), "/script/multisig-members.json"); - string memory json = vm.readFile(path); - return vm.parseJsonAddressArray(json, "$.members"); + string memory membersFilePath = vm.envString("MULTISIG_MEMBERS_JSON_FILE_NAME"); + string memory path = string.concat(vm.projectRoot(), membersFilePath); + string memory strJson = vm.readFile(path); + result = vm.parseJsonAddressArray(strJson, "$.members"); + + if (result.length == 0) revert EmptyMultisig(); } - function printDeploymentSummary(address factory) internal { + function deploySimpleGaugeVoterPluginSetup() internal returns (SimpleGaugeVoterSetup result) { + result = new SimpleGaugeVoterSetup( + address(new SimpleGaugeVoter()), + address(new QuadraticIncreasingEscrow()), + address(new ExitQueue()), + address(new VotingEscrow()), + address(new Clock()), + address(new Lock()) + ); + } + + function getTokenParameters( + bool isProduction + ) internal view returns (TokenParameters[] memory tokenParameters) { + if (isProduction) { + // USE TOKEN(s) + console.log("Using production parameters"); + + bool hasTwoTokens = vm.envAddress("TOKEN2_ADDRESS") != address(0); + tokenParameters = new TokenParameters[](hasTwoTokens ? 2 : 1); + + tokenParameters[0] = TokenParameters({ + token: vm.envAddress("TOKEN1_ADDRESS"), + veTokenName: vm.envString("VE_TOKEN1_NAME"), + veTokenSymbol: vm.envString("VE_TOKEN1_SYMBOL") + }); + + if (hasTwoTokens) { + tokenParameters[1] = TokenParameters({ + token: vm.envAddress("TOKEN2_ADDRESS"), + veTokenName: vm.envString("VE_TOKEN2_NAME"), + veTokenSymbol: vm.envString("VE_TOKEN2_SYMBOL") + }); + } + } else { + // MINT TEST TOKEN + console.log("Using testing parameters (minting 2 token)"); + + // TODO: + } + } + + function createTestToken(address[] memory holders) internal { + // TODO: + address newToken = vm.envAddress("GOVERNANCE_ERC20_BASE"); + } + + function printDeploymentSummary(GaugesDaoFactory factory) internal view { + DeploymentParameters memory deploymentParameters = factory.getDeploymentParameters(); + Deployment memory deployment = factory.getDeployment(); + + console.log(""); console.log("Factory:", address(factory)); console.log("Chain ID:", block.chainid); console.log(""); - console.log("DAO:", address(daoDeployment.dao)); - console.log("Voting token:", address(settings.tokenAddress)); + console.log("DAO:", address(deployment.dao)); console.log(""); console.log("Plugins"); - console.log("- Multisig plugin:", address(daoDeployment.multisigPlugin)); - console.log( - "- Token voting plugin:", - address(daoDeployment.optimisticTokenVotingPlugin) - ); + console.log("- Multisig plugin:", address(deployment.multisigPlugin)); + console.log(""); + for (uint i = 0; i < deployment.voterPlugins.length; ) { + console.log("- Token:", address(deploymentParameters.tokenParameters[i].token)); + console.log(" Gauge voter plugin:", address(deployment.voterPlugins[i])); + unchecked { + i++; + } + } console.log(""); console.log("Plugin repositories"); - console.log("- Multisig plugin repository:", address(daoDeployment.multisigPluginRepo)); console.log( - "- Token voting plugin repository:", - address(daoDeployment.optimisticTokenVotingPluginRepo) + "- Eultisig plugin repository (existing):", + address(deploymentParameters.multisigPluginRepo) ); + console.log("- Gauge voter plugin repository:", address(deployment.voterPluginRepo)); console.log(""); console.log("Helpers"); diff --git a/src/factory/GaugesDaoFactory.sol b/src/factory/GaugesDaoFactory.sol new file mode 100644 index 0000000..df5ea50 --- /dev/null +++ b/src/factory/GaugesDaoFactory.sol @@ -0,0 +1,328 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +import {DAO} from "@aragon/osx/core/dao/DAO.sol"; +import {DAOFactory} from "@aragon/osx/framework/dao/DAOFactory.sol"; +import {Clock} from "@clock/Clock.sol"; +import {IEscrowCurveUserStorage} from "@escrow-interfaces/IEscrowCurveIncreasing.sol"; +import {IWithdrawalQueueErrors} from "src/escrow/increasing/interfaces/IVotingEscrowIncreasing.sol"; +import {IGaugeVote} from "src/voting/ISimpleGaugeVoter.sol"; +import {SimpleGaugeVoter, SimpleGaugeVoterSetup, ISimpleGaugeVoterSetupParams} from "src/voting/SimpleGaugeVoterSetup.sol"; +import {Addresslist} from "@aragon/osx/plugins/utils/Addresslist.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {PluginSetupProcessor} from "@aragon/osx/framework/plugin/setup/PluginSetupProcessor.sol"; +import {hashHelpers, PluginSetupRef} from "@aragon/osx/framework/plugin/setup/PluginSetupProcessorHelpers.sol"; +import {PluginRepoFactory} from "@aragon/osx/framework/plugin/repo/PluginRepoFactory.sol"; +import {PluginRepo} from "@aragon/osx/framework/plugin/repo/PluginRepo.sol"; +import {IPluginSetup} from "@aragon/osx/framework/plugin/setup/IPluginSetup.sol"; +import {Multisig} from "@aragon/osx/plugins/governance/multisig/Multisig.sol"; +import {MultisigSetup as MultisigPluginSetup} from "@aragon/osx/plugins/governance/multisig/MultisigSetup.sol"; +import {IVotesUpgradeable} from "@openzeppelin/contracts-upgradeable/governance/utils/IVotesUpgradeable.sol"; +import {GovernanceERC20} from "@aragon/osx/token/ERC20/governance/GovernanceERC20.sol"; +import {createERC1967Proxy} from "@aragon/osx/utils/Proxy.sol"; +import {PermissionLib} from "@aragon/osx/core/permission/PermissionLib.sol"; + +/// @notice The struct containing all the parameters to deploy the DAO +/// @param token1Address The address of the IVotes compatible ERC20 token contract to use for the voting power +/// @param token2Address The address of the IVotes compatible ERC20 token contract to use for the voting power +/// @param minApprovals The amount of approvals required for the multisig to be able to execute a proposal on the DAO +// OSx +/// @param osxDaoFactory The address of the OSx DAO factory contract, used to retrieve the DAO implementation address +/// @param pluginSetupProcessor The address of the OSx PluginSetupProcessor contract on the target chain +/// @param pluginRepoFactory The address of the OSx PluginRepoFactory contract on the target chain +// Plugins +/// @param multisigPluginSetup The address of the already deployed plugin setup for the standard multisig +/// @param votingPluginSetup The address of the already deployed plugin setup for the optimistic voting plugin +/// @param multisigMembers The list of addresses to be defined as the initial multisig signers +/// @param multisigEnsDomain The subdomain to use as the ENS for the standard mulsitig plugin setup. Note: it must be unique and available. +/// @param votingEnsDomain The subdomain to use as the ENS for the optimistic voting plugin setup. Note: it must be unique and available. +struct DeploymentParameters { + // Multisig settings + uint64 minProposalDuration; + uint16 minApprovals; + address[] multisigMembers; + // Gauge Voter + TokenParameters[] tokenParameters; + uint256 feePercent; + uint64 warmupPeriod; + uint64 cooldownPeriod; + uint64 minLockDuration; + bool votingPaused; + // Voter plugin setup and ENS + PluginRepo multisigPluginRepo; + uint8 multisigPluginRelease; + uint16 multisigPluginBuild; + SimpleGaugeVoterSetup voterPluginSetup; + string voterEnsSubdomain; + // OSx addresses + address osxDaoFactory; + PluginSetupProcessor pluginSetupProcessor; + PluginRepoFactory pluginRepoFactory; +} + +struct TokenParameters { + address token; + string veTokenName; + string veTokenSymbol; +} + +struct Deployment { + DAO dao; + // Plugins + Multisig multisigPlugin; + SimpleGaugeVoter[] voterPlugins; + // Plugin repo's + PluginRepo voterPluginRepo; +} + +contract GaugesDaoFactory { + /// @notice Thrown when attempting to call deployOnce() when the DAO is already deployed. + error AlreadyDeployed(); + + DeploymentParameters parameters; + Deployment deployment; + + /// @notice Initializes the factory and performs the full deployment. Values become read-only after that. + /// @param _parameters The parameters of the one-time deployment. + constructor(DeploymentParameters memory _parameters) { + parameters = _parameters; + } + + function deployOnce() public { + if (address(deployment.dao) != address(0)) revert AlreadyDeployed(); + + // Deploy the DAO (this contract is the interim owner) + DAO dao = prepareDao(); + deployment.dao = dao; + + // Deploy and install the plugins + + grantApplyInstallationPermissions(dao); + + // MULTISIG + { + IPluginSetup.PreparedSetupData memory preparedMultisigSetupData; + + PluginRepo.Tag memory repoTag = PluginRepo.Tag( + parameters.multisigPluginRelease, + parameters.multisigPluginBuild + ); + + (deployment.multisigPlugin, preparedMultisigSetupData) = prepareMultisig(dao, repoTag); + + applyPluginInstallation( + dao, + address(deployment.multisigPlugin), + parameters.multisigPluginRepo, + repoTag, + preparedMultisigSetupData + ); + } + + // GAUGE VOTER(s) + { + IPluginSetup.PreparedSetupData memory preparedVoterSetupData; + SimpleGaugeVoter voterPlugin; + + PluginRepo.Tag memory repoTag = PluginRepo.Tag(1, 1); + + deployment.voterPlugins = new SimpleGaugeVoter[](parameters.tokenParameters.length); + + for (uint i = 0; i < parameters.tokenParameters.length; ) { + ( + voterPlugin, + deployment.voterPluginRepo, + preparedVoterSetupData + ) = prepareSimpleGaugeVoterPlugin(dao, parameters.tokenParameters[i], repoTag); + + applyPluginInstallation( + dao, + address(voterPlugin), + deployment.voterPluginRepo, + repoTag, + preparedVoterSetupData + ); + + deployment.voterPlugins[i] = voterPlugin; + + unchecked { + i++; + } + } + } + + // Clean up + revokeApplyInstallationPermissions(dao); + + // Remove this contract as owner + revokeOwnerPermission(deployment.dao); + } + + function prepareDao() internal returns (DAO dao) { + address daoBase = DAOFactory(parameters.osxDaoFactory).daoBase(); + + dao = DAO( + payable( + createERC1967Proxy( + address(daoBase), + abi.encodeCall( + DAO.initialize, + ( + "", // Metadata URI + address(this), // initialOwner + address(0x0), // Trusted forwarder + "" // DAO URI + ) + ) + ) + ) + ); + + // Grant DAO all the needed permissions on itself + PermissionLib.SingleTargetPermission[] + memory items = new PermissionLib.SingleTargetPermission[](3); + items[0] = PermissionLib.SingleTargetPermission( + PermissionLib.Operation.Grant, + address(dao), + dao.ROOT_PERMISSION_ID() + ); + items[1] = PermissionLib.SingleTargetPermission( + PermissionLib.Operation.Grant, + address(dao), + dao.UPGRADE_DAO_PERMISSION_ID() + ); + items[2] = PermissionLib.SingleTargetPermission( + PermissionLib.Operation.Grant, + address(dao), + dao.REGISTER_STANDARD_CALLBACK_PERMISSION_ID() + ); + + dao.applySingleTargetPermissions(address(dao), items); + } + + function prepareMultisig( + DAO dao, + PluginRepo.Tag memory repoTag + ) internal returns (Multisig, IPluginSetup.PreparedSetupData memory) { + bytes memory settingsData = abi.encode( + parameters.multisigMembers, + Multisig.MultisigSettings( + true, // onlyListed + parameters.minApprovals + ) + ); + + (address plugin, IPluginSetup.PreparedSetupData memory preparedSetupData) = parameters + .pluginSetupProcessor + .prepareInstallation( + address(dao), + PluginSetupProcessor.PrepareInstallationParams( + PluginSetupRef(repoTag, parameters.multisigPluginRepo), + settingsData + ) + ); + + return (Multisig(plugin), preparedSetupData); + } + + function prepareSimpleGaugeVoterPlugin( + DAO dao, + TokenParameters memory tokenParameters, + PluginRepo.Tag memory repoTag + ) internal returns (SimpleGaugeVoter, PluginRepo, IPluginSetup.PreparedSetupData memory) { + // Publish repo + PluginRepo pluginRepo = PluginRepoFactory(parameters.pluginRepoFactory) + .createPluginRepoWithFirstVersion( + parameters.voterEnsSubdomain, + address(parameters.voterPluginSetup), + address(dao), + " ", + " " + ); + + // Plugin settings + bytes memory settingsData = parameters.voterPluginSetup.encodeSetupData( + ISimpleGaugeVoterSetupParams({ + isPaused: parameters.votingPaused, + token: tokenParameters.token, + veTokenName: tokenParameters.veTokenName, + veTokenSymbol: tokenParameters.veTokenSymbol, + feePercent: parameters.feePercent, + warmup: parameters.warmupPeriod, + cooldown: parameters.cooldownPeriod, + minLock: parameters.minLockDuration + }) + ); + + (address plugin, IPluginSetup.PreparedSetupData memory preparedSetupData) = parameters + .pluginSetupProcessor + .prepareInstallation( + address(dao), + PluginSetupProcessor.PrepareInstallationParams( + PluginSetupRef(repoTag, pluginRepo), + settingsData + ) + ); + return (SimpleGaugeVoter(plugin), pluginRepo, preparedSetupData); + } + + function applyPluginInstallation( + DAO dao, + address plugin, + PluginRepo pluginRepo, + PluginRepo.Tag memory pluginRepoTag, + IPluginSetup.PreparedSetupData memory preparedSetupData + ) internal { + parameters.pluginSetupProcessor.applyInstallation( + address(dao), + PluginSetupProcessor.ApplyInstallationParams( + PluginSetupRef(pluginRepoTag, pluginRepo), + plugin, + preparedSetupData.permissions, + hashHelpers(preparedSetupData.helpers) + ) + ); + } + + function grantApplyInstallationPermissions(DAO dao) internal { + // The PSP can manage permissions on the new DAO + dao.grant(address(dao), address(parameters.pluginSetupProcessor), dao.ROOT_PERMISSION_ID()); + + // This factory can call applyInstallation() on the PSP + dao.grant( + address(parameters.pluginSetupProcessor), + address(this), + parameters.pluginSetupProcessor.APPLY_INSTALLATION_PERMISSION_ID() + ); + } + + function revokeApplyInstallationPermissions(DAO dao) internal { + // Revoking the permission for the factory to call applyInstallation() on the PSP + dao.revoke( + address(parameters.pluginSetupProcessor), + address(this), + parameters.pluginSetupProcessor.APPLY_INSTALLATION_PERMISSION_ID() + ); + + // Revoke the PSP permission to manage permissions on the new DAO + dao.revoke( + address(dao), + address(parameters.pluginSetupProcessor), + dao.ROOT_PERMISSION_ID() + ); + } + + function revokeOwnerPermission(DAO dao) internal { + dao.revoke(address(dao), address(this), dao.ROOT_PERMISSION_ID()); + } + + // Getters + + function getDeploymentParameters() public view returns (DeploymentParameters memory) { + return parameters; + } + + function getDeployment() public view returns (Deployment memory) { + return deployment; + } +} diff --git a/src/factory/ModeDaoFactory.sol b/src/factory/ModeDaoFactory.sol deleted file mode 100644 index b75db45..0000000 --- a/src/factory/ModeDaoFactory.sol +++ /dev/null @@ -1,312 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.17; - -import {DAO} from "@aragon/osx/core/dao/DAO.sol"; -import {DAOFactory} from "@aragon/osx/framework/dao/DAOFactory.sol"; -import {Clock} from "@clock/Clock.sol"; -import {IEscrowCurveUserStorage} from "@escrow-interfaces/IEscrowCurveIncreasing.sol"; -import {IWithdrawalQueueErrors} from "src/escrow/increasing/interfaces/IVotingEscrowIncreasing.sol"; -import {IGaugeVote} from "src/voting/ISimpleGaugeVoter.sol"; -import {VotingEscrow, Lock, QuadraticIncreasingEscrow, ExitQueue, SimpleGaugeVoter, SimpleGaugeVoterSetup, ISimpleGaugeVoterSetupParams} from "src/voting/SimpleGaugeVoterSetup.sol"; -import {Addresslist} from "@aragon/osx/plugins/utils/Addresslist.sol"; -import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; -import {PluginSetupProcessor} from "@aragon/osx/framework/plugin/setup/PluginSetupProcessor.sol"; -import {hashHelpers, PluginSetupRef} from "@aragon/osx/framework/plugin/setup/PluginSetupProcessorHelpers.sol"; -import {PluginRepoFactory} from "@aragon/osx/framework/plugin/repo/PluginRepoFactory.sol"; -import {PluginRepo} from "@aragon/osx/framework/plugin/repo/PluginRepo.sol"; -import {IPluginSetup} from "@aragon/osx/framework/plugin/setup/IPluginSetup.sol"; -import {Multisig} from "@aragon/osx/plugins/governance/multisig/Multisig.sol"; -import {MultisigSetup} from "@aragon/osx/plugins/governance/multisig/MultisigSetup.sol"; -import {IVotesUpgradeable} from "@openzeppelin/contracts-upgradeable/governance/utils/IVotesUpgradeable.sol"; -import {GovernanceERC20} from "@aragon/osx/token/ERC20/governance/GovernanceERC20.sol"; -import {createERC1967Proxy} from "@aragon/osx/utils/Proxy.sol"; -import {PermissionLib} from "@aragon/osx/core/permission/PermissionLib.sol"; - -contract ModeDaoFactory { - /// @notice The struct containing all the parameters to deploy the DAO - /// @param modeTokenAddress The address of the IVotes compatible ERC20 token contract to use for the voting power - /// @param bptTokenAddress The address of the IVotes compatible ERC20 token contract to use for the voting power - /// @param minApprovals The amount of approvals required for the multisig to be able to execute a proposal on the DAO - // OSx - /// @param osxDaoFactory The address of the OSx DAO factory contract, used to retrieve the DAO implementation address - /// @param pluginSetupProcessor The address of the OSx PluginSetupProcessor contract on the target chain - /// @param pluginRepoFactory The address of the OSx PluginRepoFactory contract on the target chain - // Plugins - /// @param multisigPluginSetup The address of the already deployed plugin setup for the standard multisig - /// @param votingPluginSetup The address of the already deployed plugin setup for the optimistic voting plugin - /// @param multisigMembers The list of addresses to be defined as the initial multisig signers - /// @param multisigExpirationPeriod How many seconds until a pending multisig proposal expires - /// @param multisigEnsDomain The subdomain to use as the ENS for the standard mulsitig plugin setup. Note: it must be unique and available. - /// @param votingEnsDomain The subdomain to use as the ENS for the optimistic voting plugin setup. Note: it must be unique and available. - struct DeploymentSettings { - // Mode plugin settings - IVotesUpgradeable tokenAddress; - uint16 minApprovals; - address[] multisigMembers; - uint64 multisigExpirationPeriod; - // OSx contracts - address osxDaoFactory; - PluginSetupProcessor pluginSetupProcessor; - PluginRepoFactory pluginRepoFactory; - // Main plugin setup - SimpleGaugeVoterSetup voterPluginSetup; - // ENS - PluginRepo multisigPluginRepo; - uint8 multisigPluginRepoRelease; - uint16 multisigPluginRepoBuild; - string voterEnsDomain; - } - - struct Deployment { - DAO dao; - // Plugins - Multisig multisigPlugin; - SimpleGaugeVoter modeVoterPlugin; - SimpleGaugeVoter bptVoterPlugin; - // Plugin repo's - PluginRepo voterPluginRepo; - } - - /// @notice Thrown when attempting to call deployOnce() when the DAO is already deployed. - error AlreadyDeployed(); - - DeploymentSettings settings; - Deployment deployment; - - /// @notice Initializes the factory and performs the full deployment. Values become read-only after that. - /// @param _settings The settings of the one-time deployment. - constructor(DeploymentSettings memory _settings) { - settings = _settings; - } - - function deployOnce() public { - if (address(deployment.dao) != address(0)) revert AlreadyDeployed(); - - IPluginSetup.PreparedSetupData memory preparedMultisigSetupData; - IPluginSetup.PreparedSetupData memory preparedVoterSetupData; - - // DEPLOY THE DAO (The factory is the interim owner) - DAO dao = prepareDao(); - deployment.dao = dao; - - // DEPLOY THE PLUGINS - ( - deployment.multisigPlugin, - deployment.multisigPluginRepo, - preparedMultisigSetupData - ) = prepareMultisig(dao); - - ( - deployment.voterPlugin, - deployment.voterPluginRepo, - preparedVoterSetupData - ) = prepareSimpleGaugeVoterPlugin(dao); - - // APPLY THE INSTALLATIONS - grantApplyInstallationPermissions(dao); - - applyPluginInstallation( - dao, - address(deployment.multisigPlugin), - deployment.multisigPluginRepo, - preparedMultisigSetupData - ); - applyPluginInstallation( - dao, - address(deployment.voterPlugin), - deployment.voterPluginRepo, - preparedVoterSetupData - ); - - revokeApplyInstallationPermissions(dao); - - // REMOVE THIS CONTRACT AS OWNER - revokeOwnerPermission(deployment.dao); - - // DEPLOY OTHER CONTRACTS - deployment.publicKeyRegistry = deployPublicKeyRegistry(); - } - - function prepareDao() internal returns (DAO dao) { - address daoBase = DAOFactory(settings.osxDaoFactory).daoBase(); - - dao = DAO( - payable( - createERC1967Proxy( - address(daoBase), - abi.encodeCall( - DAO.initialize, - ( - "", // Metadata URI - address(this), // initialOwner - address(0x0), // Trusted forwarder - "" // DAO URI - ) - ) - ) - ) - ); - - // Grant DAO all the needed permissions on itself - PermissionLib.SingleTargetPermission[] - memory items = new PermissionLib.SingleTargetPermission[](3); - items[0] = PermissionLib.SingleTargetPermission( - PermissionLib.Operation.Grant, - address(dao), - dao.ROOT_PERMISSION_ID() - ); - items[1] = PermissionLib.SingleTargetPermission( - PermissionLib.Operation.Grant, - address(dao), - dao.UPGRADE_DAO_PERMISSION_ID() - ); - items[2] = PermissionLib.SingleTargetPermission( - PermissionLib.Operation.Grant, - address(dao), - dao.REGISTER_STANDARD_CALLBACK_PERMISSION_ID() - ); - - dao.applySingleTargetPermissions(address(dao), items); - } - - function prepareMultisig( - DAO dao - ) internal returns (Multisig, PluginRepo, IPluginSetup.PreparedSetupData memory) { - // Publish repo - PluginRepo pluginRepo = PluginRepoFactory(settings.pluginRepoFactory) - .createPluginRepoWithFirstVersion( - settings.stdMultisigEnsDomain, - address(settings.multisigPluginSetup), - msg.sender, - " ", - " " - ); - - bytes memory settingsData = settings.multisigPluginSetup.encodeInstallationParameters( - settings.multisigMembers, - Multisig.MultisigSettings( - true, // onlyListed - settings.minApprovals, - settings.multisigExpirationPeriod - ) - ); - - (address plugin, IPluginSetup.PreparedSetupData memory preparedSetupData) = settings - .pluginSetupProcessor - .prepareInstallation( - address(dao), - PluginSetupProcessor.PrepareInstallationParams( - PluginSetupRef(PluginRepo.Tag(1, 1), PluginRepo(pluginRepo)), - settingsData - ) - ); - - return (Multisig(plugin), pluginRepo, preparedSetupData); - } - - function prepareSimpleGaugeVoterPlugin( - DAO dao, - address stdProposer, - address emergencyProposer - ) internal returns (SimpleGaugeVoter, PluginRepo, IPluginSetup.PreparedSetupData memory) { - // Plugin settings - bytes memory settingsData; - { - SimpleGaugeVoterSettings memory voterSettings = SimpleGaugeVoterSettings(); - // TODO - - SimpleGaugeVoterSetup.TokenSettings memory existingTokenSettings = SimpleGaugeVoterSetup - .TokenSettings(address(settings.tokenAddress), "Mode", "TKO"); - GovernanceERC20.MintSettings memory unusedMintSettings = GovernanceERC20.MintSettings( - new address[](0), - new uint256[](0) - ); - - settingsData = settings.optimisticTokenVotingPluginSetup.encodeInstallationParams( - SimpleGaugeVoterSetup.InstallationParameters( - voterSettings, - existingTokenSettings, - unusedMintSettings, - settings.modeL1ContractAddress, - settings.modeBridgeAddress, - settings.minStdProposalDuration, - stdProposer, - emergencyProposer - ) - ); - } - - (address plugin, IPluginSetup.PreparedSetupData memory preparedSetupData) = settings - .pluginSetupProcessor - .prepareInstallation( - address(dao), - PluginSetupProcessor.PrepareInstallationParams( - PluginSetupRef( - PluginRepo.Tag( - settings.multisigPluginRepoRelease, - settings.multisigPluginRepoBuild, - 2 - ), - settings.multisigPluginRepo - ), - settingsData - ) - ); - return (SimpleGaugeVoter(plugin), pluginRepo, preparedSetupData); - } - - function applyPluginInstallation( - DAO dao, - address plugin, - PluginRepo pluginRepo, - IPluginSetup.PreparedSetupData memory preparedSetupData - ) internal { - settings.pluginSetupProcessor.applyInstallation( - address(dao), - PluginSetupProcessor.ApplyInstallationParams( - PluginSetupRef(PluginRepo.Tag(1, 1), pluginRepo), - plugin, - preparedSetupData.permissions, - hashHelpers(preparedSetupData.helpers) - ) - ); - } - - function grantApplyInstallationPermissions(DAO dao) internal { - // The PSP can manage permissions on the new DAO - dao.grant(address(dao), address(settings.pluginSetupProcessor), dao.ROOT_PERMISSION_ID()); - - // This factory can call applyInstallation() on the PSP - dao.grant( - address(settings.pluginSetupProcessor), - address(this), - settings.pluginSetupProcessor.APPLY_INSTALLATION_PERMISSION_ID() - ); - } - - function revokeApplyInstallationPermissions(DAO dao) internal { - // Revoking the permission for the factory to call applyInstallation() on the PSP - dao.revoke( - address(settings.pluginSetupProcessor), - address(this), - settings.pluginSetupProcessor.APPLY_INSTALLATION_PERMISSION_ID() - ); - - // Revoke the PSP permission to manage permissions on the new DAO - dao.revoke(address(dao), address(settings.pluginSetupProcessor), dao.ROOT_PERMISSION_ID()); - } - - function revokeOwnerPermission(DAO dao) internal { - dao.revoke(address(dao), address(this), dao.ROOT_PERMISSION_ID()); - } - - // Getters - - function getSettings() public view returns (DeploymentSettings memory) { - return settings; - } - - function getDeployment() public view returns (Deployment memory) { - return deployment; - } -} From 4083b5c4255162d18c51bd078f65a4b8b54479bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Wed, 25 Sep 2024 19:02:48 +0100 Subject: [PATCH 04/14] Almost ready --- .env.example | 3 -- script/Deploy.s.sol | 84 ++++++++++++++++++++++---------- src/factory/GaugesDaoFactory.sol | 66 +++++++++++++++++-------- 3 files changed, 103 insertions(+), 50 deletions(-) diff --git a/.env.example b/.env.example index 04b761d..38d0cc9 100644 --- a/.env.example +++ b/.env.example @@ -9,7 +9,6 @@ DEPLOY_AS_PRODUCTION=true MULTISIG_MEMBERS_JSON_FILE_NAME="/script/multisig-members.json" # MULTISIG PARAMETERS -MIN_PROPOSAL_DURATION="864000" # in seconds (10 days) MIN_APPROVALS="5" # How many multisig approvals are required MULTISIG_PROPOSAL_EXPIRATION_PERIOD="864000" # How long until a pending proposal expires (10 days) @@ -51,5 +50,3 @@ SIMPLE_GAUGE_VOTER_REPO_ENS_SUBDOMAIN="my-simple-gauge-voter-0" DAO_FACTORY="0xE640Da5AD169630555A86D9b6b9C145B4961b1EB" PLUGIN_SETUP_PROCESSOR="0xCe0B4124dea6105bfB85fB4461c4D39f360E9ef3" PLUGIN_REPO_FACTORY="0x95D563382BeD5AcB458759EE05b27DF2CB019Cc7" - -# GOVERNANCE_ERC20_BASE="0xC24188a73dc09aA7C721f96Ad8857B469C01dC9f" diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index a99fee1..2d837b9 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -9,11 +9,12 @@ import {VotingEscrow, Clock, Lock, QuadraticIncreasingEscrow, ExitQueue, SimpleG import {PluginRepo} from "@aragon/osx/framework/plugin/repo/PluginRepo.sol"; import {PluginRepoFactory} from "@aragon/osx/framework/plugin/repo/PluginRepoFactory.sol"; import {PluginSetupProcessor} from "@aragon/osx/framework/plugin/setup/PluginSetupProcessor.sol"; +import {MockERC20} from "@mocks/MockERC20.sol"; contract Deploy is Script { SimpleGaugeVoterSetup simpleGaugeVoterSetup; - /// @dev Thrown when attempting to create a DAO with an empty multisig + /// @dev Thrown when attempting to deploy a multisig with no members error EmptyMultisig(); modifier broadcast() { @@ -26,18 +27,11 @@ contract Deploy is Script { vm.stopBroadcast(); } + /// @notice Runs the deployment flow, records the given parameters and artifacts, and it becomes read only function run() public broadcast { - // NOTE: - // Deploying the plugin setup's separately because of the code size limit - - // Note: Multisig is already deployed, not redeploying - - // Deploy the voter plugin setup - // TODO: - - DeploymentParameters memory parameters = getDeploymentParameters( - vm.envBool("DEPLOY_AS_PRODUCTION") - ); + // Prepare all parameters + bool isProduction = vm.envBool("DEPLOY_AS_PRODUCTION"); + DeploymentParameters memory parameters = getDeploymentParameters(isProduction); // Create the DAO GaugesDaoFactory factory = new GaugesDaoFactory(parameters); @@ -50,13 +44,20 @@ contract Deploy is Script { function getDeploymentParameters( bool isProduction ) internal returns (DeploymentParameters memory parameters) { + address[] memory multisigMembers = readMultisigMembers(); + TokenParameters[] memory tokenParameters = getTokenParameters(isProduction); + + // NOTE: Multisig is already deployed, using the existing Aragon's repo + // NOTE: Deploying the plugin setup from the current script to avoid code size constraints + + SimpleGaugeVoterSetup gaugeVoterPluginSetup = deploySimpleGaugeVoterPluginSetup(); + parameters = DeploymentParameters({ // Multisig settings - minProposalDuration: uint64(vm.envUint("MIN_PROPOSAL_DURATION")), minApprovals: uint8(vm.envUint("MIN_APPROVALS")), - multisigMembers: readMultisigMembers(), + multisigMembers: multisigMembers, // Gauge Voter - tokenParameters: getTokenParameters(isProduction), + tokenParameters: tokenParameters, feePercent: vm.envUint("FEE_PERCENT_WEI"), warmupPeriod: uint64(vm.envUint("WARMUP_PERIOD")), cooldownPeriod: uint64(vm.envUint("COOLDOWN_PERIOD")), @@ -67,7 +68,7 @@ contract Deploy is Script { multisigPluginRelease: uint8(vm.envUint("MULTISIG_PLUGIN_RELEASE")), multisigPluginBuild: uint16(vm.envUint("MULTISIG_PLUGIN_BUILD")), // Voter plugin setup and ENS - voterPluginSetup: deploySimpleGaugeVoterPluginSetup(), + voterPluginSetup: gaugeVoterPluginSetup, voterEnsSubdomain: vm.envString("SIMPLE_GAUGE_VOTER_REPO_ENS_SUBDOMAIN"), // OSx addresses osxDaoFactory: vm.envAddress("DAO_FACTORY"), @@ -99,7 +100,7 @@ contract Deploy is Script { function getTokenParameters( bool isProduction - ) internal view returns (TokenParameters[] memory tokenParameters) { + ) internal returns (TokenParameters[] memory tokenParameters) { if (isProduction) { // USE TOKEN(s) console.log("Using production parameters"); @@ -122,15 +123,39 @@ contract Deploy is Script { } } else { // MINT TEST TOKEN - console.log("Using testing parameters (minting 2 token)"); + console.log("Using testing parameters (minting 2 test tokens)"); + + address[] memory multisigMembers = readMultisigMembers(); + address[] memory tokens = new address[](2); + tokens[0] = createTestToken(multisigMembers); + tokens[1] = createTestToken(multisigMembers); - // TODO: + tokenParameters = new TokenParameters[](2); + tokenParameters[0] = TokenParameters({ + token: tokens[0], + veTokenName: "VE Token 1", + veTokenSymbol: "veTK1" + }); + tokenParameters[1] = TokenParameters({ + token: tokens[1], + veTokenName: "VE Token 2", + veTokenSymbol: "veTK2" + }); } } - function createTestToken(address[] memory holders) internal { - // TODO: - address newToken = vm.envAddress("GOVERNANCE_ERC20_BASE"); + function createTestToken(address[] memory holders) internal returns (address) { + MockERC20 newToken = new MockERC20(); + + for (uint i = 0; i < holders.length; ) { + newToken.mint(holders[i], 50 ether); + + unchecked { + i++; + } + } + + return address(newToken); } function printDeploymentSummary(GaugesDaoFactory factory) internal view { @@ -147,9 +172,16 @@ contract Deploy is Script { console.log("Plugins"); console.log("- Multisig plugin:", address(deployment.multisigPlugin)); console.log(""); - for (uint i = 0; i < deployment.voterPlugins.length; ) { - console.log("- Token:", address(deploymentParameters.tokenParameters[i].token)); - console.log(" Gauge voter plugin:", address(deployment.voterPlugins[i])); + + for (uint i = 0; i < deployment.gaugePluginSets.length; ) { + console.log("- Using token:", address(deploymentParameters.tokenParameters[i].token)); + console.log(" Gauge voter plugin:", address(deployment.gaugePluginSets[i].plugin)); + console.log(" Curve:", address(deployment.gaugePluginSets[i].curve)); + console.log(" Exit Queue:", address(deployment.gaugePluginSets[i].exitQueue)); + console.log(" Voting Escrow:", address(deployment.gaugePluginSets[i].votingEscrow)); + console.log(" Clock:", address(deployment.gaugePluginSets[i].clock)); + console.log(" NFT Lock:", address(deployment.gaugePluginSets[i].nftLock)); + unchecked { i++; } @@ -158,7 +190,7 @@ contract Deploy is Script { console.log("Plugin repositories"); console.log( - "- Eultisig plugin repository (existing):", + "- Multisig plugin repository (existing):", address(deploymentParameters.multisigPluginRepo) ); console.log("- Gauge voter plugin repository:", address(deployment.voterPluginRepo)); diff --git a/src/factory/GaugesDaoFactory.sol b/src/factory/GaugesDaoFactory.sol index df5ea50..044c123 100644 --- a/src/factory/GaugesDaoFactory.sol +++ b/src/factory/GaugesDaoFactory.sol @@ -7,7 +7,7 @@ import {Clock} from "@clock/Clock.sol"; import {IEscrowCurveUserStorage} from "@escrow-interfaces/IEscrowCurveIncreasing.sol"; import {IWithdrawalQueueErrors} from "src/escrow/increasing/interfaces/IVotingEscrowIncreasing.sol"; import {IGaugeVote} from "src/voting/ISimpleGaugeVoter.sol"; -import {SimpleGaugeVoter, SimpleGaugeVoterSetup, ISimpleGaugeVoterSetupParams} from "src/voting/SimpleGaugeVoterSetup.sol"; +import {VotingEscrow, Clock, Lock, QuadraticIncreasingEscrow, ExitQueue, SimpleGaugeVoter, SimpleGaugeVoterSetup, ISimpleGaugeVoterSetupParams} from "src/voting/SimpleGaugeVoterSetup.sol"; import {Addresslist} from "@aragon/osx/plugins/utils/Addresslist.sol"; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import {PluginSetupProcessor} from "@aragon/osx/framework/plugin/setup/PluginSetupProcessor.sol"; @@ -23,22 +23,24 @@ import {createERC1967Proxy} from "@aragon/osx/utils/Proxy.sol"; import {PermissionLib} from "@aragon/osx/core/permission/PermissionLib.sol"; /// @notice The struct containing all the parameters to deploy the DAO -/// @param token1Address The address of the IVotes compatible ERC20 token contract to use for the voting power -/// @param token2Address The address of the IVotes compatible ERC20 token contract to use for the voting power /// @param minApprovals The amount of approvals required for the multisig to be able to execute a proposal on the DAO -// OSx +/// @param multisigMembers The list of addresses to be defined as the initial multisig signers +/// @param tokenParameters A list with the tokens and metadata for which a plugin and a VE should be deployed +/// @param feePercent The fee taken on withdrawals (1 ether = 100%) +/// @param warmupPeriod Delay in seconds after depositing before voting becomes possible +/// @param cooldownPeriod Delay seconds after queuing an exit before withdrawing becomes possible +/// @param minLockDuration Min seconds a user must have locked in escrow before they can queue an exit +/// @param votingPaused Prevent voting until manually activated by the multisig +/// @param multisigPluginRepo Address of Aragon's multisig plugin repository on the given network +/// @param multisigPluginRelease The release of the multisig plugin to target +/// @param multisigPluginBuild The build of the multisig plugin to target +/// @param voterPluginSetup The address of the Gauges Voter plugin setup contract to create a repository with +/// @param voterEnsSubdomain The ENS subdomain under which the plugin reposiroty will be created /// @param osxDaoFactory The address of the OSx DAO factory contract, used to retrieve the DAO implementation address /// @param pluginSetupProcessor The address of the OSx PluginSetupProcessor contract on the target chain /// @param pluginRepoFactory The address of the OSx PluginRepoFactory contract on the target chain -// Plugins -/// @param multisigPluginSetup The address of the already deployed plugin setup for the standard multisig -/// @param votingPluginSetup The address of the already deployed plugin setup for the optimistic voting plugin -/// @param multisigMembers The list of addresses to be defined as the initial multisig signers -/// @param multisigEnsDomain The subdomain to use as the ENS for the standard mulsitig plugin setup. Note: it must be unique and available. -/// @param votingEnsDomain The subdomain to use as the ENS for the optimistic voting plugin setup. Note: it must be unique and available. struct DeploymentParameters { // Multisig settings - uint64 minProposalDuration; uint16 minApprovals; address[] multisigMembers; // Gauge Voter @@ -66,15 +68,27 @@ struct TokenParameters { string veTokenSymbol; } +/// @notice Struct containing the plugin and all of its helpers +struct GaugePluginSet { + SimpleGaugeVoter plugin; + QuadraticIncreasingEscrow curve; + ExitQueue exitQueue; + VotingEscrow votingEscrow; + Clock clock; + Lock nftLock; +} + +/// @notice Contains the artifacts that resulted from running a deployment struct Deployment { DAO dao; // Plugins Multisig multisigPlugin; - SimpleGaugeVoter[] voterPlugins; + GaugePluginSet[] gaugePluginSets; // Plugin repo's PluginRepo voterPluginRepo; } +/// @notice A singleton contract designed to run the deployment once and become a read-only store of the contracts deployed contract GaugesDaoFactory { /// @notice Thrown when attempting to call deployOnce() when the DAO is already deployed. error AlreadyDeployed(); @@ -88,6 +102,7 @@ contract GaugesDaoFactory { parameters = _parameters; } + /// @notice Run the deployment and store the artifacts in a read-only store that can be retrieved via `getDeployment()` and `getDeploymentParameters()` function deployOnce() public { if (address(deployment.dao) != address(0)) revert AlreadyDeployed(); @@ -122,29 +137,27 @@ contract GaugesDaoFactory { // GAUGE VOTER(s) { IPluginSetup.PreparedSetupData memory preparedVoterSetupData; - SimpleGaugeVoter voterPlugin; PluginRepo.Tag memory repoTag = PluginRepo.Tag(1, 1); - - deployment.voterPlugins = new SimpleGaugeVoter[](parameters.tokenParameters.length); + GaugePluginSet memory pluginSet; for (uint i = 0; i < parameters.tokenParameters.length; ) { ( - voterPlugin, + pluginSet, deployment.voterPluginRepo, preparedVoterSetupData ) = prepareSimpleGaugeVoterPlugin(dao, parameters.tokenParameters[i], repoTag); + deployment.gaugePluginSets.push(pluginSet); + applyPluginInstallation( dao, - address(voterPlugin), + address(pluginSet.plugin), deployment.voterPluginRepo, repoTag, preparedVoterSetupData ); - deployment.voterPlugins[i] = voterPlugin; - unchecked { i++; } @@ -229,7 +242,7 @@ contract GaugesDaoFactory { DAO dao, TokenParameters memory tokenParameters, PluginRepo.Tag memory repoTag - ) internal returns (SimpleGaugeVoter, PluginRepo, IPluginSetup.PreparedSetupData memory) { + ) internal returns (GaugePluginSet memory, PluginRepo, IPluginSetup.PreparedSetupData memory) { // Publish repo PluginRepo pluginRepo = PluginRepoFactory(parameters.pluginRepoFactory) .createPluginRepoWithFirstVersion( @@ -263,7 +276,18 @@ contract GaugesDaoFactory { settingsData ) ); - return (SimpleGaugeVoter(plugin), pluginRepo, preparedSetupData); + + address[] memory helpers = preparedSetupData.helpers; + GaugePluginSet memory pluginSet = GaugePluginSet({ + plugin: SimpleGaugeVoter(plugin), + curve: QuadraticIncreasingEscrow(helpers[0]), + exitQueue: ExitQueue(helpers[1]), + votingEscrow: VotingEscrow(helpers[2]), + clock: Clock(helpers[3]), + nftLock: Lock(helpers[4]) + }); + + return (pluginSet, pluginRepo, preparedSetupData); } function applyPluginInstallation( From 7e419ea1ecce0a14b5e83642433bbcfbe21f5730 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Thu, 26 Sep 2024 15:12:43 +0100 Subject: [PATCH 05/14] Finally compiling --- src/factory/GaugesDaoFactory.sol | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/factory/GaugesDaoFactory.sol b/src/factory/GaugesDaoFactory.sol index 044c123..64dafbd 100644 --- a/src/factory/GaugesDaoFactory.sol +++ b/src/factory/GaugesDaoFactory.sol @@ -96,10 +96,37 @@ contract GaugesDaoFactory { DeploymentParameters parameters; Deployment deployment; + /// @dev Solidity doesn't allow to store a struct array on a storage struct. Storing a reference to link from parameters.tokenParameters + TokenParameters[] private storageTokenParams; + /// @notice Initializes the factory and performs the full deployment. Values become read-only after that. /// @param _parameters The parameters of the one-time deployment. constructor(DeploymentParameters memory _parameters) { - parameters = _parameters; + parameters.minApprovals = _parameters.minApprovals; + parameters.multisigMembers = _parameters.multisigMembers; + + for (uint i = 0; i < _parameters.tokenParameters.length; ) { + storageTokenParams.push(_parameters.tokenParameters[i]); + + unchecked { + i++; + } + } + parameters.tokenParameters = storageTokenParams; + + parameters.feePercent = _parameters.feePercent; + parameters.warmupPeriod = _parameters.warmupPeriod; + parameters.cooldownPeriod = _parameters.cooldownPeriod; + parameters.minLockDuration = _parameters.minLockDuration; + parameters.votingPaused = _parameters.votingPaused; + parameters.multisigPluginRepo = _parameters.multisigPluginRepo; + parameters.multisigPluginRelease = _parameters.multisigPluginRelease; + parameters.multisigPluginBuild = _parameters.multisigPluginBuild; + parameters.voterPluginSetup = _parameters.voterPluginSetup; + parameters.voterEnsSubdomain = _parameters.voterEnsSubdomain; + parameters.osxDaoFactory = _parameters.osxDaoFactory; + parameters.pluginSetupProcessor = _parameters.pluginSetupProcessor; + parameters.pluginRepoFactory = _parameters.pluginRepoFactory; } /// @notice Run the deployment and store the artifacts in a read-only store that can be retrieved via `getDeployment()` and `getDeploymentParameters()` From 17e1fef85e8b1b38f88a8c22c8fadba1719324fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Thu, 26 Sep 2024 15:17:52 +0100 Subject: [PATCH 06/14] Now assigning just works. Fine --- src/factory/GaugesDaoFactory.sol | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/factory/GaugesDaoFactory.sol b/src/factory/GaugesDaoFactory.sol index 64dafbd..6233aa2 100644 --- a/src/factory/GaugesDaoFactory.sol +++ b/src/factory/GaugesDaoFactory.sol @@ -96,9 +96,6 @@ contract GaugesDaoFactory { DeploymentParameters parameters; Deployment deployment; - /// @dev Solidity doesn't allow to store a struct array on a storage struct. Storing a reference to link from parameters.tokenParameters - TokenParameters[] private storageTokenParams; - /// @notice Initializes the factory and performs the full deployment. Values become read-only after that. /// @param _parameters The parameters of the one-time deployment. constructor(DeploymentParameters memory _parameters) { @@ -106,13 +103,12 @@ contract GaugesDaoFactory { parameters.multisigMembers = _parameters.multisigMembers; for (uint i = 0; i < _parameters.tokenParameters.length; ) { - storageTokenParams.push(_parameters.tokenParameters[i]); + parameters.tokenParameters.push(_parameters.tokenParameters[i]); unchecked { i++; } } - parameters.tokenParameters = storageTokenParams; parameters.feePercent = _parameters.feePercent; parameters.warmupPeriod = _parameters.warmupPeriod; From 4e8298d68bd66b6d4fab02953dff9feeb7464186 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Thu, 26 Sep 2024 17:17:41 +0100 Subject: [PATCH 07/14] Factory testing work in progress --- script/Deploy.s.sol | 9 +- test/integration/GaugesDaoFactory.sol | 977 ++++++++++++++++++++++ test/mocks/osx/MockPluginRepoRegistry.sol | 63 ++ 3 files changed, 1043 insertions(+), 6 deletions(-) create mode 100644 test/integration/GaugesDaoFactory.sol create mode 100644 test/mocks/osx/MockPluginRepoRegistry.sol diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index 2d837b9..1912a06 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -126,18 +126,14 @@ contract Deploy is Script { console.log("Using testing parameters (minting 2 test tokens)"); address[] memory multisigMembers = readMultisigMembers(); - address[] memory tokens = new address[](2); - tokens[0] = createTestToken(multisigMembers); - tokens[1] = createTestToken(multisigMembers); - tokenParameters = new TokenParameters[](2); tokenParameters[0] = TokenParameters({ - token: tokens[0], + token: createTestToken(multisigMembers), veTokenName: "VE Token 1", veTokenSymbol: "veTK1" }); tokenParameters[1] = TokenParameters({ - token: tokens[1], + token: createTestToken(multisigMembers), veTokenName: "VE Token 2", veTokenSymbol: "veTK2" }); @@ -149,6 +145,7 @@ contract Deploy is Script { for (uint i = 0; i < holders.length; ) { newToken.mint(holders[i], 50 ether); + console.log("Minting 50 tokens for", holders[i]); unchecked { i++; diff --git a/test/integration/GaugesDaoFactory.sol b/test/integration/GaugesDaoFactory.sol new file mode 100644 index 0000000..893de75 --- /dev/null +++ b/test/integration/GaugesDaoFactory.sol @@ -0,0 +1,977 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +import "forge-std/Test.sol"; +import {GaugesDaoFactory, Deployment, DeploymentParameters, TokenParameters} from "../../src/factory/GaugesDaoFactory.sol"; +import {MockPluginSetupProcessor} from "../mocks/osx/MockPSP.sol"; +import {MockPluginRepoRegistry} from "../mocks/osx/MockPluginRepoRegistry.sol"; +import {MockDAOFactory} from "../mocks/osx/MockDaoFactory.sol"; +import {PluginSetupProcessor} from "@aragon/osx/framework/plugin/setup/PluginSetupProcessor.sol"; +import {PluginRepoFactory} from "@aragon/osx/framework/plugin/repo/PluginRepoFactory.sol"; +import {PluginRepoRegistry} from "@aragon/osx/framework/plugin/repo/PluginRepoRegistry.sol"; +import {PluginRepo} from "@aragon/osx/framework/plugin/repo/PluginRepo.sol"; +import {GovernanceERC20} from "@aragon/osx/token/ERC20/governance/GovernanceERC20.sol"; +import {GovernanceWrappedERC20} from "@aragon/osx/token/ERC20/governance/GovernanceWrappedERC20.sol"; +import {IERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; +import {DAO} from "@aragon/osx/core/dao/DAO.sol"; +import {IDAO} from "@aragon/osx/core/dao/IDAO.sol"; +import {Addresslist} from "@aragon/osx/plugins/utils/Addresslist.sol"; +import {MultisigSetup as MultisigPluginSetup} from "@aragon/osx/plugins/governance/multisig/MultisigSetup.sol"; +import {SimpleGaugeVoterSetup, VotingEscrow, Clock, Lock, QuadraticIncreasingEscrow, ExitQueue, SimpleGaugeVoter} from "../../src/voting/SimpleGaugeVoterSetup.sol"; + +contract GaugesDaoFactoryTest is Test { + function test_ShouldStoreTheSettings_1() public { + address[] memory multisigMembers = new address[](13); + for (uint256 i = 0; i < 13; i++) { + multisigMembers[i] = address(uint160(i + 5)); + } + + SimpleGaugeVoterSetup gaugeVoterPluginSetup = new SimpleGaugeVoterSetup( + address(new SimpleGaugeVoter()), + address(new QuadraticIncreasingEscrow()), + address(new ExitQueue()), + address(new VotingEscrow()), + address(new Clock()), + address(new Lock()) + ); + + MockPluginRepoRegistry pRepoRegistry = new MockPluginRepoRegistry(); + PluginRepoFactory pRefoFactory = new PluginRepoFactory( + PluginRepoRegistry(address(pRepoRegistry)) + ); + MockPluginSetupProcessor psp = new MockPluginSetupProcessor(address(0)); + MockDAOFactory daoFactory = new MockDAOFactory(psp); + + TokenParameters[] memory tokenParameters = new TokenParameters[](2); + tokenParameters[0] = TokenParameters({ + token: address(111), + veTokenName: "Name 1", + veTokenSymbol: "TK1" + }); + tokenParameters[1] = TokenParameters({ + token: address(222), + veTokenName: "Name 2", + veTokenSymbol: "TK2" + }); + + DeploymentParameters memory creationParams = DeploymentParameters({ + // Multisig settings + minApprovals: 2, + multisigMembers: multisigMembers, + // Gauge Voter + tokenParameters: tokenParameters, + feePercent: 0.5 ether, + warmupPeriod: 1234, + cooldownPeriod: 2345, + minLockDuration: 3456, + votingPaused: false, + // Standard multisig repo + multisigPluginRepo: PluginRepo(address(5555)), + multisigPluginRelease: 1, + multisigPluginBuild: 2, + // Voter plugin setup and ENS + voterPluginSetup: gaugeVoterPluginSetup, + voterEnsSubdomain: "gauge-ens-subdomain", + // OSx addresses + osxDaoFactory: address(daoFactory), + pluginSetupProcessor: PluginSetupProcessor(address(psp)), + pluginRepoFactory: pRefoFactory + }); + + GaugesDaoFactory factory = new GaugesDaoFactory(creationParams); + + // Check + DeploymentParameters memory actualParams = factory.getDeploymentParameters(); + assertEq(actualParams.minApprovals, creationParams.minApprovals, "Incorrect minApprovals"); + assertEq( + actualParams.multisigMembers.length, + creationParams.multisigMembers.length, + "Incorrect multisigMembers.length" + ); + for (uint256 i = 0; i < 13; i++) { + assertEq(multisigMembers[i], address(uint160(i + 5)), "Incorrect member address"); + } + + assertEq( + actualParams.tokenParameters.length, + creationParams.tokenParameters.length, + "Incorrect tokenParameters.length" + ); + assertEq( + actualParams.tokenParameters[0].token, + creationParams.tokenParameters[0].token, + "Incorrect tokenParameters[0].token" + ); + assertEq( + actualParams.tokenParameters[0].veTokenName, + creationParams.tokenParameters[0].veTokenName, + "Incorrect tokenParameters[0].veTokenName" + ); + assertEq( + actualParams.tokenParameters[0].veTokenSymbol, + creationParams.tokenParameters[0].veTokenSymbol, + "Incorrect tokenParameters[0].veTokenSymbol" + ); + assertEq( + actualParams.tokenParameters[1].token, + creationParams.tokenParameters[1].token, + "Incorrect tokenParameters[1].token" + ); + assertEq( + actualParams.tokenParameters[1].veTokenName, + creationParams.tokenParameters[1].veTokenName, + "Incorrect tokenParameters[1].veTokenName" + ); + assertEq( + actualParams.tokenParameters[1].veTokenSymbol, + creationParams.tokenParameters[1].veTokenSymbol, + "Incorrect tokenParameters[1].veTokenSymbol" + ); + + assertEq(actualParams.feePercent, creationParams.feePercent, "Incorrect feePercent"); + assertEq(actualParams.warmupPeriod, creationParams.warmupPeriod, "Incorrect warmupPeriod"); + assertEq( + actualParams.cooldownPeriod, + creationParams.cooldownPeriod, + "Incorrect cooldownPeriod" + ); + assertEq( + actualParams.minLockDuration, + creationParams.minLockDuration, + "Incorrect minLockDuration" + ); + assertEq(actualParams.votingPaused, creationParams.votingPaused, "Incorrect votingPaused"); + + assertEq( + address(actualParams.multisigPluginRepo), + address(creationParams.multisigPluginRepo), + "Incorrect multisigPluginRepo" + ); + assertEq( + actualParams.multisigPluginRelease, + creationParams.multisigPluginRelease, + "Incorrect multisigPluginRelease" + ); + assertEq( + actualParams.multisigPluginBuild, + creationParams.multisigPluginBuild, + "Incorrect multisigPluginBuild" + ); + assertEq( + address(actualParams.voterPluginSetup), + address(creationParams.voterPluginSetup), + "Incorrect voterPluginSetup" + ); + assertEq( + actualParams.voterEnsSubdomain, + creationParams.voterEnsSubdomain, + "Incorrect voterEnsSubdomain" + ); + + assertEq( + address(actualParams.osxDaoFactory), + address(creationParams.osxDaoFactory), + "Incorrect osxDaoFactory" + ); + assertEq( + address(actualParams.pluginSetupProcessor), + address(creationParams.pluginSetupProcessor), + "Incorrect pluginSetupProcessor" + ); + assertEq( + address(actualParams.pluginRepoFactory), + address(creationParams.pluginRepoFactory), + "Incorrect pluginRepoFactory" + ); + } + + function test_ShouldStoreTheSettings_2() public { + address[] memory multisigMembers = new address[](13); + for (uint256 i = 0; i < 13; i++) { + multisigMembers[i] = address(uint160(i + 10)); + } + + SimpleGaugeVoterSetup gaugeVoterPluginSetup = new SimpleGaugeVoterSetup( + address(new SimpleGaugeVoter()), + address(new QuadraticIncreasingEscrow()), + address(new ExitQueue()), + address(new VotingEscrow()), + address(new Clock()), + address(new Lock()) + ); + + MockPluginRepoRegistry pRepoRegistry = new MockPluginRepoRegistry(); + PluginRepoFactory pRefoFactory = new PluginRepoFactory( + PluginRepoRegistry(address(pRepoRegistry)) + ); + MockPluginSetupProcessor psp = new MockPluginSetupProcessor(address(0)); + MockDAOFactory daoFactory = new MockDAOFactory(psp); + + TokenParameters[] memory tokenParameters = new TokenParameters[](2); + tokenParameters[0] = TokenParameters({ + token: address(333), + veTokenName: "Name 3", + veTokenSymbol: "TK3" + }); + tokenParameters[1] = TokenParameters({ + token: address(444), + veTokenName: "Name 4", + veTokenSymbol: "TK4" + }); + + DeploymentParameters memory creationParams = DeploymentParameters({ + // Multisig settings + minApprovals: 3, + multisigMembers: multisigMembers, + // Gauge Voter + tokenParameters: tokenParameters, + feePercent: 0.1 ether, + warmupPeriod: 7654, + cooldownPeriod: 6543, + minLockDuration: 5432, + votingPaused: true, + // Standard multisig repo + multisigPluginRepo: PluginRepo(address(3333)), + multisigPluginRelease: 2, + multisigPluginBuild: 10, + // Voter plugin setup and ENS + voterPluginSetup: gaugeVoterPluginSetup, + voterEnsSubdomain: "gauge-ens-subdomain-bis", + // OSx addresses + osxDaoFactory: address(daoFactory), + pluginSetupProcessor: PluginSetupProcessor(address(psp)), + pluginRepoFactory: pRefoFactory + }); + + GaugesDaoFactory factory = new GaugesDaoFactory(creationParams); + + // Check + DeploymentParameters memory actualParams = factory.getDeploymentParameters(); + assertEq(actualParams.minApprovals, creationParams.minApprovals, "Incorrect minApprovals"); + assertEq( + actualParams.multisigMembers.length, + creationParams.multisigMembers.length, + "Incorrect multisigMembers.length" + ); + for (uint256 i = 0; i < 13; i++) { + assertEq(multisigMembers[i], address(uint160(i + 10)), "Incorrect member address"); + } + + assertEq( + actualParams.tokenParameters.length, + creationParams.tokenParameters.length, + "Incorrect tokenParameters.length" + ); + assertEq( + actualParams.tokenParameters[0].token, + creationParams.tokenParameters[0].token, + "Incorrect tokenParameters[0].token" + ); + assertEq( + actualParams.tokenParameters[0].veTokenName, + creationParams.tokenParameters[0].veTokenName, + "Incorrect tokenParameters[0].veTokenName" + ); + assertEq( + actualParams.tokenParameters[0].veTokenSymbol, + creationParams.tokenParameters[0].veTokenSymbol, + "Incorrect tokenParameters[0].veTokenSymbol" + ); + assertEq( + actualParams.tokenParameters[1].token, + creationParams.tokenParameters[1].token, + "Incorrect tokenParameters[1].token" + ); + assertEq( + actualParams.tokenParameters[1].veTokenName, + creationParams.tokenParameters[1].veTokenName, + "Incorrect tokenParameters[1].veTokenName" + ); + assertEq( + actualParams.tokenParameters[1].veTokenSymbol, + creationParams.tokenParameters[1].veTokenSymbol, + "Incorrect tokenParameters[1].veTokenSymbol" + ); + + assertEq(actualParams.feePercent, creationParams.feePercent, "Incorrect feePercent"); + assertEq(actualParams.warmupPeriod, creationParams.warmupPeriod, "Incorrect warmupPeriod"); + assertEq( + actualParams.cooldownPeriod, + creationParams.cooldownPeriod, + "Incorrect cooldownPeriod" + ); + assertEq( + actualParams.minLockDuration, + creationParams.minLockDuration, + "Incorrect minLockDuration" + ); + assertEq(actualParams.votingPaused, creationParams.votingPaused, "Incorrect votingPaused"); + + assertEq( + address(actualParams.multisigPluginRepo), + address(creationParams.multisigPluginRepo), + "Incorrect multisigPluginRepo" + ); + assertEq( + actualParams.multisigPluginRelease, + creationParams.multisigPluginRelease, + "Incorrect multisigPluginRelease" + ); + assertEq( + actualParams.multisigPluginBuild, + creationParams.multisigPluginBuild, + "Incorrect multisigPluginBuild" + ); + assertEq( + address(actualParams.voterPluginSetup), + address(creationParams.voterPluginSetup), + "Incorrect voterPluginSetup" + ); + assertEq( + actualParams.voterEnsSubdomain, + creationParams.voterEnsSubdomain, + "Incorrect voterEnsSubdomain" + ); + + assertEq( + address(actualParams.osxDaoFactory), + address(creationParams.osxDaoFactory), + "Incorrect osxDaoFactory" + ); + assertEq( + address(actualParams.pluginSetupProcessor), + address(creationParams.pluginSetupProcessor), + "Incorrect pluginSetupProcessor" + ); + assertEq( + address(actualParams.pluginRepoFactory), + address(creationParams.pluginRepoFactory), + "Incorrect pluginRepoFactory" + ); + } + + function test_StandardDeployment_1() public { + address[] memory multisigMembers = new address[](13); + for (uint256 i = 0; i < 13; i++) { + multisigMembers[i] = address(uint160(i + 5)); + } + + PluginRepo multisigPluginRepo = PluginRepo(vm.envAddress("MULTISIG_PLUGIN_REPO_ADDRESS")); + SimpleGaugeVoterSetup gaugeVoterPluginSetup = new SimpleGaugeVoterSetup( + address(new SimpleGaugeVoter()), + address(new QuadraticIncreasingEscrow()), + address(new ExitQueue()), + address(new VotingEscrow()), + address(new Clock()), + address(new Lock()) + ); + + MockPluginRepoRegistry pRepoRegistry = new MockPluginRepoRegistry(); + PluginRepoFactory pRefoFactory = new PluginRepoFactory( + PluginRepoRegistry(address(pRepoRegistry)) + ); + MockPluginSetupProcessor psp = new MockPluginSetupProcessor(address(0)); + MockDAOFactory daoFactory = new MockDAOFactory(psp); + + TokenParameters[] memory tokenParameters = new TokenParameters[](2); + tokenParameters[0] = TokenParameters({ + token: address(111), + veTokenName: "Name 1", + veTokenSymbol: "TK1" + }); + tokenParameters[1] = TokenParameters({ + token: address(222), + veTokenName: "Name 2", + veTokenSymbol: "TK2" + }); + + DeploymentParameters memory creationParams = DeploymentParameters({ + // Multisig settings + minApprovals: 2, + multisigMembers: multisigMembers, + // Gauge Voter + tokenParameters: tokenParameters, + feePercent: 0.5 ether, + warmupPeriod: 1234, + cooldownPeriod: 2345, + minLockDuration: 3456, + votingPaused: false, + // Standard multisig repo + multisigPluginRepo: multisigPluginRepo, + multisigPluginRelease: 1, + multisigPluginBuild: 2, + // Voter plugin setup and ENS + voterPluginSetup: gaugeVoterPluginSetup, + voterEnsSubdomain: "gauge-ens-subdomain", + // OSx addresses + osxDaoFactory: address(daoFactory), + pluginSetupProcessor: PluginSetupProcessor(address(psp)), + pluginRepoFactory: pRefoFactory + }); + + GaugesDaoFactory factory = new GaugesDaoFactory(creationParams); + + factory.deployOnce(); + Deployment memory deployment = factory.getDeployment(); + + vm.roll(block.number + 1); // mint one block + + // DAO checks + + assertNotEq(address(deployment.dao), address(0), "Empty DAO field"); + assertEq(deployment.dao.daoURI(), "", "DAO URI should be empty"); + assertEq( + address(deployment.dao.signatureValidator()), + address(0), + "signatureValidator should be empty" + ); + assertEq( + address(deployment.dao.getTrustedForwarder()), + address(0), + "trustedForwarder should be empty" + ); + assertEq( + deployment.dao.hasPermission( + address(deployment.dao), + address(deployment.dao), + deployment.dao.ROOT_PERMISSION_ID(), + bytes("") + ), + true, + "The DAO should be ROOT on itself" + ); + assertEq( + deployment.dao.hasPermission( + address(deployment.dao), + address(deployment.dao), + deployment.dao.UPGRADE_DAO_PERMISSION_ID(), + bytes("") + ), + true, + "The DAO should have UPGRADE_DAO_PERMISSION on itself" + ); + assertEq( + deployment.dao.hasPermission( + address(deployment.dao), + address(deployment.dao), + deployment.dao.REGISTER_STANDARD_CALLBACK_PERMISSION_ID(), + bytes("") + ), + true, + "The DAO should have REGISTER_STANDARD_CALLBACK_PERMISSION_ID on itself" + ); + + // Multisig plugin + + assertNotEq(address(deployment.multisigPlugin), address(0), "Empty multisig field"); + assertEq( + deployment.multisigPlugin.lastMultisigSettingsChange(), + block.number - 1, + "Invalid lastMultisigSettingsChange" + ); + assertEq(deployment.multisigPlugin.proposalCount(), 0, "Invalid proposal count"); + assertEq(deployment.multisigPlugin.addresslistLength(), 13, "Invalid addresslistLength"); + for (uint256 i = 0; i < 13; i++) { + assertEq( + deployment.multisigPlugin.isMember(multisigMembers[i]), + true, + "Should be a member" + ); + } + for (uint256 i = 14; i < 50; i++) { + assertEq( + deployment.multisigPlugin.isMember(address(uint160(i))), + false, + "Should not be a member" + ); + } + { + (bool onlyListed, uint16 minApprovals) = deployment.multisigPlugin.multisigSettings(); + + assertEq(onlyListed, true, "Invalid onlyListed"); + assertEq(minApprovals, 7, "Invalid minApprovals"); + } + + // Gauge voter plugin + + // assertNotEq( + // address(deployment.optimisticTokenVotingPlugin), + // address(0), + // "Empty optimisticTokenVotingPlugin field" + // ); + // assertEq( + // address(deployment.optimisticTokenVotingPlugin.votingToken()), + // address(tokenAddress), + // "Invalid votingToken" + // ); + // assertEq( + // address(deployment.optimisticTokenVotingPlugin.gaugesL1()), + // address(gaugesL1ContractAddress), + // "Invalid gaugesL1" + // ); + // assertEq( + // address(deployment.optimisticTokenVotingPlugin.gaugesBridge()), + // address(gaugesBridgeAddress), + // "Invalid gaugesBridge" + // ); + // assertEq( + // deployment.optimisticTokenVotingPlugin.proposalCount(), + // 0, + // "Invalid proposal count" + // ); + // { + // ( + // uint32 minVetoRatio, + // uint64 minDuration, + // uint64 l2InactivityPeriod, + // uint64 l2AggregationGracePeriod, + // bool skipL2 + // ) = deployment.optimisticTokenVotingPlugin.governanceSettings(); + + // assertEq(minVetoRatio, 200_000, "Invalid minVetoRatio"); + // assertEq(minDuration, 0, "Invalid minDuration"); // 10 days is enforced on the condition contract + // assertEq(l2InactivityPeriod, 10 minutes, "Invalid l2InactivityPeriod"); + // assertEq(l2AggregationGracePeriod, 2 days, "Invalid l2AggregationGracePeriod"); + // assertEq(skipL2, false, "Invalid skipL2"); + // } + + // PLUGIN REPO's + + PluginRepo.Version memory version; + + // Multisig code + version = multisigPluginRepo.getLatestVersion(1); + assertEq( + address(MultisigPluginSetup(version.pluginSetup).implementation()), + address(deployment.multisigPlugin.implementation()), + "Invalid multisigPluginSetup" + ); + + // // Gauge voter plugin + // assertNotEq( + // address(deployment.optimisticTokenVotingPluginRepo), + // address(0), + // "Empty optimisticTokenVotingPluginRepo field" + // ); + // assertEq( + // deployment.optimisticTokenVotingPluginRepo.latestRelease(), + // 1, + // "Invalid latestRelease" + // ); + // assertEq(deployment.optimisticTokenVotingPluginRepo.buildCount(1), 1, "Invalid buildCount"); + // version = deployment.optimisticTokenVotingPluginRepo.getLatestVersion(1); + // assertEq( + // address(version.pluginSetup), + // address(gaugeVoterPluginSetup), + // "Invalid pluginSetup" + // ); + } + + // function test_StandardDeployment_2() public { + // DAO tempMgmtDao = DAO( + // payable( + // createProxyAndCall( + // address(DAO_BASE), + // abi.encodeCall(DAO.initialize, ("", address(this), address(0x0), "")) + // ) + // ) + // ); + + // GovernanceERC20Mock tokenAddress = new GovernanceERC20Mock(address(tempMgmtDao)); + // GaugesL1Mock gaugesL1ContractAddress = new GaugesL1Mock(); + // address gaugesBridgeAddress = address(0x5678); + // address[] memory multisigMembers = new address[](16); + // for (uint256 i = 0; i < 16; i++) { + // multisigMembers[i] = address(uint160(i + 1)); + // } + + // MultisigPluginSetup multisigPluginSetup = new MultisigPluginSetup(); + // EmergencyMultisigPluginSetup emergencyMultisigPluginSetup = new EmergencyMultisigPluginSetup(); + // GovernanceERC20.MintSettings memory mintSettings = GovernanceERC20.MintSettings({ + // receivers: new address[](0), + // amounts: new uint256[](0) + // }); + // OptimisticTokenVotingPluginSetup voterPluginSetup = new OptimisticTokenVotingPluginSetup( + // new GovernanceERC20(tempMgmtDao, "", "", mintSettings), + // new GovernanceWrappedERC20(tokenAddress, "", "") + // ); + + // PluginRepoFactory pRefoFactory; + // MockPluginSetupProcessor psp; + // { + // MockPluginRepoRegistry pRepoRegistry = new MockPluginRepoRegistry(); + // pRefoFactory = new PluginRepoFactory(PluginRepoRegistry(address(pRepoRegistry))); + + // address[] memory setups = new address[](3); + // // adding in reverse order (stack) + // setups[2] = address(multisigPluginSetup); + // setups[1] = address(emergencyMultisigPluginSetup); + // setups[0] = address(voterPluginSetup); + // psp = new MockPluginSetupProcessor(setups); + // } + // MockDAOFactory daoFactory = new MockDAOFactory(psp); + + // DeploymentParameters memory creationParams = DeploymentParameters({ + // // Gauges contract settings + // tokenAddress: tokenAddress, + // gaugesL1ContractAddress: address(gaugesL1ContractAddress), // address + // gaugesBridgeAddress: gaugesBridgeAddress, // address + // l2InactivityPeriod: 27 minutes, // uint64 + // l2AggregationGracePeriod: 3 days, // uint64 + // skipL2: true, + // // Voting settings + // minVetoRatio: 456_000, // uint32 + // minStdProposalDuration: 21 days, // uint64 + // minStdApprovals: 9, // uint16 + // minEmergencyApprovals: 15, // uint16 + // // OSx contracts + // osxDaoFactory: address(daoFactory), + // pluginSetupProcessor: PluginSetupProcessor(address(psp)), // PluginSetupProcessor + // pluginRepoFactory: PluginRepoFactory(address(pRefoFactory)), // PluginRepoFactory + // // Plugin setup's + // multisigPluginSetup: multisigPluginSetup, + // emergencyMultisigPluginSetup: emergencyMultisigPluginSetup, + // voterPluginSetup: voterPluginSetup, + // // Multisig + // multisigMembers: multisigMembers, // address[] + // multisigExpirationPeriod: 22 days, + // // ENS + // stdMultisigEnsDomain: "multisig", // string + // emergencyMultisigEnsDomain: "eMultisig", // string + // optimisticTokenVotingEnsDomain: "optimistic" // string + // }); + + // // Deploy + // GaugesDaoFactory factory = new GaugesDaoFactory(creationParams); + + // factory.deployOnce(); + // GaugesDaoFactory.Deployment memory deployment = factory.getDeployment(); + + // vm.roll(block.number + 1); // mint one block + + // // DAO checks + + // assertNotEq(address(deployment.dao), address(0), "Empty DAO field"); + // assertEq(deployment.dao.daoURI(), "", "DAO URI should be empty"); + // assertEq( + // address(deployment.dao.signatureValidator()), + // address(0), + // "signatureValidator should be empty" + // ); + // assertEq( + // address(deployment.dao.getTrustedForwarder()), + // address(0), + // "trustedForwarder should be empty" + // ); + // assertEq( + // deployment.dao.hasPermission( + // address(deployment.dao), + // address(deployment.dao), + // deployment.dao.ROOT_PERMISSION_ID(), + // bytes("") + // ), + // true, + // "The DAO should be ROOT on itself" + // ); + // assertEq( + // deployment.dao.hasPermission( + // address(deployment.dao), + // address(deployment.dao), + // deployment.dao.UPGRADE_DAO_PERMISSION_ID(), + // bytes("") + // ), + // true, + // "The DAO should have UPGRADE_DAO_PERMISSION on itself" + // ); + // assertEq( + // deployment.dao.hasPermission( + // address(deployment.dao), + // address(deployment.dao), + // deployment.dao.REGISTER_STANDARD_CALLBACK_PERMISSION_ID(), + // bytes("") + // ), + // true, + // "The DAO should have REGISTER_STANDARD_CALLBACK_PERMISSION_ID on itself" + // ); + + // // Multisig plugin + + // assertNotEq(address(deployment.multisigPlugin), address(0), "Empty multisig field"); + // assertEq( + // deployment.multisigPlugin.lastMultisigSettingsChange(), + // block.number - 1, + // "Invalid lastMultisigSettingsChange" + // ); + // assertEq(deployment.multisigPlugin.proposalCount(), 0, "Invalid proposal count"); + // assertEq(deployment.multisigPlugin.addresslistLength(), 16, "Invalid addresslistLength"); + // for (uint256 i = 0; i < 16; i++) { + // assertEq( + // deployment.multisigPlugin.isMember(multisigMembers[i]), + // true, + // "Should be a member" + // ); + // } + // for (uint256 i = 17; i < 50; i++) { + // assertEq( + // deployment.multisigPlugin.isMember(address(uint160(i))), + // false, + // "Should not be a member" + // ); + // } + // { + // ( + // bool onlyListed, + // uint16 minApprovals, + // uint64 destinationProposalDuration, + // uint64 expirationPeriod + // ) = deployment.multisigPlugin.multisigSettings(); + + // assertEq(onlyListed, true, "Invalid onlyListed"); + // assertEq(minApprovals, 9, "Invalid minApprovals"); + // assertEq(destinationProposalDuration, 21 days, "Invalid destinationProposalDuration"); + // assertEq(expirationPeriod, 22 days, "Invalid expirationPeriod"); + // } + + // // Emergency Multisig plugin + + // assertNotEq( + // address(deployment.emergencyMultisigPlugin), + // address(0), + // "Empty emergencyMultisig field" + // ); + // assertEq( + // deployment.emergencyMultisigPlugin.lastMultisigSettingsChange(), + // block.number - 1, + // "Invalid lastMultisigSettingsChange" + // ); + // assertEq(deployment.emergencyMultisigPlugin.proposalCount(), 0, "Invalid proposal count"); + // for (uint256 i = 0; i < 16; i++) { + // assertEq( + // deployment.emergencyMultisigPlugin.isMember(multisigMembers[i]), + // true, + // "Should be a member" + // ); + // } + // for (uint256 i = 17; i < 50; i++) { + // assertEq( + // deployment.emergencyMultisigPlugin.isMember(address(uint160(i))), + // false, + // "Should not be a member" + // ); + // } + // { + // ( + // bool onlyListed, + // uint16 minApprovals, + // Addresslist addresslistSource, + // uint64 expirationPeriod + // ) = deployment.emergencyMultisigPlugin.multisigSettings(); + + // assertEq(onlyListed, true, "Invalid onlyListed"); + // assertEq(minApprovals, 15, "Invalid minApprovals"); + // assertEq( + // address(addresslistSource), + // address(deployment.multisigPlugin), + // "Invalid addresslistSource" + // ); + // assertEq(expirationPeriod, 22 days, "Invalid expirationPeriod"); + // } + + // // Optimistic token voting plugin checks + + // assertNotEq( + // address(deployment.optimisticTokenVotingPlugin), + // address(0), + // "Empty optimisticTokenVotingPlugin field" + // ); + // assertEq( + // address(deployment.optimisticTokenVotingPlugin.votingToken()), + // address(tokenAddress), + // "Invalid votingToken" + // ); + // assertEq( + // address(deployment.optimisticTokenVotingPlugin.gaugesL1()), + // address(gaugesL1ContractAddress), + // "Invalid gaugesL1" + // ); + // assertEq( + // address(deployment.optimisticTokenVotingPlugin.gaugesBridge()), + // address(gaugesBridgeAddress), + // "Invalid gaugesBridge" + // ); + // assertEq( + // deployment.optimisticTokenVotingPlugin.proposalCount(), + // 0, + // "Invalid proposal count" + // ); + // { + // ( + // uint32 minVetoRatio, + // uint64 minDuration, + // uint64 l2InactivityPeriod, + // uint64 l2AggregationGracePeriod, + // bool skipL2 + // ) = deployment.optimisticTokenVotingPlugin.governanceSettings(); + + // assertEq(minVetoRatio, 456_000, "Invalid minVetoRatio"); + // assertEq(minDuration, 0, "Invalid minDuration"); // 10 days is enforced on the condition contract + // assertEq(l2InactivityPeriod, 27 minutes, "Invalid l2InactivityPeriod"); + // assertEq(l2AggregationGracePeriod, 3 days, "Invalid l2AggregationGracePeriod"); + // assertEq(skipL2, true, "Invalid skipL2"); + // } + + // // PLUGIN REPO's + + // PluginRepo.Version memory version; + + // // Multisig repo + // assertNotEq( + // address(deployment.multisigPluginRepo), + // address(0), + // "Empty multisigPluginRepo field" + // ); + // assertEq(deployment.multisigPluginRepo.latestRelease(), 1, "Invalid latestRelease"); + // assertEq(deployment.multisigPluginRepo.buildCount(1), 1, "Invalid buildCount"); + // version = deployment.multisigPluginRepo.getLatestVersion(1); + // assertEq( + // address(version.pluginSetup), + // address(multisigPluginSetup), + // "Invalid multisigPluginSetup" + // ); + + // // Emergency multisig repo + // assertNotEq( + // address(deployment.emergencyMultisigPluginRepo), + // address(0), + // "Empty emergencyMultisigPluginRepo field" + // ); + // assertEq( + // deployment.emergencyMultisigPluginRepo.latestRelease(), + // 1, + // "Invalid latestRelease" + // ); + // assertEq(deployment.emergencyMultisigPluginRepo.buildCount(1), 1, "Invalid buildCount"); + // version = deployment.emergencyMultisigPluginRepo.getLatestVersion(1); + // assertEq( + // address(version.pluginSetup), + // address(emergencyMultisigPluginSetup), + // "Invalid emergencyMultisigPluginSetup" + // ); + + // // Optimistic repo + // assertNotEq( + // address(deployment.optimisticTokenVotingPluginRepo), + // address(0), + // "Empty optimisticTokenVotingPluginRepo field" + // ); + // assertEq( + // deployment.optimisticTokenVotingPluginRepo.latestRelease(), + // 1, + // "Invalid latestRelease" + // ); + // assertEq(deployment.optimisticTokenVotingPluginRepo.buildCount(1), 1, "Invalid buildCount"); + // version = deployment.optimisticTokenVotingPluginRepo.getLatestVersion(1); + // assertEq( + // address(version.pluginSetup), + // address(voterPluginSetup), + // "Invalid voterPluginSetup" + // ); + + // // PUBLIC KEY REGISTRY + // assertNotEq( + // address(deployment.publicKeyRegistry), + // address(0), + // "Empty publicKeyRegistry field" + // ); + // assertEq( + // deployment.publicKeyRegistry.registeredWalletCount(), + // 0, + // "Invalid registeredWalletCount" + // ); + // } + + // function test_MultipleDeploysDoNothing() public { + // DAO tempMgmtDao = DAO( + // payable( + // createProxyAndCall( + // address(DAO_BASE), + // abi.encodeCall(DAO.initialize, ("", address(this), address(0x0), "")) + // ) + // ) + // ); + + // GovernanceERC20Mock tokenAddress = new GovernanceERC20Mock(address(tempMgmtDao)); + // GaugesL1Mock gaugesL1ContractAddress = new GaugesL1Mock(); + // address gaugesBridgeAddress = address(0x1234); + // address[] memory multisigMembers = new address[](13); + // for (uint256 i = 0; i < 13; i++) { + // multisigMembers[i] = address(uint160(i + 1)); + // } + + // MultisigPluginSetup multisigPluginSetup = new MultisigPluginSetup(); + // EmergencyMultisigPluginSetup emergencyMultisigPluginSetup = new EmergencyMultisigPluginSetup(); + // GovernanceERC20.MintSettings memory mintSettings = GovernanceERC20.MintSettings({ + // receivers: new address[](0), + // amounts: new uint256[](0) + // }); + // OptimisticTokenVotingPluginSetup voterPluginSetup = new OptimisticTokenVotingPluginSetup( + // new GovernanceERC20(tempMgmtDao, "", "", mintSettings), + // new GovernanceWrappedERC20(tokenAddress, "", "") + // ); + + // PluginRepoFactory pRefoFactory; + // MockPluginSetupProcessor psp; + // { + // MockPluginRepoRegistry pRepoRegistry = new MockPluginRepoRegistry(); + // pRefoFactory = new PluginRepoFactory(PluginRepoRegistry(address(pRepoRegistry))); + + // address[] memory setups = new address[](3); + // // adding in reverse order (stack) + // setups[2] = address(multisigPluginSetup); + // setups[1] = address(emergencyMultisigPluginSetup); + // setups[0] = address(voterPluginSetup); + // psp = new MockPluginSetupProcessor(setups); + // } + // MockDAOFactory daoFactory = new MockDAOFactory(psp); + + // DeploymentParameters memory creationParams = DeploymentParameters({ + // // Gauges contract settings + // tokenAddress: tokenAddress, + // gaugesL1ContractAddress: address(gaugesL1ContractAddress), // address + // gaugesBridgeAddress: gaugesBridgeAddress, // address + // l2InactivityPeriod: 10 minutes, // uint64 + // l2AggregationGracePeriod: 2 days, // uint64 + // skipL2: false, + // // Voting settings + // minVetoRatio: 200_000, // uint32 + // minStdProposalDuration: 10 days, // uint64 + // minStdApprovals: 7, // uint16 + // minEmergencyApprovals: 11, // uint16 + // // OSx contracts + // osxDaoFactory: address(daoFactory), + // pluginSetupProcessor: PluginSetupProcessor(address(psp)), // PluginSetupProcessor + // pluginRepoFactory: PluginRepoFactory(address(pRefoFactory)), // PluginRepoFactory + // // Plugin setup's + // multisigPluginSetup: multisigPluginSetup, + // emergencyMultisigPluginSetup: emergencyMultisigPluginSetup, + // voterPluginSetup: voterPluginSetup, + // // Multisig + // multisigMembers: multisigMembers, // address[] + // multisigExpirationPeriod: 10 days, + // // ENS + // stdMultisigEnsDomain: "multisig", // string + // emergencyMultisigEnsDomain: "eMultisig", // string + // optimisticTokenVotingEnsDomain: "optimistic" // string + // }); + + // GaugesDaoFactory factory = new GaugesDaoFactory(creationParams); + // // ok + // factory.deployOnce(); + + // vm.expectRevert(abi.encodeWithSelector(GaugesDaoFactory.AlreadyDeployed.selector)); + // factory.deployOnce(); + + // vm.expectRevert(abi.encodeWithSelector(GaugesDaoFactory.AlreadyDeployed.selector)); + // factory.deployOnce(); + // } +} diff --git a/test/mocks/osx/MockPluginRepoRegistry.sol b/test/mocks/osx/MockPluginRepoRegistry.sol new file mode 100644 index 0000000..5fa8a89 --- /dev/null +++ b/test/mocks/osx/MockPluginRepoRegistry.sol @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity 0.8.17; + +import {IDAO} from "@aragon/osx/core/dao/IDAO.sol"; +import {InterfaceBasedRegistry} from "@aragon/osx/test/utils/InterfaceBasedRegistryMock.sol"; +import {IPluginRepo} from "@aragon/osx/framework/plugin/repo/IPluginRepo.sol"; + +/// @title MockPluginRepoRegistry +/// @author Aragon Association - 2022-2023 +/// @notice This contract maintains an address-based registry of plugin repositories in the Aragon App DAO framework. +contract MockPluginRepoRegistry is InterfaceBasedRegistry { + /// @notice The ID of the permission required to call the `register` function. + // bytes32 public constant REGISTER_PLUGIN_REPO_PERMISSION_ID = keccak256("REGISTER_PLUGIN_REPO_PERMISSION"); + + /// @notice Emitted if a new plugin repository is registered. + /// @param subdomain The subdomain of the plugin repository. + /// @param pluginRepo The address of the plugin repository. + event PluginRepoRegistered(string subdomain, address pluginRepo); + + // /// @notice Thrown if the plugin subdomain doesn't match the regex `[0-9a-z\-]` + // error InvalidPluginSubdomain(string subdomain); + + // /// @notice Thrown if the plugin repository subdomain is empty. + // error EmptyPluginRepoSubdomain(); + + /// @dev Used to disallow initializing the implementation contract by an attacker for extra safety. + constructor() { + _disableInitializers(); + } + + /// @notice Initializes the contract by setting calling the `InterfaceBasedRegistry` base class initialize method. + /// @param _dao The address of the managing DAO. + function initialize(IDAO _dao) external initializer { + bytes4 pluginRepoInterfaceId = type(IPluginRepo).interfaceId; + __InterfaceBasedRegistry_init(_dao, pluginRepoInterfaceId); + + // subdomainRegistrar = _subdomainRegistrar; + } + + /// @notice Registers a plugin repository with a subdomain and address. + /// @param subdomain The subdomain of the PluginRepo. + /// @param pluginRepo The address of the PluginRepo contract. + function registerPluginRepo( + string calldata subdomain, + address pluginRepo + ) external // auth(REGISTER_PLUGIN_REPO_PERMISSION_ID) + { + // if (!(bytes(subdomain).length > 0)) { + // revert EmptyPluginRepoSubdomain(); + // } + // if (!isSubdomainValid(subdomain)) { + // revert InvalidPluginSubdomain({subdomain: subdomain}); + // } + // bytes32 labelhash = keccak256(bytes(subdomain)); + // subdomainRegistrar.registerSubnode(labelhash, pluginRepo); + // _register(pluginRepo); + // emit PluginRepoRegistered(subdomain, pluginRepo); + } + + /// @notice This empty reserved space is put in place to allow future versions to add new variables without shifting down storage in the inheritance chain (see [OpenZeppelin's guide about storage gaps](https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps)). + uint256[50] private __gap; +} From baeed72240b6c754db6a25f09b7e9b1ce02bc2b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Fri, 27 Sep 2024 00:40:16 +0100 Subject: [PATCH 08/14] Factory tests passing --- .env.example | 6 +- script/Deploy.s.sol | 22 +- src/factory/GaugesDaoFactory.sol | 10 +- test/integration/GaugesDaoFactory.sol | 1141 ++++++++++++++----------- test/mocks/osx/MockPSPMulti.sol | 744 ++++++++++++++++ 5 files changed, 1425 insertions(+), 498 deletions(-) create mode 100644 test/mocks/osx/MockPSPMulti.sol diff --git a/.env.example b/.env.example index 38d0cc9..028f454 100644 --- a/.env.example +++ b/.env.example @@ -4,7 +4,7 @@ ALCHEMY_API_KEY="..." ETHERSCAN_API_KEY="..." NETWORK="holesky" -# With false, the script will deploy mock helpers +# With false, the script will deploy mock tokens DEPLOY_AS_PRODUCTION=true MULTISIG_MEMBERS_JSON_FILE_NAME="/script/multisig-members.json" @@ -19,7 +19,7 @@ VE_TOKEN1_NAME="Voting Escrow Token 1" VE_TOKEN1_SYMBOL="veTK1" # Additional tokens -TOKEN2_ADDRESS="0x..." # Ignored if 0x0 +TOKEN2_ADDRESS="0x0000000000000000000000000000000000000000" # Ignored if 0x0 VE_TOKEN2_NAME="Voting Escrow Token 2" VE_TOKEN2_SYMBOL="veTK2" @@ -40,7 +40,7 @@ VOTING_PAUSED=true # PLUGIN REPO PARAMETERS (per-network) # HOLESKY -MULTISIG_PLUGIN_REPO_ADDRESS="0x..." +MULTISIG_PLUGIN_REPO_ADDRESS="0x0000000000000000000000000000000000000000" MULTISIG_PLUGIN_RELEASE="1" MULTISIG_PLUGIN_BUILD="2" SIMPLE_GAUGE_VOTER_REPO_ENS_SUBDOMAIN="my-simple-gauge-voter-0" diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index 1912a06..ebc5329 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -170,14 +170,20 @@ contract Deploy is Script { console.log("- Multisig plugin:", address(deployment.multisigPlugin)); console.log(""); - for (uint i = 0; i < deployment.gaugePluginSets.length; ) { + for (uint i = 0; i < deployment.gaugeVoterPluginSets.length; ) { console.log("- Using token:", address(deploymentParameters.tokenParameters[i].token)); - console.log(" Gauge voter plugin:", address(deployment.gaugePluginSets[i].plugin)); - console.log(" Curve:", address(deployment.gaugePluginSets[i].curve)); - console.log(" Exit Queue:", address(deployment.gaugePluginSets[i].exitQueue)); - console.log(" Voting Escrow:", address(deployment.gaugePluginSets[i].votingEscrow)); - console.log(" Clock:", address(deployment.gaugePluginSets[i].clock)); - console.log(" NFT Lock:", address(deployment.gaugePluginSets[i].nftLock)); + console.log( + " Gauge voter plugin:", + address(deployment.gaugeVoterPluginSets[i].plugin) + ); + console.log(" Curve:", address(deployment.gaugeVoterPluginSets[i].curve)); + console.log(" Exit Queue:", address(deployment.gaugeVoterPluginSets[i].exitQueue)); + console.log( + " Voting Escrow:", + address(deployment.gaugeVoterPluginSets[i].votingEscrow) + ); + console.log(" Clock:", address(deployment.gaugeVoterPluginSets[i].clock)); + console.log(" NFT Lock:", address(deployment.gaugeVoterPluginSets[i].nftLock)); unchecked { i++; @@ -190,7 +196,7 @@ contract Deploy is Script { "- Multisig plugin repository (existing):", address(deploymentParameters.multisigPluginRepo) ); - console.log("- Gauge voter plugin repository:", address(deployment.voterPluginRepo)); + console.log("- Gauge voter plugin repository:", address(deployment.gaugeVoterPluginRepo)); console.log(""); console.log("Helpers"); diff --git a/src/factory/GaugesDaoFactory.sol b/src/factory/GaugesDaoFactory.sol index 6233aa2..cf16038 100644 --- a/src/factory/GaugesDaoFactory.sol +++ b/src/factory/GaugesDaoFactory.sol @@ -83,9 +83,9 @@ struct Deployment { DAO dao; // Plugins Multisig multisigPlugin; - GaugePluginSet[] gaugePluginSets; + GaugePluginSet[] gaugeVoterPluginSets; // Plugin repo's - PluginRepo voterPluginRepo; + PluginRepo gaugeVoterPluginRepo; } /// @notice A singleton contract designed to run the deployment once and become a read-only store of the contracts deployed @@ -167,16 +167,16 @@ contract GaugesDaoFactory { for (uint i = 0; i < parameters.tokenParameters.length; ) { ( pluginSet, - deployment.voterPluginRepo, + deployment.gaugeVoterPluginRepo, preparedVoterSetupData ) = prepareSimpleGaugeVoterPlugin(dao, parameters.tokenParameters[i], repoTag); - deployment.gaugePluginSets.push(pluginSet); + deployment.gaugeVoterPluginSets.push(pluginSet); applyPluginInstallation( dao, address(pluginSet.plugin), - deployment.voterPluginRepo, + deployment.gaugeVoterPluginRepo, repoTag, preparedVoterSetupData ); diff --git a/test/integration/GaugesDaoFactory.sol b/test/integration/GaugesDaoFactory.sol index 893de75..41f6a92 100644 --- a/test/integration/GaugesDaoFactory.sol +++ b/test/integration/GaugesDaoFactory.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.17; import "forge-std/Test.sol"; import {GaugesDaoFactory, Deployment, DeploymentParameters, TokenParameters} from "../../src/factory/GaugesDaoFactory.sol"; import {MockPluginSetupProcessor} from "../mocks/osx/MockPSP.sol"; +import {MockPluginSetupProcessorMulti} from "../mocks/osx/MockPSPMulti.sol"; import {MockPluginRepoRegistry} from "../mocks/osx/MockPluginRepoRegistry.sol"; import {MockDAOFactory} from "../mocks/osx/MockDaoFactory.sol"; import {PluginSetupProcessor} from "@aragon/osx/framework/plugin/setup/PluginSetupProcessor.sol"; @@ -39,8 +40,8 @@ contract GaugesDaoFactoryTest is Test { PluginRepoFactory pRefoFactory = new PluginRepoFactory( PluginRepoRegistry(address(pRepoRegistry)) ); - MockPluginSetupProcessor psp = new MockPluginSetupProcessor(address(0)); - MockDAOFactory daoFactory = new MockDAOFactory(psp); + MockPluginSetupProcessorMulti psp = new MockPluginSetupProcessorMulti(new address[](0)); + MockDAOFactory daoFactory = new MockDAOFactory(MockPluginSetupProcessor(address(psp))); TokenParameters[] memory tokenParameters = new TokenParameters[](2); tokenParameters[0] = TokenParameters({ @@ -204,8 +205,8 @@ contract GaugesDaoFactoryTest is Test { PluginRepoFactory pRefoFactory = new PluginRepoFactory( PluginRepoRegistry(address(pRepoRegistry)) ); - MockPluginSetupProcessor psp = new MockPluginSetupProcessor(address(0)); - MockDAOFactory daoFactory = new MockDAOFactory(psp); + MockPluginSetupProcessorMulti psp = new MockPluginSetupProcessorMulti(new address[](0)); + MockDAOFactory daoFactory = new MockDAOFactory(MockPluginSetupProcessor(address(psp))); TokenParameters[] memory tokenParameters = new TokenParameters[](2); tokenParameters[0] = TokenParameters({ @@ -356,7 +357,21 @@ contract GaugesDaoFactoryTest is Test { multisigMembers[i] = address(uint160(i + 5)); } - PluginRepo multisigPluginRepo = PluginRepo(vm.envAddress("MULTISIG_PLUGIN_REPO_ADDRESS")); + PluginRepoFactory pRefoFactory = new PluginRepoFactory( + PluginRepoRegistry(address(new MockPluginRepoRegistry())) + ); + + // Publish repo + MultisigPluginSetup multisigPluginSetup = new MultisigPluginSetup(); + PluginRepo multisigPluginRepo = PluginRepoFactory(pRefoFactory) + .createPluginRepoWithFirstVersion( + "multisig-subdomain", + address(multisigPluginSetup), + address(this), + " ", + " " + ); + SimpleGaugeVoterSetup gaugeVoterPluginSetup = new SimpleGaugeVoterSetup( address(new SimpleGaugeVoter()), address(new QuadraticIncreasingEscrow()), @@ -366,25 +381,30 @@ contract GaugesDaoFactoryTest is Test { address(new Lock()) ); - MockPluginRepoRegistry pRepoRegistry = new MockPluginRepoRegistry(); - PluginRepoFactory pRefoFactory = new PluginRepoFactory( - PluginRepoRegistry(address(pRepoRegistry)) - ); - MockPluginSetupProcessor psp = new MockPluginSetupProcessor(address(0)); - MockDAOFactory daoFactory = new MockDAOFactory(psp); - TokenParameters[] memory tokenParameters = new TokenParameters[](2); tokenParameters[0] = TokenParameters({ - token: address(111), + token: address(deployMockERC20("T1", "T1", 18)), veTokenName: "Name 1", veTokenSymbol: "TK1" }); tokenParameters[1] = TokenParameters({ - token: address(222), + token: address(deployMockERC20("T2", "T2", 18)), veTokenName: "Name 2", veTokenSymbol: "TK2" }); + // PSP with voter plugin setup and multisig + MockPluginSetupProcessorMulti psp; + { + address[] memory pluginSetups = new address[](3); + pluginSetups[0] = address(gaugeVoterPluginSetup); // Token 1 + pluginSetups[1] = address(gaugeVoterPluginSetup); // Token 2 + pluginSetups[2] = address(multisigPluginSetup); + + psp = new MockPluginSetupProcessorMulti(pluginSetups); + } + MockDAOFactory daoFactory = new MockDAOFactory(MockPluginSetupProcessor(address(psp))); + DeploymentParameters memory creationParams = DeploymentParameters({ // Multisig settings minApprovals: 2, @@ -480,7 +500,7 @@ contract GaugesDaoFactoryTest is Test { } for (uint256 i = 14; i < 50; i++) { assertEq( - deployment.multisigPlugin.isMember(address(uint160(i))), + deployment.multisigPlugin.isMember(address(uint160(i + 5))), false, "Should not be a member" ); @@ -489,51 +509,138 @@ contract GaugesDaoFactoryTest is Test { (bool onlyListed, uint16 minApprovals) = deployment.multisigPlugin.multisigSettings(); assertEq(onlyListed, true, "Invalid onlyListed"); - assertEq(minApprovals, 7, "Invalid minApprovals"); + assertEq(minApprovals, 2, "Invalid minApprovals"); } // Gauge voter plugin - // assertNotEq( - // address(deployment.optimisticTokenVotingPlugin), - // address(0), - // "Empty optimisticTokenVotingPlugin field" - // ); - // assertEq( - // address(deployment.optimisticTokenVotingPlugin.votingToken()), - // address(tokenAddress), - // "Invalid votingToken" - // ); - // assertEq( - // address(deployment.optimisticTokenVotingPlugin.gaugesL1()), - // address(gaugesL1ContractAddress), - // "Invalid gaugesL1" - // ); - // assertEq( - // address(deployment.optimisticTokenVotingPlugin.gaugesBridge()), - // address(gaugesBridgeAddress), - // "Invalid gaugesBridge" - // ); - // assertEq( - // deployment.optimisticTokenVotingPlugin.proposalCount(), - // 0, - // "Invalid proposal count" - // ); - // { - // ( - // uint32 minVetoRatio, - // uint64 minDuration, - // uint64 l2InactivityPeriod, - // uint64 l2AggregationGracePeriod, - // bool skipL2 - // ) = deployment.optimisticTokenVotingPlugin.governanceSettings(); - - // assertEq(minVetoRatio, 200_000, "Invalid minVetoRatio"); - // assertEq(minDuration, 0, "Invalid minDuration"); // 10 days is enforced on the condition contract - // assertEq(l2InactivityPeriod, 10 minutes, "Invalid l2InactivityPeriod"); - // assertEq(l2AggregationGracePeriod, 2 days, "Invalid l2AggregationGracePeriod"); - // assertEq(skipL2, false, "Invalid skipL2"); - // } + assertEq( + deployment.gaugeVoterPluginSets.length, + 2, + "Incorrect gaugeVoterPluginSets length" + ); + // 0 + assertNotEq( + address(deployment.gaugeVoterPluginSets[0].plugin), + address(0), + "Empty plugin address" + ); + assertNotEq( + address(deployment.gaugeVoterPluginSets[0].curve), + address(0), + "Empty curve address" + ); + assertEq( + deployment.gaugeVoterPluginSets[0].curve.warmupPeriod(), + 1234, + "Incorrect warmupPeriod" + ); + assertNotEq( + address(deployment.gaugeVoterPluginSets[0].exitQueue), + address(0), + "Empty exitQueue address" + ); + assertEq( + deployment.gaugeVoterPluginSets[0].exitQueue.feePercent(), + 0.5 ether, + "Incorrect feePercent" + ); + assertEq( + deployment.gaugeVoterPluginSets[0].exitQueue.cooldown(), + 2345, + "Incorrect cooldown" + ); + assertEq( + deployment.gaugeVoterPluginSets[0].exitQueue.minLock(), + 3456, + "Incorrect minLock" + ); + assertNotEq( + address(deployment.gaugeVoterPluginSets[0].votingEscrow), + address(0), + "Empty votingEscrow address" + ); + assertNotEq( + address(deployment.gaugeVoterPluginSets[0].clock), + address(0), + "Empty clock address" + ); + assertNotEq( + address(deployment.gaugeVoterPluginSets[0].nftLock), + address(0), + "Empty nftLock address" + ); + assertEq( + deployment.gaugeVoterPluginSets[0].nftLock.name(), + tokenParameters[0].veTokenName, + "Incorrect veTokenName" + ); + assertEq( + deployment.gaugeVoterPluginSets[0].nftLock.symbol(), + tokenParameters[0].veTokenSymbol, + "Incorrect veTokenSymbol" + ); + // 1 + assertNotEq( + address(deployment.gaugeVoterPluginSets[1].plugin), + address(0), + "Empty plugin address" + ); + assertNotEq( + address(deployment.gaugeVoterPluginSets[1].curve), + address(0), + "Empty curve address" + ); + assertEq( + deployment.gaugeVoterPluginSets[1].curve.warmupPeriod(), + 1234, + "Incorrect warmupPeriod" + ); + assertNotEq( + address(deployment.gaugeVoterPluginSets[1].exitQueue), + address(0), + "Empty exitQueue address" + ); + assertEq( + deployment.gaugeVoterPluginSets[1].exitQueue.feePercent(), + 0.5 ether, + "Incorrect feePercent" + ); + assertEq( + deployment.gaugeVoterPluginSets[1].exitQueue.cooldown(), + 2345, + "Incorrect cooldown" + ); + assertEq( + deployment.gaugeVoterPluginSets[1].exitQueue.minLock(), + 3456, + "Incorrect minLock" + ); + assertNotEq( + address(deployment.gaugeVoterPluginSets[1].votingEscrow), + address(0), + "Empty votingEscrow address" + ); + assertNotEq( + address(deployment.gaugeVoterPluginSets[1].clock), + address(0), + "Empty clock address" + ); + assertNotEq( + address(deployment.gaugeVoterPluginSets[1].nftLock), + address(0), + "Empty nftLock address" + ); + assertEq( + deployment.gaugeVoterPluginSets[1].nftLock.name(), + tokenParameters[1].veTokenName, + "Incorrect veTokenName" + ); + assertEq( + deployment.gaugeVoterPluginSets[1].nftLock.symbol(), + tokenParameters[1].veTokenSymbol, + "Incorrect veTokenSymbol" + ); // PLUGIN REPO's @@ -542,436 +649,506 @@ contract GaugesDaoFactoryTest is Test { // Multisig code version = multisigPluginRepo.getLatestVersion(1); assertEq( - address(MultisigPluginSetup(version.pluginSetup).implementation()), + address(multisigPluginSetup.implementation()), address(deployment.multisigPlugin.implementation()), "Invalid multisigPluginSetup" ); - // // Gauge voter plugin - // assertNotEq( - // address(deployment.optimisticTokenVotingPluginRepo), - // address(0), - // "Empty optimisticTokenVotingPluginRepo field" - // ); - // assertEq( - // deployment.optimisticTokenVotingPluginRepo.latestRelease(), - // 1, - // "Invalid latestRelease" - // ); - // assertEq(deployment.optimisticTokenVotingPluginRepo.buildCount(1), 1, "Invalid buildCount"); - // version = deployment.optimisticTokenVotingPluginRepo.getLatestVersion(1); - // assertEq( - // address(version.pluginSetup), - // address(gaugeVoterPluginSetup), - // "Invalid pluginSetup" - // ); + // Gauge voter plugin + assertNotEq( + address(deployment.gaugeVoterPluginRepo), + address(0), + "Empty gaugeVoterPluginRepo field" + ); + assertEq(deployment.gaugeVoterPluginRepo.latestRelease(), 1, "Invalid latestRelease"); + assertEq(deployment.gaugeVoterPluginRepo.buildCount(1), 1, "Invalid buildCount"); + version = deployment.gaugeVoterPluginRepo.getLatestVersion(1); + assertEq( + address(version.pluginSetup), + address(gaugeVoterPluginSetup), + "Invalid gaugeVoterPluginSetup" + ); } - // function test_StandardDeployment_2() public { - // DAO tempMgmtDao = DAO( - // payable( - // createProxyAndCall( - // address(DAO_BASE), - // abi.encodeCall(DAO.initialize, ("", address(this), address(0x0), "")) - // ) - // ) - // ); - - // GovernanceERC20Mock tokenAddress = new GovernanceERC20Mock(address(tempMgmtDao)); - // GaugesL1Mock gaugesL1ContractAddress = new GaugesL1Mock(); - // address gaugesBridgeAddress = address(0x5678); - // address[] memory multisigMembers = new address[](16); - // for (uint256 i = 0; i < 16; i++) { - // multisigMembers[i] = address(uint160(i + 1)); - // } - - // MultisigPluginSetup multisigPluginSetup = new MultisigPluginSetup(); - // EmergencyMultisigPluginSetup emergencyMultisigPluginSetup = new EmergencyMultisigPluginSetup(); - // GovernanceERC20.MintSettings memory mintSettings = GovernanceERC20.MintSettings({ - // receivers: new address[](0), - // amounts: new uint256[](0) - // }); - // OptimisticTokenVotingPluginSetup voterPluginSetup = new OptimisticTokenVotingPluginSetup( - // new GovernanceERC20(tempMgmtDao, "", "", mintSettings), - // new GovernanceWrappedERC20(tokenAddress, "", "") - // ); - - // PluginRepoFactory pRefoFactory; - // MockPluginSetupProcessor psp; - // { - // MockPluginRepoRegistry pRepoRegistry = new MockPluginRepoRegistry(); - // pRefoFactory = new PluginRepoFactory(PluginRepoRegistry(address(pRepoRegistry))); - - // address[] memory setups = new address[](3); - // // adding in reverse order (stack) - // setups[2] = address(multisigPluginSetup); - // setups[1] = address(emergencyMultisigPluginSetup); - // setups[0] = address(voterPluginSetup); - // psp = new MockPluginSetupProcessor(setups); - // } - // MockDAOFactory daoFactory = new MockDAOFactory(psp); - - // DeploymentParameters memory creationParams = DeploymentParameters({ - // // Gauges contract settings - // tokenAddress: tokenAddress, - // gaugesL1ContractAddress: address(gaugesL1ContractAddress), // address - // gaugesBridgeAddress: gaugesBridgeAddress, // address - // l2InactivityPeriod: 27 minutes, // uint64 - // l2AggregationGracePeriod: 3 days, // uint64 - // skipL2: true, - // // Voting settings - // minVetoRatio: 456_000, // uint32 - // minStdProposalDuration: 21 days, // uint64 - // minStdApprovals: 9, // uint16 - // minEmergencyApprovals: 15, // uint16 - // // OSx contracts - // osxDaoFactory: address(daoFactory), - // pluginSetupProcessor: PluginSetupProcessor(address(psp)), // PluginSetupProcessor - // pluginRepoFactory: PluginRepoFactory(address(pRefoFactory)), // PluginRepoFactory - // // Plugin setup's - // multisigPluginSetup: multisigPluginSetup, - // emergencyMultisigPluginSetup: emergencyMultisigPluginSetup, - // voterPluginSetup: voterPluginSetup, - // // Multisig - // multisigMembers: multisigMembers, // address[] - // multisigExpirationPeriod: 22 days, - // // ENS - // stdMultisigEnsDomain: "multisig", // string - // emergencyMultisigEnsDomain: "eMultisig", // string - // optimisticTokenVotingEnsDomain: "optimistic" // string - // }); - - // // Deploy - // GaugesDaoFactory factory = new GaugesDaoFactory(creationParams); - - // factory.deployOnce(); - // GaugesDaoFactory.Deployment memory deployment = factory.getDeployment(); - - // vm.roll(block.number + 1); // mint one block - - // // DAO checks - - // assertNotEq(address(deployment.dao), address(0), "Empty DAO field"); - // assertEq(deployment.dao.daoURI(), "", "DAO URI should be empty"); - // assertEq( - // address(deployment.dao.signatureValidator()), - // address(0), - // "signatureValidator should be empty" - // ); - // assertEq( - // address(deployment.dao.getTrustedForwarder()), - // address(0), - // "trustedForwarder should be empty" - // ); - // assertEq( - // deployment.dao.hasPermission( - // address(deployment.dao), - // address(deployment.dao), - // deployment.dao.ROOT_PERMISSION_ID(), - // bytes("") - // ), - // true, - // "The DAO should be ROOT on itself" - // ); - // assertEq( - // deployment.dao.hasPermission( - // address(deployment.dao), - // address(deployment.dao), - // deployment.dao.UPGRADE_DAO_PERMISSION_ID(), - // bytes("") - // ), - // true, - // "The DAO should have UPGRADE_DAO_PERMISSION on itself" - // ); - // assertEq( - // deployment.dao.hasPermission( - // address(deployment.dao), - // address(deployment.dao), - // deployment.dao.REGISTER_STANDARD_CALLBACK_PERMISSION_ID(), - // bytes("") - // ), - // true, - // "The DAO should have REGISTER_STANDARD_CALLBACK_PERMISSION_ID on itself" - // ); - - // // Multisig plugin - - // assertNotEq(address(deployment.multisigPlugin), address(0), "Empty multisig field"); - // assertEq( - // deployment.multisigPlugin.lastMultisigSettingsChange(), - // block.number - 1, - // "Invalid lastMultisigSettingsChange" - // ); - // assertEq(deployment.multisigPlugin.proposalCount(), 0, "Invalid proposal count"); - // assertEq(deployment.multisigPlugin.addresslistLength(), 16, "Invalid addresslistLength"); - // for (uint256 i = 0; i < 16; i++) { - // assertEq( - // deployment.multisigPlugin.isMember(multisigMembers[i]), - // true, - // "Should be a member" - // ); - // } - // for (uint256 i = 17; i < 50; i++) { - // assertEq( - // deployment.multisigPlugin.isMember(address(uint160(i))), - // false, - // "Should not be a member" - // ); - // } - // { - // ( - // bool onlyListed, - // uint16 minApprovals, - // uint64 destinationProposalDuration, - // uint64 expirationPeriod - // ) = deployment.multisigPlugin.multisigSettings(); - - // assertEq(onlyListed, true, "Invalid onlyListed"); - // assertEq(minApprovals, 9, "Invalid minApprovals"); - // assertEq(destinationProposalDuration, 21 days, "Invalid destinationProposalDuration"); - // assertEq(expirationPeriod, 22 days, "Invalid expirationPeriod"); - // } - - // // Emergency Multisig plugin - - // assertNotEq( - // address(deployment.emergencyMultisigPlugin), - // address(0), - // "Empty emergencyMultisig field" - // ); - // assertEq( - // deployment.emergencyMultisigPlugin.lastMultisigSettingsChange(), - // block.number - 1, - // "Invalid lastMultisigSettingsChange" - // ); - // assertEq(deployment.emergencyMultisigPlugin.proposalCount(), 0, "Invalid proposal count"); - // for (uint256 i = 0; i < 16; i++) { - // assertEq( - // deployment.emergencyMultisigPlugin.isMember(multisigMembers[i]), - // true, - // "Should be a member" - // ); - // } - // for (uint256 i = 17; i < 50; i++) { - // assertEq( - // deployment.emergencyMultisigPlugin.isMember(address(uint160(i))), - // false, - // "Should not be a member" - // ); - // } - // { - // ( - // bool onlyListed, - // uint16 minApprovals, - // Addresslist addresslistSource, - // uint64 expirationPeriod - // ) = deployment.emergencyMultisigPlugin.multisigSettings(); - - // assertEq(onlyListed, true, "Invalid onlyListed"); - // assertEq(minApprovals, 15, "Invalid minApprovals"); - // assertEq( - // address(addresslistSource), - // address(deployment.multisigPlugin), - // "Invalid addresslistSource" - // ); - // assertEq(expirationPeriod, 22 days, "Invalid expirationPeriod"); - // } - - // // Optimistic token voting plugin checks - - // assertNotEq( - // address(deployment.optimisticTokenVotingPlugin), - // address(0), - // "Empty optimisticTokenVotingPlugin field" - // ); - // assertEq( - // address(deployment.optimisticTokenVotingPlugin.votingToken()), - // address(tokenAddress), - // "Invalid votingToken" - // ); - // assertEq( - // address(deployment.optimisticTokenVotingPlugin.gaugesL1()), - // address(gaugesL1ContractAddress), - // "Invalid gaugesL1" - // ); - // assertEq( - // address(deployment.optimisticTokenVotingPlugin.gaugesBridge()), - // address(gaugesBridgeAddress), - // "Invalid gaugesBridge" - // ); - // assertEq( - // deployment.optimisticTokenVotingPlugin.proposalCount(), - // 0, - // "Invalid proposal count" - // ); - // { - // ( - // uint32 minVetoRatio, - // uint64 minDuration, - // uint64 l2InactivityPeriod, - // uint64 l2AggregationGracePeriod, - // bool skipL2 - // ) = deployment.optimisticTokenVotingPlugin.governanceSettings(); - - // assertEq(minVetoRatio, 456_000, "Invalid minVetoRatio"); - // assertEq(minDuration, 0, "Invalid minDuration"); // 10 days is enforced on the condition contract - // assertEq(l2InactivityPeriod, 27 minutes, "Invalid l2InactivityPeriod"); - // assertEq(l2AggregationGracePeriod, 3 days, "Invalid l2AggregationGracePeriod"); - // assertEq(skipL2, true, "Invalid skipL2"); - // } - - // // PLUGIN REPO's - - // PluginRepo.Version memory version; - - // // Multisig repo - // assertNotEq( - // address(deployment.multisigPluginRepo), - // address(0), - // "Empty multisigPluginRepo field" - // ); - // assertEq(deployment.multisigPluginRepo.latestRelease(), 1, "Invalid latestRelease"); - // assertEq(deployment.multisigPluginRepo.buildCount(1), 1, "Invalid buildCount"); - // version = deployment.multisigPluginRepo.getLatestVersion(1); - // assertEq( - // address(version.pluginSetup), - // address(multisigPluginSetup), - // "Invalid multisigPluginSetup" - // ); - - // // Emergency multisig repo - // assertNotEq( - // address(deployment.emergencyMultisigPluginRepo), - // address(0), - // "Empty emergencyMultisigPluginRepo field" - // ); - // assertEq( - // deployment.emergencyMultisigPluginRepo.latestRelease(), - // 1, - // "Invalid latestRelease" - // ); - // assertEq(deployment.emergencyMultisigPluginRepo.buildCount(1), 1, "Invalid buildCount"); - // version = deployment.emergencyMultisigPluginRepo.getLatestVersion(1); - // assertEq( - // address(version.pluginSetup), - // address(emergencyMultisigPluginSetup), - // "Invalid emergencyMultisigPluginSetup" - // ); - - // // Optimistic repo - // assertNotEq( - // address(deployment.optimisticTokenVotingPluginRepo), - // address(0), - // "Empty optimisticTokenVotingPluginRepo field" - // ); - // assertEq( - // deployment.optimisticTokenVotingPluginRepo.latestRelease(), - // 1, - // "Invalid latestRelease" - // ); - // assertEq(deployment.optimisticTokenVotingPluginRepo.buildCount(1), 1, "Invalid buildCount"); - // version = deployment.optimisticTokenVotingPluginRepo.getLatestVersion(1); - // assertEq( - // address(version.pluginSetup), - // address(voterPluginSetup), - // "Invalid voterPluginSetup" - // ); - - // // PUBLIC KEY REGISTRY - // assertNotEq( - // address(deployment.publicKeyRegistry), - // address(0), - // "Empty publicKeyRegistry field" - // ); - // assertEq( - // deployment.publicKeyRegistry.registeredWalletCount(), - // 0, - // "Invalid registeredWalletCount" - // ); - // } - - // function test_MultipleDeploysDoNothing() public { - // DAO tempMgmtDao = DAO( - // payable( - // createProxyAndCall( - // address(DAO_BASE), - // abi.encodeCall(DAO.initialize, ("", address(this), address(0x0), "")) - // ) - // ) - // ); - - // GovernanceERC20Mock tokenAddress = new GovernanceERC20Mock(address(tempMgmtDao)); - // GaugesL1Mock gaugesL1ContractAddress = new GaugesL1Mock(); - // address gaugesBridgeAddress = address(0x1234); - // address[] memory multisigMembers = new address[](13); - // for (uint256 i = 0; i < 13; i++) { - // multisigMembers[i] = address(uint160(i + 1)); - // } - - // MultisigPluginSetup multisigPluginSetup = new MultisigPluginSetup(); - // EmergencyMultisigPluginSetup emergencyMultisigPluginSetup = new EmergencyMultisigPluginSetup(); - // GovernanceERC20.MintSettings memory mintSettings = GovernanceERC20.MintSettings({ - // receivers: new address[](0), - // amounts: new uint256[](0) - // }); - // OptimisticTokenVotingPluginSetup voterPluginSetup = new OptimisticTokenVotingPluginSetup( - // new GovernanceERC20(tempMgmtDao, "", "", mintSettings), - // new GovernanceWrappedERC20(tokenAddress, "", "") - // ); - - // PluginRepoFactory pRefoFactory; - // MockPluginSetupProcessor psp; - // { - // MockPluginRepoRegistry pRepoRegistry = new MockPluginRepoRegistry(); - // pRefoFactory = new PluginRepoFactory(PluginRepoRegistry(address(pRepoRegistry))); - - // address[] memory setups = new address[](3); - // // adding in reverse order (stack) - // setups[2] = address(multisigPluginSetup); - // setups[1] = address(emergencyMultisigPluginSetup); - // setups[0] = address(voterPluginSetup); - // psp = new MockPluginSetupProcessor(setups); - // } - // MockDAOFactory daoFactory = new MockDAOFactory(psp); - - // DeploymentParameters memory creationParams = DeploymentParameters({ - // // Gauges contract settings - // tokenAddress: tokenAddress, - // gaugesL1ContractAddress: address(gaugesL1ContractAddress), // address - // gaugesBridgeAddress: gaugesBridgeAddress, // address - // l2InactivityPeriod: 10 minutes, // uint64 - // l2AggregationGracePeriod: 2 days, // uint64 - // skipL2: false, - // // Voting settings - // minVetoRatio: 200_000, // uint32 - // minStdProposalDuration: 10 days, // uint64 - // minStdApprovals: 7, // uint16 - // minEmergencyApprovals: 11, // uint16 - // // OSx contracts - // osxDaoFactory: address(daoFactory), - // pluginSetupProcessor: PluginSetupProcessor(address(psp)), // PluginSetupProcessor - // pluginRepoFactory: PluginRepoFactory(address(pRefoFactory)), // PluginRepoFactory - // // Plugin setup's - // multisigPluginSetup: multisigPluginSetup, - // emergencyMultisigPluginSetup: emergencyMultisigPluginSetup, - // voterPluginSetup: voterPluginSetup, - // // Multisig - // multisigMembers: multisigMembers, // address[] - // multisigExpirationPeriod: 10 days, - // // ENS - // stdMultisigEnsDomain: "multisig", // string - // emergencyMultisigEnsDomain: "eMultisig", // string - // optimisticTokenVotingEnsDomain: "optimistic" // string - // }); - - // GaugesDaoFactory factory = new GaugesDaoFactory(creationParams); - // // ok - // factory.deployOnce(); - - // vm.expectRevert(abi.encodeWithSelector(GaugesDaoFactory.AlreadyDeployed.selector)); - // factory.deployOnce(); - - // vm.expectRevert(abi.encodeWithSelector(GaugesDaoFactory.AlreadyDeployed.selector)); - // factory.deployOnce(); - // } + function test_StandardDeployment_2() public { + address[] memory multisigMembers = new address[](13); + for (uint256 i = 0; i < 13; i++) { + multisigMembers[i] = address(uint160(i + 10)); + } + + PluginRepoFactory pRefoFactory = new PluginRepoFactory( + PluginRepoRegistry(address(new MockPluginRepoRegistry())) + ); + + // Publish repo + MultisigPluginSetup multisigPluginSetup = new MultisigPluginSetup(); + PluginRepo multisigPluginRepo = PluginRepoFactory(pRefoFactory) + .createPluginRepoWithFirstVersion( + "multisig-2-subdomain", + address(multisigPluginSetup), + address(this), + " ", + " " + ); + + SimpleGaugeVoterSetup gaugeVoterPluginSetup = new SimpleGaugeVoterSetup( + address(new SimpleGaugeVoter()), + address(new QuadraticIncreasingEscrow()), + address(new ExitQueue()), + address(new VotingEscrow()), + address(new Clock()), + address(new Lock()) + ); + + TokenParameters[] memory tokenParameters = new TokenParameters[](3); + tokenParameters[0] = TokenParameters({ + token: address(deployMockERC20("T3", "T3", 18)), + veTokenName: "Name 3", + veTokenSymbol: "TK3" + }); + tokenParameters[1] = TokenParameters({ + token: address(deployMockERC20("T4", "T4", 18)), + veTokenName: "Name 4", + veTokenSymbol: "TK4" + }); + tokenParameters[2] = TokenParameters({ + token: address(deployMockERC20("T5", "T5", 18)), + veTokenName: "Name 5", + veTokenSymbol: "TK5" + }); + + // PSP with voter plugin setup and multisig + MockPluginSetupProcessorMulti psp; + { + address[] memory pluginSetups = new address[](4); + pluginSetups[0] = address(gaugeVoterPluginSetup); // Token 1 + pluginSetups[1] = address(gaugeVoterPluginSetup); // Token 2 + pluginSetups[2] = address(gaugeVoterPluginSetup); // Token 3 + pluginSetups[3] = address(multisigPluginSetup); + + psp = new MockPluginSetupProcessorMulti(pluginSetups); + } + MockDAOFactory daoFactory = new MockDAOFactory(MockPluginSetupProcessor(address(psp))); + + DeploymentParameters memory creationParams = DeploymentParameters({ + // Multisig settings + minApprovals: 5, + multisigMembers: multisigMembers, + // Gauge Voter + tokenParameters: tokenParameters, + feePercent: 0.2 ether, + warmupPeriod: 5678, + cooldownPeriod: 6789, + minLockDuration: 7890, + votingPaused: false, + // Standard multisig repo + multisigPluginRepo: multisigPluginRepo, + multisigPluginRelease: 1, + multisigPluginBuild: 2, + // Voter plugin setup and ENS + voterPluginSetup: gaugeVoterPluginSetup, + voterEnsSubdomain: "gauge-ens-subdomain", + // OSx addresses + osxDaoFactory: address(daoFactory), + pluginSetupProcessor: PluginSetupProcessor(address(psp)), + pluginRepoFactory: pRefoFactory + }); + + GaugesDaoFactory factory = new GaugesDaoFactory(creationParams); + + factory.deployOnce(); + Deployment memory deployment = factory.getDeployment(); + + vm.roll(block.number + 1); // mint one block + + // DAO checks + + assertNotEq(address(deployment.dao), address(0), "Empty DAO field"); + assertEq(deployment.dao.daoURI(), "", "DAO URI should be empty"); + assertEq( + address(deployment.dao.signatureValidator()), + address(0), + "signatureValidator should be empty" + ); + assertEq( + address(deployment.dao.getTrustedForwarder()), + address(0), + "trustedForwarder should be empty" + ); + assertEq( + deployment.dao.hasPermission( + address(deployment.dao), + address(deployment.dao), + deployment.dao.ROOT_PERMISSION_ID(), + bytes("") + ), + true, + "The DAO should be ROOT on itself" + ); + assertEq( + deployment.dao.hasPermission( + address(deployment.dao), + address(deployment.dao), + deployment.dao.UPGRADE_DAO_PERMISSION_ID(), + bytes("") + ), + true, + "The DAO should have UPGRADE_DAO_PERMISSION on itself" + ); + assertEq( + deployment.dao.hasPermission( + address(deployment.dao), + address(deployment.dao), + deployment.dao.REGISTER_STANDARD_CALLBACK_PERMISSION_ID(), + bytes("") + ), + true, + "The DAO should have REGISTER_STANDARD_CALLBACK_PERMISSION_ID on itself" + ); + + // Multisig plugin + + assertNotEq(address(deployment.multisigPlugin), address(0), "Empty multisig field"); + assertEq( + deployment.multisigPlugin.lastMultisigSettingsChange(), + block.number - 1, + "Invalid lastMultisigSettingsChange" + ); + assertEq(deployment.multisigPlugin.proposalCount(), 0, "Invalid proposal count"); + assertEq(deployment.multisigPlugin.addresslistLength(), 13, "Invalid addresslistLength"); + for (uint256 i = 0; i < 13; i++) { + assertEq( + deployment.multisigPlugin.isMember(multisigMembers[i]), + true, + "Should be a member" + ); + } + for (uint256 i = 14; i < 50; i++) { + assertEq( + deployment.multisigPlugin.isMember(address(uint160(i + 10))), + false, + "Should not be a member" + ); + } + { + (bool onlyListed, uint16 minApprovals) = deployment.multisigPlugin.multisigSettings(); + + assertEq(onlyListed, true, "Invalid onlyListed"); + assertEq(minApprovals, 5, "Invalid minApprovals"); + } + + // Gauge voter plugin + + assertEq( + deployment.gaugeVoterPluginSets.length, + 3, + "Incorrect gaugeVoterPluginSets length" + ); + // 0 + assertNotEq( + address(deployment.gaugeVoterPluginSets[0].plugin), + address(0), + "Empty plugin address" + ); + assertNotEq( + address(deployment.gaugeVoterPluginSets[0].curve), + address(0), + "Empty curve address" + ); + assertEq( + deployment.gaugeVoterPluginSets[0].curve.warmupPeriod(), + 5678, + "Incorrect warmupPeriod" + ); + assertNotEq( + address(deployment.gaugeVoterPluginSets[0].exitQueue), + address(0), + "Empty exitQueue address" + ); + assertEq( + deployment.gaugeVoterPluginSets[0].exitQueue.feePercent(), + 0.2 ether, + "Incorrect feePercent" + ); + assertEq( + deployment.gaugeVoterPluginSets[0].exitQueue.cooldown(), + 6789, + "Incorrect cooldown" + ); + assertEq( + deployment.gaugeVoterPluginSets[0].exitQueue.minLock(), + 7890, + "Incorrect minLock" + ); + assertNotEq( + address(deployment.gaugeVoterPluginSets[0].votingEscrow), + address(0), + "Empty votingEscrow address" + ); + assertNotEq( + address(deployment.gaugeVoterPluginSets[0].clock), + address(0), + "Empty clock address" + ); + assertNotEq( + address(deployment.gaugeVoterPluginSets[0].nftLock), + address(0), + "Empty nftLock address" + ); + assertEq( + deployment.gaugeVoterPluginSets[0].nftLock.name(), + tokenParameters[0].veTokenName, + "Incorrect veTokenName" + ); + assertEq( + deployment.gaugeVoterPluginSets[0].nftLock.symbol(), + tokenParameters[0].veTokenSymbol, + "Incorrect veTokenSymbol" + ); + // 1 + assertNotEq( + address(deployment.gaugeVoterPluginSets[1].plugin), + address(0), + "Empty plugin address" + ); + assertNotEq( + address(deployment.gaugeVoterPluginSets[1].curve), + address(0), + "Empty curve address" + ); + assertEq( + deployment.gaugeVoterPluginSets[1].curve.warmupPeriod(), + 5678, + "Incorrect warmupPeriod" + ); + assertNotEq( + address(deployment.gaugeVoterPluginSets[1].exitQueue), + address(0), + "Empty exitQueue address" + ); + assertNotEq( + address(deployment.gaugeVoterPluginSets[1].votingEscrow), + address(0), + "Empty votingEscrow address" + ); + assertEq( + deployment.gaugeVoterPluginSets[1].exitQueue.feePercent(), + 0.2 ether, + "Incorrect feePercent" + ); + assertEq( + deployment.gaugeVoterPluginSets[1].exitQueue.cooldown(), + 6789, + "Incorrect cooldown" + ); + assertEq( + deployment.gaugeVoterPluginSets[1].exitQueue.minLock(), + 7890, + "Incorrect minLock" + ); + assertNotEq( + address(deployment.gaugeVoterPluginSets[1].clock), + address(0), + "Empty clock address" + ); + assertNotEq( + address(deployment.gaugeVoterPluginSets[1].nftLock), + address(0), + "Empty nftLock address" + ); + assertEq( + deployment.gaugeVoterPluginSets[1].nftLock.name(), + tokenParameters[1].veTokenName, + "Incorrect veTokenName" + ); + assertEq( + deployment.gaugeVoterPluginSets[1].nftLock.symbol(), + tokenParameters[1].veTokenSymbol, + "Incorrect veTokenSymbol" + ); + // 2 + assertNotEq( + address(deployment.gaugeVoterPluginSets[2].plugin), + address(0), + "Empty plugin address" + ); + assertNotEq( + address(deployment.gaugeVoterPluginSets[2].curve), + address(0), + "Empty curve address" + ); + assertEq( + deployment.gaugeVoterPluginSets[2].curve.warmupPeriod(), + 5678, + "Incorrect warmupPeriod" + ); + assertNotEq( + address(deployment.gaugeVoterPluginSets[2].exitQueue), + address(0), + "Empty exitQueue address" + ); + assertEq( + deployment.gaugeVoterPluginSets[2].exitQueue.feePercent(), + 0.2 ether, + "Incorrect feePercent" + ); + assertEq( + deployment.gaugeVoterPluginSets[2].exitQueue.cooldown(), + 6789, + "Incorrect cooldown" + ); + assertEq( + deployment.gaugeVoterPluginSets[2].exitQueue.minLock(), + 7890, + "Incorrect minLock" + ); + assertNotEq( + address(deployment.gaugeVoterPluginSets[2].votingEscrow), + address(0), + "Empty votingEscrow address" + ); + assertNotEq( + address(deployment.gaugeVoterPluginSets[2].clock), + address(0), + "Empty clock address" + ); + assertNotEq( + address(deployment.gaugeVoterPluginSets[2].nftLock), + address(0), + "Empty nftLock address" + ); + assertEq( + deployment.gaugeVoterPluginSets[2].nftLock.name(), + tokenParameters[2].veTokenName, + "Incorrect veTokenName" + ); + assertEq( + deployment.gaugeVoterPluginSets[2].nftLock.symbol(), + tokenParameters[2].veTokenSymbol, + "Incorrect veTokenSymbol" + ); + + // PLUGIN REPO's + + PluginRepo.Version memory version; + + // Multisig code + version = multisigPluginRepo.getLatestVersion(1); + assertEq( + address(multisigPluginSetup.implementation()), + address(deployment.multisigPlugin.implementation()), + "Invalid multisigPluginSetup" + ); + + // Gauge voter plugin + assertNotEq( + address(deployment.gaugeVoterPluginRepo), + address(0), + "Empty gaugeVoterPluginRepo field" + ); + assertEq(deployment.gaugeVoterPluginRepo.latestRelease(), 1, "Invalid latestRelease"); + assertEq(deployment.gaugeVoterPluginRepo.buildCount(1), 1, "Invalid buildCount"); + version = deployment.gaugeVoterPluginRepo.getLatestVersion(1); + assertEq( + address(version.pluginSetup), + address(gaugeVoterPluginSetup), + "Invalid gaugeVoterPluginSetup" + ); + } + + function test_MultipleDeploysDoNothing() public { + address[] memory multisigMembers = new address[](13); + for (uint256 i = 0; i < 13; i++) { + multisigMembers[i] = address(uint160(i + 10)); + } + + PluginRepoFactory pRefoFactory = new PluginRepoFactory( + PluginRepoRegistry(address(new MockPluginRepoRegistry())) + ); + + // Publish repo + MultisigPluginSetup multisigPluginSetup = new MultisigPluginSetup(); + PluginRepo multisigPluginRepo = PluginRepoFactory(pRefoFactory) + .createPluginRepoWithFirstVersion( + "multisig-2-subdomain", + address(multisigPluginSetup), + address(this), + " ", + " " + ); + + SimpleGaugeVoterSetup gaugeVoterPluginSetup = new SimpleGaugeVoterSetup( + address(new SimpleGaugeVoter()), + address(new QuadraticIncreasingEscrow()), + address(new ExitQueue()), + address(new VotingEscrow()), + address(new Clock()), + address(new Lock()) + ); + + TokenParameters[] memory tokenParameters = new TokenParameters[](3); + tokenParameters[0] = TokenParameters({ + token: address(deployMockERC20("T3", "T3", 18)), + veTokenName: "Name 3", + veTokenSymbol: "TK3" + }); + tokenParameters[1] = TokenParameters({ + token: address(deployMockERC20("T4", "T4", 18)), + veTokenName: "Name 4", + veTokenSymbol: "TK4" + }); + tokenParameters[2] = TokenParameters({ + token: address(deployMockERC20("T5", "T5", 18)), + veTokenName: "Name 5", + veTokenSymbol: "TK5" + }); + + // PSP with voter plugin setup and multisig + MockPluginSetupProcessorMulti psp; + { + address[] memory pluginSetups = new address[](4); + pluginSetups[0] = address(gaugeVoterPluginSetup); // Token 1 + pluginSetups[1] = address(gaugeVoterPluginSetup); // Token 2 + pluginSetups[2] = address(gaugeVoterPluginSetup); // Token 3 + pluginSetups[3] = address(multisigPluginSetup); + + psp = new MockPluginSetupProcessorMulti(pluginSetups); + } + MockDAOFactory daoFactory = new MockDAOFactory(MockPluginSetupProcessor(address(psp))); + + DeploymentParameters memory creationParams = DeploymentParameters({ + // Multisig settings + minApprovals: 5, + multisigMembers: multisigMembers, + // Gauge Voter + tokenParameters: tokenParameters, + feePercent: 0.5 ether, + warmupPeriod: 1234, + cooldownPeriod: 2345, + minLockDuration: 3456, + votingPaused: false, + // Standard multisig repo + multisigPluginRepo: multisigPluginRepo, + multisigPluginRelease: 1, + multisigPluginBuild: 2, + // Voter plugin setup and ENS + voterPluginSetup: gaugeVoterPluginSetup, + voterEnsSubdomain: "gauge-ens-subdomain", + // OSx addresses + osxDaoFactory: address(daoFactory), + pluginSetupProcessor: PluginSetupProcessor(address(psp)), + pluginRepoFactory: pRefoFactory + }); + + GaugesDaoFactory factory = new GaugesDaoFactory(creationParams); + + // ok + factory.deployOnce(); + + vm.expectRevert(abi.encodeWithSelector(GaugesDaoFactory.AlreadyDeployed.selector)); + factory.deployOnce(); + + vm.expectRevert(abi.encodeWithSelector(GaugesDaoFactory.AlreadyDeployed.selector)); + factory.deployOnce(); + } } diff --git a/test/mocks/osx/MockPSPMulti.sol b/test/mocks/osx/MockPSPMulti.sol new file mode 100644 index 0000000..fab1480 --- /dev/null +++ b/test/mocks/osx/MockPSPMulti.sol @@ -0,0 +1,744 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.17; + +import {ERC165Checker} from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; +import {DAO, IDAO} from "@aragon/osx/core/dao/DAO.sol"; +import {PermissionLib} from "@aragon/osx/core/permission/PermissionLib.sol"; +import {PluginUUPSUpgradeable} from "@aragon/osx/core/plugin/PluginUUPSUpgradeable.sol"; +import {IPlugin} from "@aragon/osx/core/plugin/IPlugin.sol"; +import {PluginRepo} from "@aragon/osx/framework/plugin/repo/PluginRepo.sol"; +import {IPluginSetup} from "@aragon/osx/framework/plugin/setup/IPluginSetup.sol"; +import {PluginSetup} from "@aragon/osx/framework/plugin/setup/PluginSetup.sol"; +import {PluginSetupRef, hashHelpers, hashPermissions, _getPreparedSetupId, _getAppliedSetupId, _getPluginInstallationId, PreparationType} from "@aragon/osx/framework/plugin/setup/PluginSetupProcessorHelpers.sol"; + +/// @title PluginSetupProcessor +/// @author Aragon Association - 2022-2023 +/// @notice This contract processes the preparation and application of plugin setups (installation, update, uninstallation) on behalf of a requesting DAO. +/// @dev This contract is temporarily granted the `ROOT_PERMISSION_ID` permission on the applying DAO and therefore is highly security critical. +contract MockPluginSetupProcessorMulti { + using ERC165Checker for address; + + /// @notice The ID of the permission required to call the `applyInstallation` function. + bytes32 public constant APPLY_INSTALLATION_PERMISSION_ID = + keccak256("APPLY_INSTALLATION_PERMISSION"); + + /// @notice The ID of the permission required to call the `applyUpdate` function. + bytes32 public constant APPLY_UPDATE_PERMISSION_ID = keccak256("APPLY_UPDATE_PERMISSION"); + + /// @notice The ID of the permission required to call the `applyUninstallation` function. + bytes32 public constant APPLY_UNINSTALLATION_PERMISSION_ID = + keccak256("APPLY_UNINSTALLATION_PERMISSION"); + + /// @notice The hash obtained from the bytes-encoded empty array to be used for UI updates being required to submit an empty permission array. + /// @dev The hash is computed via `keccak256(abi.encode([]))`. + bytes32 private constant EMPTY_ARRAY_HASH = + 0x569e75fc77c1a856f6daaf9e69d8a9566ca34aa47f9133711ce065a571af0cfd; + + /// @notice The hash obtained from the bytes-encoded zero value. + /// @dev The hash is computed via `keccak256(abi.encode(0))`. + bytes32 private constant ZERO_BYTES_HASH = + 0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563; + + /// @notice A struct containing information related to plugin setups that have been applied. + /// @param blockNumber The block number at which the `applyInstallation`, `applyUpdate` or `applyUninstallation` was executed. + /// @param currentAppliedSetupId The current setup id that plugin holds. Needed to confirm that `prepareUpdate` or `prepareUninstallation` happens for the plugin's current/valid dependencies. + /// @param preparedSetupIdToBlockNumber The mapping between prepared setup IDs and block numbers at which `prepareInstallation`, `prepareUpdate` or `prepareUninstallation` was executed. + struct PluginState { + uint256 blockNumber; + bytes32 currentAppliedSetupId; + mapping(bytes32 => uint256) preparedSetupIdToBlockNumber; + } + + /// @notice A mapping between the plugin installation ID (obtained from the DAO and plugin address) and the plugin state information. + /// @dev This variable is public on purpose to allow future versions to access and migrate the storage. + mapping(bytes32 => PluginState) public states; + + /// @notice The struct containing the parameters for the `prepareInstallation` function. + /// @param pluginSetupRef The reference to the plugin setup to be used for the installation. + /// @param data The bytes-encoded data containing the input parameters for the installation preparation as specified in the corresponding ABI on the version's metadata. + struct PrepareInstallationParams { + PluginSetupRef pluginSetupRef; + bytes data; + } + + /// @notice The struct containing the parameters for the `applyInstallation` function. + /// @param pluginSetupRef The reference to the plugin setup used for the installation. + /// @param plugin The address of the plugin contract to be installed. + /// @param permissions The array of multi-targeted permission operations to be applied by the `PluginSetupProcessor` to the DAO. + /// @param helpersHash The hash of helpers that were deployed in `prepareInstallation`. This helps to derive the setup ID. + struct ApplyInstallationParams { + PluginSetupRef pluginSetupRef; + address plugin; + PermissionLib.MultiTargetPermission[] permissions; + bytes32 helpersHash; + } + + /// @notice The struct containing the parameters for the `prepareUpdate` function. + /// @param currentVersionTag The tag of the current plugin version to update from. + /// @param newVersionTag The tag of the new plugin version to update to. + /// @param pluginSetupRepo The plugin setup repository address on which the plugin exists. + /// @param setupPayload The payload containing the plugin and helper contract addresses deployed in a preparation step as well as optional data to be consumed by the plugin setup. + /// This includes the bytes-encoded data containing the input parameters for the update preparation as specified in the corresponding ABI on the version's metadata. + struct PrepareUpdateParams { + PluginRepo.Tag currentVersionTag; + PluginRepo.Tag newVersionTag; + PluginRepo pluginSetupRepo; + IPluginSetup.SetupPayload setupPayload; + } + + /// @notice The struct containing the parameters for the `applyUpdate` function. + /// @param plugin The address of the plugin contract to be updated. + /// @param pluginSetupRef The reference to the plugin setup used for the update. + /// @param initData The encoded data (function selector and arguments) to be provided to `upgradeToAndCall`. + /// @param permissions The array of multi-targeted permission operations to be applied by the `PluginSetupProcessor` to the DAO. + /// @param helpersHash The hash of helpers that were deployed in `prepareUpdate`. This helps to derive the setup ID. + struct ApplyUpdateParams { + address plugin; + PluginSetupRef pluginSetupRef; + bytes initData; + PermissionLib.MultiTargetPermission[] permissions; + bytes32 helpersHash; + } + + /// @notice The struct containing the parameters for the `prepareUninstallation` function. + /// @param pluginSetupRef The reference to the plugin setup to be used for the uninstallation. + /// @param setupPayload The payload containing the plugin and helper contract addresses deployed in a preparation step as well as optional data to be consumed by the plugin setup. + /// This includes the bytes-encoded data containing the input parameters for the uninstallation preparation as specified in the corresponding ABI on the version's metadata. + struct PrepareUninstallationParams { + PluginSetupRef pluginSetupRef; + IPluginSetup.SetupPayload setupPayload; + } + + /// @notice The struct containing the parameters for the `applyInstallation` function. + /// @param plugin The address of the plugin contract to be uninstalled. + /// @param pluginSetupRef The reference to the plugin setup used for the uninstallation. + /// @param permissions The array of multi-targeted permission operations to be applied by the `PluginSetupProcess. + struct ApplyUninstallationParams { + address plugin; + PluginSetupRef pluginSetupRef; + PermissionLib.MultiTargetPermission[] permissions; + } + + /// @notice The plugin repo registry listing the `PluginRepo` contracts versioning the `PluginSetup` contracts. + // PluginRepoRegistry public repoRegistry; + + /// @notice Thrown if a setup is unauthorized and cannot be applied because of a missing permission of the associated DAO. + /// @param dao The address of the DAO to which the plugin belongs. + /// @param caller The address (EOA or contract) that requested the application of a setup on the associated DAO. + /// @param permissionId The permission identifier. + /// @dev This is thrown if the `APPLY_INSTALLATION_PERMISSION_ID`, `APPLY_UPDATE_PERMISSION_ID`, or APPLY_UNINSTALLATION_PERMISSION_ID is missing. + error SetupApplicationUnauthorized(address dao, address caller, bytes32 permissionId); + + /// @notice Thrown if a plugin is not upgradeable. + /// @param plugin The address of the plugin contract. + error PluginNonupgradeable(address plugin); + + /// @notice Thrown if the upgrade of an `UUPSUpgradeable` proxy contract (see [ERC-1822](https://eips.ethereum.org/EIPS/eip-1822)) failed. + /// @param proxy The address of the proxy. + /// @param implementation The address of the implementation contract. + /// @param initData The initialization data to be passed to the upgradeable plugin contract via `upgradeToAndCall`. + error PluginProxyUpgradeFailed(address proxy, address implementation, bytes initData); + + /// @notice Thrown if a contract does not support the `IPlugin` interface. + /// @param plugin The address of the contract. + error IPluginNotSupported(address plugin); + + /// @notice Thrown if a plugin repository does not exist on the plugin repo registry. + error PluginRepoNonexistent(); + + /// @notice Thrown if a plugin setup was already prepared indicated by the prepared setup ID. + /// @param preparedSetupId The prepared setup ID. + error SetupAlreadyPrepared(bytes32 preparedSetupId); + + /// @notice Thrown if a prepared setup ID is not eligible to be applied. This can happen if another setup has been already applied or if the setup wasn't prepared in the first place. + /// @param preparedSetupId The prepared setup ID. + error SetupNotApplicable(bytes32 preparedSetupId); + + /// @notice Thrown if the update version is invalid. + /// @param currentVersionTag The tag of the current version to update from. + /// @param newVersionTag The tag of the new version to update to. + error InvalidUpdateVersion(PluginRepo.Tag currentVersionTag, PluginRepo.Tag newVersionTag); + + /// @notice Thrown if plugin is already installed and one tries to prepare or apply install on it. + error PluginAlreadyInstalled(); + + /// @notice Thrown if the applied setup ID resulting from the supplied setup payload does not match with the current applied setup ID. + /// @param currentAppliedSetupId The current applied setup ID with which the data in the supplied payload must match. + /// @param appliedSetupId The applied setup ID obtained from the data in the supplied setup payload. + error InvalidAppliedSetupId(bytes32 currentAppliedSetupId, bytes32 appliedSetupId); + + /// @notice Emitted with a prepared plugin installation to store data relevant for the application step. + /// @param sender The sender that prepared the plugin installation. + /// @param dao The address of the DAO to which the plugin belongs. + /// @param preparedSetupId The prepared setup ID obtained from the supplied data. + /// @param pluginSetupRepo The repository storing the `PluginSetup` contracts of all versions of a plugin. + /// @param versionTag The version tag of the plugin setup of the prepared installation. + /// @param data The bytes-encoded data containing the input parameters for the preparation as specified in the corresponding ABI on the version's metadata. + /// @param plugin The address of the plugin contract. + /// @param preparedSetupData The deployed plugin's relevant data which consists of helpers and permissions. + event InstallationPrepared( + address indexed sender, + address indexed dao, + bytes32 preparedSetupId, + PluginRepo indexed pluginSetupRepo, + PluginRepo.Tag versionTag, + bytes data, + address plugin, + IPluginSetup.PreparedSetupData preparedSetupData + ); + + /// @notice Emitted after a plugin installation was applied. + /// @param dao The address of the DAO to which the plugin belongs. + /// @param plugin The address of the plugin contract. + /// @param preparedSetupId The prepared setup ID. + /// @param appliedSetupId The applied setup ID. + event InstallationApplied( + address indexed dao, + address indexed plugin, + bytes32 preparedSetupId, + bytes32 appliedSetupId + ); + + /// @notice Emitted with a prepared plugin update to store data relevant for the application step. + /// @param sender The sender that prepared the plugin update. + /// @param dao The address of the DAO to which the plugin belongs. + /// @param preparedSetupId The prepared setup ID. + /// @param pluginSetupRepo The repository storing the `PluginSetup` contracts of all versions of a plugin. + /// @param versionTag The version tag of the plugin setup of the prepared update. + /// @param setupPayload The payload containing the plugin and helper contract addresses deployed in a preparation step as well as optional data to be consumed by the plugin setup. + /// @param preparedSetupData The deployed plugin's relevant data which consists of helpers and permissions. + /// @param initData The initialization data to be passed to the upgradeable plugin contract. + event UpdatePrepared( + address indexed sender, + address indexed dao, + bytes32 preparedSetupId, + PluginRepo indexed pluginSetupRepo, + PluginRepo.Tag versionTag, + IPluginSetup.SetupPayload setupPayload, + IPluginSetup.PreparedSetupData preparedSetupData, + bytes initData + ); + + /// @notice Emitted after a plugin update was applied. + /// @param dao The address of the DAO to which the plugin belongs. + /// @param plugin The address of the plugin contract. + /// @param preparedSetupId The prepared setup ID. + /// @param appliedSetupId The applied setup ID. + event UpdateApplied( + address indexed dao, + address indexed plugin, + bytes32 preparedSetupId, + bytes32 appliedSetupId + ); + + /// @notice Emitted with a prepared plugin uninstallation to store data relevant for the application step. + /// @param sender The sender that prepared the plugin uninstallation. + /// @param dao The address of the DAO to which the plugin belongs. + /// @param preparedSetupId The prepared setup ID. + /// @param pluginSetupRepo The repository storing the `PluginSetup` contracts of all versions of a plugin. + /// @param versionTag The version tag of the plugin to used for install preparation. + /// @param setupPayload The payload containing the plugin and helper contract addresses deployed in a preparation step as well as optional data to be consumed by the plugin setup. + /// @param permissions The list of multi-targeted permission operations to be applied to the installing DAO. + event UninstallationPrepared( + address indexed sender, + address indexed dao, + bytes32 preparedSetupId, + PluginRepo indexed pluginSetupRepo, + PluginRepo.Tag versionTag, + IPluginSetup.SetupPayload setupPayload, + PermissionLib.MultiTargetPermission[] permissions + ); + + /// @notice Emitted after a plugin installation was applied. + /// @param dao The address of the DAO to which the plugin belongs. + /// @param plugin The address of the plugin contract. + /// @param preparedSetupId The prepared setup ID. + event UninstallationApplied( + address indexed dao, + address indexed plugin, + bytes32 preparedSetupId + ); + + /// @notice A modifier to check if a caller has the permission to apply a prepared setup. + /// @param _dao The address of the DAO. + /// @param _permissionId The permission identifier. + modifier canApply(address _dao, bytes32 _permissionId) { + _canApply(_dao, _permissionId); + _; + } + + /// @notice Constructs the plugin setup processor by setting the associated plugin repo registry. + // / @param _repoRegistry The plugin repo registry contract. + constructor(address[] memory _setups) { + for (uint256 i = 0; i < _setups.length; i++) { + queueSetup(_setups[i]); + } + // repoRegistry = _repoRegistry; + } + + address[] setups; + + function queueSetup(address _setup) public { + setups.push(_setup); + } + + function popSetup() internal returns (address) { + require(setups.length > 0, "No setups queued"); + address _setup = setups[setups.length - 1]; + setups.pop(); + return _setup; + } + + /// @notice Prepares the installation of a plugin. + /// @param _dao The address of the installing DAO. + /// @param _params The struct containing the parameters for the `prepareInstallation` function. + /// @return plugin The prepared plugin contract address. + /// @return preparedSetupData The data struct containing the array of helper contracts and permissions that the setup has prepared. + function prepareInstallation( + address _dao, + PrepareInstallationParams calldata _params + ) external returns (address plugin, IPluginSetup.PreparedSetupData memory preparedSetupData) { + // PluginRepo pluginSetupRepo = _params.pluginSetupRef.pluginSetupRepo; + + // // Check that the plugin repository exists on the plugin repo registry. + // if (!repoRegistry.entries(address(pluginSetupRepo))) { + // revert PluginRepoNonexistent(); + // } + + // // reverts if not found + // PluginRepo.Version memory version = pluginSetupRepo.getVersion( + // _params.pluginSetupRef.versionTag + // ); + + // Prepare the installation + address setup = popSetup(); + (plugin, preparedSetupData) = PluginSetup(setup).prepareInstallation(_dao, _params.data); + + // bytes32 pluginInstallationId = _getPluginInstallationId(_dao, plugin); + + // bytes32 preparedSetupId = _getPreparedSetupId( + // _params.pluginSetupRef, + // hashPermissions(preparedSetupData.permissions), + // hashHelpers(preparedSetupData.helpers), + // bytes(""), + // PreparationType.Installation + // ); + + // PluginState storage pluginState = states[pluginInstallationId]; + + // // Check if this plugin is already installed. + // if (pluginState.currentAppliedSetupId != bytes32(0)) { + // revert PluginAlreadyInstalled(); + // } + + // // Check if this setup has already been prepared before and is pending. + // if (pluginState.blockNumber < pluginState.preparedSetupIdToBlockNumber[preparedSetupId]) { + // revert SetupAlreadyPrepared({preparedSetupId: preparedSetupId}); + // } + + // pluginState.preparedSetupIdToBlockNumber[preparedSetupId] = block.number; + + // emit InstallationPrepared({ + // sender: msg.sender, + // dao: _dao, + // preparedSetupId: preparedSetupId, + // pluginSetupRepo: pluginSetupRepo, + // versionTag: _params.pluginSetupRef.versionTag, + // data: _params.data, + // plugin: plugin, + // preparedSetupData: preparedSetupData + // }); + + return (plugin, preparedSetupData); + } + + /// @notice Applies the permissions of a prepared installation to a DAO. + /// @param _dao The address of the installing DAO. + /// @param _params The struct containing the parameters for the `applyInstallation` function. + function applyInstallation( + address _dao, + ApplyInstallationParams calldata _params + ) external canApply(_dao, APPLY_INSTALLATION_PERMISSION_ID) { + // bytes32 pluginInstallationId = _getPluginInstallationId(_dao, _params.plugin); + + // PluginState storage pluginState = states[pluginInstallationId]; + + // bytes32 preparedSetupId = _getPreparedSetupId( + // _params.pluginSetupRef, + // hashPermissions(_params.permissions), + // _params.helpersHash, + // bytes(""), + // PreparationType.Installation + // ); + + // // Check if this plugin is already installed. + // if (pluginState.currentAppliedSetupId != bytes32(0)) { + // revert PluginAlreadyInstalled(); + // } + + // validatePreparedSetupId(pluginInstallationId, preparedSetupId); + + // bytes32 appliedSetupId = _getAppliedSetupId(_params.pluginSetupRef, _params.helpersHash); + + // pluginState.currentAppliedSetupId = appliedSetupId; + // pluginState.blockNumber = block.number; + + // Process the permissions, which requires the `ROOT_PERMISSION_ID` from the installing DAO. + if (_params.permissions.length > 0) { + DAO(payable(_dao)).applyMultiTargetPermissions(_params.permissions); + } + + // emit InstallationApplied({ + // dao: _dao, + // plugin: _params.plugin, + // preparedSetupId: preparedSetupId, + // appliedSetupId: appliedSetupId + // }); + } + + /// @notice Prepares the update of an UUPS upgradeable plugin. + /// @param _dao The address of the DAO For which preparation of update happens. + /// @param _params The struct containing the parameters for the `prepareUpdate` function. + /// @return initData The initialization data to be passed to upgradeable contracts when the update is applied + /// @return preparedSetupData The data struct containing the array of helper contracts and permissions that the setup has prepared. + /// @dev The list of `_params.setupPayload.currentHelpers` has to be specified in the same order as they were returned from previous setups preparation steps (the latest `prepareInstallation` or `prepareUpdate` step that has happend) on which the update is prepared for. + function prepareUpdate( + address _dao, + PrepareUpdateParams calldata _params + ) + external + returns (bytes memory initData, IPluginSetup.PreparedSetupData memory preparedSetupData) + { + if ( + _params.currentVersionTag.release != _params.newVersionTag.release || + _params.currentVersionTag.build >= _params.newVersionTag.build + ) { + revert InvalidUpdateVersion({ + currentVersionTag: _params.currentVersionTag, + newVersionTag: _params.newVersionTag + }); + } + + bytes32 pluginInstallationId = _getPluginInstallationId(_dao, _params.setupPayload.plugin); + + PluginState storage pluginState = states[pluginInstallationId]; + + bytes32 currentHelpersHash = hashHelpers(_params.setupPayload.currentHelpers); + + bytes32 appliedSetupId = _getAppliedSetupId( + PluginSetupRef(_params.currentVersionTag, _params.pluginSetupRepo), + currentHelpersHash + ); + + // The following check implicitly confirms that plugin is currently installed. + // Otherwise, `currentAppliedSetupId` would not be set. + if (pluginState.currentAppliedSetupId != appliedSetupId) { + revert InvalidAppliedSetupId({ + currentAppliedSetupId: pluginState.currentAppliedSetupId, + appliedSetupId: appliedSetupId + }); + } + + PluginRepo.Version memory currentVersion = _params.pluginSetupRepo.getVersion( + _params.currentVersionTag + ); + + PluginRepo.Version memory newVersion = _params.pluginSetupRepo.getVersion( + _params.newVersionTag + ); + + bytes32 preparedSetupId; + + // If the current and new plugin setup are identical, this is an UI update. + // In this case, the permission hash is set to the empty array hash and the `prepareUpdate` call is skipped to avoid side effects. + if (currentVersion.pluginSetup == newVersion.pluginSetup) { + preparedSetupId = _getPreparedSetupId( + PluginSetupRef(_params.newVersionTag, _params.pluginSetupRepo), + EMPTY_ARRAY_HASH, + currentHelpersHash, + bytes(""), + PreparationType.Update + ); + + // Because UI updates do not change the plugin functionality, the array of helpers + // associated with this plugin version `preparedSetupData.helpers` and being returned must + // equal `_params.setupPayload.currentHelpers` returned by the previous setup step (installation or update ) + // that this update is transitioning from. + preparedSetupData.helpers = _params.setupPayload.currentHelpers; + } else { + // Check that plugin is `PluginUUPSUpgradable`. + if (!_params.setupPayload.plugin.supportsInterface(type(IPlugin).interfaceId)) { + revert IPluginNotSupported({plugin: _params.setupPayload.plugin}); + } + if (IPlugin(_params.setupPayload.plugin).pluginType() != IPlugin.PluginType.UUPS) { + revert PluginNonupgradeable({plugin: _params.setupPayload.plugin}); + } + + // Prepare the update. + (initData, preparedSetupData) = PluginSetup(newVersion.pluginSetup).prepareUpdate( + _dao, + _params.currentVersionTag.build, + _params.setupPayload + ); + + preparedSetupId = _getPreparedSetupId( + PluginSetupRef(_params.newVersionTag, _params.pluginSetupRepo), + hashPermissions(preparedSetupData.permissions), + hashHelpers(preparedSetupData.helpers), + initData, + PreparationType.Update + ); + } + + // Check if this setup has already been prepared before and is pending. + if (pluginState.blockNumber < pluginState.preparedSetupIdToBlockNumber[preparedSetupId]) { + revert SetupAlreadyPrepared({preparedSetupId: preparedSetupId}); + } + + pluginState.preparedSetupIdToBlockNumber[preparedSetupId] = block.number; + + // Avoid stack too deep. + emitPrepareUpdateEvent(_dao, preparedSetupId, _params, preparedSetupData, initData); + + return (initData, preparedSetupData); + } + + /// @notice Applies the permissions of a prepared update of an UUPS upgradeable proxy contract to a DAO. + /// @param _dao The address of the updating DAO. + /// @param _params The struct containing the parameters for the `applyInstallation` function. + function applyUpdate( + address _dao, + ApplyUpdateParams calldata _params + ) external canApply(_dao, APPLY_UPDATE_PERMISSION_ID) { + bytes32 pluginInstallationId = _getPluginInstallationId(_dao, _params.plugin); + + PluginState storage pluginState = states[pluginInstallationId]; + + bytes32 preparedSetupId = _getPreparedSetupId( + _params.pluginSetupRef, + hashPermissions(_params.permissions), + _params.helpersHash, + _params.initData, + PreparationType.Update + ); + + validatePreparedSetupId(pluginInstallationId, preparedSetupId); + + bytes32 appliedSetupId = _getAppliedSetupId(_params.pluginSetupRef, _params.helpersHash); + + pluginState.blockNumber = block.number; + pluginState.currentAppliedSetupId = appliedSetupId; + + PluginRepo.Version memory version = _params.pluginSetupRef.pluginSetupRepo.getVersion( + _params.pluginSetupRef.versionTag + ); + + address currentImplementation = PluginUUPSUpgradeable(_params.plugin).implementation(); + address newImplementation = PluginSetup(version.pluginSetup).implementation(); + + if (currentImplementation != newImplementation) { + _upgradeProxy(_params.plugin, newImplementation, _params.initData); + } + + // Process the permissions, which requires the `ROOT_PERMISSION_ID` from the updating DAO. + if (_params.permissions.length > 0) { + DAO(payable(_dao)).applyMultiTargetPermissions(_params.permissions); + } + + emit UpdateApplied({ + dao: _dao, + plugin: _params.plugin, + preparedSetupId: preparedSetupId, + appliedSetupId: appliedSetupId + }); + } + + /// @notice Prepares the uninstallation of a plugin. + /// @param _dao The address of the uninstalling DAO. + /// @param _params The struct containing the parameters for the `prepareUninstallation` function. + /// @return permissions The list of multi-targeted permission operations to be applied to the uninstalling DAO. + /// @dev The list of `_params.setupPayload.currentHelpers` has to be specified in the same order as they were returned from previous setups preparation steps (the latest `prepareInstallation` or `prepareUpdate` step that has happend) on which the uninstallation was prepared for. + function prepareUninstallation( + address _dao, + PrepareUninstallationParams calldata _params + ) external returns (PermissionLib.MultiTargetPermission[] memory permissions) { + // bytes32 pluginInstallationId = _getPluginInstallationId(_dao, _params.setupPayload.plugin); + + // PluginState storage pluginState = states[pluginInstallationId]; + + // bytes32 appliedSetupId = _getAppliedSetupId( + // _params.pluginSetupRef, + // hashHelpers(_params.setupPayload.currentHelpers) + // ); + + // if (pluginState.currentAppliedSetupId != appliedSetupId) { + // revert InvalidAppliedSetupId({ + // currentAppliedSetupId: pluginState.currentAppliedSetupId, + // appliedSetupId: appliedSetupId + // }); + // } + + // PluginRepo.Version memory version = _params.pluginSetupRef.pluginSetupRepo.getVersion( + // _params.pluginSetupRef.versionTag + // ); + + permissions = PluginSetup(popSetup()).prepareUninstallation(_dao, _params.setupPayload); + + // bytes32 preparedSetupId = _getPreparedSetupId( + // _params.pluginSetupRef, + // hashPermissions(permissions), + // ZERO_BYTES_HASH, + // bytes(""), + // PreparationType.Uninstallation + // ); + + // // Check if this setup has already been prepared before and is pending. + // if (pluginState.blockNumber < pluginState.preparedSetupIdToBlockNumber[preparedSetupId]) { + // revert SetupAlreadyPrepared({preparedSetupId: preparedSetupId}); + // } + + // pluginState.preparedSetupIdToBlockNumber[preparedSetupId] = block.number; + + // emit UninstallationPrepared({ + // sender: msg.sender, + // dao: _dao, + // preparedSetupId: preparedSetupId, + // pluginSetupRepo: _params.pluginSetupRef.pluginSetupRepo, + // versionTag: _params.pluginSetupRef.versionTag, + // setupPayload: _params.setupPayload, + // permissions: permissions + // }); + } + + /// @notice Applies the permissions of a prepared uninstallation to a DAO. + /// @param _dao The address of the DAO. + /// @param _dao The address of the uninstalling DAO. + /// @param _params The struct containing the parameters for the `applyUninstallation` function. + /// @dev The list of `_params.setupPayload.currentHelpers` has to be specified in the same order as they were returned from previous setups preparation steps (the latest `prepareInstallation` or `prepareUpdate` step that has happend) on which the uninstallation was prepared for. + function applyUninstallation( + address _dao, + ApplyUninstallationParams calldata _params + ) external canApply(_dao, APPLY_UNINSTALLATION_PERMISSION_ID) { + // bytes32 pluginInstallationId = _getPluginInstallationId(_dao, _params.plugin); + + // PluginState storage pluginState = states[pluginInstallationId]; + + // bytes32 preparedSetupId = _getPreparedSetupId( + // _params.pluginSetupRef, + // hashPermissions(_params.permissions), + // ZERO_BYTES_HASH, + // bytes(""), + // PreparationType.Uninstallation + // ); + + // validatePreparedSetupId(pluginInstallationId, preparedSetupId); + + // // Since the plugin is uninstalled, only the current block number must be updated. + // pluginState.blockNumber = block.number; + // pluginState.currentAppliedSetupId = bytes32(0); + + // Process the permissions, which requires the `ROOT_PERMISSION_ID` from the uninstalling DAO. + if (_params.permissions.length > 0) { + DAO(payable(_dao)).applyMultiTargetPermissions(_params.permissions); + } + + // emit UninstallationApplied({ + // dao: _dao, + // plugin: _params.plugin, + // preparedSetupId: preparedSetupId + // }); + } + + /// @notice Validates that a setup ID can be applied for `applyInstallation`, `applyUpdate`, or `applyUninstallation`. + /// @param pluginInstallationId The plugin installation ID obtained from the hash of `abi.encode(daoAddress, pluginAddress)`. + /// @param preparedSetupId The prepared setup ID to be validated. + /// @dev If the block number stored in `states[pluginInstallationId].blockNumber` exceeds the one stored in `pluginState.preparedSetupIdToBlockNumber[preparedSetupId]`, the prepared setup with `preparedSetupId` is outdated and not applicable anymore. + function validatePreparedSetupId( + bytes32 pluginInstallationId, + bytes32 preparedSetupId + ) public view { + PluginState storage pluginState = states[pluginInstallationId]; + if (pluginState.blockNumber >= pluginState.preparedSetupIdToBlockNumber[preparedSetupId]) { + revert SetupNotApplicable({preparedSetupId: preparedSetupId}); + } + } + + /// @notice Upgrades a UUPS upgradeable proxy contract (see [ERC-1822](https://eips.ethereum.org/EIPS/eip-1822)). + /// @param _proxy The address of the proxy. + /// @param _implementation The address of the implementation contract. + /// @param _initData The initialization data to be passed to the upgradeable plugin contract via `upgradeToAndCall`. + function _upgradeProxy( + address _proxy, + address _implementation, + bytes memory _initData + ) private { + if (_initData.length > 0) { + try + PluginUUPSUpgradeable(_proxy).upgradeToAndCall(_implementation, _initData) + {} catch Error(string memory reason) { + revert(reason); + } catch (bytes memory) /*lowLevelData*/ { + revert PluginProxyUpgradeFailed({ + proxy: _proxy, + implementation: _implementation, + initData: _initData + }); + } + } else { + try PluginUUPSUpgradeable(_proxy).upgradeTo(_implementation) {} catch Error( + string memory reason + ) { + revert(reason); + } catch (bytes memory) /*lowLevelData*/ { + revert PluginProxyUpgradeFailed({ + proxy: _proxy, + implementation: _implementation, + initData: _initData + }); + } + } + } + + /// @notice Checks if a caller can apply a setup. The caller can be either the DAO to which the plugin setup is applied to or another account to which the DAO has granted the respective permission. + /// @param _dao The address of the applying DAO. + /// @param _permissionId The permission ID. + function _canApply(address _dao, bytes32 _permissionId) private view { + if ( + msg.sender != _dao && + !DAO(payable(_dao)).hasPermission(address(this), msg.sender, _permissionId, bytes("")) + ) { + revert SetupApplicationUnauthorized({ + dao: _dao, + caller: msg.sender, + permissionId: _permissionId + }); + } + } + + /// @notice A helper to emit the `UpdatePrepared` event from the supplied, structured data. + /// @param _dao The address of the updating DAO. + /// @param _preparedSetupId The prepared setup ID. + /// @param _params The struct containing the parameters for the `prepareUpdate` function. + /// @param _preparedSetupData The deployed plugin's relevant data which consists of helpers and permissions. + /// @param _initData The initialization data to be passed to upgradeable contracts when the update is applied + /// @dev This functions exists to avoid stack-too-deep errors. + function emitPrepareUpdateEvent( + address _dao, + bytes32 _preparedSetupId, + PrepareUpdateParams calldata _params, + IPluginSetup.PreparedSetupData memory _preparedSetupData, + bytes memory _initData + ) private { + emit UpdatePrepared({ + sender: msg.sender, + dao: _dao, + preparedSetupId: _preparedSetupId, + pluginSetupRepo: _params.pluginSetupRepo, + versionTag: _params.newVersionTag, + setupPayload: _params.setupPayload, + preparedSetupData: _preparedSetupData, + initData: _initData + }); + } +} From 55c75e8c578b6b4107c13d5dcbb3100043d3fa45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Fri, 27 Sep 2024 01:04:35 +0100 Subject: [PATCH 09/14] Full parameter test --- test/integration/GaugesDaoFactory.sol | 45 +++++++++------------------ 1 file changed, 15 insertions(+), 30 deletions(-) diff --git a/test/integration/GaugesDaoFactory.sol b/test/integration/GaugesDaoFactory.sol index 41f6a92..04bcb6e 100644 --- a/test/integration/GaugesDaoFactory.sol +++ b/test/integration/GaugesDaoFactory.sol @@ -525,6 +525,7 @@ contract GaugesDaoFactoryTest is Test { address(0), "Empty plugin address" ); + assertEq(deployment.gaugeVoterPluginSets[0].plugin.paused(), false, "Should not be paused"); assertNotEq( address(deployment.gaugeVoterPluginSets[0].curve), address(0), @@ -550,11 +551,7 @@ contract GaugesDaoFactoryTest is Test { 2345, "Incorrect cooldown" ); - assertEq( - deployment.gaugeVoterPluginSets[0].exitQueue.minLock(), - 3456, - "Incorrect minLock" - ); + assertEq(deployment.gaugeVoterPluginSets[0].exitQueue.minLock(), 3456, "Incorrect minLock"); assertNotEq( address(deployment.gaugeVoterPluginSets[0].votingEscrow), address(0), @@ -586,6 +583,7 @@ contract GaugesDaoFactoryTest is Test { address(0), "Empty plugin address" ); + assertEq(deployment.gaugeVoterPluginSets[1].plugin.paused(), false, "Should not be paused"); assertNotEq( address(deployment.gaugeVoterPluginSets[1].curve), address(0), @@ -611,11 +609,7 @@ contract GaugesDaoFactoryTest is Test { 2345, "Incorrect cooldown" ); - assertEq( - deployment.gaugeVoterPluginSets[1].exitQueue.minLock(), - 3456, - "Incorrect minLock" - ); + assertEq(deployment.gaugeVoterPluginSets[1].exitQueue.minLock(), 3456, "Incorrect minLock"); assertNotEq( address(deployment.gaugeVoterPluginSets[1].votingEscrow), address(0), @@ -740,7 +734,7 @@ contract GaugesDaoFactoryTest is Test { warmupPeriod: 5678, cooldownPeriod: 6789, minLockDuration: 7890, - votingPaused: false, + votingPaused: true, // Standard multisig repo multisigPluginRepo: multisigPluginRepo, multisigPluginRelease: 1, @@ -850,6 +844,7 @@ contract GaugesDaoFactoryTest is Test { address(0), "Empty plugin address" ); + assertEq(deployment.gaugeVoterPluginSets[0].plugin.paused(), true, "Should be paused"); assertNotEq( address(deployment.gaugeVoterPluginSets[0].curve), address(0), @@ -875,11 +870,7 @@ contract GaugesDaoFactoryTest is Test { 6789, "Incorrect cooldown" ); - assertEq( - deployment.gaugeVoterPluginSets[0].exitQueue.minLock(), - 7890, - "Incorrect minLock" - ); + assertEq(deployment.gaugeVoterPluginSets[0].exitQueue.minLock(), 7890, "Incorrect minLock"); assertNotEq( address(deployment.gaugeVoterPluginSets[0].votingEscrow), address(0), @@ -911,6 +902,7 @@ contract GaugesDaoFactoryTest is Test { address(0), "Empty plugin address" ); + assertEq(deployment.gaugeVoterPluginSets[1].plugin.paused(), true, "Should be paused"); assertNotEq( address(deployment.gaugeVoterPluginSets[1].curve), address(0), @@ -926,11 +918,6 @@ contract GaugesDaoFactoryTest is Test { address(0), "Empty exitQueue address" ); - assertNotEq( - address(deployment.gaugeVoterPluginSets[1].votingEscrow), - address(0), - "Empty votingEscrow address" - ); assertEq( deployment.gaugeVoterPluginSets[1].exitQueue.feePercent(), 0.2 ether, @@ -941,10 +928,11 @@ contract GaugesDaoFactoryTest is Test { 6789, "Incorrect cooldown" ); - assertEq( - deployment.gaugeVoterPluginSets[1].exitQueue.minLock(), - 7890, - "Incorrect minLock" + assertEq(deployment.gaugeVoterPluginSets[1].exitQueue.minLock(), 7890, "Incorrect minLock"); + assertNotEq( + address(deployment.gaugeVoterPluginSets[1].votingEscrow), + address(0), + "Empty votingEscrow address" ); assertNotEq( address(deployment.gaugeVoterPluginSets[1].clock), @@ -972,6 +960,7 @@ contract GaugesDaoFactoryTest is Test { address(0), "Empty plugin address" ); + assertEq(deployment.gaugeVoterPluginSets[2].plugin.paused(), true, "Should be paused"); assertNotEq( address(deployment.gaugeVoterPluginSets[2].curve), address(0), @@ -997,11 +986,7 @@ contract GaugesDaoFactoryTest is Test { 6789, "Incorrect cooldown" ); - assertEq( - deployment.gaugeVoterPluginSets[2].exitQueue.minLock(), - 7890, - "Incorrect minLock" - ); + assertEq(deployment.gaugeVoterPluginSets[2].exitQueue.minLock(), 7890, "Incorrect minLock"); assertNotEq( address(deployment.gaugeVoterPluginSets[2].votingEscrow), address(0), From 8491aeefd68b0804b2425052c9acbdfe8573f294 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Fri, 27 Sep 2024 01:41:23 +0100 Subject: [PATCH 10/14] Deployment instructions --- .env.example | 14 +++++++------- README.md | 27 +++++++++++++++++++++++++++ foundry.toml | 1 + src/factory/GaugesDaoFactory.sol | 5 ----- test/integration/GaugesDaoFactory.sol | 3 --- 5 files changed, 35 insertions(+), 15 deletions(-) diff --git a/.env.example b/.env.example index 028f454..d5b6628 100644 --- a/.env.example +++ b/.env.example @@ -2,7 +2,7 @@ DEPLOYMENT_PRIVATE_KEY="..." ALCHEMY_API_KEY="..." ETHERSCAN_API_KEY="..." -NETWORK="holesky" +NETWORK="sepolia" # With false, the script will deploy mock tokens DEPLOY_AS_PRODUCTION=true @@ -39,14 +39,14 @@ MIN_LOCK_DURATION="4838400" # 8 weeks VOTING_PAUSED=true # PLUGIN REPO PARAMETERS (per-network) -# HOLESKY -MULTISIG_PLUGIN_REPO_ADDRESS="0x0000000000000000000000000000000000000000" +# SEPOLIA +MULTISIG_PLUGIN_REPO_ADDRESS="0x9e7956C8758470dE159481e5DD0d08F8B59217A2" MULTISIG_PLUGIN_RELEASE="1" MULTISIG_PLUGIN_BUILD="2" SIMPLE_GAUGE_VOTER_REPO_ENS_SUBDOMAIN="my-simple-gauge-voter-0" # OSx IMPLEMENTATIONS ADDRESSES (network dependent, see active_contracts.json on lib/osx) -# HOLESKY -DAO_FACTORY="0xE640Da5AD169630555A86D9b6b9C145B4961b1EB" -PLUGIN_SETUP_PROCESSOR="0xCe0B4124dea6105bfB85fB4461c4D39f360E9ef3" -PLUGIN_REPO_FACTORY="0x95D563382BeD5AcB458759EE05b27DF2CB019Cc7" +# SEPOLIA +DAO_FACTORY="0x7a62da7B56fB3bfCdF70E900787010Bc4c9Ca42e" +PLUGIN_SETUP_PROCESSOR="0xC24188a73dc09aA7C721f96Ad8857B469C01dC9f" +PLUGIN_REPO_FACTORY="0x07f49c49Ce2A99CF7C28F66673d406386BDD8Ff4" diff --git a/README.md b/README.md index 1e007cf..4ea83a4 100644 --- a/README.md +++ b/README.md @@ -61,3 +61,30 @@ The main workflow in the Mode Governance build is as follows: ## Curve design To build a flexible approach to curve design, we reviewed implementations such as seen in Curve and Aerodrome and attempted to generalise to higher order polynomials [Details on the curve design research can be found here](https://github.com/jordaniza/ve-explainer/blob/main/README.md) + +## Deployment + +To deploy the DAO, ensure that [Foundry](https://getfoundry.sh/) is installed on your computer. + +1. Edit `script/multisig-members.json` with the list of addresses to set as signers +2. Run `forge build && forge test` +3. Copy `.env.example` into `.env` and define the parameters +4. Run `source .env` to load them +5. Set the RPC URL and run the deployment script + +```sh +RPC_URL="https://eth-sepolia.g.alchemy.com/v2/${ALCHEMY_API_KEY}" +forge script --chain "$NETWORK" script/Deploy.s.sol:Deploy --rpc-url "$RPC_URL" --broadcast --verify +``` + +If you get the error Failed to get EIP-1559 fees, add --legacy to the last command: + +```sh +forge script --chain "$NETWORK" script/Deploy.s.sol:Deploy --rpc-url "$RPC_URL" --broadcast --verify --legacy +``` + +If a some contracts fail to verify on Etherscan, retry with this command: + +```sh +forge script --chain "$NETWORK" script/Deploy.s.sol:Deploy --rpc-url "$RPC_URL" --verify --legacy --private-key "$DEPLOYMENT_PRIVATE_KEY" --resume +``` diff --git a/foundry.toml b/foundry.toml index 3a70d53..7e652f8 100644 --- a/foundry.toml +++ b/foundry.toml @@ -6,6 +6,7 @@ bytecode_hash = "none" cbor_metadata = false fuzz = { runs = 256 } gas_reports = ["*"] +fs_permissions = [{ access = "read", path = "./script" }] libs = ["lib"] optimizer = true optimizer_runs = 10_000 diff --git a/src/factory/GaugesDaoFactory.sol b/src/factory/GaugesDaoFactory.sol index cf16038..2e7a482 100644 --- a/src/factory/GaugesDaoFactory.sol +++ b/src/factory/GaugesDaoFactory.sol @@ -3,13 +3,10 @@ pragma solidity ^0.8.17; import {DAO} from "@aragon/osx/core/dao/DAO.sol"; import {DAOFactory} from "@aragon/osx/framework/dao/DAOFactory.sol"; -import {Clock} from "@clock/Clock.sol"; import {IEscrowCurveUserStorage} from "@escrow-interfaces/IEscrowCurveIncreasing.sol"; import {IWithdrawalQueueErrors} from "src/escrow/increasing/interfaces/IVotingEscrowIncreasing.sol"; import {IGaugeVote} from "src/voting/ISimpleGaugeVoter.sol"; import {VotingEscrow, Clock, Lock, QuadraticIncreasingEscrow, ExitQueue, SimpleGaugeVoter, SimpleGaugeVoterSetup, ISimpleGaugeVoterSetupParams} from "src/voting/SimpleGaugeVoterSetup.sol"; -import {Addresslist} from "@aragon/osx/plugins/utils/Addresslist.sol"; -import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import {PluginSetupProcessor} from "@aragon/osx/framework/plugin/setup/PluginSetupProcessor.sol"; import {hashHelpers, PluginSetupRef} from "@aragon/osx/framework/plugin/setup/PluginSetupProcessorHelpers.sol"; import {PluginRepoFactory} from "@aragon/osx/framework/plugin/repo/PluginRepoFactory.sol"; @@ -17,8 +14,6 @@ import {PluginRepo} from "@aragon/osx/framework/plugin/repo/PluginRepo.sol"; import {IPluginSetup} from "@aragon/osx/framework/plugin/setup/IPluginSetup.sol"; import {Multisig} from "@aragon/osx/plugins/governance/multisig/Multisig.sol"; import {MultisigSetup as MultisigPluginSetup} from "@aragon/osx/plugins/governance/multisig/MultisigSetup.sol"; -import {IVotesUpgradeable} from "@openzeppelin/contracts-upgradeable/governance/utils/IVotesUpgradeable.sol"; -import {GovernanceERC20} from "@aragon/osx/token/ERC20/governance/GovernanceERC20.sol"; import {createERC1967Proxy} from "@aragon/osx/utils/Proxy.sol"; import {PermissionLib} from "@aragon/osx/core/permission/PermissionLib.sol"; diff --git a/test/integration/GaugesDaoFactory.sol b/test/integration/GaugesDaoFactory.sol index 04bcb6e..db16407 100644 --- a/test/integration/GaugesDaoFactory.sol +++ b/test/integration/GaugesDaoFactory.sol @@ -11,9 +11,6 @@ import {PluginSetupProcessor} from "@aragon/osx/framework/plugin/setup/PluginSet import {PluginRepoFactory} from "@aragon/osx/framework/plugin/repo/PluginRepoFactory.sol"; import {PluginRepoRegistry} from "@aragon/osx/framework/plugin/repo/PluginRepoRegistry.sol"; import {PluginRepo} from "@aragon/osx/framework/plugin/repo/PluginRepo.sol"; -import {GovernanceERC20} from "@aragon/osx/token/ERC20/governance/GovernanceERC20.sol"; -import {GovernanceWrappedERC20} from "@aragon/osx/token/ERC20/governance/GovernanceWrappedERC20.sol"; -import {IERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; import {DAO} from "@aragon/osx/core/dao/DAO.sol"; import {IDAO} from "@aragon/osx/core/dao/IDAO.sol"; import {Addresslist} from "@aragon/osx/plugins/utils/Addresslist.sol"; From 089bd685ed59b2700ebfcb6a083afde84266ff22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Fri, 27 Sep 2024 01:50:03 +0100 Subject: [PATCH 11/14] Ready --- src/factory/GaugesDaoFactory.sol | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/factory/GaugesDaoFactory.sol b/src/factory/GaugesDaoFactory.sol index 2e7a482..75eeeb9 100644 --- a/src/factory/GaugesDaoFactory.sol +++ b/src/factory/GaugesDaoFactory.sol @@ -159,12 +159,19 @@ contract GaugesDaoFactory { PluginRepo.Tag memory repoTag = PluginRepo.Tag(1, 1); GaugePluginSet memory pluginSet; + PluginRepo pluginRepo = prepareSimpleGaugeVoterPluginRepo(dao); + for (uint i = 0; i < parameters.tokenParameters.length; ) { ( pluginSet, deployment.gaugeVoterPluginRepo, preparedVoterSetupData - ) = prepareSimpleGaugeVoterPlugin(dao, parameters.tokenParameters[i], repoTag); + ) = prepareSimpleGaugeVoterPlugin( + dao, + parameters.tokenParameters[i], + pluginRepo, + repoTag + ); deployment.gaugeVoterPluginSets.push(pluginSet); @@ -256,13 +263,9 @@ contract GaugesDaoFactory { return (Multisig(plugin), preparedSetupData); } - function prepareSimpleGaugeVoterPlugin( - DAO dao, - TokenParameters memory tokenParameters, - PluginRepo.Tag memory repoTag - ) internal returns (GaugePluginSet memory, PluginRepo, IPluginSetup.PreparedSetupData memory) { + function prepareSimpleGaugeVoterPluginRepo(DAO dao) internal returns (PluginRepo pluginRepo) { // Publish repo - PluginRepo pluginRepo = PluginRepoFactory(parameters.pluginRepoFactory) + pluginRepo = PluginRepoFactory(parameters.pluginRepoFactory) .createPluginRepoWithFirstVersion( parameters.voterEnsSubdomain, address(parameters.voterPluginSetup), @@ -270,7 +273,14 @@ contract GaugesDaoFactory { " ", " " ); + } + function prepareSimpleGaugeVoterPlugin( + DAO dao, + TokenParameters memory tokenParameters, + PluginRepo pluginRepo, + PluginRepo.Tag memory repoTag + ) internal returns (GaugePluginSet memory, PluginRepo, IPluginSetup.PreparedSetupData memory) { // Plugin settings bytes memory settingsData = parameters.voterPluginSetup.encodeSetupData( ISimpleGaugeVoterSetupParams({ From d6d4e3346fa0871311d25b0bb6f62b49a03f2986 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Fri, 27 Sep 2024 11:50:12 +0100 Subject: [PATCH 12/14] Updated deployment instructions --- README.md | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 4ea83a4..7762dd9 100644 --- a/README.md +++ b/README.md @@ -69,21 +69,34 @@ To deploy the DAO, ensure that [Foundry](https://getfoundry.sh/) is installed on 1. Edit `script/multisig-members.json` with the list of addresses to set as signers 2. Run `forge build && forge test` 3. Copy `.env.example` into `.env` and define the parameters -4. Run `source .env` to load them -5. Set the RPC URL and run the deployment script ```sh +# Load the env vars +source .env +``` + +```sh +# Set the right RPC URL RPC_URL="https://eth-sepolia.g.alchemy.com/v2/${ALCHEMY_API_KEY}" +``` + +```sh +# Run the deployment script + +# If using Etherscan forge script --chain "$NETWORK" script/Deploy.s.sol:Deploy --rpc-url "$RPC_URL" --broadcast --verify + +# If using BlockScout +forge script --chain "$NETWORK" script/Deploy.s.sol:Deploy --rpc-url "$RPC_URL" --broadcast --verify --verifier blockscout --verifier-url "https://sepolia.explorer.mode.network/api\?" ``` -If you get the error Failed to get EIP-1559 fees, add --legacy to the last command: +If you get the error Failed to get EIP-1559 fees, add `--legacy` to the command: ```sh forge script --chain "$NETWORK" script/Deploy.s.sol:Deploy --rpc-url "$RPC_URL" --broadcast --verify --legacy ``` -If a some contracts fail to verify on Etherscan, retry with this command: +If some contracts fail to verify on Etherscan, retry with this command: ```sh forge script --chain "$NETWORK" script/Deploy.s.sol:Deploy --rpc-url "$RPC_URL" --verify --legacy --private-key "$DEPLOYMENT_PRIVATE_KEY" --resume From ae0967f00cdb6c9192252b31b213a00ced253a4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Fri, 27 Sep 2024 13:15:50 +0100 Subject: [PATCH 13/14] Activating the deployed contracts + testing --- script/Deploy.s.sol | 12 +- src/factory/GaugesDaoFactory.sol | 24 ++++ test/integration/GaugesDaoFactory.sol | 160 ++++++++++++++++++++++++++ 3 files changed, 189 insertions(+), 7 deletions(-) diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index ebc5329..1a91f0f 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -123,7 +123,7 @@ contract Deploy is Script { } } else { // MINT TEST TOKEN - console.log("Using testing parameters (minting 2 test tokens)"); + console.log("Using testing parameters (minting 2 dev tokens)"); address[] memory multisigMembers = readMultisigMembers(); tokenParameters = new TokenParameters[](2); @@ -141,11 +141,12 @@ contract Deploy is Script { } function createTestToken(address[] memory holders) internal returns (address) { + console.log(""); MockERC20 newToken = new MockERC20(); for (uint i = 0; i < holders.length; ) { newToken.mint(holders[i], 50 ether); - console.log("Minting 50 tokens for", holders[i]); + console.log("Minting 50 eth for", holders[i]); unchecked { i++; @@ -160,8 +161,8 @@ contract Deploy is Script { Deployment memory deployment = factory.getDeployment(); console.log(""); - console.log("Factory:", address(factory)); console.log("Chain ID:", block.chainid); + console.log("Factory:", address(factory)); console.log(""); console.log("DAO:", address(deployment.dao)); console.log(""); @@ -184,12 +185,12 @@ contract Deploy is Script { ); console.log(" Clock:", address(deployment.gaugeVoterPluginSets[i].clock)); console.log(" NFT Lock:", address(deployment.gaugeVoterPluginSets[i].nftLock)); + console.log(""); unchecked { i++; } } - console.log(""); console.log("Plugin repositories"); console.log( @@ -197,8 +198,5 @@ contract Deploy is Script { address(deploymentParameters.multisigPluginRepo) ); console.log("- Gauge voter plugin repository:", address(deployment.gaugeVoterPluginRepo)); - console.log(""); - - console.log("Helpers"); } } diff --git a/src/factory/GaugesDaoFactory.sol b/src/factory/GaugesDaoFactory.sol index 75eeeb9..f86df67 100644 --- a/src/factory/GaugesDaoFactory.sol +++ b/src/factory/GaugesDaoFactory.sol @@ -183,6 +183,8 @@ contract GaugesDaoFactory { preparedVoterSetupData ); + activateSimpleGaugeVoterInstallation(dao, pluginSet); + unchecked { i++; } @@ -336,6 +338,28 @@ contract GaugesDaoFactory { ); } + function activateSimpleGaugeVoterInstallation( + DAO dao, + GaugePluginSet memory pluginSet + ) internal { + dao.grant( + address(pluginSet.votingEscrow), + address(this), + pluginSet.votingEscrow.ESCROW_ADMIN_ROLE() + ); + + pluginSet.votingEscrow.setCurve(address(pluginSet.curve)); + pluginSet.votingEscrow.setQueue(address(pluginSet.exitQueue)); + pluginSet.votingEscrow.setVoter(address(pluginSet.plugin)); + pluginSet.votingEscrow.setLockNFT(address(pluginSet.nftLock)); + + dao.revoke( + address(pluginSet.votingEscrow), + address(this), + pluginSet.votingEscrow.ESCROW_ADMIN_ROLE() + ); + } + function grantApplyInstallationPermissions(DAO dao) internal { // The PSP can manage permissions on the new DAO dao.grant(address(dao), address(parameters.pluginSetupProcessor), dao.ROOT_PERMISSION_ID()); diff --git a/test/integration/GaugesDaoFactory.sol b/test/integration/GaugesDaoFactory.sol index db16407..98d6ef5 100644 --- a/test/integration/GaugesDaoFactory.sol +++ b/test/integration/GaugesDaoFactory.sol @@ -554,6 +554,38 @@ contract GaugesDaoFactoryTest is Test { address(0), "Empty votingEscrow address" ); + + assertEq( + deployment.gaugeVoterPluginSets[0].votingEscrow.token(), + tokenParameters[0].token, + "Incorrect token contract" + ); + assertEq( + deployment.gaugeVoterPluginSets[0].votingEscrow.voter(), + address(deployment.gaugeVoterPluginSets[0].plugin), + "Incorrect voter" + ); + assertEq( + deployment.gaugeVoterPluginSets[0].votingEscrow.curve(), + address(deployment.gaugeVoterPluginSets[0].curve), + "Incorrect curve" + ); + assertEq( + deployment.gaugeVoterPluginSets[0].votingEscrow.queue(), + address(deployment.gaugeVoterPluginSets[0].exitQueue), + "Incorrect queue" + ); + assertEq( + deployment.gaugeVoterPluginSets[0].votingEscrow.clock(), + address(deployment.gaugeVoterPluginSets[0].clock), + "Incorrect clock" + ); + assertEq( + deployment.gaugeVoterPluginSets[0].votingEscrow.lockNFT(), + address(deployment.gaugeVoterPluginSets[0].nftLock), + "Incorrect lockNFT" + ); + assertNotEq( address(deployment.gaugeVoterPluginSets[0].clock), address(0), @@ -612,6 +644,38 @@ contract GaugesDaoFactoryTest is Test { address(0), "Empty votingEscrow address" ); + + assertEq( + deployment.gaugeVoterPluginSets[1].votingEscrow.token(), + tokenParameters[1].token, + "Incorrect token contract" + ); + assertEq( + deployment.gaugeVoterPluginSets[1].votingEscrow.voter(), + address(deployment.gaugeVoterPluginSets[1].plugin), + "Incorrect voter" + ); + assertEq( + deployment.gaugeVoterPluginSets[1].votingEscrow.curve(), + address(deployment.gaugeVoterPluginSets[1].curve), + "Incorrect curve" + ); + assertEq( + deployment.gaugeVoterPluginSets[1].votingEscrow.queue(), + address(deployment.gaugeVoterPluginSets[1].exitQueue), + "Incorrect queue" + ); + assertEq( + deployment.gaugeVoterPluginSets[1].votingEscrow.clock(), + address(deployment.gaugeVoterPluginSets[1].clock), + "Incorrect clock" + ); + assertEq( + deployment.gaugeVoterPluginSets[1].votingEscrow.lockNFT(), + address(deployment.gaugeVoterPluginSets[1].nftLock), + "Incorrect lockNFT" + ); + assertNotEq( address(deployment.gaugeVoterPluginSets[1].clock), address(0), @@ -873,6 +937,38 @@ contract GaugesDaoFactoryTest is Test { address(0), "Empty votingEscrow address" ); + + assertEq( + deployment.gaugeVoterPluginSets[0].votingEscrow.token(), + tokenParameters[0].token, + "Incorrect token contract" + ); + assertEq( + deployment.gaugeVoterPluginSets[0].votingEscrow.voter(), + address(deployment.gaugeVoterPluginSets[0].plugin), + "Incorrect voter" + ); + assertEq( + deployment.gaugeVoterPluginSets[0].votingEscrow.curve(), + address(deployment.gaugeVoterPluginSets[0].curve), + "Incorrect curve" + ); + assertEq( + deployment.gaugeVoterPluginSets[0].votingEscrow.queue(), + address(deployment.gaugeVoterPluginSets[0].exitQueue), + "Incorrect queue" + ); + assertEq( + deployment.gaugeVoterPluginSets[0].votingEscrow.clock(), + address(deployment.gaugeVoterPluginSets[0].clock), + "Incorrect clock" + ); + assertEq( + deployment.gaugeVoterPluginSets[0].votingEscrow.lockNFT(), + address(deployment.gaugeVoterPluginSets[0].nftLock), + "Incorrect lockNFT" + ); + assertNotEq( address(deployment.gaugeVoterPluginSets[0].clock), address(0), @@ -931,6 +1027,38 @@ contract GaugesDaoFactoryTest is Test { address(0), "Empty votingEscrow address" ); + + assertEq( + deployment.gaugeVoterPluginSets[1].votingEscrow.token(), + tokenParameters[1].token, + "Incorrect token contract" + ); + assertEq( + deployment.gaugeVoterPluginSets[1].votingEscrow.voter(), + address(deployment.gaugeVoterPluginSets[1].plugin), + "Incorrect voter" + ); + assertEq( + deployment.gaugeVoterPluginSets[1].votingEscrow.curve(), + address(deployment.gaugeVoterPluginSets[1].curve), + "Incorrect curve" + ); + assertEq( + deployment.gaugeVoterPluginSets[1].votingEscrow.queue(), + address(deployment.gaugeVoterPluginSets[1].exitQueue), + "Incorrect queue" + ); + assertEq( + deployment.gaugeVoterPluginSets[1].votingEscrow.clock(), + address(deployment.gaugeVoterPluginSets[1].clock), + "Incorrect clock" + ); + assertEq( + deployment.gaugeVoterPluginSets[1].votingEscrow.lockNFT(), + address(deployment.gaugeVoterPluginSets[1].nftLock), + "Incorrect lockNFT" + ); + assertNotEq( address(deployment.gaugeVoterPluginSets[1].clock), address(0), @@ -989,6 +1117,38 @@ contract GaugesDaoFactoryTest is Test { address(0), "Empty votingEscrow address" ); + + assertEq( + deployment.gaugeVoterPluginSets[2].votingEscrow.token(), + tokenParameters[2].token, + "Incorrect token contract" + ); + assertEq( + deployment.gaugeVoterPluginSets[2].votingEscrow.voter(), + address(deployment.gaugeVoterPluginSets[2].plugin), + "Incorrect voter" + ); + assertEq( + deployment.gaugeVoterPluginSets[2].votingEscrow.curve(), + address(deployment.gaugeVoterPluginSets[2].curve), + "Incorrect curve" + ); + assertEq( + deployment.gaugeVoterPluginSets[2].votingEscrow.queue(), + address(deployment.gaugeVoterPluginSets[2].exitQueue), + "Incorrect queue" + ); + assertEq( + deployment.gaugeVoterPluginSets[2].votingEscrow.clock(), + address(deployment.gaugeVoterPluginSets[2].clock), + "Incorrect clock" + ); + assertEq( + deployment.gaugeVoterPluginSets[2].votingEscrow.lockNFT(), + address(deployment.gaugeVoterPluginSets[2].nftLock), + "Incorrect lockNFT" + ); + assertNotEq( address(deployment.gaugeVoterPluginSets[2].clock), address(0), From d8b08c185526baa60f40f6f523600129a5acb98a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Fri, 27 Sep 2024 13:41:39 +0100 Subject: [PATCH 14/14] Updating the test import path (CI) --- test/integration/GaugesDaoFactory.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/integration/GaugesDaoFactory.sol b/test/integration/GaugesDaoFactory.sol index 98d6ef5..b2833fa 100644 --- a/test/integration/GaugesDaoFactory.sol +++ b/test/integration/GaugesDaoFactory.sol @@ -3,10 +3,10 @@ pragma solidity ^0.8.17; import "forge-std/Test.sol"; import {GaugesDaoFactory, Deployment, DeploymentParameters, TokenParameters} from "../../src/factory/GaugesDaoFactory.sol"; -import {MockPluginSetupProcessor} from "../mocks/osx/MockPSP.sol"; -import {MockPluginSetupProcessorMulti} from "../mocks/osx/MockPSPMulti.sol"; -import {MockPluginRepoRegistry} from "../mocks/osx/MockPluginRepoRegistry.sol"; -import {MockDAOFactory} from "../mocks/osx/MockDaoFactory.sol"; +import {MockPluginSetupProcessor} from "@mocks/osx/MockPSP.sol"; +import {MockPluginSetupProcessorMulti} from "@mocks/osx/MockPSPMulti.sol"; +import {MockPluginRepoRegistry} from "@mocks/osx/MockPluginRepoRegistry.sol"; +import {MockDAOFactory} from "@mocks/osx/MockDAOFactory.sol"; import {PluginSetupProcessor} from "@aragon/osx/framework/plugin/setup/PluginSetupProcessor.sol"; import {PluginRepoFactory} from "@aragon/osx/framework/plugin/repo/PluginRepoFactory.sol"; import {PluginRepoRegistry} from "@aragon/osx/framework/plugin/repo/PluginRepoRegistry.sol";