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
Submitted on: 2025-11-07 21:21:23
Comments
Log in to comment.
No comments yet.