SubDaoFactory

Description:

Multi-signature wallet contract requiring multiple confirmations for transaction execution.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "src/SubDaoFactory.sol": {
      "content": "// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.17;

import { DAO } from "@aragon/osx/core/dao/DAO.sol";
import { DAOFactory } from "@aragon/osx/framework/dao/DAOFactory.sol";
import { PluginSetupProcessor } from "@aragon/osx/framework/plugin/setup/PluginSetupProcessor.sol";
import { PluginRepoFactory } from "@aragon/osx/framework/plugin/repo/PluginRepoFactory.sol";
import { PluginRepo } from "@aragon/osx/framework/plugin/repo/PluginRepo.sol";
import { PermissionManager } from "@aragon/osx/core/permission/PermissionManager.sol";
import { PermissionLib } from "@aragon/osx-commons-contracts/src/permission/PermissionLib.sol";
import { IPermissionCondition } from "@aragon/osx-commons-contracts/src/permission/condition/IPermissionCondition.sol";
import { Action } from "@aragon/osx-commons-contracts/src/executors/IExecutor.sol";
import { IPluginSetup } from "@aragon/osx-commons-contracts/src/plugin/setup/IPluginSetup.sol";
import { hashHelpers, PluginSetupRef } from "@aragon/osx/framework/plugin/setup/PluginSetupProcessorHelpers.sol";
import { IPlugin } from "@aragon/osx-commons-contracts/src/plugin/IPlugin.sol";

import { TokenVotingSetupHats } from "@token-voting-hats/TokenVotingSetupHats.sol";
import { TokenVotingHats } from "@token-voting-hats/TokenVotingHats.sol";
import { AdminSetup } from "@admin-plugin/AdminSetup.sol";
import { Admin } from "@admin-plugin/Admin.sol";
import { MajorityVotingBase } from "@token-voting-hats/base/MajorityVotingBase.sol";
import { GovernanceERC20 } from "@token-voting-hats/erc20/GovernanceERC20.sol";
import { StagedProposalProcessor } from "staged-proposal-processor-plugin/StagedProposalProcessor.sol";
import { RuledCondition } from "@aragon/osx-commons-contracts/src/permission/condition/extensions/RuledCondition.sol";

/// @notice DAO configuration parameters
struct DaoConfig {
  string metadataUri;
  string subdomain;
}

/// @notice Admin plugin configuration
struct AdminPluginConfig {
  address adminAddress;
}

/// @notice Stage 1 configuration (veto or approve mode)
struct Stage1Config {
  string mode; // "veto" or "approve"
  uint256 proposerHatId; // If 0, use direct grant to controllerAddress. Otherwise use HatsCondition.
  address controllerAddress; // Proposer in veto mode, approver in approve mode
  uint48 minAdvance;
  uint48 maxAdvance;
  uint48 voteDuration;
}

/// @notice Token voting hats plugin configuration for Stage 2
struct TokenVotingHatsPluginConfig {
  MajorityVotingBase.VotingMode votingMode;
  uint32 supportThreshold;
  uint32 minParticipation;
  uint64 minDuration;
  uint256 minProposerVotingPower;
  uint256 proposerHatId;
  uint256 voterHatId;
  uint256 executorHatId;
}

/// @notice Stage 2 configuration (veto stage)
struct Stage2Config {
  TokenVotingHatsPluginConfig tokenVotingHats;
  uint48 minAdvance;
  uint48 maxAdvance;
  uint48 voteDuration;
}

/// @notice SPP plugin configuration
struct SppPluginConfig {
  uint8 release;
  uint16 build;
  bool useExisting;
  address repositoryAddress;
  string metadata;
}

/// @notice The struct containing all the parameters to deploy the subDAO
struct DeploymentParameters {
  // Configuration structs
  DaoConfig dao;
  AdminPluginConfig adminPlugin;
  Stage1Config stage1;
  Stage2Config stage2;
  SppPluginConfig sppPlugin;

  // Main DAO address (to grant ROOT_PERMISSION for plugin management)
  address mainDaoAddress;

  // IVotesAdapter address (queried from main DAO factory in deployment script)
  address ivotesAdapter;

  // Plugin setup contracts (must be deployed first)
  TokenVotingSetupHats tokenVotingSetup;
  PluginRepo tokenVotingPluginRepo;
  AdminSetup adminSetup;
  PluginRepo adminPluginRepo;
  address sppPluginSetup;
  PluginRepo sppPluginRepo;

  // Plugin repo version info
  uint8 tokenVotingPluginRepoRelease;
  uint16 tokenVotingPluginRepoBuild;

  // OSx framework addresses (chain-specific)
  address osxDaoFactory;
  PluginSetupProcessor pluginSetupProcessor;
  PluginRepoFactory pluginRepoFactory;
}

/// @notice Contains the artifacts that resulted from running a deployment
struct Deployment {
  DAO dao;
  Admin adminPlugin;
  PluginRepo adminPluginRepo;
  TokenVotingHats tokenVotingPlugin;
  PluginRepo tokenVotingPluginRepo;
  address sppPlugin;
  PluginRepo sppPluginRepo;
  address hatsCondition; // HatsCondition from TokenVotingHats, used for SPP permissions
}

/**
 * @title SubDaoFactory
 * @notice Generic factory for deploying SubDAOs that share main DAO infrastructure
 * @dev Can be used for any SubDAO type (approver-hat-minter, member-curator, etc.)
 * @dev A singleton contract designed to run the deployment once and become a read-only store of the contracts deployed
 */
contract SubDaoFactory {
  address public immutable deployer;

  function version() external pure returns (string memory) {
    return "1.0.0";
  }

  error AlreadyDeployed();
  error Unauthorized();
  error InvalidMainDaoAddress();
  error InvalidIVotesAdapterAddress();
  error InvalidControllerAddress();
  error InvalidStage1Mode();

  DeploymentParameters parameters;
  Deployment deployment;

  constructor(DeploymentParameters memory _parameters) {
    deployer = msg.sender;

    parameters.dao = _parameters.dao;
    parameters.adminPlugin = _parameters.adminPlugin;
    parameters.stage1 = _parameters.stage1;
    parameters.stage2 = _parameters.stage2;
    parameters.sppPlugin = _parameters.sppPlugin;
    parameters.mainDaoAddress = _parameters.mainDaoAddress;
    parameters.ivotesAdapter = _parameters.ivotesAdapter;
    parameters.tokenVotingSetup = _parameters.tokenVotingSetup;
    parameters.tokenVotingPluginRepo = _parameters.tokenVotingPluginRepo;
    parameters.adminSetup = _parameters.adminSetup;
    parameters.adminPluginRepo = _parameters.adminPluginRepo;
    parameters.sppPluginSetup = _parameters.sppPluginSetup;
    parameters.sppPluginRepo = _parameters.sppPluginRepo;
    parameters.tokenVotingPluginRepoRelease = _parameters.tokenVotingPluginRepoRelease;
    parameters.tokenVotingPluginRepoBuild = _parameters.tokenVotingPluginRepoBuild;
    parameters.osxDaoFactory = _parameters.osxDaoFactory;
    parameters.pluginSetupProcessor = _parameters.pluginSetupProcessor;
    parameters.pluginRepoFactory = _parameters.pluginRepoFactory;
  }

  function deployOnce() public {
    if (msg.sender != deployer) revert Unauthorized();
    if (address(deployment.dao) != address(0)) revert AlreadyDeployed();

    // Validate main DAO address
    if (parameters.mainDaoAddress == address(0)) revert InvalidMainDaoAddress();

    DAO dao = _prepareDao();
    deployment.dao = dao;

    _grantApplyInstallationPermissions(dao);

    // Install Admin plugin
    (Admin adminPlugin, PluginRepo adminRepo) = _installAdminPlugin(dao);
    deployment.adminPlugin = adminPlugin;
    deployment.adminPluginRepo = adminRepo;

    // Use IVotesAdapter from parameters (queried in deployment script)
    if (parameters.ivotesAdapter == address(0)) revert InvalidIVotesAdapterAddress();

    // Validate Stage 1 configuration
    if (parameters.stage1.controllerAddress == address(0)) revert InvalidControllerAddress();
    bytes32 modeHash = keccak256(bytes(parameters.stage1.mode));
    if (modeHash != keccak256(bytes("veto")) && modeHash != keccak256(bytes("approve"))) {
      revert InvalidStage1Mode();
    }

    // Install TokenVotingHats plugin (for Stage 2)
    (TokenVotingHats tvPlugin, PluginRepo tvRepo) = _installTokenVotingHats(dao, parameters.ivotesAdapter);
    deployment.tokenVotingPlugin = tvPlugin;
    deployment.tokenVotingPluginRepo = tvRepo;

    // Install SPP plugin with 2 stages
    (address sppPlugin, PluginRepo sppRepo) = _installSppPlugin(dao, address(tvPlugin));
    deployment.sppPlugin = sppPlugin;
    deployment.sppPluginRepo = sppRepo;

    // Grant main DAO ROOT_PERMISSION on SubDAO for plugin management
    _grantMainDaoPermissions(dao);

    _revokeApplyInstallationPermissions(dao);
    _revokeOwnerPermission(dao);
  }

  function _prepareDao() internal returns (DAO dao) {
    (dao,) = DAOFactory(parameters.osxDaoFactory)
      .createDao(
        DAOFactory.DAOSettings({
          trustedForwarder: address(0),
          daoURI: "",
          subdomain: parameters.dao.subdomain,
          metadata: bytes(parameters.dao.metadataUri)
        }),
        new DAOFactory.PluginSettings[](0)
      );

    // Grant ROOT_PERMISSION to this factory so it can install plugins
    Action[] memory actions = new Action[](1);
    actions[0].to = address(dao);
    actions[0].data = abi.encodeCall(PermissionManager.grant, (address(dao), address(this), dao.ROOT_PERMISSION_ID()));

    dao.execute(bytes32(0), actions, 0);
  }

  function _installAdminPlugin(DAO dao) internal returns (Admin plugin, PluginRepo pluginRepo) {
    pluginRepo = parameters.adminPluginRepo;

    // Use release 1, build 2 (latest admin plugin version)
    PluginRepo.Tag memory repoTag = PluginRepo.Tag(1, 2);

    // Encode installation parameters: admin address and target config
    bytes memory installData = abi.encode(
      parameters.adminPlugin.adminAddress,
      IPlugin.TargetConfig({ target: address(dao), operation: IPlugin.Operation.Call })
    );

    (address pluginAddress, IPluginSetup.PreparedSetupData memory preparedSetupData) = parameters.pluginSetupProcessor
      .prepareInstallation(
        address(dao),
        PluginSetupProcessor.PrepareInstallationParams({
          pluginSetupRef: PluginSetupRef(repoTag, pluginRepo), data: installData
        })
      );

    parameters.pluginSetupProcessor
      .applyInstallation(
        address(dao),
        PluginSetupProcessor.ApplyInstallationParams({
          pluginSetupRef: PluginSetupRef(repoTag, pluginRepo),
          plugin: pluginAddress,
          permissions: preparedSetupData.permissions,
          helpersHash: hashHelpers(preparedSetupData.helpers)
        })
      );

    plugin = Admin(pluginAddress);
  }

  function _installTokenVotingHats(DAO dao, address ivotesAdapter)
    internal
    returns (TokenVotingHats plugin, PluginRepo pluginRepo)
  {
    pluginRepo = parameters.tokenVotingPluginRepo;
    if (address(pluginRepo) == address(0)) {
      pluginRepo = PluginRepoFactory(parameters.pluginRepoFactory)
        .createPluginRepoWithFirstVersion(
          "token-voting-hats-subdao", address(parameters.tokenVotingSetup), address(dao), " ", " "
        );
    }

    PluginRepo.Tag memory repoTag =
      PluginRepo.Tag(parameters.tokenVotingPluginRepoRelease, parameters.tokenVotingPluginRepoBuild);

    bytes memory installData = parameters.tokenVotingSetup
      .encodeInstallationParametersHats(
        MajorityVotingBase.VotingSettings({
          votingMode: parameters.stage2.tokenVotingHats.votingMode,
          supportThreshold: parameters.stage2.tokenVotingHats.supportThreshold,
          minParticipation: parameters.stage2.tokenVotingHats.minParticipation,
          minDuration: parameters.stage2.tokenVotingHats.minDuration,
          minProposerVotingPower: parameters.stage2.tokenVotingHats.minProposerVotingPower
        }),
        TokenVotingSetupHats.TokenSettings({ addr: ivotesAdapter, name: "", symbol: "" }),
        GovernanceERC20.MintSettings({
          receivers: new address[](0), amounts: new uint256[](0), ensureDelegationOnMint: false
        }),
        IPlugin.TargetConfig({ target: address(dao), operation: IPlugin.Operation.Call }),
        0,
        bytes(""),
        new address[](0),
        TokenVotingSetupHats.HatsConfig({
          proposerHatId: parameters.stage2.tokenVotingHats.proposerHatId,
          voterHatId: parameters.stage2.tokenVotingHats.voterHatId,
          executorHatId: parameters.stage2.tokenVotingHats.executorHatId
        })
      );

    (address pluginAddress, IPluginSetup.PreparedSetupData memory preparedSetupData) = parameters.pluginSetupProcessor
      .prepareInstallation(
        address(dao),
        PluginSetupProcessor.PrepareInstallationParams({
          pluginSetupRef: PluginSetupRef(repoTag, pluginRepo), data: installData
        })
      );

    parameters.pluginSetupProcessor
      .applyInstallation(
        address(dao),
        PluginSetupProcessor.ApplyInstallationParams({
          pluginSetupRef: PluginSetupRef(repoTag, pluginRepo),
          plugin: pluginAddress,
          permissions: preparedSetupData.permissions,
          helpersHash: hashHelpers(preparedSetupData.helpers)
        })
      );

    plugin = TokenVotingHats(pluginAddress);

    // Store HatsCondition (helpers[0]) for use in SPP permission grants
    if (preparedSetupData.helpers.length > 0) {
      deployment.hatsCondition = preparedSetupData.helpers[0];
    }
  }

  function _installSppPlugin(DAO dao, address tokenVotingHatsPlugin)
    internal
    returns (address sppPlugin, PluginRepo sppPluginRepo)
  {
    sppPluginRepo = parameters.sppPluginRepo;
    if (address(sppPluginRepo) == address(0)) {
      sppPluginRepo = PluginRepoFactory(parameters.pluginRepoFactory)
        .createPluginRepoWithFirstVersion(
          "staged-proposal-processor-subdao", parameters.sppPluginSetup, address(dao), " ", " "
        );
    }

    PluginRepo.Tag memory repoTag = PluginRepo.Tag(parameters.sppPlugin.release, parameters.sppPlugin.build);

    // Encode SPP installation parameters with 2 stages
    // Stage 1: Manual body (proposerAddress)
    // Stage 2: Automatic body (tokenVotingHatsPlugin)
    bytes memory installData = _encodeSppInstallationData(tokenVotingHatsPlugin);

    (address pluginAddress, IPluginSetup.PreparedSetupData memory preparedSetupData) = parameters.pluginSetupProcessor
      .prepareInstallation(
        address(dao),
        PluginSetupProcessor.PrepareInstallationParams({
          pluginSetupRef: PluginSetupRef(repoTag, sppPluginRepo), data: installData
        })
      );

    parameters.pluginSetupProcessor
      .applyInstallation(
        address(dao),
        PluginSetupProcessor.ApplyInstallationParams({
          pluginSetupRef: PluginSetupRef(repoTag, sppPluginRepo),
          plugin: pluginAddress,
          permissions: preparedSetupData.permissions,
          helpersHash: hashHelpers(preparedSetupData.helpers)
        })
      );

    sppPlugin = pluginAddress;

    // Grant necessary permissions for SPP
    _grantSppPermissions(dao, sppPlugin);
  }

  /// @notice Encodes installation data for the SPP plugin
  /// @dev Follows the StagedProposalProcessorSetup.prepareInstallation encoding:
  /// @dev abi.encode(bytes metadata, Stage[] stages, Rule[] rules, TargetConfig targetConfig)
  /// @param tokenVotingHatsPlugin Address of the TokenVotingHats plugin for stage 2
  /// @return Encoded installation data
  function _encodeSppInstallationData(address tokenVotingHatsPlugin) internal view returns (bytes memory) {
    // Create 2-stage array
    StagedProposalProcessor.Stage[] memory stages = new StagedProposalProcessor.Stage[](2);

    // Stage 1: Manual veto or approval by controller address
    // Mode determines ResultType and thresholds:
    // - "veto" mode: default allow, controller can block (approvalThreshold=0, vetoThreshold=1)
    // - "approve" mode: default block, controller must approve (approvalThreshold=1, vetoThreshold=0)
    bool isApproveMode = keccak256(bytes(parameters.stage1.mode)) == keccak256(bytes("approve"));

    StagedProposalProcessor.Body[] memory stage1Bodies = new StagedProposalProcessor.Body[](1);
    stage1Bodies[0] = StagedProposalProcessor.Body({
      addr: parameters.stage1.controllerAddress,
      isManual: true,
      tryAdvance: true, // Advance immediately on approval (both modes)
      resultType: isApproveMode ? StagedProposalProcessor.ResultType.Approval : StagedProposalProcessor.ResultType.Veto
    });

    stages[0] = StagedProposalProcessor.Stage({
      bodies: stage1Bodies,
      maxAdvance: uint64(parameters.stage1.maxAdvance),
      minAdvance: uint64(parameters.stage1.minAdvance),
      voteDuration: uint64(parameters.stage1.voteDuration),
      approvalThreshold: isApproveMode ? uint16(1) : uint16(0),
      vetoThreshold: isApproveMode ? uint16(0) : uint16(1),
      cancelable: true,
      editable: true
    });

    // Stage 2: Automatic voting via TokenVotingHats plugin
    StagedProposalProcessor.Body[] memory stage2Bodies = new StagedProposalProcessor.Body[](1);
    stage2Bodies[0] = StagedProposalProcessor.Body({
      addr: tokenVotingHatsPlugin,
      isManual: false,
      tryAdvance: false,
      resultType: StagedProposalProcessor.ResultType.Veto
    });

    stages[1] = StagedProposalProcessor.Stage({
      bodies: stage2Bodies,
      maxAdvance: uint64(parameters.stage2.maxAdvance),
      minAdvance: uint64(parameters.stage2.minAdvance),
      voteDuration: uint64(parameters.stage2.voteDuration),
      approvalThreshold: 0,
      vetoThreshold: 1,
      cancelable: false,
      editable: false
    });

    // Empty rules array - we'll use direct permission grants instead
    // This keeps the setup simple; permissions are managed in _grantSppPermissions()
    RuledCondition.Rule[] memory rules = new RuledCondition.Rule[](0);

    // Target config for executing actions on the DAO
    IPlugin.TargetConfig memory targetConfig =
      IPlugin.TargetConfig({ target: address(deployment.dao), operation: IPlugin.Operation.Call });

    // Encode according to StagedProposalProcessorSetup interface
    return abi.encode(bytes(parameters.sppPlugin.metadata), stages, rules, targetConfig);
  }

  function _grantSppPermissions(DAO dao, address sppPlugin) internal {
    // Permission IDs from SPP plugin
    bytes32 CREATE_PROPOSAL_PERMISSION_ID = keccak256("CREATE_PROPOSAL_PERMISSION");
    bytes32 EXECUTE_PROPOSAL_PERMISSION_ID = keccak256("EXECUTE_PROPOSAL_PERMISSION");

    // ANY_ADDR constant used by plugin setup (matches StagedProposalProcessorSetup)
    address ANY_ADDR = address(type(uint160).max);

    // Step 1: Revoke the broad CREATE_PROPOSAL permission granted by setup to ANY_ADDR
    // The setup grants this with a RuledCondition, but since we're using empty rules,
    // it effectively allows anyone. We revoke it to restrict permissions.
    dao.revoke(sppPlugin, ANY_ADDR, CREATE_PROPOSAL_PERMISSION_ID);

    // Step 2: Grant CREATE_PROPOSAL permission based on proposerHatId configuration
    if (parameters.stage1.proposerHatId != 0) {
      // Hat-based permissions: Any address wearing the proposer hat can create proposals
      // Use HatsCondition from TokenVotingHats to check hat eligibility
      dao.grantWithCondition(
        sppPlugin, ANY_ADDR, CREATE_PROPOSAL_PERMISSION_ID, IPermissionCondition(deployment.hatsCondition)
      );
    } else {
      // Direct grant: Only controllerAddress can create proposals
      dao.grant(sppPlugin, parameters.stage1.controllerAddress, CREATE_PROPOSAL_PERMISSION_ID);
    }

    // Step 3: Grant SPP permission to create proposals in TokenVotingHats for Stage 2
    // This allows SPP to create sub-proposals when advancing to the voting stage
    dao.grant(address(deployment.tokenVotingPlugin), sppPlugin, CREATE_PROPOSAL_PERMISSION_ID);

    // Grant EXECUTE_PROPOSAL permission to SPP plugin so it can execute on the DAO
    dao.grant(address(dao), sppPlugin, EXECUTE_PROPOSAL_PERMISSION_ID);
  }

  function _grantMainDaoPermissions(DAO dao) internal {
    // Grant main DAO ROOT_PERMISSION on SubDAO
    // This allows main DAO to install/uninstall plugins and manage all SubDAO permissions
    dao.grant(address(dao), parameters.mainDaoAddress, dao.ROOT_PERMISSION_ID());
  }

  function _grantApplyInstallationPermissions(DAO dao) internal {
    dao.grant(address(dao), address(parameters.pluginSetupProcessor), dao.ROOT_PERMISSION_ID());
    dao.grant(
      address(parameters.pluginSetupProcessor),
      address(this),
      parameters.pluginSetupProcessor.APPLY_INSTALLATION_PERMISSION_ID()
    );
  }

  function _revokeApplyInstallationPermissions(DAO dao) internal {
    dao.revoke(
      address(parameters.pluginSetupProcessor),
      address(this),
      parameters.pluginSetupProcessor.APPLY_INSTALLATION_PERMISSION_ID()
    );
    dao.revoke(address(dao), address(parameters.pluginSetupProcessor), dao.ROOT_PERMISSION_ID());
  }

  function _revokeOwnerPermission(DAO dao) internal {
    dao.revoke(address(dao), address(this), dao.EXECUTE_PERMISSION_ID());
    dao.revoke(address(dao), address(this), dao.ROOT_PERMISSION_ID());
  }

  function getDeploymentParameters() public view returns (DeploymentParameters memory) {
    return parameters;
  }

  function getDeployment() public view returns (Deployment memory) {
    return deployment;
  }
}
"
    },
    "lib/osx/packages/contracts/src/core/dao/DAO.sol": {
      "content": "// SPDX-License-Identifier: AGPL-3.0-or-later

pragma solidity ^0.8.8;

import {ERC165StorageUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165StorageUpgradeable.sol";
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import {SafeERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import {IERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import {IERC721ReceiverUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/IERC721ReceiverUpgradeable.sol";
import {IERC1155Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC1155/IERC1155Upgradeable.sol";
import {IERC1155ReceiverUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC1155/IERC1155ReceiverUpgradeable.sol";
import {AddressUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol";

import {IProtocolVersion} from "@aragon/osx-commons-contracts/src/utils/versioning/IProtocolVersion.sol";
import {ProtocolVersion} from "@aragon/osx-commons-contracts/src/utils/versioning/ProtocolVersion.sol";
import {VersionComparisonLib} from "@aragon/osx-commons-contracts/src/utils/versioning/VersionComparisonLib.sol";
import {hasBit, flipBit} from "@aragon/osx-commons-contracts/src/utils/math/BitMap.sol";
import {Action} from "@aragon/osx-commons-contracts/src/executors/Executor.sol";
import {IExecutor} from "@aragon/osx-commons-contracts/src/executors/IExecutor.sol";
import {IDAO} from "@aragon/osx-commons-contracts/src/dao/IDAO.sol";

import {PermissionManager} from "../permission/PermissionManager.sol";
import {CallbackHandler} from "../utils/CallbackHandler.sol";
import {IEIP4824} from "./IEIP4824.sol";

/// @title DAO
/// @author Aragon X - 2021-2024
/// @notice This contract is the entry point to the Aragon DAO framework and provides our users a simple and easy to use public interface.
/// @dev Public API of the Aragon DAO framework.
/// @custom:security-contact sirt@aragon.org
contract DAO is
    IEIP4824,
    Initializable,
    IERC1271,
    ERC165StorageUpgradeable,
    IDAO,
    IExecutor,
    UUPSUpgradeable,
    ProtocolVersion,
    PermissionManager,
    CallbackHandler
{
    using SafeERC20Upgradeable for IERC20Upgradeable;
    using AddressUpgradeable for address;
    using VersionComparisonLib for uint8[3];

    /// @notice The ID of the permission required to call the `execute` function.
    bytes32 public constant EXECUTE_PERMISSION_ID = keccak256("EXECUTE_PERMISSION");

    /// @notice The ID of the permission required to call the `_authorizeUpgrade` function.
    bytes32 public constant UPGRADE_DAO_PERMISSION_ID = keccak256("UPGRADE_DAO_PERMISSION");

    /// @notice The ID of the permission required to call the `setMetadata` function.
    bytes32 public constant SET_METADATA_PERMISSION_ID = keccak256("SET_METADATA_PERMISSION");

    /// @notice The ID of the permission required to call the `setTrustedForwarder` function.
    bytes32 public constant SET_TRUSTED_FORWARDER_PERMISSION_ID =
        keccak256("SET_TRUSTED_FORWARDER_PERMISSION");

    /// @notice The ID of the permission required to call the `registerStandardCallback` function.
    bytes32 public constant REGISTER_STANDARD_CALLBACK_PERMISSION_ID =
        keccak256("REGISTER_STANDARD_CALLBACK_PERMISSION");

    /// @notice The ID of the permission required to validate [ERC-1271](https://eips.ethereum.org/EIPS/eip-1271) signatures.
    bytes32 public constant VALIDATE_SIGNATURE_PERMISSION_ID =
        keccak256("VALIDATE_SIGNATURE_PERMISSION");

    /// @notice The internal constant storing the maximal action array length.
    uint256 internal constant MAX_ACTIONS = 256;

    /// @notice The first out of two values to which the `_reentrancyStatus` state variable (used by the `nonReentrant` modifier) can be set indicating that a function was not entered.
    uint256 private constant _NOT_ENTERED = 1;

    /// @notice The second out of two values to which the `_reentrancyStatus` state variable (used by the `nonReentrant` modifier) can be set indicating that a function was entered.
    uint256 private constant _ENTERED = 2;

    /// @notice Removed variable that is left here to maintain the storage layout.
    /// @dev Introduced in v1.0.0. Removed in v1.4.0.
    /// @custom:oz-renamed-from signatureValidator
    address private __removed0;

    /// @notice The address of the trusted forwarder verifying meta transactions.
    /// @dev Added in v1.0.0.
    address private trustedForwarder;

    /// @notice The [EIP-4824](https://eips.ethereum.org/EIPS/eip-4824) DAO URI.
    /// @dev Added in v1.0.0.
    string private _daoURI;

    /// @notice The state variable for the reentrancy guard of the `execute` function.
    /// @dev Added in v1.3.0. The variable can be of value `_NOT_ENTERED = 1` or `_ENTERED = 2` in usage and is initialized with `_NOT_ENTERED`.
    uint256 private _reentrancyStatus;

    /// @notice Thrown if a call is reentrant.
    error ReentrantCall();

    /// @notice Thrown if the action array length is larger than `MAX_ACTIONS`.
    error TooManyActions();

    /// @notice Thrown if action execution has failed.
    /// @param index The index of the action in the action array that failed.
    error ActionFailed(uint256 index);

    /// @notice Thrown if an action has insufficient gas left.
    error InsufficientGas();

    /// @notice Thrown if the deposit amount is zero.
    error ZeroAmount();

    /// @notice Thrown if there is a mismatch between the expected and actually deposited amount of native tokens.
    /// @param expected The expected native token amount.
    /// @param actual The actual native token amount deposited.
    error NativeTokenDepositAmountMismatch(uint256 expected, uint256 actual);

    /// @notice Thrown if an upgrade is not supported from a specific protocol version .
    error ProtocolVersionUpgradeNotSupported(uint8[3] protocolVersion);

    /// @notice Thrown when a function is removed but left to not corrupt the interface ID.
    error FunctionRemoved();

    /// @notice Thrown when initialize is called after it has already been executed.
    error AlreadyInitialized();

    /// @notice Emitted when a new DAO URI is set.
    /// @param daoURI The new URI.
    event NewURI(string daoURI);

    /// @notice A modifier to protect a function from calling itself, directly or indirectly (reentrancy).
    /// @dev Currently, this modifier is only applied to the `execute()` function. If this is used multiple times, private `_beforeNonReentrant()` and `_afterNonReentrant()` functions should be created to prevent code duplication.
    modifier nonReentrant() {
        if (_reentrancyStatus == _ENTERED) {
            revert ReentrantCall();
        }
        _reentrancyStatus = _ENTERED;

        _;

        _reentrancyStatus = _NOT_ENTERED;
    }

    /// @notice This ensures that the initialize function cannot be called during the upgrade process.
    modifier onlyCallAtInitialization() {
        if (_getInitializedVersion() != 0) {
            revert AlreadyInitialized();
        }

        _;
    }

    /// @notice Disables the initializers on the implementation contract to prevent it from being left uninitialized.
    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() {
        _disableInitializers();
    }

    /// @notice Initializes the DAO by
    /// - setting the reentrancy status variable to `_NOT_ENTERED`
    /// - registering the [ERC-165](https://eips.ethereum.org/EIPS/eip-165) interface ID
    /// - setting the trusted forwarder for meta transactions
    /// - giving the `ROOT_PERMISSION_ID` permission to the initial owner (that should be revoked and transferred to the DAO after setup).
    /// @dev This method is required to support [ERC-1822](https://eips.ethereum.org/EIPS/eip-1822).
    /// @param _metadata IPFS hash that points to all the metadata (logo, description, tags, etc.) of a DAO.
    /// @param _initialOwner The initial owner of the DAO having the `ROOT_PERMISSION_ID` permission.
    /// @param _trustedForwarder The trusted forwarder responsible for verifying meta transactions.
    /// @param daoURI_ The DAO URI required to support [ERC-4824](https://eips.ethereum.org/EIPS/eip-4824).
    function initialize(
        bytes calldata _metadata,
        address _initialOwner,
        address _trustedForwarder,
        string calldata daoURI_
    ) external onlyCallAtInitialization reinitializer(3) {
        _reentrancyStatus = _NOT_ENTERED; // added in v1.3.0

        // In addition to the current interfaceId, also support previous version of the interfaceId.
        _registerInterface(type(IDAO).interfaceId ^ IExecutor.execute.selector);

        _registerInterface(type(IDAO).interfaceId);
        _registerInterface(type(IExecutor).interfaceId);
        _registerInterface(type(IERC1271).interfaceId);
        _registerInterface(type(IEIP4824).interfaceId);
        _registerInterface(type(IProtocolVersion).interfaceId); // added in v1.3.0
        _registerTokenInterfaces();

        _setMetadata(_metadata);
        _setTrustedForwarder(_trustedForwarder);
        _setDaoURI(daoURI_);
        __PermissionManager_init(_initialOwner);
    }

    /// @notice Initializes the DAO after an upgrade from a previous protocol version.
    /// @param _previousProtocolVersion The semantic protocol version number of the previous DAO implementation contract this upgrade is transitioning from.
    /// @param _initData The initialization data to be passed to via `upgradeToAndCall` (see [ERC-1967](https://docs.openzeppelin.com/contracts/4.x/api/proxy#ERC1967Upgrade)).
    function initializeFrom(
        uint8[3] calldata _previousProtocolVersion,
        bytes calldata _initData
    ) external reinitializer(3) {
        _initData; // Silences the unused function parameter warning.

        // Check that the contract is not upgrading from a different major release.
        if (_previousProtocolVersion[0] != 1) {
            revert ProtocolVersionUpgradeNotSupported(_previousProtocolVersion);
        }

        // Initialize `_reentrancyStatus` that was added in v1.3.0.
        // Register Interface `ProtocolVersion` that was added in v1.3.0.
        if (_previousProtocolVersion.lt([1, 3, 0])) {
            _reentrancyStatus = _NOT_ENTERED;
            _registerInterface(type(IProtocolVersion).interfaceId);
        }

        // Revoke the `SET_SIGNATURE_VALIDATOR_PERMISSION` that was deprecated in v1.4.0.
        if (_previousProtocolVersion.lt([1, 4, 0])) {
            _revoke({
                _where: address(this),
                _who: address(this),
                _permissionId: keccak256("SET_SIGNATURE_VALIDATOR_PERMISSION")
            });

            _registerInterface(type(IDAO).interfaceId);
            _registerInterface(type(IExecutor).interfaceId);
        }
    }

    /// @inheritdoc PermissionManager
    function isPermissionRestrictedForAnyAddr(
        bytes32 _permissionId
    ) internal pure override returns (bool) {
        return
            _permissionId == EXECUTE_PERMISSION_ID ||
            _permissionId == UPGRADE_DAO_PERMISSION_ID ||
            _permissionId == SET_METADATA_PERMISSION_ID ||
            _permissionId == SET_TRUSTED_FORWARDER_PERMISSION_ID ||
            _permissionId == REGISTER_STANDARD_CALLBACK_PERMISSION_ID;
    }

    /// @notice Internal method authorizing the upgrade of the contract via the [upgradeability mechanism for UUPS proxies](https://docs.openzeppelin.com/contracts/4.x/api/proxy#UUPSUpgradeable) (see [ERC-1822](https://eips.ethereum.org/EIPS/eip-1822)).
    /// @dev The caller must have the `UPGRADE_DAO_PERMISSION_ID` permission.
    function _authorizeUpgrade(address) internal virtual override auth(UPGRADE_DAO_PERMISSION_ID) {}

    /// @inheritdoc IDAO
    function setTrustedForwarder(
        address _newTrustedForwarder
    ) external override auth(SET_TRUSTED_FORWARDER_PERMISSION_ID) {
        _setTrustedForwarder(_newTrustedForwarder);
    }

    /// @inheritdoc IDAO
    function getTrustedForwarder() external view virtual override returns (address) {
        return trustedForwarder;
    }

    /// @inheritdoc IDAO
    function hasPermission(
        address _where,
        address _who,
        bytes32 _permissionId,
        bytes memory _data
    ) external view override returns (bool) {
        return isGranted({_where: _where, _who: _who, _permissionId: _permissionId, _data: _data});
    }

    /// @inheritdoc IDAO
    function setMetadata(
        bytes calldata _metadata
    ) external override auth(SET_METADATA_PERMISSION_ID) {
        _setMetadata(_metadata);
    }

    /// @inheritdoc IExecutor
    function execute(
        bytes32 _callId,
        Action[] calldata _actions,
        uint256 _allowFailureMap
    )
        external
        override
        nonReentrant
        auth(EXECUTE_PERMISSION_ID)
        returns (bytes[] memory execResults, uint256 failureMap)
    {
        // Check that the action array length is within bounds.
        if (_actions.length > MAX_ACTIONS) {
            revert TooManyActions();
        }

        execResults = new bytes[](_actions.length);

        uint256 gasBefore;
        uint256 gasAfter;

        for (uint256 i = 0; i < _actions.length; ) {
            gasBefore = gasleft();

            (bool success, bytes memory result) = _actions[i].to.call{value: _actions[i].value}(
                _actions[i].data
            );
            gasAfter = gasleft();

            // Check if failure is allowed
            if (!hasBit(_allowFailureMap, uint8(i))) {
                // Check if the call failed.
                if (!success) {
                    revert ActionFailed(i);
                }
            } else {
                // Check if the call failed.
                if (!success) {
                    // Make sure that the action call did not fail because 63/64 of `gasleft()` was insufficient to execute the external call `.to.call` (see [ERC-150](https://eips.ethereum.org/EIPS/eip-150)).
                    // In specific scenarios, i.e. proposal execution where the last action in the action array is allowed to fail, the account calling `execute` could force-fail this action by setting a gas limit
                    // where 63/64 is insufficient causing the `.to.call` to fail, but where the remaining 1/64 gas are sufficient to successfully finish the `execute` call.
                    if (gasAfter < gasBefore / 64) {
                        revert InsufficientGas();
                    }

                    // Store that this action failed.
                    failureMap = flipBit(failureMap, uint8(i));
                }
            }

            execResults[i] = result;

            unchecked {
                ++i;
            }
        }

        emit Executed({
            actor: msg.sender,
            callId: _callId,
            actions: _actions,
            allowFailureMap: _allowFailureMap,
            failureMap: failureMap,
            execResults: execResults
        });
    }

    /// @inheritdoc IDAO
    function deposit(
        address _token,
        uint256 _amount,
        string calldata _reference
    ) external payable override {
        if (_amount == 0) revert ZeroAmount();

        if (_token == address(0)) {
            if (msg.value != _amount)
                revert NativeTokenDepositAmountMismatch({expected: _amount, actual: msg.value});
        } else {
            if (msg.value != 0)
                revert NativeTokenDepositAmountMismatch({expected: 0, actual: msg.value});

            IERC20Upgradeable(_token).safeTransferFrom(msg.sender, address(this), _amount);
        }

        emit Deposited(msg.sender, _token, _amount, _reference);
    }

    /// @inheritdoc IDAO
    function setSignatureValidator(address) external pure override {
        revert FunctionRemoved();
    }

    /// @inheritdoc IDAO
    /// @dev Relays the validation logic determining who is allowed to sign on behalf of the DAO to its permission manager.
    /// Caller specific bypassing can be set direct granting (i.e., `grant({_where: dao, _who: specificErc1271Caller, _permissionId: VALIDATE_SIGNATURE_PERMISSION_ID})`).
    /// Caller specific signature validation logic can be set by granting with a `PermissionCondition` (i.e., `grantWithCondition({_where: dao, _who: specificErc1271Caller, _permissionId: VALIDATE_SIGNATURE_PERMISSION_ID, _condition: yourConditionImplementation})`)
    /// Generic signature validation logic can be set for all calling contracts by granting with a `PermissionCondition` to `PermissionManager.ANY_ADDR()` (i.e., `grantWithCondition({_where: dao, _who: PermissionManager.ANY_ADDR(), _permissionId: VALIDATE_SIGNATURE_PERMISSION_ID, _condition: yourConditionImplementation})`).
    function isValidSignature(
        bytes32 _hash,
        bytes memory _signature
    ) external view override(IDAO, IERC1271) returns (bytes4) {
        if (
            isGranted({
                _where: address(this),
                _who: msg.sender,
                _permissionId: VALIDATE_SIGNATURE_PERMISSION_ID,
                _data: abi.encode(_hash, _signature)
            })
        ) {
            return 0x1626ba7e; // `type(IERC1271).interfaceId` = bytes4(keccak256("isValidSignature(bytes32,bytes)")`
        }
        return 0xffffffff; // `bytes4(uint32(type(uint32).max-1))`
    }

    /// @notice Emits the `NativeTokenDeposited` event to track native token deposits that weren't made via the deposit method.
    /// @dev This call is bound by the gas limitations for `send`/`transfer` calls introduced by [ERC-2929](https://eips.ethereum.org/EIPS/eip-2929).
    /// Gas cost increases in future hard forks might break this function. As an alternative, [ERC-2930](https://eips.ethereum.org/EIPS/eip-2930)-type transactions using access lists can be employed.
    receive() external payable {
        emit NativeTokenDeposited(msg.sender, msg.value);
    }

    /// @notice Fallback to handle future versions of the [ERC-165](https://eips.ethereum.org/EIPS/eip-165) standard.
    /// @param _input An alias being equivalent to `msg.data`. This feature of the fallback function was introduced with the [solidity compiler version 0.7.6](https://github.com/ethereum/solidity/releases/tag/v0.7.6)
    /// @return The magic number registered for the function selector triggering the fallback.
    fallback(bytes calldata _input) external returns (bytes memory) {
        bytes4 magicNumber = _handleCallback(msg.sig, _input);
        return abi.encode(magicNumber);
    }

    /// @notice Emits the MetadataSet event if new metadata is set.
    /// @param _metadata Hash of the IPFS metadata object.
    function _setMetadata(bytes calldata _metadata) internal {
        emit MetadataSet(_metadata);
    }

    /// @notice Sets the trusted forwarder on the DAO and emits the associated event.
    /// @param _trustedForwarder The trusted forwarder address.
    function _setTrustedForwarder(address _trustedForwarder) internal {
        trustedForwarder = _trustedForwarder;

        emit TrustedForwarderSet(_trustedForwarder);
    }

    /// @notice Registers the [ERC-721](https://eips.ethereum.org/EIPS/eip-721) and [ERC-1155](https://eips.ethereum.org/EIPS/eip-1155) interfaces and callbacks.
    function _registerTokenInterfaces() private {
        _registerInterface(type(IERC721ReceiverUpgradeable).interfaceId);
        _registerInterface(type(IERC1155ReceiverUpgradeable).interfaceId);

        _registerCallback(
            IERC721ReceiverUpgradeable.onERC721Received.selector,
            IERC721ReceiverUpgradeable.onERC721Received.selector
        );
        _registerCallback(
            IERC1155ReceiverUpgradeable.onERC1155Received.selector,
            IERC1155ReceiverUpgradeable.onERC1155Received.selector
        );
        _registerCallback(
            IERC1155ReceiverUpgradeable.onERC1155BatchReceived.selector,
            IERC1155ReceiverUpgradeable.onERC1155BatchReceived.selector
        );
    }

    /// @inheritdoc IDAO
    function registerStandardCallback(
        bytes4 _interfaceId,
        bytes4 _callbackSelector,
        bytes4 _magicNumber
    ) external override auth(REGISTER_STANDARD_CALLBACK_PERMISSION_ID) {
        _registerInterface(_interfaceId);
        _registerCallback(_callbackSelector, _magicNumber);
        emit StandardCallbackRegistered(_interfaceId, _callbackSelector, _magicNumber);
    }

    /// @inheritdoc IEIP4824
    function daoURI() external view returns (string memory) {
        return _daoURI;
    }

    /// @notice Updates the set DAO URI to a new value.
    /// @param newDaoURI The new DAO URI to be set.
    function setDaoURI(string calldata newDaoURI) external auth(SET_METADATA_PERMISSION_ID) {
        _setDaoURI(newDaoURI);
    }

    /// @notice Sets the new [ERC-4824](https://eips.ethereum.org/EIPS/eip-4824) DAO URI and emits the associated event.
    /// @param daoURI_ The new DAO URI.
    function _setDaoURI(string calldata daoURI_) internal {
        _daoURI = daoURI_;

        emit NewURI(daoURI_);
    }

    /// @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[46] private __gap;
}
"
    },
    "lib/osx/packages/contracts/src/framework/dao/DAOFactory.sol": {
      "content": "// SPDX-License-Identifier: AGPL-3.0-or-later

pragma solidity ^0.8.8;

import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol";

import {IDAO} from "@aragon/osx-commons-contracts/src/dao/IDAO.sol";
import {IProtocolVersion} from "@aragon/osx-commons-contracts/src/utils/versioning/IProtocolVersion.sol";
import {ProtocolVersion} from "@aragon/osx-commons-contracts/src/utils/versioning/ProtocolVersion.sol";
import {IPluginSetup} from "@aragon/osx-commons-contracts/src/plugin/setup/IPluginSetup.sol";
import {PermissionLib} from "@aragon/osx-commons-contracts/src/permission/PermissionLib.sol";
import {ProxyLib} from "@aragon/osx-commons-contracts/src/utils/deployment/ProxyLib.sol";

import {DAO} from "../../core/dao/DAO.sol";
import {PluginRepo} from "../plugin/repo/PluginRepo.sol";
import {PluginSetupProcessor} from "../plugin/setup/PluginSetupProcessor.sol";
import {hashHelpers, PluginSetupRef} from "../plugin/setup/PluginSetupProcessorHelpers.sol";
import {DAORegistry} from "./DAORegistry.sol";

/// @title DAOFactory
/// @author Aragon X - 2022-2023
/// @notice This contract is used to create a DAO.
/// @custom:security-contact sirt@aragon.org
contract DAOFactory is ERC165, ProtocolVersion {
    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;

    // Cache permission IDs for optimized access
    bytes32 internal immutable ROOT_PERMISSION_ID;
    bytes32 internal immutable UPGRADE_DAO_PERMISSION_ID;
    bytes32 internal immutable SET_TRUSTED_FORWARDER_PERMISSION_ID;
    bytes32 internal immutable SET_METADATA_PERMISSION_ID;
    bytes32 internal immutable REGISTER_STANDARD_CALLBACK_PERMISSION_ID;
    bytes32 internal immutable EXECUTE_PERMISSION_ID;
    bytes32 internal immutable APPLY_INSTALLATION_PERMISSION_ID;

    /// @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 The container with the information about an installed plugin on a DAO.
    /// @param plugin The address of the deployed plugin instance.
    /// @param preparedSetupData The applied preparedSetupData which contains arrays of helpers and permissions.
    struct InstalledPlugin {
        address plugin;
        IPluginSetup.PreparedSetupData preparedSetupData;
    }

    /// @notice Thrown if `PluginSettings` array is empty, and no plugin is provided.
    error NoPluginProvided();

    /// @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(DAORegistry _registry, PluginSetupProcessor _pluginSetupProcessor) {
        daoRegistry = _registry;
        pluginSetupProcessor = _pluginSetupProcessor;

        DAO dao = new DAO();
        daoBase = address(dao);

        // Cache permission IDs for reduced gas usage in future function calls
        ROOT_PERMISSION_ID = dao.ROOT_PERMISSION_ID();
        UPGRADE_DAO_PERMISSION_ID = dao.UPGRADE_DAO_PERMISSION_ID();
        SET_TRUSTED_FORWARDER_PERMISSION_ID = dao.SET_TRUSTED_FORWARDER_PERMISSION_ID();
        SET_METADATA_PERMISSION_ID = dao.SET_METADATA_PERMISSION_ID();
        REGISTER_STANDARD_CALLBACK_PERMISSION_ID = dao.REGISTER_STANDARD_CALLBACK_PERMISSION_ID();
        EXECUTE_PERMISSION_ID = dao.EXECUTE_PERMISSION_ID();
        APPLY_INSTALLATION_PERMISSION_ID = pluginSetupProcessor.APPLY_INSTALLATION_PERMISSION_ID();
    }

    /// @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 in the DAO registry, and optionally installs plugins via the plugin setup processor.
    /// @dev If `_pluginSettings` is empty, the caller is granted `EXECUTE_PERMISSION` on the DAO.
    /// @param _daoSettings The settings to configure during DAO initialization.
    /// @param _pluginSettings An array containing plugin references and settings. If provided, each plugin is installed
    /// after the DAO creation.
    /// @return createdDao The address of the newly created DAO instance.
    /// @return installedPlugins An array of `InstalledPlugin` structs, each containing the plugin address and associated
    /// helper contracts and permissions, if plugins were installed; otherwise, an empty array.
    function createDao(
        DAOSettings calldata _daoSettings,
        PluginSettings[] calldata _pluginSettings
    ) external returns (DAO createdDao, InstalledPlugin[] memory installedPlugins) {
        // Create DAO.
        createdDao = _createDAO(_daoSettings);

        // Register DAO.
        daoRegistry.register(createdDao, msg.sender, _daoSettings.subdomain);

        // Cache address to save gas
        address daoAddress = address(createdDao);

        // Install plugins if plugin settings are provided.
        if (_pluginSettings.length != 0) {
            installedPlugins = new InstalledPlugin[](_pluginSettings.length);

            // Cache address to save gas
            address pluginSetupProcessorAddress = address(pluginSetupProcessor);

            // Grant the temporary permissions.
            // Grant Temporarily `ROOT_PERMISSION` to `pluginSetupProcessor`.
            createdDao.grant(daoAddress, pluginSetupProcessorAddress, ROOT_PERMISSION_ID);

            // Grant Temporarily `APPLY_INSTALLATION_PERMISSION` on `pluginSetupProcessor` to this `DAOFactory`.
            createdDao.grant(
                pluginSetupProcessorAddress,
                address(this),
                APPLY_INSTALLATION_PERMISSION_ID
            );

            // Install plugins on the newly created DAO.
            for (uint256 i; i < _pluginSettings.length; ++i) {
                // Prepare plugin.
                (
                    address plugin,
                    IPluginSetup.PreparedSetupData memory preparedSetupData
                ) = pluginSetupProcessor.prepareInstallation(
                        daoAddress,
                        PluginSetupProcessor.PrepareInstallationParams(
                            _pluginSettings[i].pluginSetupRef,
                            _pluginSettings[i].data
                        )
                    );

                // Apply plugin.
                pluginSetupProcessor.applyInstallation(
                    daoAddress,
                    PluginSetupProcessor.ApplyInstallationParams(
                        _pluginSettings[i].pluginSetupRef,
                        plugin,
                        preparedSetupData.permissions,
                        hashHelpers(preparedSetupData.helpers)
                    )
                );

                installedPlugins[i] = InstalledPlugin(plugin, preparedSetupData);
            }

            // Revoke the temporarily granted permissions.
            // Revoke Temporarily `ROOT_PERMISSION` from `pluginSetupProcessor`.
            createdDao.revoke(daoAddress, pluginSetupProcessorAddress, ROOT_PERMISSION_ID);

            // Revoke `APPLY_INSTALLATION_PERMISSION` on `pluginSetupProcessor` from this `DAOFactory` .
            createdDao.revoke(
                pluginSetupProcessorAddress,
                address(this),
                APPLY_INSTALLATION_PERMISSION_ID
            );
        } else {
            // if no plugin setting is provided, grant EXECUTE_PERMISSION_ID to msg.sender
            createdDao.grant(daoAddress, msg.sender, EXECUTE_PERMISSION_ID);
        }

        // Set the rest of DAO's permissions.
        _setDAOPermissions(daoAddress);

        // Revoke Temporarily `ROOT_PERMISSION_ID` that implicitly granted to this `DaoFactory`
        // at the create dao step `address(this)` being the initial owner of the new created DAO.
        createdDao.revoke(daoAddress, address(this), ROOT_PERMISSION_ID);

        return (createdDao, installedPlugins);
    }

    /// @notice Deploys a new DAO `ERC1967` proxy, and initialize it with this contract as the initial owner.
    /// @param _daoSettings The trusted forwarder, name and metadata hash of the DAO it creates.
    function _createDAO(DAOSettings calldata _daoSettings) internal returns (DAO dao) {
        // Create a DAO proxy and initialize it with the DAOFactory (`address(this)`) as the initial owner.
        // As a result, the DAOFactory has `ROOT_PERMISSION_`ID` permission on the DAO.
        dao = DAO(
            payable(
                daoBase.deployUUPSProxy(
                    abi.encodeCall(
                        DAO.initialize,
                        (
                            _daoSettings.metadata,
                            address(this),
                            _daoSettings.trustedForwarder,
                            _daoSettings.daoURI
                        )
                    )
                )
            )
        );
    }

    /// @notice Sets the required permissions for the new DAO.
    /// @param _daoAddress The address of the DAO just created.
    function _setDAOPermissions(address _daoAddress) 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,
            _daoAddress,
            ROOT_PERMISSION_ID
        );
        items[1] = PermissionLib.SingleTargetPermission(
            PermissionLib.Operation.Grant,
            _daoAddress,
            UPGRADE_DAO_PERMISSION_ID
        );
        items[2] = PermissionLib.SingleTargetPermission(
            PermissionLib.Operation.Grant,
            _daoAddress,
            SET_TRUSTED_FORWARDER_PERMISSION_ID
        );
        items[3] = PermissionLib.SingleTargetPermission(
            PermissionLib.Operation.Grant,
            _daoAddress,
            SET_METADATA_PERMISSION_ID
        );
        items[4] = PermissionLib.SingleTargetPermission(
            PermissionLib.Operation.Grant,
            _daoAddress,
            REGISTER_STANDARD_CALLBACK_PERMISSION_ID
        );

        DAO(payable(_daoAddress)).applySingleTargetPermissions(_daoAddress, items);
    }
}
"
    },
    "lib/osx/packages/contracts/src/framework/plugin/setup/PluginSetupProcessor.sol": {
      "content": "// SPDX-License-Identifier: AGPL-3.0-or-later

pragma solidity ^0.8.8;

import {ERC165Checker} from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol";

import {ProtocolVersion} from "@aragon/osx-commons-contracts/src/utils/versioning/ProtocolVersion.sol";
import {IPluginSetup} from "@aragon/osx-commons-contracts/src/plugin/setup/IPluginSetup.sol";
import {PluginSetup} from "@aragon/osx-commons-contracts/src/plugin/setup/PluginSetup.sol";
import {DAO} from "../../../core/dao/DAO.sol";
import {PermissionLib} from "@aragon/osx-commons-contracts/src/permission/PermissionLib.sol";
import {PluginUUPSUpgradeable} from "@aragon/osx-commons-contracts/src/plugin/PluginUUPSUpgradeable.sol";
import {IPlugin} from "@aragon/osx-commons-contracts/src/plugin/IPlugin.sol";

import {PluginRepoRegistry} from "../repo/PluginRepoRegistry.sol";
import {PluginRepo} from "../repo/PluginRepo.sol";

import {PluginSetupRef, hashHelpers, hashPermissions, _getPreparedSetupId, _getAppliedSetupId, _getPluginInstallationId, PreparationType} from "./PluginSetupProcessorHelpers.sol";

/// @title PluginSetupProcessor
/// @author Aragon X - 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.
/// @custom:security-contact sirt@aragon.org
contract PluginSetupProcessor is ProtocolVersion {
    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 initializ

Tags:
ERC20, ERC1155, ERC165, Multisig, Mintable, Non-Fungible, Swap, Yield, Voting, Upgradeable, Multi-Signature, Factory, Oracle|addr:0x121a3a708d22935060b93042f22ec7c9dc2ba442|verified:true|block:23749440|tx:0x76458adb67043c345c9caf2342f0f8a8fb1fc0fab8b644ee1e9a4c04f67bf5a1|first_check:1762546931

Submitted on: 2025-11-07 21:22:12

Comments

Log in to comment.

No comments yet.