From b160261d95ae48c5b1abc1b1b391c7b300ab9f7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Thu, 30 May 2024 17:18:06 +0200 Subject: [PATCH 1/3] WIP: Adding a factory test --- test/integration/TaikoDaoFactory.t.sol | 208 +++++++++++++++++++++++++ 1 file changed, 208 insertions(+) create mode 100644 test/integration/TaikoDaoFactory.t.sol diff --git a/test/integration/TaikoDaoFactory.t.sol b/test/integration/TaikoDaoFactory.t.sol new file mode 100644 index 0000000..9f6232a --- /dev/null +++ b/test/integration/TaikoDaoFactory.t.sol @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +import {AragonTest} from "../base/AragonTest.sol"; +import {TaikoDaoFactory} from "../../src/factory/TaikoDaoFactory.sol"; +import {GovernanceERC20Mock} from "../mocks/GovernanceERC20Mock.sol"; +import {TaikoL1Mock} from "../mocks/TaikoL1Mock.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 {ENSSubdomainRegistrar} from "@aragon/osx/framework/utils/ens/ENSSubdomainRegistrar.sol"; +import {GovernanceERC20} from "@aragon/osx/token/ERC20/governance/GovernanceERC20.sol"; +import {GovernanceWrappedERC20} from "@aragon/osx/token/ERC20/governance/GovernanceWrappedERC20.sol"; +import {DAO} from "@aragon/osx/core/dao/DAO.sol"; +import {IDAO} from "@aragon/osx/core/dao/IDAO.sol"; +import {ENSRegistry} from "@ensdomains/ens-contracts/contracts/registry/ENSRegistry.sol"; +import {PublicResolver} from "@ensdomains/ens-contracts/contracts/resolvers/PublicResolver.sol"; +import {NameWrapper} from "@ensdomains/ens-contracts/contracts/wrapper/NameWrapper.sol"; +import {createProxyAndCall} from "../../src/helpers/proxy.sol"; + +contract TaikoDaoFactoryTest is AragonTest { + function test_ShouldStoreTheSettings() public { + DAO tempMgmtDao = DAO( + payable( + createProxyAndCall( + address(DAO_BASE), abi.encodeCall(DAO.initialize, ("", address(this), address(0x0), "")) + ) + ) + ); + + GovernanceERC20Mock tokenAddress = new GovernanceERC20Mock(address(tempMgmtDao)); + TaikoL1Mock taikoL1ContractAddress = new TaikoL1Mock(); + address taikoBridgeAddress = address(0x1234); + address[] memory multisigMembers = new address[](13); + PluginRepoFactory pRefoFactory; + PluginSetupProcessor psp; + { + ENSRegistry registry = new ENSRegistry(); + PublicResolver resolver = new PublicResolver(registry, new NameWrapper(), address(0), address(0)); + + bytes32 node = bytes32(uint256(0x1234)); + ENSSubdomainRegistrar ensSubdomainReg = ENSSubdomainRegistrar( + payable( + createProxyAndCall( + address(new ENSSubdomainRegistrar()), + abi.encodeCall(ENSSubdomainRegistrar.initialize, (tempMgmtDao, registry, node)) + ) + ) + ); + PluginRepoRegistry pRepoRegistry = PluginRepoRegistry( + payable( + createProxyAndCall( + address(new PluginRepoRegistry()), + abi.encodeCall(PluginRepoRegistry.initialize, (tempMgmtDao, ensSubdomainReg)) + ) + ) + ); + pRefoFactory = new PluginRepoFactory(pRepoRegistry); + psp = new PluginSetupProcessor(pRepoRegistry); + } + + TaikoDaoFactory.DeploymentSettings memory creationSettings = TaikoDaoFactory.DeploymentSettings({ + // Taiko contract settings + tokenAddress: tokenAddress, + taikoL1ContractAddress: address(taikoL1ContractAddress), // address + taikoBridgeAddress: taikoBridgeAddress, // address + l2InactivityPeriod: 10 minutes, // uint64 + l2AggregationGracePeriod: 2 days, // uint64 + // Voting settings + minVetoRatio: 200_000, // uint32 + minStdProposalDelay: 10 days, // uint64 + minStdApprovals: 7, // uint16 + minEmergencyApprovals: 11, // uint16 + // OSx contracts + pluginSetupProcessor: psp, // PluginSetupProcessor + pluginRepoFactory: pRefoFactory, // PluginRepoFactory + // Token contracts + governanceErc20Base: new GovernanceERC20( + tempMgmtDao, "", "", GovernanceERC20.MintSettings(new address[](0), new uint256[](0)) + ), // GovernanceERC20 + governanceErcWrapped20Base: new GovernanceWrappedERC20(tokenAddress, "", ""), // GovernanceWrappedERC20 + // Multisig + multisigMembers: multisigMembers, // address[] + // ENS + stdMultisigEnsDomain: "multisig", // string + emergencyMultisigEnsDomain: "eMultisig", // string + optimisticTokenVotingEnsDomain: "optimistic" // string + }); + + TaikoDaoFactory factory = new TaikoDaoFactory(creationSettings); + + // Check + TaikoDaoFactory.DeploymentSettings memory actualSettings = factory.getSettings(); + assertEq(address(actualSettings.tokenAddress), address(creationSettings.tokenAddress), "Incorrect tokenAddress"); + assertEq( + actualSettings.taikoL1ContractAddress, + creationSettings.taikoL1ContractAddress, + "Incorrect taikoL1ContractAddress" + ); + assertEq(actualSettings.taikoBridgeAddress, creationSettings.taikoBridgeAddress, "Incorrect taikoBridgeAddress"); + assertEq(actualSettings.l2InactivityPeriod, creationSettings.l2InactivityPeriod, "Incorrect l2InactivityPeriod"); + assertEq( + actualSettings.l2AggregationGracePeriod, + creationSettings.l2AggregationGracePeriod, + "Incorrect l2AggregationGracePeriod" + ); + assertEq(actualSettings.minVetoRatio, creationSettings.minVetoRatio, "Incorrect minVetoRatio"); + assertEq( + actualSettings.minStdProposalDelay, creationSettings.minStdProposalDelay, "Incorrect minStdProposalDelay" + ); + assertEq(actualSettings.minStdApprovals, creationSettings.minStdApprovals, "Incorrect minStdApprovals"); + assertEq( + actualSettings.minEmergencyApprovals, + creationSettings.minEmergencyApprovals, + "Incorrect minEmergencyApprovals" + ); + assertEq( + address(actualSettings.pluginSetupProcessor), + address(creationSettings.pluginSetupProcessor), + "Incorrect pluginSetupProcessor" + ); + assertEq( + address(actualSettings.pluginRepoFactory), + address(creationSettings.pluginRepoFactory), + "Incorrect pluginRepoFactory" + ); + assertEq( + address(actualSettings.governanceErc20Base), + address(creationSettings.governanceErc20Base), + "Incorrect governanceErc20Base" + ); + assertEq( + address(actualSettings.governanceErcWrapped20Base), + address(creationSettings.governanceErcWrapped20Base), + "Incorrect governanceErcWrapped20Base" + ); + assertEq( + actualSettings.multisigMembers.length, + creationSettings.multisigMembers.length, + "Incorrect multisigMembers.length" + ); + assertEq( + actualSettings.stdMultisigEnsDomain, creationSettings.stdMultisigEnsDomain, "Incorrect stdMultisigEnsDomain" + ); + assertEq( + actualSettings.emergencyMultisigEnsDomain, + creationSettings.emergencyMultisigEnsDomain, + "Incorrect emergencyMultisigEnsDomain" + ); + assertEq( + actualSettings.optimisticTokenVotingEnsDomain, + creationSettings.optimisticTokenVotingEnsDomain, + "Incorrect optimisticTokenVotingEnsDomain" + ); + } + + // function test_StandardDeployment() public { + // DAO tempMgmtDao = new DAO(); + // tempMgmtDao.initialize("", address(this), address(0), ""); + + // GovernanceERC20Mock tokenAddress = new GovernanceERC20Mock(address(tempMgmtDao)); + // TaikoL1Mock taikoL1ContractAddress = new TaikoL1Mock(); + // address taikoBridgeAddress = address(0x1234); + // address[] memory multisigMembers = new address[](13); + // PluginRepoFactory pRefoFactory; + // PluginSetupProcessor psp; + // { + // PluginRepoRegistry pRepoRegistry = new PluginRepoRegistry(); + // ENSSubdomainRegistrar ensSubdomainReg = new ENSSubdomainRegistrar(); + // ENSRegistry registry = new ENSRegistry(); + // ensSubdomainReg.initialize(tempMgmtDao, registry, bytes32(uint256(0x1234))); + // pRepoRegistry.initialize(tempMgmtDao, ensSubdomainReg); + // pRefoFactory = new PluginRepoFactory(pRepoRegistry); + // psp = new PluginSetupProcessor(pRepoRegistry); + // } + + // TaikoDaoFactory.DeploymentSettings memory settings = TaikoDaoFactory.DeploymentSettings({ + // // Taiko contract settings + // tokenAddress: tokenAddress, + // taikoL1ContractAddress: address(taikoL1ContractAddress), // address + // taikoBridgeAddress: taikoBridgeAddress, // address + // l2InactivityPeriod: 10 minutes, // uint64 + // l2AggregationGracePeriod: 2 days, // uint64 + // // Voting settings + // minVetoRatio: 200_000, // uint32 + // minStdProposalDelay: 10 days, // uint64 + // minStdApprovals: 7, // uint16 + // minEmergencyApprovals: 11, // uint16 + // // OSx contracts + // pluginSetupProcessor: psp, // PluginSetupProcessor + // pluginRepoFactory: pRefoFactory, // PluginRepoFactory + // // Token contracts + // governanceErc20Base: new GovernanceERC20( + // tempMgmtDao, "", "", GovernanceERC20.MintSettings(new address[](0), new uint256[](0)) + // ), // GovernanceERC20 + // governanceErcWrapped20Base: new GovernanceWrappedERC20(tokenAddress, "", ""), // GovernanceWrappedERC20 + // // Multisig + // multisigMembers: multisigMembers, // address[] + // // ENS + // stdMultisigEnsDomain: "multisig", // string + // emergencyMultisigEnsDomain: "eMultisig", // string + // optimisticTokenVotingEnsDomain: "optimistic" // string + // }); + + // TaikoDaoFactory factory = new TaikoDaoFactory(settings); + // factory.getDeployment(); + // } +} From ef737cba6128a3156c5aefcf08b31b5a81e17b76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Fri, 2 Aug 2024 16:48:12 +0200 Subject: [PATCH 2/3] WIP: Testing settings storage, working the deployment out --- src/factory/TaikoDaoFactory.sol | 16 +- test/integration/TaikoDaoFactory.t.sol | 343 +++++++---- test/mocks/osx/MockDaoFactory.sol | 184 ++++++ test/mocks/osx/MockPSP.sol | 706 ++++++++++++++++++++++ test/mocks/osx/MockPluginRepoRegistry.sol | 65 ++ 5 files changed, 1208 insertions(+), 106 deletions(-) create mode 100644 test/mocks/osx/MockDaoFactory.sol create mode 100644 test/mocks/osx/MockPSP.sol create mode 100644 test/mocks/osx/MockPluginRepoRegistry.sol diff --git a/src/factory/TaikoDaoFactory.sol b/src/factory/TaikoDaoFactory.sol index 0868fed..8ec231b 100644 --- a/src/factory/TaikoDaoFactory.sol +++ b/src/factory/TaikoDaoFactory.sol @@ -20,6 +20,7 @@ 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 {createERC1967Proxy} from "@aragon/osx/utils/Proxy.sol"; +import {PermissionLib} from "@aragon/osx/core/permission/PermissionLib.sol"; contract TaikoDaoFactory { struct DeploymentSettings { @@ -141,7 +142,7 @@ contract TaikoDaoFactory { DAO.initialize, ( "", // Metadata URI - address(this), + address(this), // initialOwner address(0x0), // Trusted forwarder "" // DAO URI ) @@ -149,6 +150,19 @@ contract TaikoDaoFactory { ) ) ); + + // 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) { diff --git a/test/integration/TaikoDaoFactory.t.sol b/test/integration/TaikoDaoFactory.t.sol index 9f6232a..1eb8daa 100644 --- a/test/integration/TaikoDaoFactory.t.sol +++ b/test/integration/TaikoDaoFactory.t.sol @@ -4,60 +4,39 @@ pragma solidity ^0.8.17; import {AragonTest} from "../base/AragonTest.sol"; import {TaikoDaoFactory} from "../../src/factory/TaikoDaoFactory.sol"; import {GovernanceERC20Mock} from "../mocks/GovernanceERC20Mock.sol"; +import {MockPluginSetupProcessor} from "../mocks/osx/MockPSP.sol"; +import {MockPluginRepoRegistry} from "../mocks/osx/MockPluginRepoRegistry.sol"; +import {MockDaoFactory} from "../mocks/osx/MockDaoFactory.sol"; import {TaikoL1Mock} from "../mocks/TaikoL1Mock.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 {ENSSubdomainRegistrar} from "@aragon/osx/framework/utils/ens/ENSSubdomainRegistrar.sol"; import {GovernanceERC20} from "@aragon/osx/token/ERC20/governance/GovernanceERC20.sol"; import {GovernanceWrappedERC20} from "@aragon/osx/token/ERC20/governance/GovernanceWrappedERC20.sol"; import {DAO} from "@aragon/osx/core/dao/DAO.sol"; import {IDAO} from "@aragon/osx/core/dao/IDAO.sol"; -import {ENSRegistry} from "@ensdomains/ens-contracts/contracts/registry/ENSRegistry.sol"; -import {PublicResolver} from "@ensdomains/ens-contracts/contracts/resolvers/PublicResolver.sol"; -import {NameWrapper} from "@ensdomains/ens-contracts/contracts/wrapper/NameWrapper.sol"; import {createProxyAndCall} from "../../src/helpers/proxy.sol"; +import {MultisigPluginSetup} from "../../src/setup/MultisigPluginSetup.sol"; +import {EmergencyMultisigPluginSetup} from "../../src/setup/EmergencyMultisigPluginSetup.sol"; +import {OptimisticTokenVotingPluginSetup} from "../../src/setup/OptimisticTokenVotingPluginSetup.sol"; contract TaikoDaoFactoryTest is AragonTest { - function test_ShouldStoreTheSettings() public { - DAO tempMgmtDao = DAO( - payable( - createProxyAndCall( - address(DAO_BASE), abi.encodeCall(DAO.initialize, ("", address(this), address(0x0), "")) - ) - ) - ); - + function test_ShouldStoreTheSettings_1() public { + DAO tempMgmtDao = DAO(payable(address(0))); GovernanceERC20Mock tokenAddress = new GovernanceERC20Mock(address(tempMgmtDao)); TaikoL1Mock taikoL1ContractAddress = new TaikoL1Mock(); address taikoBridgeAddress = address(0x1234); address[] memory multisigMembers = new address[](13); - PluginRepoFactory pRefoFactory; - PluginSetupProcessor psp; - { - ENSRegistry registry = new ENSRegistry(); - PublicResolver resolver = new PublicResolver(registry, new NameWrapper(), address(0), address(0)); - - bytes32 node = bytes32(uint256(0x1234)); - ENSSubdomainRegistrar ensSubdomainReg = ENSSubdomainRegistrar( - payable( - createProxyAndCall( - address(new ENSSubdomainRegistrar()), - abi.encodeCall(ENSSubdomainRegistrar.initialize, (tempMgmtDao, registry, node)) - ) - ) - ); - PluginRepoRegistry pRepoRegistry = PluginRepoRegistry( - payable( - createProxyAndCall( - address(new PluginRepoRegistry()), - abi.encodeCall(PluginRepoRegistry.initialize, (tempMgmtDao, ensSubdomainReg)) - ) - ) - ); - pRefoFactory = new PluginRepoFactory(pRepoRegistry); - psp = new PluginSetupProcessor(pRepoRegistry); - } + + MultisigPluginSetup multisigPluginSetup = new MultisigPluginSetup(); + EmergencyMultisigPluginSetup emergencyMultisigPluginSetup = new EmergencyMultisigPluginSetup(); + OptimisticTokenVotingPluginSetup optimisticTokenVotingPluginSetup = + new OptimisticTokenVotingPluginSetup(GovernanceERC20(address(0)), GovernanceWrappedERC20(address(0))); + + MockPluginRepoRegistry pRepoRegistry = new MockPluginRepoRegistry(); + PluginRepoFactory pRefoFactory = new PluginRepoFactory(PluginRepoRegistry(address(pRepoRegistry))); + MockPluginSetupProcessor psp = new MockPluginSetupProcessor(new address[](0)); + MockDaoFactory daoFactory = new MockDaoFactory(psp); TaikoDaoFactory.DeploymentSettings memory creationSettings = TaikoDaoFactory.DeploymentSettings({ // Taiko contract settings @@ -66,19 +45,20 @@ contract TaikoDaoFactoryTest is AragonTest { taikoBridgeAddress: taikoBridgeAddress, // address l2InactivityPeriod: 10 minutes, // uint64 l2AggregationGracePeriod: 2 days, // uint64 + skipL2: false, // Voting settings minVetoRatio: 200_000, // uint32 - minStdProposalDelay: 10 days, // uint64 + minStdProposalDuration: 10 days, // uint64 minStdApprovals: 7, // uint16 minEmergencyApprovals: 11, // uint16 // OSx contracts - pluginSetupProcessor: psp, // PluginSetupProcessor - pluginRepoFactory: pRefoFactory, // PluginRepoFactory - // Token contracts - governanceErc20Base: new GovernanceERC20( - tempMgmtDao, "", "", GovernanceERC20.MintSettings(new address[](0), new uint256[](0)) - ), // GovernanceERC20 - governanceErcWrapped20Base: new GovernanceWrappedERC20(tokenAddress, "", ""), // GovernanceWrappedERC20 + osxDaoFactory: address(daoFactory), + pluginSetupProcessor: PluginSetupProcessor(address(psp)), // PluginSetupProcessor + pluginRepoFactory: PluginRepoFactory(address(pRefoFactory)), // PluginRepoFactory + // Plugin setup's + multisigPluginSetup: multisigPluginSetup, + emergencyMultisigPluginSetup: emergencyMultisigPluginSetup, + optimisticTokenVotingPluginSetup: optimisticTokenVotingPluginSetup, // Multisig multisigMembers: multisigMembers, // address[] // ENS @@ -104,9 +84,136 @@ contract TaikoDaoFactoryTest is AragonTest { creationSettings.l2AggregationGracePeriod, "Incorrect l2AggregationGracePeriod" ); + assertEq(actualSettings.skipL2, creationSettings.skipL2, "Incorrect skipL2"); + assertEq(actualSettings.minVetoRatio, creationSettings.minVetoRatio, "Incorrect minVetoRatio"); + assertEq( + actualSettings.minStdProposalDuration, + creationSettings.minStdProposalDuration, + "Incorrect minStdProposalDuration" + ); + assertEq(actualSettings.minStdApprovals, creationSettings.minStdApprovals, "Incorrect minStdApprovals"); + assertEq( + actualSettings.minEmergencyApprovals, + creationSettings.minEmergencyApprovals, + "Incorrect minEmergencyApprovals" + ); + assertEq( + address(actualSettings.osxDaoFactory), address(creationSettings.osxDaoFactory), "Incorrect osxDaoFactory" + ); + assertEq( + address(actualSettings.pluginSetupProcessor), + address(creationSettings.pluginSetupProcessor), + "Incorrect pluginSetupProcessor" + ); + assertEq( + address(actualSettings.pluginRepoFactory), + address(creationSettings.pluginRepoFactory), + "Incorrect pluginRepoFactory" + ); + assertEq( + address(actualSettings.multisigPluginSetup), + address(creationSettings.multisigPluginSetup), + "Incorrect multisigPluginSetup" + ); + assertEq( + address(actualSettings.emergencyMultisigPluginSetup), + address(creationSettings.emergencyMultisigPluginSetup), + "Incorrect emergencyMultisigPluginSetup" + ); + assertEq( + address(actualSettings.optimisticTokenVotingPluginSetup), + address(creationSettings.optimisticTokenVotingPluginSetup), + "Incorrect optimisticTokenVotingPluginSetup" + ); + assertEq( + actualSettings.multisigMembers.length, + creationSettings.multisigMembers.length, + "Incorrect multisigMembers.length" + ); + assertEq( + actualSettings.stdMultisigEnsDomain, creationSettings.stdMultisigEnsDomain, "Incorrect stdMultisigEnsDomain" + ); + assertEq( + actualSettings.emergencyMultisigEnsDomain, + creationSettings.emergencyMultisigEnsDomain, + "Incorrect emergencyMultisigEnsDomain" + ); + assertEq( + actualSettings.optimisticTokenVotingEnsDomain, + creationSettings.optimisticTokenVotingEnsDomain, + "Incorrect optimisticTokenVotingEnsDomain" + ); + } + + function test_ShouldStoreTheSettings_2() public { + DAO tempMgmtDao = DAO(payable(address(0))); + GovernanceERC20Mock tokenAddress = new GovernanceERC20Mock(address(tempMgmtDao)); + TaikoL1Mock taikoL1ContractAddress = new TaikoL1Mock(); + address taikoBridgeAddress = address(0x1234); + address[] memory multisigMembers = new address[](20); + + MultisigPluginSetup multisigPluginSetup = new MultisigPluginSetup(); + EmergencyMultisigPluginSetup emergencyMultisigPluginSetup = new EmergencyMultisigPluginSetup(); + OptimisticTokenVotingPluginSetup optimisticTokenVotingPluginSetup = + new OptimisticTokenVotingPluginSetup(GovernanceERC20(address(0)), GovernanceWrappedERC20(address(0))); + + MockPluginRepoRegistry pRepoRegistry = new MockPluginRepoRegistry(); + PluginRepoFactory pRefoFactory = new PluginRepoFactory(PluginRepoRegistry(address(pRepoRegistry))); + MockPluginSetupProcessor psp = new MockPluginSetupProcessor(new address[](0)); + MockDaoFactory daoFactory = new MockDaoFactory(psp); + + TaikoDaoFactory.DeploymentSettings memory creationSettings = TaikoDaoFactory.DeploymentSettings({ + // Taiko contract settings + tokenAddress: tokenAddress, + taikoL1ContractAddress: address(taikoL1ContractAddress), // address + taikoBridgeAddress: taikoBridgeAddress, // address + l2InactivityPeriod: 27 minutes, // uint64 + l2AggregationGracePeriod: 77 days, // uint64 + skipL2: false, + // Voting settings + minVetoRatio: 456_000, // uint32 + minStdProposalDuration: 14 days, // uint64 + minStdApprovals: 4, // uint16 + minEmergencyApprovals: 27, // uint16 + // OSx contracts + osxDaoFactory: address(daoFactory), // DaoFactory + pluginSetupProcessor: PluginSetupProcessor(address(psp)), // PluginSetupProcessor + pluginRepoFactory: PluginRepoFactory(address(pRefoFactory)), // PluginRepoFactory + // Plugin setup's + multisigPluginSetup: multisigPluginSetup, + emergencyMultisigPluginSetup: emergencyMultisigPluginSetup, + optimisticTokenVotingPluginSetup: optimisticTokenVotingPluginSetup, + // Multisig + multisigMembers: multisigMembers, // address[] + // ENS + stdMultisigEnsDomain: "multisig-1234", // string + emergencyMultisigEnsDomain: "eMultisig-1234", // string + optimisticTokenVotingEnsDomain: "optimistic-1234" // string + }); + + TaikoDaoFactory factory = new TaikoDaoFactory(creationSettings); + + // Check + TaikoDaoFactory.DeploymentSettings memory actualSettings = factory.getSettings(); + assertEq(address(actualSettings.tokenAddress), address(creationSettings.tokenAddress), "Incorrect tokenAddress"); + assertEq( + actualSettings.taikoL1ContractAddress, + creationSettings.taikoL1ContractAddress, + "Incorrect taikoL1ContractAddress" + ); + assertEq(actualSettings.taikoBridgeAddress, creationSettings.taikoBridgeAddress, "Incorrect taikoBridgeAddress"); + assertEq(actualSettings.l2InactivityPeriod, creationSettings.l2InactivityPeriod, "Incorrect l2InactivityPeriod"); + assertEq( + actualSettings.l2AggregationGracePeriod, + creationSettings.l2AggregationGracePeriod, + "Incorrect l2AggregationGracePeriod" + ); + assertEq(actualSettings.skipL2, creationSettings.skipL2, "Incorrect skipL2"); assertEq(actualSettings.minVetoRatio, creationSettings.minVetoRatio, "Incorrect minVetoRatio"); assertEq( - actualSettings.minStdProposalDelay, creationSettings.minStdProposalDelay, "Incorrect minStdProposalDelay" + actualSettings.minStdProposalDuration, + creationSettings.minStdProposalDuration, + "Incorrect minStdProposalDuration" ); assertEq(actualSettings.minStdApprovals, creationSettings.minStdApprovals, "Incorrect minStdApprovals"); assertEq( @@ -114,6 +221,9 @@ contract TaikoDaoFactoryTest is AragonTest { creationSettings.minEmergencyApprovals, "Incorrect minEmergencyApprovals" ); + assertEq( + address(actualSettings.osxDaoFactory), address(creationSettings.osxDaoFactory), "Incorrect osxDaoFactory" + ); assertEq( address(actualSettings.pluginSetupProcessor), address(creationSettings.pluginSetupProcessor), @@ -125,14 +235,19 @@ contract TaikoDaoFactoryTest is AragonTest { "Incorrect pluginRepoFactory" ); assertEq( - address(actualSettings.governanceErc20Base), - address(creationSettings.governanceErc20Base), - "Incorrect governanceErc20Base" + address(actualSettings.multisigPluginSetup), + address(creationSettings.multisigPluginSetup), + "Incorrect multisigPluginSetup" ); assertEq( - address(actualSettings.governanceErcWrapped20Base), - address(creationSettings.governanceErcWrapped20Base), - "Incorrect governanceErcWrapped20Base" + address(actualSettings.emergencyMultisigPluginSetup), + address(creationSettings.emergencyMultisigPluginSetup), + "Incorrect emergencyMultisigPluginSetup" + ); + assertEq( + address(actualSettings.optimisticTokenVotingPluginSetup), + address(creationSettings.optimisticTokenVotingPluginSetup), + "Incorrect optimisticTokenVotingPluginSetup" ); assertEq( actualSettings.multisigMembers.length, @@ -154,55 +269,73 @@ contract TaikoDaoFactoryTest is AragonTest { ); } - // function test_StandardDeployment() public { - // DAO tempMgmtDao = new DAO(); - // tempMgmtDao.initialize("", address(this), address(0), ""); - - // GovernanceERC20Mock tokenAddress = new GovernanceERC20Mock(address(tempMgmtDao)); - // TaikoL1Mock taikoL1ContractAddress = new TaikoL1Mock(); - // address taikoBridgeAddress = address(0x1234); - // address[] memory multisigMembers = new address[](13); - // PluginRepoFactory pRefoFactory; - // PluginSetupProcessor psp; - // { - // PluginRepoRegistry pRepoRegistry = new PluginRepoRegistry(); - // ENSSubdomainRegistrar ensSubdomainReg = new ENSSubdomainRegistrar(); - // ENSRegistry registry = new ENSRegistry(); - // ensSubdomainReg.initialize(tempMgmtDao, registry, bytes32(uint256(0x1234))); - // pRepoRegistry.initialize(tempMgmtDao, ensSubdomainReg); - // pRefoFactory = new PluginRepoFactory(pRepoRegistry); - // psp = new PluginSetupProcessor(pRepoRegistry); - // } - - // TaikoDaoFactory.DeploymentSettings memory settings = TaikoDaoFactory.DeploymentSettings({ - // // Taiko contract settings - // tokenAddress: tokenAddress, - // taikoL1ContractAddress: address(taikoL1ContractAddress), // address - // taikoBridgeAddress: taikoBridgeAddress, // address - // l2InactivityPeriod: 10 minutes, // uint64 - // l2AggregationGracePeriod: 2 days, // uint64 - // // Voting settings - // minVetoRatio: 200_000, // uint32 - // minStdProposalDelay: 10 days, // uint64 - // minStdApprovals: 7, // uint16 - // minEmergencyApprovals: 11, // uint16 - // // OSx contracts - // pluginSetupProcessor: psp, // PluginSetupProcessor - // pluginRepoFactory: pRefoFactory, // PluginRepoFactory - // // Token contracts - // governanceErc20Base: new GovernanceERC20( - // tempMgmtDao, "", "", GovernanceERC20.MintSettings(new address[](0), new uint256[](0)) - // ), // GovernanceERC20 - // governanceErcWrapped20Base: new GovernanceWrappedERC20(tokenAddress, "", ""), // GovernanceWrappedERC20 - // // Multisig - // multisigMembers: multisigMembers, // address[] - // // ENS - // stdMultisigEnsDomain: "multisig", // string - // emergencyMultisigEnsDomain: "eMultisig", // string - // optimisticTokenVotingEnsDomain: "optimistic" // string - // }); - - // TaikoDaoFactory factory = new TaikoDaoFactory(settings); - // factory.getDeployment(); - // } + function test_StandardDeployment() public { + DAO tempMgmtDao = DAO( + payable( + createProxyAndCall( + address(DAO_BASE), abi.encodeCall(DAO.initialize, ("", address(this), address(0x0), "")) + ) + ) + ); + + GovernanceERC20Mock tokenAddress = new GovernanceERC20Mock(address(tempMgmtDao)); + TaikoL1Mock taikoL1ContractAddress = new TaikoL1Mock(); + address taikoBridgeAddress = address(0x1234); + address[] memory multisigMembers = new address[](13); + + MultisigPluginSetup multisigPluginSetup = new MultisigPluginSetup(); + EmergencyMultisigPluginSetup emergencyMultisigPluginSetup = new EmergencyMultisigPluginSetup(); + OptimisticTokenVotingPluginSetup optimisticTokenVotingPluginSetup = + new OptimisticTokenVotingPluginSetup(GovernanceERC20(address(0)), GovernanceWrappedERC20(address(0))); + + PluginRepoFactory pRefoFactory; + MockPluginSetupProcessor psp; + { + MockPluginRepoRegistry pRepoRegistry = new MockPluginRepoRegistry(); + pRefoFactory = new PluginRepoFactory(PluginRepoRegistry(address(pRepoRegistry))); + + address[] memory setups = new address[](3); + setups[0] = address(multisigPluginSetup); + setups[1] = address(emergencyMultisigPluginSetup); + setups[2] = address(optimisticTokenVotingPluginSetup); + psp = new MockPluginSetupProcessor(setups); + } + MockDaoFactory daoFactory = new MockDaoFactory(psp); + + TaikoDaoFactory.DeploymentSettings memory creationSettings = TaikoDaoFactory.DeploymentSettings({ + // Taiko contract settings + tokenAddress: tokenAddress, + taikoL1ContractAddress: address(taikoL1ContractAddress), // address + taikoBridgeAddress: taikoBridgeAddress, // 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, + optimisticTokenVotingPluginSetup: optimisticTokenVotingPluginSetup, + // Multisig + multisigMembers: multisigMembers, // address[] + // ENS + stdMultisigEnsDomain: "multisig", // string + emergencyMultisigEnsDomain: "eMultisig", // string + optimisticTokenVotingEnsDomain: "optimistic" // string + }); + + TaikoDaoFactory factory = new TaikoDaoFactory(creationSettings); + factory.deployOnce(); + + TaikoDaoFactory.Deployment memory deployment = factory.getDeployment(); + + // TODO: + } } diff --git a/test/mocks/osx/MockDaoFactory.sol b/test/mocks/osx/MockDaoFactory.sol new file mode 100644 index 0000000..c623d08 --- /dev/null +++ b/test/mocks/osx/MockDaoFactory.sol @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.8; + +import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; + +import {DAO} from "@aragon/osx/core/dao/DAO.sol"; +import {IDAO} from "@aragon/osx/core/dao/IDAO.sol"; +import {IProtocolVersion} from "@aragon/osx/utils/protocol/IProtocolVersion.sol"; +import {ProtocolVersion} from "@aragon/osx/utils/protocol/ProtocolVersion.sol"; +import {IPluginSetup} from "@aragon/osx/framework/plugin/setup/IPluginSetup.sol"; +import {PermissionLib} from "@aragon/osx/core/permission/PermissionLib.sol"; +import {createProxyAndCall} from "../../../src/helpers/proxy.sol"; +import {hashHelpers, PluginSetupRef} from "@aragon/osx/framework/plugin/setup/PluginSetupProcessorHelpers.sol"; +import {MockPluginSetupProcessor as PluginSetupProcessor} from "./MockPSP.sol"; + +// import {DAORegistry} from "./DAORegistry.sol"; + +/// @title MockDAOFactory +/// @dev spoof of DAOFactory with minimal dependencies +/// Best to use with MockPSP: this allows setting of a single setup contract manually to avoid +/// all the other logic, however means you cannot easily deploy the DAO with multiple plugins +contract MockDaoFactory { + // using ProxyLib for address; + + /// @notice The DAO base contract, to be used for creating new `DAO`s via `createERC1967Proxy` function. + address public immutable daoBase; + + /// @notice The DAO registry listing the `DAO` contracts created via this contract. + // DAORegistry public immutable daoRegistry; + + /// @notice The plugin setup processor for installing plugins on the newly created `DAO`s. + PluginSetupProcessor public immutable pluginSetupProcessor; + + /// @notice The container for the DAO settings to be set during the DAO initialization. + /// @param trustedForwarder The address of the trusted forwarder required for meta transactions. + /// @param daoURI The DAO uri used with [EIP-4824](https://eips.ethereum.org/EIPS/eip-4824). + /// @param subdomain The ENS subdomain to be registered for the DAO contract. + /// @param metadata The metadata of the DAO. + struct DAOSettings { + address trustedForwarder; + string daoURI; + string subdomain; + bytes metadata; + } + + /// @notice The container with the information required to install a plugin on the DAO. + /// @param pluginSetupRef The `PluginSetupRepo` address of the plugin and the version tag. + /// @param data The bytes-encoded data containing the input parameters for the installation as specified in the plugin's build metadata JSON file. + struct PluginSettings { + PluginSetupRef pluginSetupRef; + bytes data; + } + + /// @notice Thrown if `PluginSettings` array is empty, and no plugin is provided. + error NoPluginProvided(); + + /// @notice lifted from dao registry + event DAORegistered(address indexed dao, address indexed creator, string subdomain); + + /// @notice The constructor setting the registry and plugin setup processor and creating the base contracts for the factory. + // / @param _registry The DAO registry to register the DAO by its name. + /// @param _pluginSetupProcessor The address of PluginSetupProcessor. + constructor(PluginSetupProcessor _pluginSetupProcessor) { + pluginSetupProcessor = _pluginSetupProcessor; + + daoBase = address(new DAO()); + } + + // /// @notice Checks if this or the parent contract supports an interface by its ID. + // /// @param _interfaceId The ID of the interface. + // /// @return Returns `true` if the interface is supported. + // function supportsInterface(bytes4 _interfaceId) public view virtual override returns (bool) { + // return + // _interfaceId == type(IProtocolVersion).interfaceId || + // super.supportsInterface(_interfaceId); + // } + + /// @notice Creates a new DAO, registers it on the DAO registry, and installs a list of plugins via the plugin setup processor. + /// @param _daoSettings The DAO settings to be set during the DAO initialization. + /// @param _pluginSettings The array containing references to plugins and their settings to be installed after the DAO has been created. + function createDao(DAOSettings calldata _daoSettings, PluginSettings[] calldata _pluginSettings) + external + returns (DAO createdDao) + { + // Check if no plugin is provided. + if (_pluginSettings.length == 0) { + revert NoPluginProvided(); + } + + // Create DAO. + createdDao = _createDAO(_daoSettings); + + // MOCK: SKIP + // // Register DAO. + // daoRegistry.register(createdDao, msg.sender, _daoSettings.subdomain); + emit DAORegistered(address(createdDao), msg.sender, _daoSettings.subdomain); + + // Get Permission IDs + bytes32 rootPermissionID = createdDao.ROOT_PERMISSION_ID(); + bytes32 applyInstallationPermissionID = pluginSetupProcessor.APPLY_INSTALLATION_PERMISSION_ID(); + + // Grant the temporary permissions. + // Grant Temporarly `ROOT_PERMISSION` to `pluginSetupProcessor`. + createdDao.grant(address(createdDao), address(pluginSetupProcessor), rootPermissionID); + + // Grant Temporarly `APPLY_INSTALLATION_PERMISSION` on `pluginSetupProcessor` to this `DAOFactory`. + createdDao.grant(address(pluginSetupProcessor), address(this), applyInstallationPermissionID); + + // Install plugins on the newly created DAO. + for (uint256 i; i < _pluginSettings.length; ++i) { + // Prepare plugin. + (address plugin, IPluginSetup.PreparedSetupData memory preparedSetupData) = pluginSetupProcessor + .prepareInstallation( + address(createdDao), + PluginSetupProcessor.PrepareInstallationParams( + _pluginSettings[i].pluginSetupRef, _pluginSettings[i].data + ) + ); + + // Apply plugin. + pluginSetupProcessor.applyInstallation( + address(createdDao), + PluginSetupProcessor.ApplyInstallationParams( + _pluginSettings[i].pluginSetupRef, + plugin, + preparedSetupData.permissions, + hashHelpers(preparedSetupData.helpers) + ) + ); + } + + // Set the rest of DAO's permissions. + _setDAOPermissions(createdDao); + + // Revoke the temporarly granted permissions. + // Revoke Temporarly `ROOT_PERMISSION` from `pluginSetupProcessor`. + createdDao.revoke(address(createdDao), address(pluginSetupProcessor), rootPermissionID); + + // Revoke `APPLY_INSTALLATION_PERMISSION` on `pluginSetupProcessor` from this `DAOFactory` . + createdDao.revoke(address(pluginSetupProcessor), address(this), applyInstallationPermissionID); + + // Revoke Temporarly `ROOT_PERMISSION_ID` from `pluginSetupProcessor` that implicitly granted to this `DaoFactory` + // at the create dao step `address(this)` being the initial owner of the new created DAO. + createdDao.revoke(address(createdDao), address(this), rootPermissionID); + } + + /// @notice Deploys a new DAO `ERC1967` proxy, and initialize it with this contract as the intial owner. + function _createDAO(DAOSettings calldata) internal returns (DAO dao) { + dao = DAO( + payable( + createProxyAndCall( + address(daoBase), abi.encodeCall(DAO.initialize, ("", address(this), address(0x0), "")) + ) + ) + ); + } + + /// @notice Sets the required permissions for the new DAO. + /// @param _dao The DAO instance just created. + function _setDAOPermissions(DAO _dao) internal { + // set permissionIds on the dao itself. + PermissionLib.SingleTargetPermission[] memory items = new PermissionLib.SingleTargetPermission[](5); + + // Grant DAO all the permissions required + 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.SET_TRUSTED_FORWARDER_PERMISSION_ID() + ); + items[3] = PermissionLib.SingleTargetPermission( + PermissionLib.Operation.Grant, address(_dao), _dao.SET_METADATA_PERMISSION_ID() + ); + items[4] = PermissionLib.SingleTargetPermission( + PermissionLib.Operation.Grant, address(_dao), _dao.REGISTER_STANDARD_CALLBACK_PERMISSION_ID() + ); + + _dao.applySingleTargetPermissions(address(_dao), items); + } +} diff --git a/test/mocks/osx/MockPSP.sol b/test/mocks/osx/MockPSP.sol new file mode 100644 index 0000000..534e102 --- /dev/null +++ b/test/mocks/osx/MockPSP.sol @@ -0,0 +1,706 @@ +// 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 {PluginRepoRegistry} from "@aragon/osx/framework/plugin/repo/PluginRepoRegistry.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 MockPluginSetupProcessor { + 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 + }); + } +} diff --git a/test/mocks/osx/MockPluginRepoRegistry.sol b/test/mocks/osx/MockPluginRepoRegistry.sol new file mode 100644 index 0000000..5425ed9 --- /dev/null +++ b/test/mocks/osx/MockPluginRepoRegistry.sol @@ -0,0 +1,65 @@ +// 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 1ad5ea6d89c0f4e0297da0bfef78012c925b4a60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Fri, 2 Aug 2024 18:41:10 +0200 Subject: [PATCH 3/3] DAO factory tested --- test/integration/TaikoDaoFactory.t.sol | 495 ++++++++++++++++++++++++- 1 file changed, 488 insertions(+), 7 deletions(-) diff --git a/test/integration/TaikoDaoFactory.t.sol b/test/integration/TaikoDaoFactory.t.sol index 1eb8daa..a8558e8 100644 --- a/test/integration/TaikoDaoFactory.t.sol +++ b/test/integration/TaikoDaoFactory.t.sol @@ -11,10 +11,13 @@ import {TaikoL1Mock} from "../mocks/TaikoL1Mock.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 {createProxyAndCall} from "../../src/helpers/proxy.sol"; import {MultisigPluginSetup} from "../../src/setup/MultisigPluginSetup.sol"; import {EmergencyMultisigPluginSetup} from "../../src/setup/EmergencyMultisigPluginSetup.sol"; @@ -150,7 +153,7 @@ contract TaikoDaoFactoryTest is AragonTest { GovernanceERC20Mock tokenAddress = new GovernanceERC20Mock(address(tempMgmtDao)); TaikoL1Mock taikoL1ContractAddress = new TaikoL1Mock(); address taikoBridgeAddress = address(0x1234); - address[] memory multisigMembers = new address[](20); + address[] memory multisigMembers = new address[](15); MultisigPluginSetup multisigPluginSetup = new MultisigPluginSetup(); EmergencyMultisigPluginSetup emergencyMultisigPluginSetup = new EmergencyMultisigPluginSetup(); @@ -269,7 +272,7 @@ contract TaikoDaoFactoryTest is AragonTest { ); } - function test_StandardDeployment() public { + function test_StandardDeployment_1() public { DAO tempMgmtDao = DAO( payable( createProxyAndCall( @@ -282,11 +285,17 @@ contract TaikoDaoFactoryTest is AragonTest { TaikoL1Mock taikoL1ContractAddress = new TaikoL1Mock(); address taikoBridgeAddress = 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(); - OptimisticTokenVotingPluginSetup optimisticTokenVotingPluginSetup = - new OptimisticTokenVotingPluginSetup(GovernanceERC20(address(0)), GovernanceWrappedERC20(address(0))); + GovernanceERC20.MintSettings memory mintSettings = + GovernanceERC20.MintSettings({receivers: new address[](0), amounts: new uint256[](0)}); + OptimisticTokenVotingPluginSetup optimisticTokenVotingPluginSetup = new OptimisticTokenVotingPluginSetup( + new GovernanceERC20(tempMgmtDao, "", "", mintSettings), new GovernanceWrappedERC20(tokenAddress, "", "") + ); PluginRepoFactory pRefoFactory; MockPluginSetupProcessor psp; @@ -295,9 +304,10 @@ contract TaikoDaoFactoryTest is AragonTest { pRefoFactory = new PluginRepoFactory(PluginRepoRegistry(address(pRepoRegistry))); address[] memory setups = new address[](3); - setups[0] = address(multisigPluginSetup); + // adding in reverse order (stack) + setups[2] = address(multisigPluginSetup); setups[1] = address(emergencyMultisigPluginSetup); - setups[2] = address(optimisticTokenVotingPluginSetup); + setups[0] = address(optimisticTokenVotingPluginSetup); psp = new MockPluginSetupProcessor(setups); } MockDaoFactory daoFactory = new MockDaoFactory(psp); @@ -331,11 +341,482 @@ contract TaikoDaoFactoryTest is AragonTest { optimisticTokenVotingEnsDomain: "optimistic" // string }); + // Deploy TaikoDaoFactory factory = new TaikoDaoFactory(creationSettings); + factory.deployOnce(); + TaikoDaoFactory.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, uint64 destinationProposalDuration) = + deployment.multisigPlugin.multisigSettings(); + + assertEq(onlyListed, true, "Invalid onlyListed"); + assertEq(minApprovals, 7, "Invalid minApprovals"); + assertEq(destinationProposalDuration, 10 days, "Invalid destinationProposalDuration"); + } + + // 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 < 13; i++) { + assertEq(deployment.emergencyMultisigPlugin.isMember(multisigMembers[i]), true, "Should be a member"); + } + for (uint256 i = 14; i < 50; i++) { + assertEq(deployment.emergencyMultisigPlugin.isMember(address(uint160(i))), false, "Should not be a member"); + } + { + (bool onlyListed, uint16 minApprovals, Addresslist addresslistSource) = + deployment.emergencyMultisigPlugin.multisigSettings(); + + assertEq(onlyListed, true, "Invalid onlyListed"); + assertEq(minApprovals, 11, "Invalid minApprovals"); + assertEq(address(addresslistSource), address(deployment.multisigPlugin), "Invalid addresslistSource"); + } + + // 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.taikoL1()), + address(taikoL1ContractAddress), + "Invalid taikoL1" + ); + assertEq( + address(deployment.optimisticTokenVotingPlugin.taikoBridge()), + address(taikoBridgeAddress), + "Invalid taikoBridge" + ); + 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 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(optimisticTokenVotingPluginSetup), + "Invalid optimisticTokenVotingPluginSetup" + ); + + // PUBLIC KEY REGISTRY + assertNotEq(address(deployment.publicKeyRegistry), address(0), "Empty publicKeyRegistry field"); + assertEq(deployment.publicKeyRegistry.registeredWalletCount(), 0, "Invalid registeredWalletCount"); + } + 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)); + TaikoL1Mock taikoL1ContractAddress = new TaikoL1Mock(); + address taikoBridgeAddress = 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 optimisticTokenVotingPluginSetup = 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(optimisticTokenVotingPluginSetup); + psp = new MockPluginSetupProcessor(setups); + } + MockDaoFactory daoFactory = new MockDaoFactory(psp); + + TaikoDaoFactory.DeploymentSettings memory creationSettings = TaikoDaoFactory.DeploymentSettings({ + // Taiko contract settings + tokenAddress: tokenAddress, + taikoL1ContractAddress: address(taikoL1ContractAddress), // address + taikoBridgeAddress: taikoBridgeAddress, // 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, + optimisticTokenVotingPluginSetup: optimisticTokenVotingPluginSetup, + // Multisig + multisigMembers: multisigMembers, // address[] + // ENS + stdMultisigEnsDomain: "multisig", // string + emergencyMultisigEnsDomain: "eMultisig", // string + optimisticTokenVotingEnsDomain: "optimistic" // string + }); + + // Deploy + TaikoDaoFactory factory = new TaikoDaoFactory(creationSettings); + + factory.deployOnce(); TaikoDaoFactory.Deployment memory deployment = factory.getDeployment(); - // TODO: + 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) = + deployment.multisigPlugin.multisigSettings(); + + assertEq(onlyListed, true, "Invalid onlyListed"); + assertEq(minApprovals, 9, "Invalid minApprovals"); + assertEq(destinationProposalDuration, 21 days, "Invalid destinationProposalDuration"); + } + + // 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) = + deployment.emergencyMultisigPlugin.multisigSettings(); + + assertEq(onlyListed, true, "Invalid onlyListed"); + assertEq(minApprovals, 15, "Invalid minApprovals"); + assertEq(address(addresslistSource), address(deployment.multisigPlugin), "Invalid addresslistSource"); + } + + // 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.taikoL1()), + address(taikoL1ContractAddress), + "Invalid taikoL1" + ); + assertEq( + address(deployment.optimisticTokenVotingPlugin.taikoBridge()), + address(taikoBridgeAddress), + "Invalid taikoBridge" + ); + 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(optimisticTokenVotingPluginSetup), + "Invalid optimisticTokenVotingPluginSetup" + ); + + // 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)); + TaikoL1Mock taikoL1ContractAddress = new TaikoL1Mock(); + address taikoBridgeAddress = 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 optimisticTokenVotingPluginSetup = 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(optimisticTokenVotingPluginSetup); + psp = new MockPluginSetupProcessor(setups); + } + MockDaoFactory daoFactory = new MockDaoFactory(psp); + + TaikoDaoFactory.DeploymentSettings memory creationSettings = TaikoDaoFactory.DeploymentSettings({ + // Taiko contract settings + tokenAddress: tokenAddress, + taikoL1ContractAddress: address(taikoL1ContractAddress), // address + taikoBridgeAddress: taikoBridgeAddress, // 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, + optimisticTokenVotingPluginSetup: optimisticTokenVotingPluginSetup, + // Multisig + multisigMembers: multisigMembers, // address[] + // ENS + stdMultisigEnsDomain: "multisig", // string + emergencyMultisigEnsDomain: "eMultisig", // string + optimisticTokenVotingEnsDomain: "optimistic" // string + }); + + TaikoDaoFactory factory = new TaikoDaoFactory(creationSettings); + // ok + factory.deployOnce(); + + vm.expectRevert(abi.encodeWithSelector(TaikoDaoFactory.AlreadyDeployed.selector)); + factory.deployOnce(); + + vm.expectRevert(abi.encodeWithSelector(TaikoDaoFactory.AlreadyDeployed.selector)); + factory.deployOnce(); } }