diff --git a/snapshots/SignatureGateway.Operations.json b/snapshots/SignatureGateway.Operations.json index 93a4414b0..6e064a08d 100644 --- a/snapshots/SignatureGateway.Operations.json +++ b/snapshots/SignatureGateway.Operations.json @@ -1,10 +1,10 @@ { - "borrowWithSig": "222144", + "borrowWithSig": "222132", "repayWithSig": "192513", "setSelfAsUserPositionManagerWithSig": "75138", "setUsingAsCollateralWithSig": "85380", "supplyWithSig": "155914", - "updateUserDynamicConfigWithSig": "63113", - "updateUserRiskPremiumWithSig": "61995", + "updateUserDynamicConfigWithSig": "63101", + "updateUserRiskPremiumWithSig": "62007", "withdrawWithSig": "135124" } \ No newline at end of file diff --git a/snapshots/TokenizationSpoke.Operations.json b/snapshots/TokenizationSpoke.Operations.json index 9568365f8..dc97f6db3 100644 --- a/snapshots/TokenizationSpoke.Operations.json +++ b/snapshots/TokenizationSpoke.Operations.json @@ -13,5 +13,5 @@ "withdraw: on behalf, partial": "119557", "withdraw: self, full": "94824", "withdraw: self, partial": "114024", - "withdrawWithSig": "129405" + "withdrawWithSig": "129417" } \ No newline at end of file diff --git a/src/deployments/README.md b/src/deployments/README.md index 17cbc2592..ff8e52359 100644 --- a/src/deployments/README.md +++ b/src/deployments/README.md @@ -109,14 +109,17 @@ Hub and spoke labels (provided via `FullDeployInputs.hubLabels` / `spokeLabels`) Roles are namespaced by contract domain: Hub (100-199), HubConfigurator (200-299), Spoke (300-399), SpokeConfigurator (400-499). -For configurators, initially a single Domain Admin role (HUB_CONFIGURATOR_DOMAIN_ADMIN_ROLE = 200, SPOKE_CONFIGURATOR_DOMAIN_ADMIN_ROLE = 400) holds all target selectors. As more granular roles are introduced, they should be added at the next available ID (201, 202, ... / 401, 402, ...) and the corresponding selectors reassigned from the Domain Admin role to the new granular role: +Each configurator has five granular roles covering the same five concerns, plus a Domain Admin role (200/400) holding the selectors that fall outside them. A role never spans both configurators — a Hub role only holds HubConfigurator selectors and a Spoke role only holds SpokeConfigurator selectors — so Hub and Spoke access is always granted separately. The first two roles are named after the flag they own rather than after pause/freeze, because the Hub has no `paused`/`frozen` flags of its own: the equivalent state lives on the Spoke config the Hub holds for each asset. -- Existing role IDs should never be overwritten or reused for a different purpose. -- New roles are always appended with an incremented ID. -- The Domain Admin role (200/400) only ever has its selector set shrink over time as selectors are divided into more granular roles. -- Addresses holding the Domain Admin role should be granted the new granular role being added to retain their existing access. +| Concern | Hub role | Spoke role | Purpose | +| -------------------- | ------------------------------------- | --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Prevent all activity | HUB_CONFIGURATOR_SPOKE_ACTIVE_ROLE | SPOKE_CONFIGURATOR_PAUSE_ROLE | Flips the flag that prevents all activity, in both directions. The Spoke `paused` flag, and the Hub's per-asset Spoke `active` flag which gates every Hub action. | +| Prevent new activity | HUB_CONFIGURATOR_SPOKE_HALTED_ROLE | SPOKE_CONFIGURATOR_FREEZE_ROLE | Flips the flag that prevents new activity, in both directions. The Spoke `frozen` flag, and the Hub's per-asset Spoke `halted` flag which gates the actions that instantly update liquidity. | +| Listing | HUB_CONFIGURATOR_LISTING_ROLE | SPOKE_CONFIGURATOR_LISTING_ROLE | Onboards new assets, Spokes and reserves, and sets the properties fixed at listing time. | +| Emergency | HUB_CONFIGURATOR_EMERGENCY_ROLE | SPOKE_CONFIGURATOR_EMERGENCY_ROLE | The one-directional batch flag actions. Every selector only ever moves a target to a safer state (pause, freeze, deactivate, halt) and cannot revert it, so it can be held by a faster-moving entity than the two-way flag roles above. The Hub's batch cap resets are deliberately excluded — see below. | +| Risk management | HUB_CONFIGURATOR_RISK_MANAGEMENT_ROLE | SPOKE_CONFIGURATOR_RISK_MANAGEMENT_ROLE | The risk parameters of an already listed asset or reserve: caps, interest rates, collateral risk, dynamic configs, liquidation config. | -See `Roles.sol` NatSpec for the full role strategy and evolution guidelines. All roles are labeled on the `AccessManagerEnumerable` during deployment via `AaveV4AccessManagerRolesProcedure.labelAllRoles()`. Each role is labeled with its `Roles.sol` constant name (e.g., role 101 is labeled `"HUB_CONFIGURATOR_ROLE"`). Labels are queryable on-chain via `getLabelOfRole()` and `getRoleOfLabel()`. +See `Roles.sol` NatSpec for the role strategy and the rules for adding new roles. All roles are labeled on the `AccessManagerEnumerable` during deployment via `AaveV4AccessManagerRolesProcedure.labelAllRoles()`. Each role is labeled with its `Roles.sol` constant name (e.g., role 101 is labeled `"HUB_CONFIGURATOR_ROLE"`). Labels are queryable on-chain via `getLabelOfRole()` and `getRoleOfLabel()`. #### `AccessManager` Role @@ -135,11 +138,18 @@ See `Roles.sol` NatSpec for the full role strategy and evolution guidelines. All #### `HubConfigurator` Roles -| ID | Name | Granted To | Functions | -| --- | ---------------------------------- | -------------------- | ------------------------------------------------ | -| 200 | HUB_CONFIGURATOR_DOMAIN_ADMIN_ROLE | hubConfiguratorAdmin | All 22 HubConfigurator selectors (see Roles.sol) | +| ID | Name | Granted To | Functions | +| --- | ------------------------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | +| 200 | HUB_CONFIGURATOR_DOMAIN_ADMIN_ROLE | hubConfiguratorAdmin | updateLiquidityFee, updateFeeReceiver, updateFeeConfig, updateInterestRateStrategy, updateReinvestmentController, resetAssetCaps, resetSpokeCaps | +| 201 | HUB_CONFIGURATOR_SPOKE_ACTIVE_ROLE | hubConfiguratorAdmin | updateSpokeActive | +| 202 | HUB_CONFIGURATOR_SPOKE_HALTED_ROLE | hubConfiguratorAdmin | updateSpokeHalted | +| 203 | HUB_CONFIGURATOR_LISTING_ROLE | hubConfiguratorAdmin | addAsset, addAssetWithDecimals, addSpoke, addSpokeToAssets | +| 204 | HUB_CONFIGURATOR_EMERGENCY_ROLE | hubConfiguratorAdmin | deactivateAsset, haltAsset, deactivateSpoke, haltSpoke | +| 205 | HUB_CONFIGURATOR_RISK_MANAGEMENT_ROLE | hubConfiguratorAdmin | updateSpokeAddCap, updateSpokeDrawCap, updateSpokeCaps, updateSpokeRiskPremiumThreshold, updateInterestRateData | -Domain admin role holds all selectors initially. Granular roles (201+) are carved out as needed. +`resetAssetCaps` and `resetSpokeCaps` stay with the Domain Admin role rather than Emergency. Zeroing caps is as one-directional as a halt, but only risk management can restore them, so it is left to governance rather than to a fast-moving emergency holder. + +`grantHubConfiguratorAllRoles` grants all six. #### `Spoke` Roles (on Spoke contract) @@ -151,11 +161,16 @@ Domain admin role holds all selectors initially. Granular roles (201+) are carve #### `SpokeConfigurator` Roles (on SpokeConfigurator contract) -| ID | Name | Granted To | Functions | -| --- | ------------------------------------ | ---------------------- | -------------------------------------------------- | -| 400 | SPOKE_CONFIGURATOR_DOMAIN_ADMIN_ROLE | spokeConfiguratorAdmin | All 24 SpokeConfigurator selectors (see Roles.sol) | +| ID | Name | Granted To | Functions | +| --- | --------------------------------------- | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 400 | SPOKE_CONFIGURATOR_DOMAIN_ADMIN_ROLE | spokeConfiguratorAdmin | updateReservePriceSource, updatePositionManager | +| 401 | SPOKE_CONFIGURATOR_PAUSE_ROLE | spokeConfiguratorAdmin | updatePaused | +| 402 | SPOKE_CONFIGURATOR_FREEZE_ROLE | spokeConfiguratorAdmin | updateFrozen | +| 403 | SPOKE_CONFIGURATOR_LISTING_ROLE | spokeConfiguratorAdmin | addReserve, updateBorrowable, updateReceiveSharesEnabled | +| 404 | SPOKE_CONFIGURATOR_EMERGENCY_ROLE | spokeConfiguratorAdmin | pauseReserve, pauseAllReserves, freezeReserve, freezeAllReserves | +| 405 | SPOKE_CONFIGURATOR_RISK_MANAGEMENT_ROLE | spokeConfiguratorAdmin | updateCollateralRisk, addCollateralFactor, updateCollateralFactor, addMaxLiquidationBonus, updateMaxLiquidationBonus, addLiquidationFee, updateLiquidationFee, addDynamicReserveConfig, updateDynamicReserveConfig, updateLiquidationTargetHealthFactor, updateHealthFactorForMaxBonus, updateLiquidationBonusFactor, updateLiquidationConfig | -Domain admin role holds all selectors initially. Granular roles (401+) are carved out as needed. +`grantSpokeConfiguratorAllRoles` grants all six. ## Data Flow @@ -242,12 +257,12 @@ AaveV4DeployBatchBase.s.sol (Foundry script entry point) | | AaveV4HubRolesProcedure.grantHubAllRoles() hubAdmin gets roles 101-103 | | AaveV4HubRolesProcedure.grantHubRole() HubConfigurator gets role 101 | | AaveV4HubConfiguratorRolesProcedure.grantHubConfiguratorAllRoles() - | | hubConfiguratorAdmin gets role 200 + | | hubConfiguratorAdmin gets roles 200-205 | | _grantSpokeRoles() (if spokeLabels.length > 0) | | AaveV4SpokeRolesProcedure.grantSpokeAllRoles() spokeAdmin gets roles 301-302 | | AaveV4SpokeRolesProcedure.grantSpokeRole() SpokeConfigurator gets role 301 | | AaveV4SpokeConfiguratorRolesProcedure.grantSpokeConfiguratorAllRoles() - | | spokeConfiguratorAdmin gets role 400 + | | spokeConfiguratorAdmin gets roles 400-405 | | AaveV4AccessManagerRolesProcedure.replaceDefaultAdminRole() | | (if accessManagerAdmin != deployer) | | grant role 0 to accessManagerAdmin, revoke from deployer diff --git a/src/deployments/procedures/roles/AaveV4AccessManagerRolesProcedure.sol b/src/deployments/procedures/roles/AaveV4AccessManagerRolesProcedure.sol index ee03597b4..ccb2d2e55 100644 --- a/src/deployments/procedures/roles/AaveV4AccessManagerRolesProcedure.sol +++ b/src/deployments/procedures/roles/AaveV4AccessManagerRolesProcedure.sol @@ -22,6 +22,14 @@ library AaveV4AccessManagerRolesProcedure { // HubConfigurator roles am.labelRole(Roles.HUB_CONFIGURATOR_DOMAIN_ADMIN_ROLE, 'HUB_CONFIGURATOR_DOMAIN_ADMIN_ROLE'); + am.labelRole(Roles.HUB_CONFIGURATOR_SPOKE_ACTIVE_ROLE, 'HUB_CONFIGURATOR_SPOKE_ACTIVE_ROLE'); + am.labelRole(Roles.HUB_CONFIGURATOR_SPOKE_HALTED_ROLE, 'HUB_CONFIGURATOR_SPOKE_HALTED_ROLE'); + am.labelRole(Roles.HUB_CONFIGURATOR_LISTING_ROLE, 'HUB_CONFIGURATOR_LISTING_ROLE'); + am.labelRole(Roles.HUB_CONFIGURATOR_EMERGENCY_ROLE, 'HUB_CONFIGURATOR_EMERGENCY_ROLE'); + am.labelRole( + Roles.HUB_CONFIGURATOR_RISK_MANAGEMENT_ROLE, + 'HUB_CONFIGURATOR_RISK_MANAGEMENT_ROLE' + ); // Spoke roles am.labelRole(Roles.SPOKE_DOMAIN_ADMIN_ROLE, 'SPOKE_DOMAIN_ADMIN_ROLE'); @@ -33,6 +41,14 @@ library AaveV4AccessManagerRolesProcedure { Roles.SPOKE_CONFIGURATOR_DOMAIN_ADMIN_ROLE, 'SPOKE_CONFIGURATOR_DOMAIN_ADMIN_ROLE' ); + am.labelRole(Roles.SPOKE_CONFIGURATOR_PAUSE_ROLE, 'SPOKE_CONFIGURATOR_PAUSE_ROLE'); + am.labelRole(Roles.SPOKE_CONFIGURATOR_FREEZE_ROLE, 'SPOKE_CONFIGURATOR_FREEZE_ROLE'); + am.labelRole(Roles.SPOKE_CONFIGURATOR_LISTING_ROLE, 'SPOKE_CONFIGURATOR_LISTING_ROLE'); + am.labelRole(Roles.SPOKE_CONFIGURATOR_EMERGENCY_ROLE, 'SPOKE_CONFIGURATOR_EMERGENCY_ROLE'); + am.labelRole( + Roles.SPOKE_CONFIGURATOR_RISK_MANAGEMENT_ROLE, + 'SPOKE_CONFIGURATOR_RISK_MANAGEMENT_ROLE' + ); } /// @notice Replaces the default admin by granting the role to a new address and revoking it from the old one. diff --git a/src/deployments/procedures/roles/AaveV4HubConfiguratorRolesProcedure.sol b/src/deployments/procedures/roles/AaveV4HubConfiguratorRolesProcedure.sol index d76575471..bca1d6661 100644 --- a/src/deployments/procedures/roles/AaveV4HubConfiguratorRolesProcedure.sol +++ b/src/deployments/procedures/roles/AaveV4HubConfiguratorRolesProcedure.sol @@ -8,11 +8,16 @@ import {Roles} from 'src/deployments/utils/libraries/Roles.sol'; /// @author Aave Labs /// @notice Procedures for granting and setting up HubConfigurator roles on the AccessManager. library AaveV4HubConfiguratorRolesProcedure { - /// @notice Grants the HubConfigurator domain admin role (200) to `admin`. + /// @notice Grants every HubConfigurator role (200-205) to `admin`. /// @param accessManager The address of the AccessManager contract. - /// @param admin The address to receive the HubConfigurator domain admin role. + /// @param admin The address to receive the HubConfigurator roles. function grantHubConfiguratorAllRoles(address accessManager, address admin) internal { grantHubConfiguratorRole(accessManager, Roles.HUB_CONFIGURATOR_DOMAIN_ADMIN_ROLE, admin); + grantHubConfiguratorRole(accessManager, Roles.HUB_CONFIGURATOR_SPOKE_ACTIVE_ROLE, admin); + grantHubConfiguratorRole(accessManager, Roles.HUB_CONFIGURATOR_SPOKE_HALTED_ROLE, admin); + grantHubConfiguratorRole(accessManager, Roles.HUB_CONFIGURATOR_LISTING_ROLE, admin); + grantHubConfiguratorRole(accessManager, Roles.HUB_CONFIGURATOR_EMERGENCY_ROLE, admin); + grantHubConfiguratorRole(accessManager, Roles.HUB_CONFIGURATOR_RISK_MANAGEMENT_ROLE, admin); } /// @notice Grants a specific HubConfigurator role to the given address. @@ -25,7 +30,7 @@ library AaveV4HubConfiguratorRolesProcedure { IAccessManager(accessManager).grantRole({roleId: role, account: admin, executionDelay: 0}); } - /// @notice Sets up the HubConfigurator domain admin role with all target selectors. + /// @notice Sets up every HubConfigurator role (200-205) with its target selectors. /// @param accessManager The address of the AccessManager contract. /// @param hubConfigurator The address of the HubConfigurator contract. function setupHubConfiguratorAllRoles(address accessManager, address hubConfigurator) internal { @@ -35,6 +40,36 @@ library AaveV4HubConfiguratorRolesProcedure { Roles.HUB_CONFIGURATOR_DOMAIN_ADMIN_ROLE, Roles.getHubConfiguratorDomainAdminRoleSelectors() ); + setupHubConfiguratorRole( + accessManager, + hubConfigurator, + Roles.HUB_CONFIGURATOR_SPOKE_ACTIVE_ROLE, + Roles.getHubConfiguratorSpokeActiveRoleSelectors() + ); + setupHubConfiguratorRole( + accessManager, + hubConfigurator, + Roles.HUB_CONFIGURATOR_SPOKE_HALTED_ROLE, + Roles.getHubConfiguratorSpokeHaltedRoleSelectors() + ); + setupHubConfiguratorRole( + accessManager, + hubConfigurator, + Roles.HUB_CONFIGURATOR_LISTING_ROLE, + Roles.getHubConfiguratorListingRoleSelectors() + ); + setupHubConfiguratorRole( + accessManager, + hubConfigurator, + Roles.HUB_CONFIGURATOR_EMERGENCY_ROLE, + Roles.getHubConfiguratorEmergencyRoleSelectors() + ); + setupHubConfiguratorRole( + accessManager, + hubConfigurator, + Roles.HUB_CONFIGURATOR_RISK_MANAGEMENT_ROLE, + Roles.getHubConfiguratorRiskManagementRoleSelectors() + ); } /// @notice Sets up a specific HubConfigurator role by assigning function selectors to the target. diff --git a/src/deployments/procedures/roles/AaveV4SpokeConfiguratorRolesProcedure.sol b/src/deployments/procedures/roles/AaveV4SpokeConfiguratorRolesProcedure.sol index 944dbcef8..cc230f5ff 100644 --- a/src/deployments/procedures/roles/AaveV4SpokeConfiguratorRolesProcedure.sol +++ b/src/deployments/procedures/roles/AaveV4SpokeConfiguratorRolesProcedure.sol @@ -8,11 +8,16 @@ import {Roles} from 'src/deployments/utils/libraries/Roles.sol'; /// @author Aave Labs /// @notice Procedures for granting and setting up SpokeConfigurator roles on the AccessManager. library AaveV4SpokeConfiguratorRolesProcedure { - /// @notice Grants the SpokeConfigurator domain admin role (400) to `admin`. + /// @notice Grants every SpokeConfigurator role (400-405) to `admin`. /// @param accessManager The address of the AccessManager contract. - /// @param admin The address to receive the SpokeConfigurator domain admin role. + /// @param admin The address to receive the SpokeConfigurator roles. function grantSpokeConfiguratorAllRoles(address accessManager, address admin) internal { grantSpokeConfiguratorRole(accessManager, Roles.SPOKE_CONFIGURATOR_DOMAIN_ADMIN_ROLE, admin); + grantSpokeConfiguratorRole(accessManager, Roles.SPOKE_CONFIGURATOR_PAUSE_ROLE, admin); + grantSpokeConfiguratorRole(accessManager, Roles.SPOKE_CONFIGURATOR_FREEZE_ROLE, admin); + grantSpokeConfiguratorRole(accessManager, Roles.SPOKE_CONFIGURATOR_LISTING_ROLE, admin); + grantSpokeConfiguratorRole(accessManager, Roles.SPOKE_CONFIGURATOR_EMERGENCY_ROLE, admin); + grantSpokeConfiguratorRole(accessManager, Roles.SPOKE_CONFIGURATOR_RISK_MANAGEMENT_ROLE, admin); } /// @notice Grants a specific SpokeConfigurator role to the given address. @@ -25,7 +30,7 @@ library AaveV4SpokeConfiguratorRolesProcedure { IAccessManager(accessManager).grantRole({roleId: role, account: admin, executionDelay: 0}); } - /// @notice Sets up the SpokeConfigurator domain admin role with all target selectors. + /// @notice Sets up every SpokeConfigurator role (400-405) with its target selectors. function setupSpokeConfiguratorAllRoles( address accessManager, address spokeConfigurator @@ -36,6 +41,36 @@ library AaveV4SpokeConfiguratorRolesProcedure { Roles.SPOKE_CONFIGURATOR_DOMAIN_ADMIN_ROLE, Roles.getSpokeConfiguratorDomainAdminRoleSelectors() ); + setupSpokeConfiguratorRole( + accessManager, + spokeConfigurator, + Roles.SPOKE_CONFIGURATOR_PAUSE_ROLE, + Roles.getSpokeConfiguratorPauseRoleSelectors() + ); + setupSpokeConfiguratorRole( + accessManager, + spokeConfigurator, + Roles.SPOKE_CONFIGURATOR_FREEZE_ROLE, + Roles.getSpokeConfiguratorFreezeRoleSelectors() + ); + setupSpokeConfiguratorRole( + accessManager, + spokeConfigurator, + Roles.SPOKE_CONFIGURATOR_LISTING_ROLE, + Roles.getSpokeConfiguratorListingRoleSelectors() + ); + setupSpokeConfiguratorRole( + accessManager, + spokeConfigurator, + Roles.SPOKE_CONFIGURATOR_EMERGENCY_ROLE, + Roles.getSpokeConfiguratorEmergencyRoleSelectors() + ); + setupSpokeConfiguratorRole( + accessManager, + spokeConfigurator, + Roles.SPOKE_CONFIGURATOR_RISK_MANAGEMENT_ROLE, + Roles.getSpokeConfiguratorRiskManagementRoleSelectors() + ); } /// @notice Sets up a specific SpokeConfigurator role by assigning function selectors to the target. diff --git a/src/deployments/utils/libraries/Roles.sol b/src/deployments/utils/libraries/Roles.sol index 697d9cf74..288759a51 100644 --- a/src/deployments/utils/libraries/Roles.sol +++ b/src/deployments/utils/libraries/Roles.sol @@ -19,26 +19,49 @@ import {ISpokeConfigurator} from 'src/spoke/interfaces/ISpokeConfigurator.sol'; /// /// ## Role strategy /// -/// A single authority contract will be used to manage the roles for all applicable contracts on a given chain. +/// A single authority contract manages the roles for all applicable contracts on a given chain. /// Role IDs, selector mappings, and overall configuration should be kept identical /// across chains to avoid additional overhead and role divergence. /// -/// Hub and Spoke roles remain granular (e.g. HUB_CONFIGURATOR_ROLE, -/// HUB_FEE_MINTER_ROLE, HUB_DEFICIT_ELIMINATOR_ROLE each control a distinct set -/// of selectors). +/// Role IDs are never reused for a different purpose. A new role is appended at the next +/// available ID in its domain range, and its selectors are reassigned from the Domain Admin role +/// of that domain (HUB_CONFIGURATOR_DOMAIN_ADMIN_ROLE = 200, +/// SPOKE_CONFIGURATOR_DOMAIN_ADMIN_ROLE = 400), whose selector set only ever shrinks. Addresses +/// holding the Domain Admin role should be granted the new granular role to retain their +/// existing access. /// -/// HubConfigurator and SpokeConfigurator follow a different approach: initially, -/// a single Domain Admin role per domain (HUB_CONFIGURATOR_DOMAIN_ADMIN_ROLE = 200, -/// SPOKE_CONFIGURATOR_DOMAIN_ADMIN_ROLE = 400) holds all target selectors. -/// As more granular roles are introduced, they are added at the next available ID -/// (201, 202, ... / 401, 402, ...) and the corresponding selectors are reassigned -/// from the Domain Admin role to the new granular role: -/// - Existing role IDs should never be overwritten or reused for a different purpose. -/// - New roles are always appended with an incremented ID. -/// - The Domain Admin role (200/400) only ever has its selector set shrink over -/// time as selectors are divided into more granular roles. -/// - Addresses holding the Domain Admin role should be granted the new -/// granular role to retain their existing access. +/// ## Configurator role breakdown +/// +/// Each configurator has five granular roles covering the same five concerns. A role never +/// spans both configurators: a Hub role only holds HubConfigurator selectors and a Spoke role +/// only holds SpokeConfigurator selectors, so Hub and Spoke access is always granted separately. +/// The first two roles are named after the flag they own, because the Hub has no +/// `paused`/`frozen` flags of its own — the equivalent state lives on the Spoke config it +/// holds on each asset. +/// +/// - HUB_CONFIGURATOR_SPOKE_ACTIVE_ROLE (201) / SPOKE_CONFIGURATOR_PAUSE_ROLE (401): +/// flips the flag that prevents all activity on a target, in both directions. The +/// Spoke `paused` flag, and the Hub's per-asset Spoke `active` flag which gates +/// every Hub action. +/// - HUB_CONFIGURATOR_SPOKE_HALTED_ROLE (202) / SPOKE_CONFIGURATOR_FREEZE_ROLE (402): +/// flips the flag that prevents new activity on a target, in both directions. The +/// Spoke `frozen` flag, and the Hub's per-asset Spoke `halted` flag which gates the +/// actions that instantly update liquidity. +/// - Listing (203/403): onboards new assets, Spokes and reserves, and sets the +/// properties fixed at listing time. +/// - Emergency (204/404): the one-directional batch flag actions. Every selector only +/// ever moves a target to a safer state (pause, freeze, deactivate, halt) and cannot +/// revert it, so the role can be held by a faster-moving entity than the two-way flag +/// roles above. The batch cap resets stay with the Domain Admin role: zeroing caps is +/// equally one-directional but only risk management can restore them, so it is left +/// to governance rather than to a fast-moving holder. +/// - Risk management (205/405): the risk parameters of an already listed asset or +/// reserve (caps, interest rates, collateral risk, dynamic configs, liquidation +/// config). +/// +/// The Domain Admin role (200/400) retains the selectors that fall outside the five: +/// on the Hub the fee and strategy configuration plus the batch cap resets, on the +/// Spoke the reserve price source and position managers. library Roles { // AccessManager roles uint64 public constant ACCESS_MANAGER_ADMIN_ROLE = 0; @@ -51,6 +74,11 @@ library Roles { // HubConfigurator roles — granularize as needed with new roles appended uint64 public constant HUB_CONFIGURATOR_DOMAIN_ADMIN_ROLE = 200; + uint64 public constant HUB_CONFIGURATOR_SPOKE_ACTIVE_ROLE = 201; + uint64 public constant HUB_CONFIGURATOR_SPOKE_HALTED_ROLE = 202; + uint64 public constant HUB_CONFIGURATOR_LISTING_ROLE = 203; + uint64 public constant HUB_CONFIGURATOR_EMERGENCY_ROLE = 204; + uint64 public constant HUB_CONFIGURATOR_RISK_MANAGEMENT_ROLE = 205; // Spoke roles uint64 public constant SPOKE_DOMAIN_ADMIN_ROLE = 300; @@ -59,6 +87,11 @@ library Roles { // SpokeConfigurator roles — granularize as needed with new roles appended uint64 public constant SPOKE_CONFIGURATOR_DOMAIN_ADMIN_ROLE = 400; + uint64 public constant SPOKE_CONFIGURATOR_PAUSE_ROLE = 401; + uint64 public constant SPOKE_CONFIGURATOR_FREEZE_ROLE = 402; + uint64 public constant SPOKE_CONFIGURATOR_LISTING_ROLE = 403; + uint64 public constant SPOKE_CONFIGURATOR_EMERGENCY_ROLE = 404; + uint64 public constant SPOKE_CONFIGURATOR_RISK_MANAGEMENT_ROLE = 405; // ─── Hub selector getters ─── @@ -91,29 +124,59 @@ library Roles { /// @notice Returns the function selectors associated with the HubConfigurator Domain Admin role. function getHubConfiguratorDomainAdminRoleSelectors() internal pure returns (bytes4[] memory) { - bytes4[] memory selectors = new bytes4[](22); + bytes4[] memory selectors = new bytes4[](7); + selectors[0] = IHubConfigurator.updateLiquidityFee.selector; + selectors[1] = IHubConfigurator.updateFeeReceiver.selector; + selectors[2] = IHubConfigurator.updateFeeConfig.selector; + selectors[3] = IHubConfigurator.updateInterestRateStrategy.selector; + selectors[4] = IHubConfigurator.updateReinvestmentController.selector; + selectors[5] = IHubConfigurator.resetAssetCaps.selector; + selectors[6] = IHubConfigurator.resetSpokeCaps.selector; + return selectors; + } + + /// @notice Returns the function selectors associated with the HubConfigurator Spoke Active role. + function getHubConfiguratorSpokeActiveRoleSelectors() internal pure returns (bytes4[] memory) { + bytes4[] memory selectors = new bytes4[](1); + selectors[0] = IHubConfigurator.updateSpokeActive.selector; + return selectors; + } + + /// @notice Returns the function selectors associated with the HubConfigurator Spoke Halted role. + function getHubConfiguratorSpokeHaltedRoleSelectors() internal pure returns (bytes4[] memory) { + bytes4[] memory selectors = new bytes4[](1); + selectors[0] = IHubConfigurator.updateSpokeHalted.selector; + return selectors; + } + + /// @notice Returns the function selectors associated with the HubConfigurator Listing role. + function getHubConfiguratorListingRoleSelectors() internal pure returns (bytes4[] memory) { + bytes4[] memory selectors = new bytes4[](4); selectors[0] = IHubConfigurator.addAsset.selector; selectors[1] = IHubConfigurator.addAssetWithDecimals.selector; - selectors[2] = IHubConfigurator.updateLiquidityFee.selector; - selectors[3] = IHubConfigurator.updateFeeReceiver.selector; - selectors[4] = IHubConfigurator.updateFeeConfig.selector; - selectors[5] = IHubConfigurator.updateInterestRateStrategy.selector; - selectors[6] = IHubConfigurator.updateReinvestmentController.selector; - selectors[7] = IHubConfigurator.resetAssetCaps.selector; - selectors[8] = IHubConfigurator.deactivateAsset.selector; - selectors[9] = IHubConfigurator.haltAsset.selector; - selectors[10] = IHubConfigurator.addSpoke.selector; - selectors[11] = IHubConfigurator.addSpokeToAssets.selector; - selectors[12] = IHubConfigurator.updateSpokeActive.selector; - selectors[13] = IHubConfigurator.updateSpokeHalted.selector; - selectors[14] = IHubConfigurator.updateSpokeAddCap.selector; - selectors[15] = IHubConfigurator.updateSpokeDrawCap.selector; - selectors[16] = IHubConfigurator.updateSpokeRiskPremiumThreshold.selector; - selectors[17] = IHubConfigurator.updateSpokeCaps.selector; - selectors[18] = IHubConfigurator.deactivateSpoke.selector; - selectors[19] = IHubConfigurator.haltSpoke.selector; - selectors[20] = IHubConfigurator.resetSpokeCaps.selector; - selectors[21] = IHubConfigurator.updateInterestRateData.selector; + selectors[2] = IHubConfigurator.addSpoke.selector; + selectors[3] = IHubConfigurator.addSpokeToAssets.selector; + return selectors; + } + + /// @notice Returns the function selectors associated with the HubConfigurator Emergency role. + function getHubConfiguratorEmergencyRoleSelectors() internal pure returns (bytes4[] memory) { + bytes4[] memory selectors = new bytes4[](4); + selectors[0] = IHubConfigurator.deactivateAsset.selector; + selectors[1] = IHubConfigurator.haltAsset.selector; + selectors[2] = IHubConfigurator.deactivateSpoke.selector; + selectors[3] = IHubConfigurator.haltSpoke.selector; + return selectors; + } + + /// @notice Returns the function selectors associated with the HubConfigurator Risk Management role. + function getHubConfiguratorRiskManagementRoleSelectors() internal pure returns (bytes4[] memory) { + bytes4[] memory selectors = new bytes4[](5); + selectors[0] = IHubConfigurator.updateSpokeAddCap.selector; + selectors[1] = IHubConfigurator.updateSpokeDrawCap.selector; + selectors[2] = IHubConfigurator.updateSpokeCaps.selector; + selectors[3] = IHubConfigurator.updateSpokeRiskPremiumThreshold.selector; + selectors[4] = IHubConfigurator.updateInterestRateData.selector; return selectors; } @@ -144,31 +207,65 @@ library Roles { /// @notice Returns the function selectors associated with the SpokeConfigurator Domain Admin role. function getSpokeConfiguratorDomainAdminRoleSelectors() internal pure returns (bytes4[] memory) { - bytes4[] memory selectors = new bytes4[](24); + bytes4[] memory selectors = new bytes4[](2); selectors[0] = ISpokeConfigurator.updateReservePriceSource.selector; - selectors[1] = ISpokeConfigurator.updateLiquidationTargetHealthFactor.selector; - selectors[2] = ISpokeConfigurator.updateHealthFactorForMaxBonus.selector; - selectors[3] = ISpokeConfigurator.updateLiquidationBonusFactor.selector; - selectors[4] = ISpokeConfigurator.updateLiquidationConfig.selector; - selectors[5] = ISpokeConfigurator.addReserve.selector; - selectors[6] = ISpokeConfigurator.updatePaused.selector; - selectors[7] = ISpokeConfigurator.updateFrozen.selector; - selectors[8] = ISpokeConfigurator.updateBorrowable.selector; - selectors[9] = ISpokeConfigurator.updateReceiveSharesEnabled.selector; - selectors[10] = ISpokeConfigurator.updateCollateralRisk.selector; - selectors[11] = ISpokeConfigurator.addCollateralFactor.selector; - selectors[12] = ISpokeConfigurator.updateCollateralFactor.selector; - selectors[13] = ISpokeConfigurator.addMaxLiquidationBonus.selector; - selectors[14] = ISpokeConfigurator.updateMaxLiquidationBonus.selector; - selectors[15] = ISpokeConfigurator.addLiquidationFee.selector; - selectors[16] = ISpokeConfigurator.updateLiquidationFee.selector; - selectors[17] = ISpokeConfigurator.addDynamicReserveConfig.selector; - selectors[18] = ISpokeConfigurator.updateDynamicReserveConfig.selector; - selectors[19] = ISpokeConfigurator.pauseAllReserves.selector; - selectors[20] = ISpokeConfigurator.freezeAllReserves.selector; - selectors[21] = ISpokeConfigurator.pauseReserve.selector; - selectors[22] = ISpokeConfigurator.freezeReserve.selector; - selectors[23] = ISpokeConfigurator.updatePositionManager.selector; + selectors[1] = ISpokeConfigurator.updatePositionManager.selector; + return selectors; + } + + /// @notice Returns the function selectors associated with the SpokeConfigurator Pause role. + function getSpokeConfiguratorPauseRoleSelectors() internal pure returns (bytes4[] memory) { + bytes4[] memory selectors = new bytes4[](1); + selectors[0] = ISpokeConfigurator.updatePaused.selector; + return selectors; + } + + /// @notice Returns the function selectors associated with the SpokeConfigurator Freeze role. + function getSpokeConfiguratorFreezeRoleSelectors() internal pure returns (bytes4[] memory) { + bytes4[] memory selectors = new bytes4[](1); + selectors[0] = ISpokeConfigurator.updateFrozen.selector; + return selectors; + } + + /// @notice Returns the function selectors associated with the SpokeConfigurator Listing role. + function getSpokeConfiguratorListingRoleSelectors() internal pure returns (bytes4[] memory) { + bytes4[] memory selectors = new bytes4[](3); + selectors[0] = ISpokeConfigurator.addReserve.selector; + selectors[1] = ISpokeConfigurator.updateBorrowable.selector; + selectors[2] = ISpokeConfigurator.updateReceiveSharesEnabled.selector; + return selectors; + } + + /// @notice Returns the function selectors associated with the SpokeConfigurator Emergency role. + function getSpokeConfiguratorEmergencyRoleSelectors() internal pure returns (bytes4[] memory) { + bytes4[] memory selectors = new bytes4[](4); + selectors[0] = ISpokeConfigurator.pauseReserve.selector; + selectors[1] = ISpokeConfigurator.pauseAllReserves.selector; + selectors[2] = ISpokeConfigurator.freezeReserve.selector; + selectors[3] = ISpokeConfigurator.freezeAllReserves.selector; + return selectors; + } + + /// @notice Returns the function selectors associated with the SpokeConfigurator Risk Management role. + function getSpokeConfiguratorRiskManagementRoleSelectors() + internal + pure + returns (bytes4[] memory) + { + bytes4[] memory selectors = new bytes4[](13); + selectors[0] = ISpokeConfigurator.updateCollateralRisk.selector; + selectors[1] = ISpokeConfigurator.addCollateralFactor.selector; + selectors[2] = ISpokeConfigurator.updateCollateralFactor.selector; + selectors[3] = ISpokeConfigurator.addMaxLiquidationBonus.selector; + selectors[4] = ISpokeConfigurator.updateMaxLiquidationBonus.selector; + selectors[5] = ISpokeConfigurator.addLiquidationFee.selector; + selectors[6] = ISpokeConfigurator.updateLiquidationFee.selector; + selectors[7] = ISpokeConfigurator.addDynamicReserveConfig.selector; + selectors[8] = ISpokeConfigurator.updateDynamicReserveConfig.selector; + selectors[9] = ISpokeConfigurator.updateLiquidationTargetHealthFactor.selector; + selectors[10] = ISpokeConfigurator.updateHealthFactorForMaxBonus.selector; + selectors[11] = ISpokeConfigurator.updateLiquidationBonusFactor.selector; + selectors[12] = ISpokeConfigurator.updateLiquidationConfig.selector; return selectors; } } diff --git a/tests/config-engine/AaveV4Payload.t.sol b/tests/config-engine/AaveV4Payload.t.sol index fa71d3ac2..74de86a65 100644 --- a/tests/config-engine/AaveV4Payload.t.sol +++ b/tests/config-engine/AaveV4Payload.t.sol @@ -14,8 +14,7 @@ contract AaveV4PayloadTest is BaseConfigEngineTest { // Grant same roles to payload (since delegatecall makes msg.sender = payload) vm.startPrank(ADMIN); - accessManager.grantRole(Roles.HUB_CONFIGURATOR_DOMAIN_ADMIN_ROLE, address(payload), 0); - accessManager.grantRole(Roles.SPOKE_CONFIGURATOR_DOMAIN_ADMIN_ROLE, address(payload), 0); + _grantConfiguratorRolesTo(address(payload)); accessManager.grantRole(Roles.ACCESS_MANAGER_ADMIN_ROLE, address(payload), 0); vm.stopPrank(); @@ -600,8 +599,9 @@ contract AaveV4PayloadTest is BaseConfigEngineTest { payload.execute(); payload = new AaveV4PayloadWrapper(IAaveV4ConfigEngine(address(engine))); - vm.prank(ADMIN); - accessManager.grantRole(Roles.HUB_CONFIGURATOR_DOMAIN_ADMIN_ROLE, address(payload), 0); + vm.startPrank(ADMIN); + _grantConfiguratorRolesTo(address(payload)); + vm.stopPrank(); IAaveV4ConfigEngine.AssetDeactivation[] memory deactivations = new IAaveV4ConfigEngine.AssetDeactivation[](1); @@ -847,8 +847,7 @@ contract AaveV4PayloadTest is BaseConfigEngineTest { payload = new AaveV4PayloadWrapper(IAaveV4ConfigEngine(address(engine))); vm.startPrank(ADMIN); - accessManager.grantRole(Roles.HUB_CONFIGURATOR_DOMAIN_ADMIN_ROLE, address(payload), 0); - accessManager.grantRole(Roles.SPOKE_CONFIGURATOR_DOMAIN_ADMIN_ROLE, address(payload), 0); + _grantConfiguratorRolesTo(address(payload)); accessManager.grantRole(Roles.ACCESS_MANAGER_ADMIN_ROLE, address(payload), 0); vm.stopPrank(); diff --git a/tests/config-engine/BaseConfigEngine.t.sol b/tests/config-engine/BaseConfigEngine.t.sol index 89b22cca1..379d966d3 100644 --- a/tests/config-engine/BaseConfigEngine.t.sol +++ b/tests/config-engine/BaseConfigEngine.t.sol @@ -230,6 +230,20 @@ abstract contract BaseConfigEngineTest is Test, Create2TestHelper { accessManager.renounceRole(Roles.ACCESS_MANAGER_ADMIN_ROLE, address(this)); } + /// @dev Grants every HubConfigurator and SpokeConfigurator role, the full configurator + /// access the single domain admin role used to carry on its own. Caller must be an + /// AccessManager admin for the whole call. + function _grantConfiguratorRolesTo(address account) internal { + AaveV4HubConfiguratorRolesProcedure.grantHubConfiguratorAllRoles( + address(accessManager), + account + ); + AaveV4SpokeConfiguratorRolesProcedure.grantSpokeConfiguratorAllRoles( + address(accessManager), + account + ); + } + function _seedAsset( IHub hub, AssetInterestRateStrategy strategy, diff --git a/tests/config-engine/GovernanceTopology.t.sol b/tests/config-engine/GovernanceTopology.t.sol index 5fb12064f..64fc14797 100644 --- a/tests/config-engine/GovernanceTopology.t.sol +++ b/tests/config-engine/GovernanceTopology.t.sol @@ -28,8 +28,9 @@ contract ConfigEngineGovernanceTopologyTest is BaseConfigEngineTest { }); // in production the Executor, not the payload or the engine, holds the configurator permissions - vm.prank(ADMIN); - accessManager.grantRole(Roles.HUB_CONFIGURATOR_DOMAIN_ADMIN_ROLE, address(executor), 0); + vm.startPrank(ADMIN); + _grantConfiguratorRolesTo(address(executor)); + vm.stopPrank(); } function _executePayload(address target) internal { diff --git a/tests/contracts/libraries/types/Roles.t.sol b/tests/contracts/libraries/types/Roles.t.sol index 5d5adaafe..5011c3664 100644 --- a/tests/contracts/libraries/types/Roles.t.sol +++ b/tests/contracts/libraries/types/Roles.t.sol @@ -12,9 +12,19 @@ contract RolesTest is Test { assertEq(Roles.HUB_FEE_MINTER_ROLE, 102); assertEq(Roles.HUB_DEFICIT_ELIMINATOR_ROLE, 103); assertEq(Roles.HUB_CONFIGURATOR_DOMAIN_ADMIN_ROLE, 200); + assertEq(Roles.HUB_CONFIGURATOR_SPOKE_ACTIVE_ROLE, 201); + assertEq(Roles.HUB_CONFIGURATOR_SPOKE_HALTED_ROLE, 202); + assertEq(Roles.HUB_CONFIGURATOR_LISTING_ROLE, 203); + assertEq(Roles.HUB_CONFIGURATOR_EMERGENCY_ROLE, 204); + assertEq(Roles.HUB_CONFIGURATOR_RISK_MANAGEMENT_ROLE, 205); assertEq(Roles.SPOKE_DOMAIN_ADMIN_ROLE, 300); assertEq(Roles.SPOKE_CONFIGURATOR_ROLE, 301); assertEq(Roles.SPOKE_USER_POSITION_UPDATER_ROLE, 302); assertEq(Roles.SPOKE_CONFIGURATOR_DOMAIN_ADMIN_ROLE, 400); + assertEq(Roles.SPOKE_CONFIGURATOR_PAUSE_ROLE, 401); + assertEq(Roles.SPOKE_CONFIGURATOR_FREEZE_ROLE, 402); + assertEq(Roles.SPOKE_CONFIGURATOR_LISTING_ROLE, 403); + assertEq(Roles.SPOKE_CONFIGURATOR_EMERGENCY_ROLE, 404); + assertEq(Roles.SPOKE_CONFIGURATOR_RISK_MANAGEMENT_ROLE, 405); } } diff --git a/tests/deployments/procedures/ProceduresBase.t.sol b/tests/deployments/procedures/ProceduresBase.t.sol index 1e21a15e5..f71517b40 100644 --- a/tests/deployments/procedures/ProceduresBase.t.sol +++ b/tests/deployments/procedures/ProceduresBase.t.sol @@ -92,4 +92,11 @@ contract ProceduresBase is Create2TestHelper { assertEq(delay, 0); } } + + function _assertCannotCall(address target, bytes4[] memory selectors) internal view { + for (uint256 idx; idx < selectors.length; idx++) { + (bool allowed, ) = IAccessManager(accessManager).canCall(admin, target, selectors[idx]); + assertFalse(allowed); + } + } } diff --git a/tests/deployments/procedures/deploy/roles/AaveV4AccessManagerRolesProcedure.t.sol b/tests/deployments/procedures/deploy/roles/AaveV4AccessManagerRolesProcedure.t.sol index 76afa4493..8088124e4 100644 --- a/tests/deployments/procedures/deploy/roles/AaveV4AccessManagerRolesProcedure.t.sol +++ b/tests/deployments/procedures/deploy/roles/AaveV4AccessManagerRolesProcedure.t.sol @@ -96,110 +96,87 @@ contract AaveV4AccessManagerRolesProcedureTest is ProceduresBase { ); aaveV4AccessManagerRolesProcedureWrapper.labelAllRoles(accessManager); - IAccessManagerEnumerable accessManager = IAccessManagerEnumerable(accessManager); + IAccessManagerEnumerable am = IAccessManagerEnumerable(accessManager); // Hub roles - assertTrue( - accessManager.isRoleLabeled(Roles.HUB_DOMAIN_ADMIN_ROLE), - 'HUB_DOMAIN_ADMIN labeled' - ); - assertEq(accessManager.getLabelOfRole(Roles.HUB_DOMAIN_ADMIN_ROLE), 'HUB_DOMAIN_ADMIN_ROLE'); - assertEq(accessManager.getRoleOfLabel('HUB_DOMAIN_ADMIN_ROLE'), Roles.HUB_DOMAIN_ADMIN_ROLE); - - assertTrue( - accessManager.isRoleLabeled(Roles.HUB_CONFIGURATOR_ROLE), - 'HUB_CONFIGURATOR labeled' - ); - assertEq(accessManager.getLabelOfRole(Roles.HUB_CONFIGURATOR_ROLE), 'HUB_CONFIGURATOR_ROLE'); - assertEq(accessManager.getRoleOfLabel('HUB_CONFIGURATOR_ROLE'), Roles.HUB_CONFIGURATOR_ROLE); - - assertTrue(accessManager.isRoleLabeled(Roles.HUB_FEE_MINTER_ROLE), 'HUB_FEE_MINTER labeled'); - assertEq(accessManager.getLabelOfRole(Roles.HUB_FEE_MINTER_ROLE), 'HUB_FEE_MINTER_ROLE'); - assertEq(accessManager.getRoleOfLabel('HUB_FEE_MINTER_ROLE'), Roles.HUB_FEE_MINTER_ROLE); - - assertTrue( - accessManager.isRoleLabeled(Roles.HUB_DEFICIT_ELIMINATOR_ROLE), - 'HUB_DEFICIT_ELIMINATOR labeled' - ); - assertEq( - accessManager.getLabelOfRole(Roles.HUB_DEFICIT_ELIMINATOR_ROLE), - 'HUB_DEFICIT_ELIMINATOR_ROLE' - ); - assertEq( - accessManager.getRoleOfLabel('HUB_DEFICIT_ELIMINATOR_ROLE'), - Roles.HUB_DEFICIT_ELIMINATOR_ROLE - ); + _assertRoleLabeled(am, Roles.HUB_DOMAIN_ADMIN_ROLE, 'HUB_DOMAIN_ADMIN_ROLE'); + _assertRoleLabeled(am, Roles.HUB_CONFIGURATOR_ROLE, 'HUB_CONFIGURATOR_ROLE'); + _assertRoleLabeled(am, Roles.HUB_FEE_MINTER_ROLE, 'HUB_FEE_MINTER_ROLE'); + _assertRoleLabeled(am, Roles.HUB_DEFICIT_ELIMINATOR_ROLE, 'HUB_DEFICIT_ELIMINATOR_ROLE'); // HubConfigurator roles - assertTrue( - accessManager.isRoleLabeled(Roles.HUB_CONFIGURATOR_DOMAIN_ADMIN_ROLE), - 'HUB_CONFIGURATOR_DOMAIN_ADMIN labeled' - ); - assertEq( - accessManager.getLabelOfRole(Roles.HUB_CONFIGURATOR_DOMAIN_ADMIN_ROLE), + _assertRoleLabeled( + am, + Roles.HUB_CONFIGURATOR_DOMAIN_ADMIN_ROLE, 'HUB_CONFIGURATOR_DOMAIN_ADMIN_ROLE' ); - assertEq( - accessManager.getRoleOfLabel('HUB_CONFIGURATOR_DOMAIN_ADMIN_ROLE'), - Roles.HUB_CONFIGURATOR_DOMAIN_ADMIN_ROLE + _assertRoleLabeled( + am, + Roles.HUB_CONFIGURATOR_SPOKE_ACTIVE_ROLE, + 'HUB_CONFIGURATOR_SPOKE_ACTIVE_ROLE' ); - - // Spoke roles - assertTrue( - accessManager.isRoleLabeled(Roles.SPOKE_DOMAIN_ADMIN_ROLE), - 'SPOKE_DOMAIN_ADMIN labeled' + _assertRoleLabeled( + am, + Roles.HUB_CONFIGURATOR_SPOKE_HALTED_ROLE, + 'HUB_CONFIGURATOR_SPOKE_HALTED_ROLE' ); - assertEq( - accessManager.getLabelOfRole(Roles.SPOKE_DOMAIN_ADMIN_ROLE), - 'SPOKE_DOMAIN_ADMIN_ROLE' + _assertRoleLabeled(am, Roles.HUB_CONFIGURATOR_LISTING_ROLE, 'HUB_CONFIGURATOR_LISTING_ROLE'); + _assertRoleLabeled( + am, + Roles.HUB_CONFIGURATOR_EMERGENCY_ROLE, + 'HUB_CONFIGURATOR_EMERGENCY_ROLE' ); - assertEq( - accessManager.getRoleOfLabel('SPOKE_DOMAIN_ADMIN_ROLE'), - Roles.SPOKE_DOMAIN_ADMIN_ROLE + _assertRoleLabeled( + am, + Roles.HUB_CONFIGURATOR_RISK_MANAGEMENT_ROLE, + 'HUB_CONFIGURATOR_RISK_MANAGEMENT_ROLE' ); - assertTrue( - accessManager.isRoleLabeled(Roles.SPOKE_CONFIGURATOR_ROLE), - 'SPOKE_CONFIGURATOR labeled' - ); - assertEq( - accessManager.getLabelOfRole(Roles.SPOKE_CONFIGURATOR_ROLE), - 'SPOKE_CONFIGURATOR_ROLE' - ); - assertEq( - accessManager.getRoleOfLabel('SPOKE_CONFIGURATOR_ROLE'), - Roles.SPOKE_CONFIGURATOR_ROLE - ); - - assertTrue( - accessManager.isRoleLabeled(Roles.SPOKE_USER_POSITION_UPDATER_ROLE), - 'SPOKE_USER_POSITION_UPDATER labeled' - ); - assertEq( - accessManager.getLabelOfRole(Roles.SPOKE_USER_POSITION_UPDATER_ROLE), + // Spoke roles + _assertRoleLabeled(am, Roles.SPOKE_DOMAIN_ADMIN_ROLE, 'SPOKE_DOMAIN_ADMIN_ROLE'); + _assertRoleLabeled(am, Roles.SPOKE_CONFIGURATOR_ROLE, 'SPOKE_CONFIGURATOR_ROLE'); + _assertRoleLabeled( + am, + Roles.SPOKE_USER_POSITION_UPDATER_ROLE, 'SPOKE_USER_POSITION_UPDATER_ROLE' ); - assertEq( - accessManager.getRoleOfLabel('SPOKE_USER_POSITION_UPDATER_ROLE'), - Roles.SPOKE_USER_POSITION_UPDATER_ROLE - ); // SpokeConfigurator roles - assertTrue( - accessManager.isRoleLabeled(Roles.SPOKE_CONFIGURATOR_DOMAIN_ADMIN_ROLE), - 'SPOKE_CONFIGURATOR_DOMAIN_ADMIN labeled' - ); - assertEq( - accessManager.getLabelOfRole(Roles.SPOKE_CONFIGURATOR_DOMAIN_ADMIN_ROLE), + _assertRoleLabeled( + am, + Roles.SPOKE_CONFIGURATOR_DOMAIN_ADMIN_ROLE, 'SPOKE_CONFIGURATOR_DOMAIN_ADMIN_ROLE' ); - assertEq( - accessManager.getRoleOfLabel('SPOKE_CONFIGURATOR_DOMAIN_ADMIN_ROLE'), - Roles.SPOKE_CONFIGURATOR_DOMAIN_ADMIN_ROLE + _assertRoleLabeled(am, Roles.SPOKE_CONFIGURATOR_PAUSE_ROLE, 'SPOKE_CONFIGURATOR_PAUSE_ROLE'); + _assertRoleLabeled(am, Roles.SPOKE_CONFIGURATOR_FREEZE_ROLE, 'SPOKE_CONFIGURATOR_FREEZE_ROLE'); + _assertRoleLabeled( + am, + Roles.SPOKE_CONFIGURATOR_LISTING_ROLE, + 'SPOKE_CONFIGURATOR_LISTING_ROLE' + ); + _assertRoleLabeled( + am, + Roles.SPOKE_CONFIGURATOR_EMERGENCY_ROLE, + 'SPOKE_CONFIGURATOR_EMERGENCY_ROLE' + ); + _assertRoleLabeled( + am, + Roles.SPOKE_CONFIGURATOR_RISK_MANAGEMENT_ROLE, + 'SPOKE_CONFIGURATOR_RISK_MANAGEMENT_ROLE' ); // Total label count - assertEq(accessManager.getRoleLabelCount(), 9, 'total label count'); + assertEq(am.getRoleLabelCount(), 19, 'total label count'); + } + + function _assertRoleLabeled( + IAccessManagerEnumerable am, + uint64 role, + string memory label + ) internal view { + assertTrue(am.isRoleLabeled(role), string.concat(label, ' labeled')); + assertEq(am.getLabelOfRole(role), label); + assertEq(am.getRoleOfLabel(label), role); } function test_labelAllRoles_reverts_zeroAddress() public { diff --git a/tests/deployments/procedures/deploy/roles/AaveV4HubConfiguratorRolesProcedure.t.sol b/tests/deployments/procedures/deploy/roles/AaveV4HubConfiguratorRolesProcedure.t.sol index 70358d7fd..2e13970f0 100644 --- a/tests/deployments/procedures/deploy/roles/AaveV4HubConfiguratorRolesProcedure.t.sol +++ b/tests/deployments/procedures/deploy/roles/AaveV4HubConfiguratorRolesProcedure.t.sol @@ -68,11 +68,11 @@ contract AaveV4HubConfiguratorRolesProcedureTest is ProceduresBase { _grantAdminToWrapper(address(wrapper)); wrapper.grantHubConfiguratorAllRoles({accessManager: accessManager, admin: admin}); - (bool hasRole, ) = IAccessManager(accessManager).hasRole( - Roles.HUB_CONFIGURATOR_DOMAIN_ADMIN_ROLE, - admin - ); - assertTrue(hasRole); + uint64[] memory roles = _allRoles(); + for (uint256 i; i < roles.length; i++) { + (bool hasRole, ) = IAccessManager(accessManager).hasRole(roles[i], admin); + assertTrue(hasRole, vm.toString(roles[i])); + } } function test_setupHubConfiguratorAllRoles() public { @@ -82,12 +82,15 @@ contract AaveV4HubConfiguratorRolesProcedureTest is ProceduresBase { hubConfigurator: hubConfigurator }); - bytes4[] memory selectors = wrapper.getHubConfiguratorDomainAdminRoleSelectors(); - for (uint256 i; i < selectors.length; i++) { - assertEq( - IAccessManager(accessManager).getTargetFunctionRole(hubConfigurator, selectors[i]), - Roles.HUB_CONFIGURATOR_DOMAIN_ADMIN_ROLE - ); + uint64[] memory roles = _allRoles(); + for (uint256 i; i < roles.length; i++) { + bytes4[] memory selectors = _selectorsOf(roles[i]); + for (uint256 j; j < selectors.length; j++) { + assertEq( + IAccessManager(accessManager).getTargetFunctionRole(hubConfigurator, selectors[j]), + roles[i] + ); + } } } @@ -98,29 +101,74 @@ contract AaveV4HubConfiguratorRolesProcedureTest is ProceduresBase { function test_getHubConfiguratorDomainAdminRoleSelectors() public view { bytes4[] memory selectors = wrapper.getHubConfiguratorDomainAdminRoleSelectors(); - assertEq(selectors.length, 22); + assertEq(selectors.length, 7); + assertEq(selectors[0], IHubConfigurator.updateLiquidityFee.selector); + assertEq(selectors[1], IHubConfigurator.updateFeeReceiver.selector); + assertEq(selectors[2], IHubConfigurator.updateFeeConfig.selector); + assertEq(selectors[3], IHubConfigurator.updateInterestRateStrategy.selector); + assertEq(selectors[4], IHubConfigurator.updateReinvestmentController.selector); + assertEq(selectors[5], IHubConfigurator.resetAssetCaps.selector); + assertEq(selectors[6], IHubConfigurator.resetSpokeCaps.selector); + } + + function test_getHubConfiguratorSpokeActiveRoleSelectors() public view { + bytes4[] memory selectors = wrapper.getHubConfiguratorSpokeActiveRoleSelectors(); + assertEq(selectors.length, 1); + assertEq(selectors[0], IHubConfigurator.updateSpokeActive.selector); + } + + function test_getHubConfiguratorSpokeHaltedRoleSelectors() public view { + bytes4[] memory selectors = wrapper.getHubConfiguratorSpokeHaltedRoleSelectors(); + assertEq(selectors.length, 1); + assertEq(selectors[0], IHubConfigurator.updateSpokeHalted.selector); + } + + function test_getHubConfiguratorListingRoleSelectors() public view { + bytes4[] memory selectors = wrapper.getHubConfiguratorListingRoleSelectors(); + assertEq(selectors.length, 4); assertEq(selectors[0], IHubConfigurator.addAsset.selector); assertEq(selectors[1], IHubConfigurator.addAssetWithDecimals.selector); - assertEq(selectors[2], IHubConfigurator.updateLiquidityFee.selector); - assertEq(selectors[3], IHubConfigurator.updateFeeReceiver.selector); - assertEq(selectors[4], IHubConfigurator.updateFeeConfig.selector); - assertEq(selectors[5], IHubConfigurator.updateInterestRateStrategy.selector); - assertEq(selectors[6], IHubConfigurator.updateReinvestmentController.selector); - assertEq(selectors[7], IHubConfigurator.resetAssetCaps.selector); - assertEq(selectors[8], IHubConfigurator.deactivateAsset.selector); - assertEq(selectors[9], IHubConfigurator.haltAsset.selector); - assertEq(selectors[10], IHubConfigurator.addSpoke.selector); - assertEq(selectors[11], IHubConfigurator.addSpokeToAssets.selector); - assertEq(selectors[12], IHubConfigurator.updateSpokeActive.selector); - assertEq(selectors[13], IHubConfigurator.updateSpokeHalted.selector); - assertEq(selectors[14], IHubConfigurator.updateSpokeAddCap.selector); - assertEq(selectors[15], IHubConfigurator.updateSpokeDrawCap.selector); - assertEq(selectors[16], IHubConfigurator.updateSpokeRiskPremiumThreshold.selector); - assertEq(selectors[17], IHubConfigurator.updateSpokeCaps.selector); - assertEq(selectors[18], IHubConfigurator.deactivateSpoke.selector); - assertEq(selectors[19], IHubConfigurator.haltSpoke.selector); - assertEq(selectors[20], IHubConfigurator.resetSpokeCaps.selector); - assertEq(selectors[21], IHubConfigurator.updateInterestRateData.selector); + assertEq(selectors[2], IHubConfigurator.addSpoke.selector); + assertEq(selectors[3], IHubConfigurator.addSpokeToAssets.selector); + } + + function test_getHubConfiguratorEmergencyRoleSelectors() public view { + bytes4[] memory selectors = wrapper.getHubConfiguratorEmergencyRoleSelectors(); + assertEq(selectors.length, 4); + assertEq(selectors[0], IHubConfigurator.deactivateAsset.selector); + assertEq(selectors[1], IHubConfigurator.haltAsset.selector); + assertEq(selectors[2], IHubConfigurator.deactivateSpoke.selector); + assertEq(selectors[3], IHubConfigurator.haltSpoke.selector); + } + + function test_getHubConfiguratorRiskManagementRoleSelectors() public view { + bytes4[] memory selectors = wrapper.getHubConfiguratorRiskManagementRoleSelectors(); + assertEq(selectors.length, 5); + assertEq(selectors[0], IHubConfigurator.updateSpokeAddCap.selector); + assertEq(selectors[1], IHubConfigurator.updateSpokeDrawCap.selector); + assertEq(selectors[2], IHubConfigurator.updateSpokeCaps.selector); + assertEq(selectors[3], IHubConfigurator.updateSpokeRiskPremiumThreshold.selector); + assertEq(selectors[4], IHubConfigurator.updateInterestRateData.selector); + } + + /// @dev The five granular roles plus the residual domain admin must partition the + /// HubConfigurator selectors: no selector is left unassigned and none is shared. + function test_rolesPartitionAllSelectors() public view { + uint64[] memory roles = _allRoles(); + bytes4[] memory seen = new bytes4[](22); + uint256 count; + + for (uint256 i; i < roles.length; i++) { + bytes4[] memory selectors = _selectorsOf(roles[i]); + for (uint256 j; j < selectors.length; j++) { + for (uint256 k; k < count; k++) { + assertTrue(seen[k] != selectors[j], 'selector assigned to two roles'); + } + seen[count++] = selectors[j]; + } + } + + assertEq(count, 22, 'selector count diverges from the HubConfigurator surface'); } function test_canCall_hubConfiguratorAllRoles() public { @@ -131,6 +179,58 @@ contract AaveV4HubConfiguratorRolesProcedureTest is ProceduresBase { hubConfigurator: hubConfigurator }); - _assertCanCall(hubConfigurator, wrapper.getHubConfiguratorDomainAdminRoleSelectors()); + uint64[] memory roles = _allRoles(); + for (uint256 i; i < roles.length; i++) { + _assertCanCall(hubConfigurator, _selectorsOf(roles[i])); + } + } + + /// @dev A holder of a single granular role can only call that role's selectors. + function test_canCall_hubConfiguratorRiskManagementRoleOnly() public { + _grantAdminToWrapper(address(wrapper)); + wrapper.grantHubConfiguratorRole({ + accessManager: accessManager, + role: Roles.HUB_CONFIGURATOR_RISK_MANAGEMENT_ROLE, + admin: admin + }); + wrapper.setupHubConfiguratorAllRoles({ + accessManager: accessManager, + hubConfigurator: hubConfigurator + }); + + _assertCanCall(hubConfigurator, wrapper.getHubConfiguratorRiskManagementRoleSelectors()); + _assertCannotCall(hubConfigurator, wrapper.getHubConfiguratorListingRoleSelectors()); + _assertCannotCall(hubConfigurator, wrapper.getHubConfiguratorEmergencyRoleSelectors()); + _assertCannotCall(hubConfigurator, wrapper.getHubConfiguratorDomainAdminRoleSelectors()); + } + + function _allRoles() internal pure returns (uint64[] memory) { + uint64[] memory roles = new uint64[](6); + roles[0] = Roles.HUB_CONFIGURATOR_DOMAIN_ADMIN_ROLE; + roles[1] = Roles.HUB_CONFIGURATOR_SPOKE_ACTIVE_ROLE; + roles[2] = Roles.HUB_CONFIGURATOR_SPOKE_HALTED_ROLE; + roles[3] = Roles.HUB_CONFIGURATOR_LISTING_ROLE; + roles[4] = Roles.HUB_CONFIGURATOR_EMERGENCY_ROLE; + roles[5] = Roles.HUB_CONFIGURATOR_RISK_MANAGEMENT_ROLE; + return roles; + } + + function _selectorsOf(uint64 role) internal view returns (bytes4[] memory) { + if (role == Roles.HUB_CONFIGURATOR_DOMAIN_ADMIN_ROLE) { + return wrapper.getHubConfiguratorDomainAdminRoleSelectors(); + } + if (role == Roles.HUB_CONFIGURATOR_SPOKE_ACTIVE_ROLE) { + return wrapper.getHubConfiguratorSpokeActiveRoleSelectors(); + } + if (role == Roles.HUB_CONFIGURATOR_SPOKE_HALTED_ROLE) { + return wrapper.getHubConfiguratorSpokeHaltedRoleSelectors(); + } + if (role == Roles.HUB_CONFIGURATOR_LISTING_ROLE) { + return wrapper.getHubConfiguratorListingRoleSelectors(); + } + if (role == Roles.HUB_CONFIGURATOR_EMERGENCY_ROLE) { + return wrapper.getHubConfiguratorEmergencyRoleSelectors(); + } + return wrapper.getHubConfiguratorRiskManagementRoleSelectors(); } } diff --git a/tests/deployments/procedures/deploy/roles/AaveV4SpokeConfiguratorRolesProcedure.t.sol b/tests/deployments/procedures/deploy/roles/AaveV4SpokeConfiguratorRolesProcedure.t.sol index ba5214d0e..34526e6f2 100644 --- a/tests/deployments/procedures/deploy/roles/AaveV4SpokeConfiguratorRolesProcedure.t.sol +++ b/tests/deployments/procedures/deploy/roles/AaveV4SpokeConfiguratorRolesProcedure.t.sol @@ -68,11 +68,11 @@ contract AaveV4SpokeConfiguratorRolesProcedureTest is ProceduresBase { _grantAdminToWrapper(address(wrapper)); wrapper.grantSpokeConfiguratorAllRoles({accessManager: accessManager, admin: admin}); - (bool hasRole, ) = IAccessManager(accessManager).hasRole( - Roles.SPOKE_CONFIGURATOR_DOMAIN_ADMIN_ROLE, - admin - ); - assertTrue(hasRole); + uint64[] memory roles = _allRoles(); + for (uint256 i; i < roles.length; i++) { + (bool hasRole, ) = IAccessManager(accessManager).hasRole(roles[i], admin); + assertTrue(hasRole, vm.toString(roles[i])); + } } function test_setupSpokeConfiguratorRoles() public { @@ -82,12 +82,15 @@ contract AaveV4SpokeConfiguratorRolesProcedureTest is ProceduresBase { spokeConfigurator: spokeConfigurator }); - bytes4[] memory selectors = wrapper.getSpokeConfiguratorDomainAdminRoleSelectors(); - for (uint256 i; i < selectors.length; i++) { - assertEq( - IAccessManager(accessManager).getTargetFunctionRole(spokeConfigurator, selectors[i]), - Roles.SPOKE_CONFIGURATOR_DOMAIN_ADMIN_ROLE - ); + uint64[] memory roles = _allRoles(); + for (uint256 i; i < roles.length; i++) { + bytes4[] memory selectors = _selectorsOf(roles[i]); + for (uint256 j; j < selectors.length; j++) { + assertEq( + IAccessManager(accessManager).getTargetFunctionRole(spokeConfigurator, selectors[j]), + roles[i] + ); + } } } @@ -98,31 +101,76 @@ contract AaveV4SpokeConfiguratorRolesProcedureTest is ProceduresBase { function test_getSpokeConfiguratorDomainAdminRoleSelectors() public view { bytes4[] memory selectors = wrapper.getSpokeConfiguratorDomainAdminRoleSelectors(); - assertEq(selectors.length, 24); + assertEq(selectors.length, 2); assertEq(selectors[0], ISpokeConfigurator.updateReservePriceSource.selector); - assertEq(selectors[1], ISpokeConfigurator.updateLiquidationTargetHealthFactor.selector); - assertEq(selectors[2], ISpokeConfigurator.updateHealthFactorForMaxBonus.selector); - assertEq(selectors[3], ISpokeConfigurator.updateLiquidationBonusFactor.selector); - assertEq(selectors[4], ISpokeConfigurator.updateLiquidationConfig.selector); - assertEq(selectors[5], ISpokeConfigurator.addReserve.selector); - assertEq(selectors[6], ISpokeConfigurator.updatePaused.selector); - assertEq(selectors[7], ISpokeConfigurator.updateFrozen.selector); - assertEq(selectors[8], ISpokeConfigurator.updateBorrowable.selector); - assertEq(selectors[9], ISpokeConfigurator.updateReceiveSharesEnabled.selector); - assertEq(selectors[10], ISpokeConfigurator.updateCollateralRisk.selector); - assertEq(selectors[11], ISpokeConfigurator.addCollateralFactor.selector); - assertEq(selectors[12], ISpokeConfigurator.updateCollateralFactor.selector); - assertEq(selectors[13], ISpokeConfigurator.addMaxLiquidationBonus.selector); - assertEq(selectors[14], ISpokeConfigurator.updateMaxLiquidationBonus.selector); - assertEq(selectors[15], ISpokeConfigurator.addLiquidationFee.selector); - assertEq(selectors[16], ISpokeConfigurator.updateLiquidationFee.selector); - assertEq(selectors[17], ISpokeConfigurator.addDynamicReserveConfig.selector); - assertEq(selectors[18], ISpokeConfigurator.updateDynamicReserveConfig.selector); - assertEq(selectors[19], ISpokeConfigurator.pauseAllReserves.selector); - assertEq(selectors[20], ISpokeConfigurator.freezeAllReserves.selector); - assertEq(selectors[21], ISpokeConfigurator.pauseReserve.selector); - assertEq(selectors[22], ISpokeConfigurator.freezeReserve.selector); - assertEq(selectors[23], ISpokeConfigurator.updatePositionManager.selector); + assertEq(selectors[1], ISpokeConfigurator.updatePositionManager.selector); + } + + function test_getSpokeConfiguratorPauseRoleSelectors() public view { + bytes4[] memory selectors = wrapper.getSpokeConfiguratorPauseRoleSelectors(); + assertEq(selectors.length, 1); + assertEq(selectors[0], ISpokeConfigurator.updatePaused.selector); + } + + function test_getSpokeConfiguratorFreezeRoleSelectors() public view { + bytes4[] memory selectors = wrapper.getSpokeConfiguratorFreezeRoleSelectors(); + assertEq(selectors.length, 1); + assertEq(selectors[0], ISpokeConfigurator.updateFrozen.selector); + } + + function test_getSpokeConfiguratorListingRoleSelectors() public view { + bytes4[] memory selectors = wrapper.getSpokeConfiguratorListingRoleSelectors(); + assertEq(selectors.length, 3); + assertEq(selectors[0], ISpokeConfigurator.addReserve.selector); + assertEq(selectors[1], ISpokeConfigurator.updateBorrowable.selector); + assertEq(selectors[2], ISpokeConfigurator.updateReceiveSharesEnabled.selector); + } + + function test_getSpokeConfiguratorEmergencyRoleSelectors() public view { + bytes4[] memory selectors = wrapper.getSpokeConfiguratorEmergencyRoleSelectors(); + assertEq(selectors.length, 4); + assertEq(selectors[0], ISpokeConfigurator.pauseReserve.selector); + assertEq(selectors[1], ISpokeConfigurator.pauseAllReserves.selector); + assertEq(selectors[2], ISpokeConfigurator.freezeReserve.selector); + assertEq(selectors[3], ISpokeConfigurator.freezeAllReserves.selector); + } + + function test_getSpokeConfiguratorRiskManagementRoleSelectors() public view { + bytes4[] memory selectors = wrapper.getSpokeConfiguratorRiskManagementRoleSelectors(); + assertEq(selectors.length, 13); + assertEq(selectors[0], ISpokeConfigurator.updateCollateralRisk.selector); + assertEq(selectors[1], ISpokeConfigurator.addCollateralFactor.selector); + assertEq(selectors[2], ISpokeConfigurator.updateCollateralFactor.selector); + assertEq(selectors[3], ISpokeConfigurator.addMaxLiquidationBonus.selector); + assertEq(selectors[4], ISpokeConfigurator.updateMaxLiquidationBonus.selector); + assertEq(selectors[5], ISpokeConfigurator.addLiquidationFee.selector); + assertEq(selectors[6], ISpokeConfigurator.updateLiquidationFee.selector); + assertEq(selectors[7], ISpokeConfigurator.addDynamicReserveConfig.selector); + assertEq(selectors[8], ISpokeConfigurator.updateDynamicReserveConfig.selector); + assertEq(selectors[9], ISpokeConfigurator.updateLiquidationTargetHealthFactor.selector); + assertEq(selectors[10], ISpokeConfigurator.updateHealthFactorForMaxBonus.selector); + assertEq(selectors[11], ISpokeConfigurator.updateLiquidationBonusFactor.selector); + assertEq(selectors[12], ISpokeConfigurator.updateLiquidationConfig.selector); + } + + /// @dev The five granular roles plus the residual domain admin must partition the + /// SpokeConfigurator selectors: no selector is left unassigned and none is shared. + function test_rolesPartitionAllSelectors() public view { + uint64[] memory roles = _allRoles(); + bytes4[] memory seen = new bytes4[](24); + uint256 count; + + for (uint256 i; i < roles.length; i++) { + bytes4[] memory selectors = _selectorsOf(roles[i]); + for (uint256 j; j < selectors.length; j++) { + for (uint256 k; k < count; k++) { + assertTrue(seen[k] != selectors[j], 'selector assigned to two roles'); + } + seen[count++] = selectors[j]; + } + } + + assertEq(count, 24, 'selector count diverges from the SpokeConfigurator surface'); } function test_canCall_spokeConfiguratorAllRoles() public { @@ -133,6 +181,58 @@ contract AaveV4SpokeConfiguratorRolesProcedureTest is ProceduresBase { spokeConfigurator: spokeConfigurator }); - _assertCanCall(spokeConfigurator, wrapper.getSpokeConfiguratorDomainAdminRoleSelectors()); + uint64[] memory roles = _allRoles(); + for (uint256 i; i < roles.length; i++) { + _assertCanCall(spokeConfigurator, _selectorsOf(roles[i])); + } + } + + /// @dev A holder of a single granular role can only call that role's selectors. + function test_canCall_spokeConfiguratorEmergencyRoleOnly() public { + _grantAdminToWrapper(address(wrapper)); + wrapper.grantSpokeConfiguratorRole({ + accessManager: accessManager, + role: Roles.SPOKE_CONFIGURATOR_EMERGENCY_ROLE, + admin: admin + }); + wrapper.setupSpokeConfiguratorRoles({ + accessManager: accessManager, + spokeConfigurator: spokeConfigurator + }); + + _assertCanCall(spokeConfigurator, wrapper.getSpokeConfiguratorEmergencyRoleSelectors()); + _assertCannotCall(spokeConfigurator, wrapper.getSpokeConfiguratorPauseRoleSelectors()); + _assertCannotCall(spokeConfigurator, wrapper.getSpokeConfiguratorFreezeRoleSelectors()); + _assertCannotCall(spokeConfigurator, wrapper.getSpokeConfiguratorRiskManagementRoleSelectors()); + } + + function _allRoles() internal pure returns (uint64[] memory) { + uint64[] memory roles = new uint64[](6); + roles[0] = Roles.SPOKE_CONFIGURATOR_DOMAIN_ADMIN_ROLE; + roles[1] = Roles.SPOKE_CONFIGURATOR_PAUSE_ROLE; + roles[2] = Roles.SPOKE_CONFIGURATOR_FREEZE_ROLE; + roles[3] = Roles.SPOKE_CONFIGURATOR_LISTING_ROLE; + roles[4] = Roles.SPOKE_CONFIGURATOR_EMERGENCY_ROLE; + roles[5] = Roles.SPOKE_CONFIGURATOR_RISK_MANAGEMENT_ROLE; + return roles; + } + + function _selectorsOf(uint64 role) internal view returns (bytes4[] memory) { + if (role == Roles.SPOKE_CONFIGURATOR_DOMAIN_ADMIN_ROLE) { + return wrapper.getSpokeConfiguratorDomainAdminRoleSelectors(); + } + if (role == Roles.SPOKE_CONFIGURATOR_PAUSE_ROLE) { + return wrapper.getSpokeConfiguratorPauseRoleSelectors(); + } + if (role == Roles.SPOKE_CONFIGURATOR_FREEZE_ROLE) { + return wrapper.getSpokeConfiguratorFreezeRoleSelectors(); + } + if (role == Roles.SPOKE_CONFIGURATOR_LISTING_ROLE) { + return wrapper.getSpokeConfiguratorListingRoleSelectors(); + } + if (role == Roles.SPOKE_CONFIGURATOR_EMERGENCY_ROLE) { + return wrapper.getSpokeConfiguratorEmergencyRoleSelectors(); + } + return wrapper.getSpokeConfiguratorRiskManagementRoleSelectors(); } } diff --git a/tests/helpers/mocks/deployments/procedures/AaveV4HubConfiguratorRolesProcedureWrapper.sol b/tests/helpers/mocks/deployments/procedures/AaveV4HubConfiguratorRolesProcedureWrapper.sol index 30260922d..2809f76ae 100644 --- a/tests/helpers/mocks/deployments/procedures/AaveV4HubConfiguratorRolesProcedureWrapper.sol +++ b/tests/helpers/mocks/deployments/procedures/AaveV4HubConfiguratorRolesProcedureWrapper.sol @@ -39,4 +39,24 @@ contract AaveV4HubConfiguratorRolesProcedureWrapper { function getHubConfiguratorDomainAdminRoleSelectors() external pure returns (bytes4[] memory) { return Roles.getHubConfiguratorDomainAdminRoleSelectors(); } + + function getHubConfiguratorSpokeActiveRoleSelectors() external pure returns (bytes4[] memory) { + return Roles.getHubConfiguratorSpokeActiveRoleSelectors(); + } + + function getHubConfiguratorSpokeHaltedRoleSelectors() external pure returns (bytes4[] memory) { + return Roles.getHubConfiguratorSpokeHaltedRoleSelectors(); + } + + function getHubConfiguratorListingRoleSelectors() external pure returns (bytes4[] memory) { + return Roles.getHubConfiguratorListingRoleSelectors(); + } + + function getHubConfiguratorEmergencyRoleSelectors() external pure returns (bytes4[] memory) { + return Roles.getHubConfiguratorEmergencyRoleSelectors(); + } + + function getHubConfiguratorRiskManagementRoleSelectors() external pure returns (bytes4[] memory) { + return Roles.getHubConfiguratorRiskManagementRoleSelectors(); + } } diff --git a/tests/helpers/mocks/deployments/procedures/AaveV4SpokeConfiguratorRolesProcedureWrapper.sol b/tests/helpers/mocks/deployments/procedures/AaveV4SpokeConfiguratorRolesProcedureWrapper.sol index e72d58b38..f3ea2d4ee 100644 --- a/tests/helpers/mocks/deployments/procedures/AaveV4SpokeConfiguratorRolesProcedureWrapper.sol +++ b/tests/helpers/mocks/deployments/procedures/AaveV4SpokeConfiguratorRolesProcedureWrapper.sol @@ -39,4 +39,28 @@ contract AaveV4SpokeConfiguratorRolesProcedureWrapper { function getSpokeConfiguratorDomainAdminRoleSelectors() external pure returns (bytes4[] memory) { return Roles.getSpokeConfiguratorDomainAdminRoleSelectors(); } + + function getSpokeConfiguratorPauseRoleSelectors() external pure returns (bytes4[] memory) { + return Roles.getSpokeConfiguratorPauseRoleSelectors(); + } + + function getSpokeConfiguratorFreezeRoleSelectors() external pure returns (bytes4[] memory) { + return Roles.getSpokeConfiguratorFreezeRoleSelectors(); + } + + function getSpokeConfiguratorListingRoleSelectors() external pure returns (bytes4[] memory) { + return Roles.getSpokeConfiguratorListingRoleSelectors(); + } + + function getSpokeConfiguratorEmergencyRoleSelectors() external pure returns (bytes4[] memory) { + return Roles.getSpokeConfiguratorEmergencyRoleSelectors(); + } + + function getSpokeConfiguratorRiskManagementRoleSelectors() + external + pure + returns (bytes4[] memory) + { + return Roles.getSpokeConfiguratorRiskManagementRoleSelectors(); + } } diff --git a/tests/utils/BatchTestProcedures.sol b/tests/utils/BatchTestProcedures.sol index ed33f852e..718855299 100644 --- a/tests/utils/BatchTestProcedures.sol +++ b/tests/utils/BatchTestProcedures.sol @@ -438,7 +438,7 @@ contract BatchTestProcedures is Test, Create2TestHelper, WETHDeployProcedure { } function _checkRoleLabels(IAccessManagerEnumerable accessManager) internal view { - assertEq(accessManager.getRoleLabelCount(), 9, 'role label count'); + assertEq(accessManager.getRoleLabelCount(), 19, 'role label count'); // Hub roles assertTrue( @@ -458,6 +458,26 @@ contract BatchTestProcedures is Test, Create2TestHelper, WETHDeployProcedure { accessManager.getLabelOfRole(Roles.HUB_CONFIGURATOR_DOMAIN_ADMIN_ROLE), 'HUB_CONFIGURATOR_DOMAIN_ADMIN_ROLE' ); + assertEq( + accessManager.getLabelOfRole(Roles.HUB_CONFIGURATOR_SPOKE_ACTIVE_ROLE), + 'HUB_CONFIGURATOR_SPOKE_ACTIVE_ROLE' + ); + assertEq( + accessManager.getLabelOfRole(Roles.HUB_CONFIGURATOR_SPOKE_HALTED_ROLE), + 'HUB_CONFIGURATOR_SPOKE_HALTED_ROLE' + ); + assertEq( + accessManager.getLabelOfRole(Roles.HUB_CONFIGURATOR_LISTING_ROLE), + 'HUB_CONFIGURATOR_LISTING_ROLE' + ); + assertEq( + accessManager.getLabelOfRole(Roles.HUB_CONFIGURATOR_EMERGENCY_ROLE), + 'HUB_CONFIGURATOR_EMERGENCY_ROLE' + ); + assertEq( + accessManager.getLabelOfRole(Roles.HUB_CONFIGURATOR_RISK_MANAGEMENT_ROLE), + 'HUB_CONFIGURATOR_RISK_MANAGEMENT_ROLE' + ); // Spoke roles assertEq( @@ -478,6 +498,26 @@ contract BatchTestProcedures is Test, Create2TestHelper, WETHDeployProcedure { accessManager.getLabelOfRole(Roles.SPOKE_CONFIGURATOR_DOMAIN_ADMIN_ROLE), 'SPOKE_CONFIGURATOR_DOMAIN_ADMIN_ROLE' ); + assertEq( + accessManager.getLabelOfRole(Roles.SPOKE_CONFIGURATOR_PAUSE_ROLE), + 'SPOKE_CONFIGURATOR_PAUSE_ROLE' + ); + assertEq( + accessManager.getLabelOfRole(Roles.SPOKE_CONFIGURATOR_FREEZE_ROLE), + 'SPOKE_CONFIGURATOR_FREEZE_ROLE' + ); + assertEq( + accessManager.getLabelOfRole(Roles.SPOKE_CONFIGURATOR_LISTING_ROLE), + 'SPOKE_CONFIGURATOR_LISTING_ROLE' + ); + assertEq( + accessManager.getLabelOfRole(Roles.SPOKE_CONFIGURATOR_EMERGENCY_ROLE), + 'SPOKE_CONFIGURATOR_EMERGENCY_ROLE' + ); + assertEq( + accessManager.getLabelOfRole(Roles.SPOKE_CONFIGURATOR_RISK_MANAGEMENT_ROLE), + 'SPOKE_CONFIGURATOR_RISK_MANAGEMENT_ROLE' + ); } function _checkSpokeRoles( @@ -767,24 +807,34 @@ contract BatchTestProcedures is Test, Create2TestHelper, WETHDeployProcedure { InputUtils.FullDeployInputs memory inputs ) internal view { address hubConfigurator = report.configuratorBatchReport.hubConfigurator; - bytes4[] memory selectors = Roles.getHubConfiguratorDomainAdminRoleSelectors(); + uint64[] memory roles = new uint64[](6); + roles[0] = Roles.HUB_CONFIGURATOR_DOMAIN_ADMIN_ROLE; + roles[1] = Roles.HUB_CONFIGURATOR_SPOKE_ACTIVE_ROLE; + roles[2] = Roles.HUB_CONFIGURATOR_SPOKE_HALTED_ROLE; + roles[3] = Roles.HUB_CONFIGURATOR_LISTING_ROLE; + roles[4] = Roles.HUB_CONFIGURATOR_EMERGENCY_ROLE; + roles[5] = Roles.HUB_CONFIGURATOR_RISK_MANAGEMENT_ROLE; - for (uint256 i; i < selectors.length; i++) { - assertEq( - accessManager.getTargetFunctionRole(hubConfigurator, selectors[i]), - Roles.HUB_CONFIGURATOR_DOMAIN_ADMIN_ROLE, - 'HubConfigurator domain admin selector role mapping' - ); - } + for (uint256 r; r < roles.length; r++) { + bytes4[] memory selectors = _selectorsOfHubConfiguratorRole(roles[r]); - if (inputs.grantRoles && inputs.hubLabels.length > 0) { for (uint256 i; i < selectors.length; i++) { - (bool allowed, ) = accessManager.canCall( - inputs.hubConfiguratorAdmin, - hubConfigurator, - selectors[i] + assertEq( + accessManager.getTargetFunctionRole(hubConfigurator, selectors[i]), + roles[r], + 'HubConfigurator selector role mapping' ); - assertTrue(allowed, 'HubConfigurator admin canCall selector'); + } + + if (inputs.grantRoles && inputs.hubLabels.length > 0) { + for (uint256 i; i < selectors.length; i++) { + (bool allowed, ) = accessManager.canCall( + inputs.hubConfiguratorAdmin, + hubConfigurator, + selectors[i] + ); + assertTrue(allowed, 'HubConfigurator admin canCall selector'); + } } } } @@ -795,26 +845,74 @@ contract BatchTestProcedures is Test, Create2TestHelper, WETHDeployProcedure { InputUtils.FullDeployInputs memory inputs ) internal view { address spokeConfigurator = report.configuratorBatchReport.spokeConfigurator; - bytes4[] memory selectors = Roles.getSpokeConfiguratorDomainAdminRoleSelectors(); + uint64[] memory roles = new uint64[](6); + roles[0] = Roles.SPOKE_CONFIGURATOR_DOMAIN_ADMIN_ROLE; + roles[1] = Roles.SPOKE_CONFIGURATOR_PAUSE_ROLE; + roles[2] = Roles.SPOKE_CONFIGURATOR_FREEZE_ROLE; + roles[3] = Roles.SPOKE_CONFIGURATOR_LISTING_ROLE; + roles[4] = Roles.SPOKE_CONFIGURATOR_EMERGENCY_ROLE; + roles[5] = Roles.SPOKE_CONFIGURATOR_RISK_MANAGEMENT_ROLE; - for (uint256 i; i < selectors.length; i++) { - assertEq( - accessManager.getTargetFunctionRole(spokeConfigurator, selectors[i]), - Roles.SPOKE_CONFIGURATOR_DOMAIN_ADMIN_ROLE, - 'SpokeConfigurator domain admin selector role mapping' - ); - } + for (uint256 r; r < roles.length; r++) { + bytes4[] memory selectors = _selectorsOfSpokeConfiguratorRole(roles[r]); - if (inputs.grantRoles && inputs.spokeLabels.length > 0) { for (uint256 i; i < selectors.length; i++) { - (bool allowed, ) = accessManager.canCall( - inputs.spokeConfiguratorAdmin, - spokeConfigurator, - selectors[i] + assertEq( + accessManager.getTargetFunctionRole(spokeConfigurator, selectors[i]), + roles[r], + 'SpokeConfigurator selector role mapping' ); - assertTrue(allowed, 'SpokeConfigurator admin canCall selector'); } + + if (inputs.grantRoles && inputs.spokeLabels.length > 0) { + for (uint256 i; i < selectors.length; i++) { + (bool allowed, ) = accessManager.canCall( + inputs.spokeConfiguratorAdmin, + spokeConfigurator, + selectors[i] + ); + assertTrue(allowed, 'SpokeConfigurator admin canCall selector'); + } + } + } + } + + function _selectorsOfHubConfiguratorRole(uint64 role) internal pure returns (bytes4[] memory) { + if (role == Roles.HUB_CONFIGURATOR_DOMAIN_ADMIN_ROLE) { + return Roles.getHubConfiguratorDomainAdminRoleSelectors(); + } + if (role == Roles.HUB_CONFIGURATOR_SPOKE_ACTIVE_ROLE) { + return Roles.getHubConfiguratorSpokeActiveRoleSelectors(); + } + if (role == Roles.HUB_CONFIGURATOR_SPOKE_HALTED_ROLE) { + return Roles.getHubConfiguratorSpokeHaltedRoleSelectors(); + } + if (role == Roles.HUB_CONFIGURATOR_LISTING_ROLE) { + return Roles.getHubConfiguratorListingRoleSelectors(); + } + if (role == Roles.HUB_CONFIGURATOR_EMERGENCY_ROLE) { + return Roles.getHubConfiguratorEmergencyRoleSelectors(); + } + return Roles.getHubConfiguratorRiskManagementRoleSelectors(); + } + + function _selectorsOfSpokeConfiguratorRole(uint64 role) internal pure returns (bytes4[] memory) { + if (role == Roles.SPOKE_CONFIGURATOR_DOMAIN_ADMIN_ROLE) { + return Roles.getSpokeConfiguratorDomainAdminRoleSelectors(); + } + if (role == Roles.SPOKE_CONFIGURATOR_PAUSE_ROLE) { + return Roles.getSpokeConfiguratorPauseRoleSelectors(); + } + if (role == Roles.SPOKE_CONFIGURATOR_FREEZE_ROLE) { + return Roles.getSpokeConfiguratorFreezeRoleSelectors(); + } + if (role == Roles.SPOKE_CONFIGURATOR_LISTING_ROLE) { + return Roles.getSpokeConfiguratorListingRoleSelectors(); + } + if (role == Roles.SPOKE_CONFIGURATOR_EMERGENCY_ROLE) { + return Roles.getSpokeConfiguratorEmergencyRoleSelectors(); } + return Roles.getSpokeConfiguratorRiskManagementRoleSelectors(); } function _checkGatewayRoles(