# E-Mode Management Flow End-to-end execution flow for setting a user's Efficiency Mode (eMode) category in Aave V3. ## Quick Reference | Aspect | Details | |--------|---------| | **Entry Point** | `Pool.setUserEMode(categoryId)` or `Pool.setUserEModeOnBehalfOf(categoryId, onBehalfOf)` | | **Key Transformations** | [Liquidation & health-factor calculations](../transformations/index.md#liquidation-calculations) | | **State Changes** | `_usersEModeCategory[user] = categoryId` | | **Events Emitted** | `UserEModeSet` | --- ## Flow Diagram ```mermaid flowchart TD %% Styling definitions classDef validation fill:#ffcccc,stroke:#ff0000,stroke-width:2px classDef transformation fill:#ccffcc,stroke:#00aa00,stroke-width:2px classDef storage fill:#ccccff,stroke:#0000ff,stroke-width:2px classDef event fill:#ffffcc,stroke:#aaaa00,stroke-width:2px classDef error fill:#ff0000,stroke:#000000,color:#fff classDef position fill:#ccffff,stroke:#00aaaa,stroke-width:2px %% Entry points Entry1["Pool.setUserEMode
categoryId"] --> Execute["SupplyLogic
executeSetUserEMode"] Entry2["Pool.setUserEModeOnBehalfOf
categoryId, onBehalfOf"] --> PositionManager{"Is Position
Manager Approved?"} class PositionManager position PositionManager -->|Yes| Execute PositionManager -->|No| RevertNotManager["REVERT
CallerNotPositionManager"] class RevertNotManager error subgraph ValidationPhase ["1. Validation Phase"] direction TB CategoryCheck{"categoryId ==
currentCategory?"} -->|Yes| ReturnEarly[Return Early] CategoryCheck -->|No| ValidateCategory["ValidationLogic
validateSetUserEMode"] class ValidateCategory validation ValidateCategory --> CategoryExists{"categoryId != 0 AND
liqThreshold == 0?"} CategoryExists -->|Yes| RevertInvalidCategory["REVERT
InconsistentEModeCategory"] class RevertInvalidCategory error CategoryExists -->|No| CheckEmptyConfig CheckEmptyConfig{"userConfig
.isEmpty()?"} -->|Yes| SkipAssetValidation CheckEmptyConfig -->|No| ValidateAssets ValidateAssets --> IterateAssets["Iterate through
user assets"] IterateAssets --> CheckBorrowed{"Asset is
Borrowed?"} CheckBorrowed -->|Yes| CheckBorrowable["Asset in category borrowableBitmap"] class CheckBorrowable validation CheckBorrowable -->|No| RevertInvalidDebt["REVERT
InvalidDebtInEmode"] class RevertInvalidDebt error CheckBorrowable -->|Yes| NextAsset CheckBorrowed -->|No| CheckCollateral CheckCollateral{"Asset is
Collateral?"} -->|Yes| CheckLtv["getUserReserveLtv
!= 0?"] class CheckLtv validation CheckLtv -->|No| RevertInvalidCollateral["REVERT
InvalidCollateralInEmode"] class RevertInvalidCollateral error CheckLtv -->|Yes| NextAsset CheckCollateral -->|No| NextAsset NextAsset --> MoreAssets{"More Assets?"} MoreAssets -->|Yes| IterateAssets MoreAssets -->|No| SkipAssetValidation end Execute --> ValidationPhase SkipAssetValidation --> UpdateStorage["STORAGE UPDATE
_usersEModeCategory
[user] = categoryId"] class UpdateStorage storage subgraph HealthFactorCheck ["2. Health Factor Validation"] direction TB ValidateHF["ValidationLogic
validateHealthFactor"] class ValidateHF validation ValidateHF --> CalculateHF["GenericLogic
calculateUserAccountData
with new eMode"] class CalculateHF transformation CalculateHF --> HFCheck{"healthFactor >=
LIQUIDATION_THRESHOLD?"} HFCheck -->|No| RevertHF["REVERT
HealthFactorLowerThanLiquidationThreshold"] class RevertHF error HFCheck -->|Yes| EmitEvent end UpdateStorage --> HealthFactorCheck EmitEvent["EMIT
UserEModeSet"] class EmitEvent event %% Error annotations %% CRITICAL: All asset validations must pass for target category %% CRITICAL: Health factor must remain healthy after category switch %% CRITICAL: Category 0 is always valid (no eMode) %% Link styles for critical paths linkStyle 6 stroke:#ff0000,stroke-width:3px linkStyle 15 stroke:#ff0000,stroke-width:3px linkStyle 21 stroke:#ff0000,stroke-width:3px ``` --- ## Step-by-Step Execution ### 1. Entry Points #### Direct Call (User setting their own eMode) **File:** `contracts/protocol/pool/Pool.sol` ```solidity function setUserEMode(uint8 categoryId) external virtual override { SupplyLogic.executeSetUserEMode( _reserves, _reservesList, _eModeCategories, _usersEModeCategory, _usersConfig[_msgSender()], _msgSender(), ADDRESSES_PROVIDER.getPriceOracle(), categoryId ); } ``` #### On Behalf Of Call (Position Manager) **File:** `contracts/protocol/pool/Pool.sol` ```solidity function setUserEModeOnBehalfOf( uint8 categoryId, address onBehalfOf ) external override onlyPositionManager(onBehalfOf) { SupplyLogic.executeSetUserEMode( _reserves, _reservesList, _eModeCategories, _usersEModeCategory, _usersConfig[onBehalfOf], onBehalfOf, ADDRESSES_PROVIDER.getPriceOracle(), categoryId ); } ``` **Note:** `setUserEModeOnBehalfOf` requires the caller to be an approved position manager for the user. ### 2. Execute Set User EMode **File:** `contracts/protocol/libraries/logic/SupplyLogic.sol` ```solidity function executeSetUserEMode( mapping(address => DataTypes.ReserveData) storage reservesData, mapping(uint256 => address) storage reservesList, mapping(uint8 => DataTypes.EModeCategory) storage eModeCategories, mapping(address => uint8) storage usersEModeCategory, DataTypes.UserConfigurationMap storage userConfig, address user, address oracle, uint8 categoryId ) external { // Early return if already in target category if (usersEModeCategory[user] == categoryId) return; // Validate that all user assets are valid in target category ValidationLogic.validateSetUserEMode( reservesData, reservesList, eModeCategories, userConfig, categoryId ); // Update user's eMode category usersEModeCategory[user] = categoryId; // Validate health factor remains healthy with new category ValidationLogic.validateHealthFactor( reservesData, reservesList, eModeCategories, userConfig, user, categoryId, oracle ); emit IPool.UserEModeSet(user, categoryId); } ``` ### 3. Validate Set User EMode **File:** `contracts/protocol/libraries/logic/ValidationLogic.sol` ```solidity function validateSetUserEMode( mapping(address => DataTypes.ReserveData) storage reservesData, mapping(uint256 => address) storage reservesList, mapping(uint8 => DataTypes.EModeCategory) storage eModeCategories, DataTypes.UserConfigurationMap memory userConfig, uint8 categoryId ) internal view { DataTypes.EModeCategory storage eModeCategory = eModeCategories[categoryId]; // Category is invalid if liquidation threshold is not set (except category 0) require( categoryId == 0 || eModeCategory.liquidationThreshold != 0, Errors.InconsistentEModeCategory() ); // eMode can always be enabled if user hasn't supplied anything if (userConfig.isEmpty()) { return; } uint256 i = 0; bool isBorrowed = false; bool isEnabledAsCollateral = false; uint256 unsafe_cachedUserConfig = userConfig.data; // Iterate through all user assets unchecked { while (unsafe_cachedUserConfig != 0) { (unsafe_cachedUserConfig, isBorrowed, isEnabledAsCollateral) = UserConfiguration .getNextFlags(unsafe_cachedUserConfig); // Ensure borrowed Reserves can be borrowed in target category if (isBorrowed) { require( categoryId != 0 ? EModeConfiguration.isReserveEnabledOnBitmap(eModeCategory.borrowableBitmap, i) : reservesData[reservesList[i]].configuration.getBorrowingEnabled(), Errors.InvalidDebtInEmode(reservesList[i], categoryId) ); } // Ensure collateral assets have non-zero LTV in target category if (isEnabledAsCollateral) { require( getUserReserveLtv(reservesData[reservesList[i]], eModeCategory, categoryId) != 0, Errors.InvalidCollateralInEmode(reservesList[i], categoryId) ); } ++i; } } } ``` ### 4. Get User Reserve LTV **File:** `contracts/protocol/libraries/logic/ValidationLogic.sol` ```solidity function getUserReserveLtv( DataTypes.ReserveData storage reserve, DataTypes.EModeCategory storage eModeCategory, uint8 categoryId ) internal view returns (uint256) { // Category 0 uses regular reserve LTV if (categoryId == 0) { return reserve.configuration.getLtv(); } // Check if asset is in category's collateral bitmap bool isCollateral = EModeConfiguration.isReserveEnabledOnBitmap( eModeCategory.collateralBitmap, reserve.id ); // Check if asset has zero LTV in eMode bool hasLtvZero = EModeConfiguration.isReserveEnabledOnBitmap( eModeCategory.ltvzeroBitmap, reserve.id ); // Return eMode LTV if asset is collateral and not ltv-zero if (isCollateral && !hasLtvZero) { return eModeCategory.ltv; } return 0; } ``` ### 5. Validate Health Factor **File:** `contracts/protocol/libraries/logic/ValidationLogic.sol` ```solidity function validateHealthFactor( mapping(address => DataTypes.ReserveData) storage reservesData, mapping(uint256 => address) storage reservesList, mapping(uint8 => DataTypes.EModeCategory) storage eModeCategories, DataTypes.UserConfigurationMap storage userConfig, address user, uint8 categoryId, address oracle ) internal view { ( uint256 totalCollateralInBaseCurrency, uint256 totalDebtInBaseCurrency, uint256 avgLtv, uint256 avgLiquidationThreshold, uint256 healthFactor, ) = GenericLogic.calculateUserAccountData( reservesData, reservesList, eModeCategories, DataTypes.CalculateUserAccountDataParams({ userConfig: userConfig, reservesCount: reservesCount, user: user, oracle: oracle, userEModeCategory: categoryId // [CRITICAL] Use new category for calculation }) ); require( healthFactor >= HEALTH_FACTOR_LIQUIDATION_THRESHOLD, Errors.HealthFactorLowerThanLiquidationThreshold() ); } ``` --- ## Amount Transformations ### E-Mode Category Configuration E-Mode categories define enhanced parameters for correlated assets: ``` Category Configuration (set by PoolConfigurator): ltv: 97_00 // 97% LTV (e.g., ETH-correlated) liquidationThreshold: 98_00 // 98% liquidation threshold liquidationBonus: 101_00 // 1% liquidation bonus collateralBitmap: 0b1010 // Which reserves can be collateral borrowableBitmap: 0b1100 // Which reserves can be borrowed ltvzeroBitmap: 0b0000 // Which collaterals have 0 LTV Standard Asset LTV (outside eMode): ltv: 82_50 // 82.5% typical for volatile assets liquidationThreshold: 86_00 // 86% typical threshold ``` ### Health Factor Calculation with E-Mode ``` User enters eMode category 1 (ETH-correlated): Before (eMode 0 - no efficiency mode): Collateral: 10 ETH @ $2000 = $20,000 LTV: 82.5% Borrowing Power: $20,000 * 0.825 = $16,500 Debt: 5 ETH @ $2000 = $10,000 Health Factor: (20,000 * 0.86) / 10,000 = 1.72 After (eMode 1 - ETH correlated): Collateral: 10 ETH @ $2000 = $20,000 LTV: 97% Borrowing Power: $20,000 * 0.97 = $19,400 Debt: 5 ETH @ $2000 = $10,000 Health Factor: (20,000 * 0.98) / 10,000 = 1.96 Key Points: - Higher LTV = more borrowing power - Higher liquidation threshold = safer position - Only works for correlated assets in same category - Price correlation reduces liquidation risk ``` --- ## Event Details ### UserEModeSet Event ```solidity event UserEModeSet( address indexed user, // The user whose eMode was updated uint8 categoryId // The new eMode category (0 = no eMode) ); ``` **Emitted when:** - User successfully changes their eMode category - Position manager changes eMode on behalf of a user **Category 0:** Represents "no eMode" - standard protocol behavior with individual reserve LTVs --- ## Error Conditions | Error | Condition | File | |-------|-----------|------| | `InconsistentEModeCategory` | `categoryId != 0` AND `eModeCategory.liquidationThreshold == 0` | ValidationLogic.sol | | `InvalidDebtInEmode` | User has borrowed from an Asset not in target category's `borrowableBitmap` | ValidationLogic.sol | | `InvalidCollateralInEmode` | User has collateral with zero LTV in target category | ValidationLogic.sol | | `HealthFactorLowerThanLiquidationThreshold` | HF < 1 after switching categories | ValidationLogic.sol | | `CallerNotPositionManager` | Caller is not approved position manager for `onBehalfOf` | Pool.sol | --- ## Related Flows - [Supply Flow](./supply.md) - Supplying assets to use as collateral - [Borrow Flow](./borrow.md) - Borrowing against eMode collateral - [Withdraw Flow](./withdraw.md) - Withdrawing collateral affects eMode eligibility - [Liquidation Flow](./liquidation.md) - Liquidation uses eMode thresholds - [Collateral Management](./collateral_management.md) - Managing collateral in eMode --- ## Source File Locations ``` contracts/protocol/pool/Pool.sol contracts/protocol/libraries/logic/SupplyLogic.sol contracts/protocol/libraries/logic/ValidationLogic.sol contracts/protocol/libraries/logic/GenericLogic.sol contracts/protocol/libraries/configuration/EModeConfiguration.sol contracts/protocol/libraries/types/DataTypes.sol ``` --- ## E-Mode Category Details ### What is E-Mode? **Efficiency Mode (eMode)** allows users to maximize borrowing power when using correlated assets as collateral and debt. It works by: 1. **Category-based LTV:** Assets in the same category share higher LTV/liquidation thresholds 2. **Correlation assumption:** Assumes assets in same category move together in price 3. **Reduced liquidation risk:** Lower risk of liquidation due to price correlation ### Category Structure ```solidity struct EModeCategory { uint16 ltv; // Loan to Value (in bps, e.g., 97_00 = 97%) uint16 liquidationThreshold; // Liquidation threshold (in bps) uint16 liquidationBonus; // Liquidation bonus (in bps) string label; // Human-readable label uint128 collateralBitmap; // Bitmap of reserves usable as collateral uint128 borrowableBitmap; // Bitmap of reserves that can be borrowed uint128 ltvzeroBitmap; // Bitmap of collaterals with 0 LTV } ``` ### Common Categories | Category | Label | Typical LTV | Typical LT | Assets | |----------|-------|-------------|------------|--------| | 0 | None | Asset-specific | Asset-specific | All (no efficiency) | | 1 | ETH Correlated | 97% | 98% | ETH, stETH, rETH, cbETH | | 2 | Stablecoins | 97% | 98% | USDC, USDT, DAI, LUSD | | 3 | LSTs | 95% | 96% | stETH, rETH only | ### Important Notes - **Category 0 is always valid:** It represents standard protocol behavior - **Category 0 cannot be configured:** Reserved for volatile heterogeneous assets - **Bitmap system:** Uses 128-bit bitmaps to track up to 128 reserves per category - **Health factor recalculation:** Changing categories recalculates HF with new parameters - **Position managers:** Approved contracts can change eMode on behalf of users