From 7c5bdb31d39006ad3d64eba9d98dd7c33682b918 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Wed, 29 May 2024 17:39:01 +0200 Subject: [PATCH] Adding the Taiko DAO Factory --- script/Deploy.s.sol | 6 +- src/OptimisticTokenVotingPlugin.sol | 4 +- src/factory/TaikoDaoFactory.sol | 295 ++++++++++++++++++ .../OptimisticTokenVotingPluginSetup.sol | 2 +- test/helpers/DaoBuilder.sol | 2 +- 5 files changed, 302 insertions(+), 7 deletions(-) create mode 100644 src/factory/TaikoDaoFactory.sol diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index 9c73cf9..e664c75 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -57,13 +57,13 @@ contract Deploy is Script { pluginSetupProcessor = PluginSetupProcessor(vm.envAddress("PLUGIN_SETUP_PROCESSOR")); pluginRepoFactory = vm.envAddress("PLUGIN_REPO_FACTORY"); - minVetoRatio = uint32(vm.envUint("MIN_VETO_RATIO")); - l2InactivityPeriod = uint64(vm.envUint("L2_INACTIVITY_PERIOD")); - l2AggregationGracePeriod = uint64(vm.envUint("L2_AGGREGATION_GRACE_PERIOD")); tokenAddress = vm.envAddress("TOKEN_ADDRESS"); taikoL1 = vm.envAddress("TAIKO_L1_ADDRESS"); taikoBridge = vm.envAddress("TAIKO_BRIDGE_ADDRESS"); + l2InactivityPeriod = uint64(vm.envUint("L2_INACTIVITY_PERIOD")); + l2AggregationGracePeriod = uint64(vm.envUint("L2_AGGREGATION_GRACE_PERIOD")); + minVetoRatio = uint32(vm.envUint("MIN_VETO_RATIO")); minStdProposalDelay = uint64(vm.envUint("MIN_STD_PROPOSAL_DELAY")); minStdApprovals = uint16(vm.envUint("MIN_STD_APPROVALS")); minEmergencyApprovals = uint16(vm.envUint("MIN_EMERGENCY_APPROVALS")); diff --git a/src/OptimisticTokenVotingPlugin.sol b/src/OptimisticTokenVotingPlugin.sol index f79ea3f..b220c1a 100644 --- a/src/OptimisticTokenVotingPlugin.sol +++ b/src/OptimisticTokenVotingPlugin.sol @@ -153,13 +153,13 @@ contract OptimisticTokenVotingPlugin is IDAO _dao, OptimisticGovernanceSettings calldata _governanceSettings, IVotesUpgradeable _token, - TaikoL1 _taikoL1, + address _taikoL1, address _taikoBridge ) external initializer { __PluginUUPSUpgradeable_init(_dao); votingToken = _token; - taikoL1 = _taikoL1; + taikoL1 = TaikoL1(_taikoL1); taikoBridge = _taikoBridge; _updateOptimisticGovernanceSettings(_governanceSettings); diff --git a/src/factory/TaikoDaoFactory.sol b/src/factory/TaikoDaoFactory.sol new file mode 100644 index 0000000..14443df --- /dev/null +++ b/src/factory/TaikoDaoFactory.sol @@ -0,0 +1,295 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {DAO} from "@aragon/osx/core/dao/DAO.sol"; +import {IDAO} from "@aragon/osx/core/dao/IDAO.sol"; +import {Multisig} from "../Multisig.sol"; +import {EmergencyMultisig} from "../EmergencyMultisig.sol"; +import {PublicKeyRegistry} from "../PublicKeyRegistry.sol"; +import {OptimisticTokenVotingPlugin} from "../OptimisticTokenVotingPlugin.sol"; +import {OptimisticTokenVotingPluginSetup} from "../setup/OptimisticTokenVotingPluginSetup.sol"; +import {MultisigPluginSetup} from "../setup/MultisigPluginSetup.sol"; +import {EmergencyMultisigPluginSetup} from "../setup/EmergencyMultisigPluginSetup.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 {IVotesUpgradeable} from "@openzeppelin/contracts-upgradeable/governance/utils/IVotesUpgradeable.sol"; +import {GovernanceERC20} from "@aragon/osx/token/ERC20/governance/GovernanceERC20.sol"; +import {GovernanceWrappedERC20} from "@aragon/osx/token/ERC20/governance/GovernanceWrappedERC20.sol"; +import {createERC1967Proxy} from "@aragon/osx/utils/Proxy.sol"; + +contract TaikoDaoFactory { + struct DeploymentSettings { + // Taiko contract settings + IVotesUpgradeable tokenAddress; + address taikoL1ContractAddress; + address taikoBridgeAddress; + uint64 l2InactivityPeriod; + uint64 l2AggregationGracePeriod; + // Voting settings + uint32 minVetoRatio; + uint64 minStdProposalDelay; + uint16 minStdApprovals; + uint16 minEmergencyApprovals; + // OSx contracts + PluginSetupProcessor pluginSetupProcessor; + PluginRepoFactory pluginRepoFactory; + // Token contracts + GovernanceERC20 governanceErc20Base; + GovernanceWrappedERC20 governanceErcWrapped20Base; + // Multisig + address[] multisigMembers; + // ENS + string stdMultisigEnsDomain; + string emergencyMultisigEnsDomain; + string optimisticTokenVotingEnsDomain; + } + + struct Deployment { + DAO dao; + // Plugins + OptimisticTokenVotingPlugin optimisticTokenVotingPlugin; + Multisig multisigPlugin; + EmergencyMultisig emergencyMultisigPlugin; + // Plugin repo's + PluginRepo optimisticVotingPluginRepo; + PluginRepo multisigPluginRepo; + PluginRepo emergencyMultisigPluginRepo; + // Other + PublicKeyRegistry publicKeyRegistry; + } + + DeploymentSettings public settings; + Deployment public deployment; + + // Implementations + address immutable DAO_BASE = address(new DAO()); + + /// @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; + + deploy(); + } + + function deploy() internal { + IPluginSetup.PreparedSetupData memory preparedMultisigSetupData; + IPluginSetup.PreparedSetupData memory preparedEmergencyMultisigSetupData; + IPluginSetup.PreparedSetupData memory preparedOptimisticSetupData; + + // Deploy the DAO with this contract as the owner + deployment.dao = prepareDao(); + + // Plugins + (deployment.multisigPlugin, deployment.multisigPluginRepo, preparedMultisigSetupData) = + prepareMultisig(deployment.dao); + + (deployment.emergencyMultisigPlugin, deployment.emergencyMultisigPluginRepo, preparedEmergencyMultisigSetupData) + = prepareEmergencyMultisig(deployment.dao, deployment.multisigPlugin); + + (deployment.optimisticTokenVotingPlugin, deployment.optimisticVotingPluginRepo, preparedOptimisticSetupData) = + prepareOptimisticTokenVoting( + deployment.dao, address(deployment.multisigPlugin), address(deployment.emergencyMultisigPlugin) + ); + + // Apply permissions + + applyPluginInstallation( + deployment.dao, address(deployment.multisigPlugin), deployment.multisigPluginRepo, preparedMultisigSetupData + ); + applyPluginInstallation( + deployment.dao, + address(deployment.emergencyMultisigPlugin), + deployment.emergencyMultisigPluginRepo, + preparedEmergencyMultisigSetupData + ); + applyPluginInstallation( + deployment.dao, + address(deployment.optimisticTokenVotingPlugin), + deployment.optimisticVotingPluginRepo, + preparedOptimisticSetupData + ); + + // Other contracts + deployment.publicKeyRegistry = deployPublicKeyRegistry(); + + dropRootPermission(deployment.dao); + } + + function prepareDao() internal returns (DAO dao) { + dao = DAO( + payable( + createERC1967Proxy( + address(DAO_BASE), + abi.encodeCall( + DAO.initialize, + ( + "", // Metadata URI + address(this), + address(0x0), // Trusted forwarder + "" // DAO URI + ) + ) + ) + ) + ); + } + + function prepareMultisig(DAO dao) internal returns (Multisig, PluginRepo, IPluginSetup.PreparedSetupData memory) { + // Deploy plugin setup + MultisigPluginSetup pluginSetup = new MultisigPluginSetup(); + + // Publish repo + PluginRepo pluginRepo = PluginRepoFactory(settings.pluginRepoFactory).createPluginRepoWithFirstVersion( + settings.stdMultisigEnsDomain, address(pluginSetup), msg.sender, "", "" + ); + + bytes memory settingsData = pluginSetup.encodeInstallationParameters( + settings.multisigMembers, + Multisig.MultisigSettings( + true, // onlyListed + settings.minStdApprovals, + settings.minStdProposalDelay // destination minDuration + ) + ); + + (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 prepareEmergencyMultisig(DAO dao, Addresslist multisigPlugin) + internal + returns (EmergencyMultisig, PluginRepo, IPluginSetup.PreparedSetupData memory) + { + // Deploy plugin setup + EmergencyMultisigPluginSetup pluginSetup = new EmergencyMultisigPluginSetup(); + + // Publish repo + PluginRepo pluginRepo = PluginRepoFactory(settings.pluginRepoFactory).createPluginRepoWithFirstVersion( + settings.emergencyMultisigEnsDomain, address(pluginSetup), msg.sender, "", "" + ); + + bytes memory settingsData = pluginSetup.encodeInstallationParameters( + settings.multisigMembers, + EmergencyMultisig.MultisigSettings( + true, // onlyListed + settings.minEmergencyApprovals, // minAppovals + Addresslist(multisigPlugin) + ) + ); + + (address plugin, IPluginSetup.PreparedSetupData memory preparedSetupData) = settings + .pluginSetupProcessor + .prepareInstallation( + address(dao), + PluginSetupProcessor.PrepareInstallationParams( + PluginSetupRef(PluginRepo.Tag(1, 1), PluginRepo(pluginRepo)), settingsData + ) + ); + + return (EmergencyMultisig(plugin), pluginRepo, preparedSetupData); + } + + function prepareOptimisticTokenVoting(DAO dao, address stdProposer, address emergencyProposer) + internal + returns (OptimisticTokenVotingPlugin, PluginRepo, IPluginSetup.PreparedSetupData memory) + { + // Deploy plugin setup + OptimisticTokenVotingPluginSetup pluginSetup = + new OptimisticTokenVotingPluginSetup(settings.governanceErc20Base, settings.governanceErcWrapped20Base); + + // Publish repo + PluginRepo pluginRepo = PluginRepoFactory(settings.pluginRepoFactory).createPluginRepoWithFirstVersion( + settings.optimisticTokenVotingEnsDomain, address(pluginSetup), msg.sender, "", "" + ); + + // Plugin settings + bytes memory settingsData; + { + OptimisticTokenVotingPlugin.OptimisticGovernanceSettings memory votingSettings = OptimisticTokenVotingPlugin + .OptimisticGovernanceSettings( + settings.minVetoRatio, + 0, // minDuration (the condition contract will enforce it) + settings.l2InactivityPeriod, + settings.l2AggregationGracePeriod + ); + + OptimisticTokenVotingPluginSetup.TokenSettings memory tokenSettings = + OptimisticTokenVotingPluginSetup.TokenSettings(address(settings.tokenAddress), "", ""); + + GovernanceERC20.MintSettings memory mintSettings = + GovernanceERC20.MintSettings(new address[](0), new uint256[](0)); + + settingsData = pluginSetup.encodeInstallationParams( + OptimisticTokenVotingPluginSetup.InstallationParameters( + votingSettings, + tokenSettings, + mintSettings, + settings.taikoL1ContractAddress, + settings.taikoBridgeAddress, + settings.minStdProposalDelay, + stdProposer, + emergencyProposer + ) + ); + } + + (address plugin, IPluginSetup.PreparedSetupData memory preparedSetupData) = settings + .pluginSetupProcessor + .prepareInstallation( + address(dao), + PluginSetupProcessor.PrepareInstallationParams( + PluginSetupRef(PluginRepo.Tag(1, 1), PluginRepo(pluginRepo)), settingsData + ) + ); + + return (OptimisticTokenVotingPlugin(plugin), pluginRepo, preparedSetupData); + } + + function deployPublicKeyRegistry() internal returns (PublicKeyRegistry) { + return new PublicKeyRegistry(); + } + + function applyPluginInstallation( + DAO dao, + address plugin, + PluginRepo pluginRepo, + IPluginSetup.PreparedSetupData memory preparedSetupData + ) internal { + IDAO.Action[] memory actions = new IDAO.Action[](1); + actions[0] = IDAO.Action( + address(dao), + 0, + abi.encodeCall( + PluginSetupProcessor.applyInstallation, + ( + address(dao), + PluginSetupProcessor.ApplyInstallationParams( + PluginSetupRef(PluginRepo.Tag(1, 1), pluginRepo), + plugin, + preparedSetupData.permissions, + hashHelpers(preparedSetupData.helpers) + ) + ) + ) + ); + dao.execute(bytes32(uint256(0x1)), actions, 0); + } + + function dropRootPermission(DAO dao) internal { + dao.revoke(address(dao), address(this), dao.ROOT_PERMISSION_ID()); + } +} diff --git a/src/setup/OptimisticTokenVotingPluginSetup.sol b/src/setup/OptimisticTokenVotingPluginSetup.sol index 8964f1e..08a1516 100644 --- a/src/setup/OptimisticTokenVotingPluginSetup.sol +++ b/src/setup/OptimisticTokenVotingPluginSetup.sol @@ -53,7 +53,7 @@ contract OptimisticTokenVotingPluginSetup is PluginSetup { TokenSettings tokenSettings; // only used for GovernanceERC20 (when token is not passed) GovernanceERC20.MintSettings mintSettings; - TaikoL1 taikoL1; + address taikoL1; address taikoBridge; uint64 stdProposalMinDuration; address stdProposer; diff --git a/test/helpers/DaoBuilder.sol b/test/helpers/DaoBuilder.sol index fed9a22..76680cb 100644 --- a/test/helpers/DaoBuilder.sol +++ b/test/helpers/DaoBuilder.sol @@ -188,7 +188,7 @@ contract DaoBuilder is Test { address(OPTIMISTIC_BASE), abi.encodeCall( OptimisticTokenVotingPlugin.initialize, - (dao, targetContractSettings, votingToken, taikoL1, taikoBridge) + (dao, targetContractSettings, votingToken, address(taikoL1), taikoBridge) ) ) );