---
# System prepended metadata

title: Understanding Aave V2 Code (3/n) - Borrow Mechanism & Health Factor

---

# Understanding Aave V2 Code (3/n) - Borrow Mechanism & Health Factor

## TLDR
- **Borrow Flow**: Collateral required -> health factor check -> debt token minted -> interest compounds
- **Health Factor**: Ratio of collateral value to debt value, must stay > 1
- **Variable vs Stable**: Two debt token types with different rate behaviors
- **Liquidation Trigger**: When health factor < 1, postion becomes liquidatable
- **Core Innovation**: Continuous health monitoring via aTokens and debt tokens

## What's Borrowing in Aave V2
Simple explanation:
- You deposit collateral (eg. ETH)
- Borrow against it (eg. USDC) up to your borrowing power
- Pay compound interest on debt
- Must maintain health factor >1

## High-Level Flow
User borrows USDC against ETH collateral → Receives debt tokens → Debt grows over time
```
┌──────────────┐
│   User       │
│ (has 10 ETH  │
│  deposited)  │
└──────┬───────┘
       │ 1. borrow(USDC, 5000, VARIABLE)
       ▼
┌─────────────────────────────┐
│  LendingPool.sol            │
│                             │
│ 2. Get price oracle         │
│ 3. Calculate ETH value      │
│ 4. Validate borrow:         │
│    ✓ Reserve active?        │
│    ✓ Has collateral?        │
│    ✓ Health factor > 1?     │
│    ✓ Enough borrowing power?│
│ 5. Update state & rates     │
│ 6. Mint debt tokens         │
│ 7. Transfer USDC to user    │
└─────────────────────────────┘
       │
       ▼
┌─────────────────────────────┐
│  VariableDebtToken.sol      │  or  StableDebtToken.sol
│                             │
│ 8. Mint scaled debt         │
│ 9. Mark user as borrowing   │
│10. Debt grows via index     │
│    (compound interest)      │
└─────────────────────────────┘
       │
       ▼
┌─────────────────────────────┐
│  Continuous Monitoring      │
│                             │
│ Health Factor = (Collateral │
│   × Liq.Threshold) / Debt   │
│                             │
│ HF > 1.0 ✅ Safe            │
│ HF < 1.0 ⚠️ Liquidatable    │
└─────────────────────────────┘
```

**Key Differences from Deposit:**
- Requires existing collateral (aTokens)
- Price oracle used to value collateral
- Health factor validated before and after
- Debt tokens minted (instead of aTokens)
- Borrowed assets transferred OUT (instead of IN)
- Continuous health monitoring (liquidation risk)

## Data Structure
### 1. User Account Data - Aggregated user metrics
```solidity
// GenericLogic.sol tracks account health
struct CalculateUserAccountDataVars {
    // pre-reserve variables (used in each loop iteration)
    uint256 reserveUnitPrice;           // asset price in ETH
    uint256 tokenUnit;                  // normalized unit
    uint256 compoundedLiquidityBalance; // user's deposit balance
    uint256 compoundedBorrowBalance;    // user's debt balance
    uint256 decimals;                   // token decimals
    uint256 ltv;                        // loan-to-value ratio
    uint256 liquidationThreshold;       // liquidation threshold
    uint256 i;                          // loop count

    // accumulator variables (build up across all iterations)
    uint256 healthFactor;               // health factor
    uint256 totalCollateralInETH;       // sum of all collateral value in ETH
    uint256 totalDebtInETH;             // sum of all debt value in ETH
    uint256 avgLtv;                     // weighted average LTV
    uint256 avgLiquidationThreshold;    // weighted average liquidation threshold
    uint256 reservesLength;             // total number of reserves in the protocol

    // state flags
    bool healthFactorBelowThreshold;    // flag indicating if health factor < 1
    address currentReserveAddress;      // reserve address being processed in current loop
    bool usageAsCollateralEnabled;      // flag indicating if this reserve can be used as collateral
    bool userUsesReserveAsCollateral;   // flag indicating if the user choses to use this reserve as collateral
}
```
| Variable | Usage | 
|----------|----------|
| reserveUnitPrice  | Convert asset amounts to a common denomination (ETH) for comparison | 
| tokenUnit  | Normalize different token decimals when calculating values  | 
| compoundedLiquidityBalance  | Deposit value (incl. accrued interest) =  `scaledBalance * currentLiquidityIndex` | 
| compoundedBorrowBalance  | Debt value (incl. accrued interest) = `scaledDebt * currentBorrowIndex` | 
| decimals  | Used with `tokenUnit` for precision calculations | 
| ltv  | Loan-to-value, max borrowing percentage for THIS reserve when used as collateral, stored in basis points  | 
| liquidationThreshold  | The percentage at which THIS reserve becomes liquidatable if used as collateral, always higher than ltv  | 
| i  | Loop counter, track position in the reserves list iteration | 
| healthFactor  | The final calculated health factor = `(totalCollateralInETH × avgLiquidationThreshold) / totalDebtInETH`, if < 1 position can be liquidated,   | 
| totalCollateralInETH  | Sum of all user's collateral across all reserves, valued in ETH = `totalCollateralInETH += balance × price`  | 
| totalDebtInETH  | Sum of all user's borrowed amounts across all reserves, valued in ETH = `totalDebtInETH += debt × price`| 
| avgLtv  | Weighted average LTV across all collateral assets, determine available borrowing capacity, `Σ(assetValueETH × assetLTV) / totalCollateralETH` | 
| avgLiquidationThreshold  | Weighted average liquidation threshold across all collateral, determine when liquidation can occur, `Σ(assetValueETH × liquidationThreshold) / totalCollateralETH` | 
| reservesLength  | Total number of reserves in the protocol  | 
| healthFactorBelowThreshold  | Set the flag when health factor < 1 | 
| currentReserveAddress  |  Address of the reserve being processed in current loop iteration | 
| usageAsCollateralEnabled  | Whether THIS reserve can be used as collateral (protocol-level config)  | 
| userUsesReserveAsCollateral  | Whether the USER chose to use THIS reserve as collateral  | 

### 2. Reserve Data
```solidity
// DataTypes.sol
struct ReserveData {
    uint128 variableBorrowIndex;                 // variable debt multiplier
    uint128 currentVariableBorrowRate;           // variable borrow APY (what borrowers pay)
    uint128 currentStableBorrowRate;             // stable borrow APY (what borrowers pay)
    // others etc.
}
```

## Math & Key Concepts
Aave's borrow mechanism uses several mathematical concepts to ensure safe lending:

  - Health Factor - Safety metric for liquidation risk
  - LTV vs Liquidation Threshold - Two different collateral ratios
  - Borrowing Power - Maximum amount user can borrow
  - Variable vs Stable Debt - Two interest rate modes
  - Scaled Debt Balance - Storage-efficient debt representation
  - Weighted Averages - Handling multi-asset portfolios
  - Oracle Integration - Real-time price feeds for risk assessment

### 1. Health Factor - The Safety Metric
- Definition:  Health factor determines if a position can be liquidated. It's the core risk management metric in Aave.
- Formula = `(Total collateral * Liquidation threshold) / Total debt`
- Code Implementation
    ```solidity
    // GenericLogic.sol
    function calculateHealthFactorFromBalances(
        uint256 totalCollateralInETH,
        uint256 totalDebtInETH,
        uint256 liquidationThreshold
    ) internal pure returns (uint256) {
        if (totalDebtInETH == 0) return uint256(-1); // no debt = infinite health

        return (totalCollateralInETH.percentMul(liquidationThreshold)).wadDiv(totalDebtInETH);
    }
    ```
- Interpretation
    - HF > 1.0: Position is safe (healthy) ✅
    - HF = 1.0: Position is at liquidation threshold ⚠️
    - HF < 1.0: Position can be liquidated ❌
    - HF = uint256(-1): No debt (infinite health) 🎉
- Example
    - User deposits:
        - 10 ETH (price: $2,000/ETH)
        - Total collateral value: $20,000
        - ETH liquidation threshold: 80% (8000 in basis points)
    - User borrows:
        - 5,000 USDC
        - Total debt value: $5,000
    - Health Factor = ($20,000 × 0.80) / $5,000 = 3.2 ✅
        - Safe! User can borrow more or ETH price can drop significantly before liquidation.
- When does liquidation occur?
    
    Let's see what happens as ETH price drops:

    | ETH Price | Collateral Value | Health Factor | Status                |
    |-----------|------------------|---------------|-----------------------|
    | $2,000    | $20,000          | 3.20          | ✅ Very Safe          |
    | $1,500    | $15,000          | 2.40          | ✅ Safe               |
    | $1,000    | $10,000          | 1.60          | ✅ Safe but declining |
    | $781.25   | $7,812.50        | 1.25          | ⚠️ Risky              |
    | $625      | $6,250           | 1.00          | ⚠️ AT THRESHOLD       |
    | $600      | $6,000           | 0.96          | ❌ LIQUIDATABLE       |

    When HF fails < 1, liquidators can step in to repay debt and seize collateral.

### 2. Loan-to-Value (LTV) & Liquidation Threshold
These are two different but related concepts that often confuse users:

- **LTV (Loan-to-Value)**
    - Purpose: Max % user can borrow initially
    - Conservative limit: Prevents immediate liquidation after borrowing
    - Used for: Borrowing power calculation
    - Example: 79% LTV on ETH = can borrow up to 75% of ETH value
- **Liquidation Threshold**
    - Purpose: % at which user get liquidated
    - Higher than LTV: Create a safety buffer
    - Used for: Health factor calculation
    - Example: 80% threshold = liquidated when debt reaches 80% of collateral value
- The Safety Buffer
    ```
    Collateral: $20,000 ETH
    ├──────────────────────────────────────┤
    0%                                   100%

            75% LTV      80% Threshold
            ↓              ↓
        $15,000        $16,000

        Max Initial    Liquidation
        Borrow         Point
    ```
    - Prevents instant liquidation: If LTV = Liquidation Threshold, borrowing max amount would immediately put you at liquidation risk
    - Price volatility buffer: Gives borrowers time to react to price movements
    - Interest accrual buffer: Debt grows over time due to interest, so you need headroom
    - Protocol safety: Protects against rapid price crashes
- Example
    - User borrows max at 75% LTV
        - Collateral: $20,000 ETH
        - Max borrow: $15,000
        - Health Factor: ($20,000 × 0.80) / $15,000 = 1.067 ✅
    - Buffer: 1.067 - 1 = 0.067 (6.7% price drop before liquidation)

### 3. Borrowing Power - Maximum amount user can borrow
- Definition: Borrowing power determines how much user can borrow across all assets.
- Formula:
    ```
    Borrowing power = Total collateral value * Average LTV
    Available to borrow = Borrowing power - Current debt
    ```
- Example
    - Single-asset
        - User deposits 10 ETH (value: $20,000, LTV: 75%)
        - Borrowing power = 20,000 * 0.75 = $15,000
        - Current debt = $0
        - Available to borrow = $15,000
    - Multiple-asset (Weighted average)
        - User has: 
            ```
            - 10 ETH deposited (value: $20,000, LTV: 75%, Threshold: 80%)
            - 5,000 DAI deposited (value: $5,000, LTV: 80%, Threshold: 85%)
            ```
        - Total collateral = $25,000
        - Weighted average LTV = (20,000 * 0.75 + 5,000 * 0.8) / 25,000 = 0.76
        - Borrowing power = 25,000 * 0.76 = $19,000
        - Current debt = $5,000 USDC
        - Available to borrow = 19,000 - 5,000 = $14,000

### 4. Variable vs Stable Debt - Two interest rate modes
Aave offers two types of borrowing interest rates
- **Variable Rate Debt**
    - Characteristics
        - Interest rate fluctuates based on supply/demand (utilization)
        - Use `variableBorrowIndex` for auto balance growth
        - Generally lower rates when utilization is low
        - Risk: Rates can spike during high utilization period
    - How it works
        ```solidity
        // VariableDebtToken.sol
        function balanceOf(address user) public view override returns (uint256) {
            uint256 scaledBalance = super.balanceOf(user);
            if (scaledBalance == 0) {
                return 0;
            }
            return scaledBalance.rayMul(POOL.getReserveNormalizedVariableDebt(_underlyingAsset));
        }
        ```
        - Store only scaled balance (principal)
        - Actual debt = `scaled balance * current index`
        - Index grows continuously based on variable rate
        - Gas efficient, no storage updates for interest accrual

- **Stable Rate Debt**
    - Characteristics
        - Interest rate locked at time of borrow
        - No index used - each user has their own rate
        - Predictable payments, protection from rate spikes
        - Usually slightly higher than current variable rate
        - Can be rebalanced in extreme conditions
    - How it works
        ```solidity
        // StableDebtToken.sol
        function balanceOf(address account) public view virtual override returns (uint256) {
            uint256 accountBalance = super.balanceOf(account);
            uint256 stableRate = _usersStableRate[account];
            if (accountBalance == 0) {
                return 0;
            }
            uint256 cumulatedInterest = MathUtils.calculateCompoundedInterest(
                stableRate,
                _timestamps[account]
            );
            return accountBalance.rayMul(cumulatedInterest);
        }       
        ```
        - Store actual balance (principal + accrued interest)
        - Each user has their own fixed rate
        - Interest calculated from timestamp
        - More gas intensive but predictable payments

- **When to Use Each**
    - Choose Variable If
        - ✅ Short-term borrowing (days to weeks)
        - ✅ Expect utilization to stay low
        - ✅ Want lowest possible rate
        - ✅ Can handle rate volatility
        - ✅ Actively manage position
    - Choose Stable If
        - ✅ Long-term borrowing (months to years)
        - ✅ Need predictable payments
        - ✅ Expect market volatility
        - ✅ Want rate protection
        - ✅ Set-and-forget approach
    - Comparison Table
        | Feature       | Variable Rate               | Stable Rate                         |
        |---------------|-----------------------------|-------------------------------------|
        | Rate Behavior | Fluctuates with utilization | Locked at borrow time               |
        | Index Used    | `variableBorrowIndex `        | User-specific rate                  |
        | Token         | `VariableDebtToken  `         | `StableDebtToken  `                   |
        | Initial Rate  | Lower (typically)           | Higher (typically)                  |
        | Risk          | Rate volatility             | Locked at suboptimal rate           |
        | Rebalancing   | N/A                         | Can be rebalanced if conditions met |
        | Best For      | Short-term, rate-optimizers | Long-term, risk-averse              |
        | Availability  | All assets                  | Limited assets                      |     

### 5. Scaled Debt Balance - Storage-efficient debt representation
- Debt tokens use the same scaled balance architecture as aTokens to enable auto balance growth without per-user storage updates.
- Core concepts
    - `Scaled balance = Actual balance / Current index`
    - `Actual balance = Scaled balance * Current index`
- Why this matters
    - Only the scaled-balance is stored onchain
    - The actual balance is calculated on demand
    - As the index grows, actual balance grows automatically

### 6. Weighted Averages - Handling multi-asset portfolios
- When users have multiple types of collateral, Aave calculates weighted averages for LTV and Liquidation Threshold.
- Formula: `avgLTV = Σ(assetValueETH × assetLTV) / totalCollateralETH`
- Why weighted average?
    - Different assets have different risk profiles
        - ETH: LTV 80%, Threshold 82.5%
        - WBTC: LTV 70%, Threshold 75%
        - USDC: LTV 80%, Threshold 85%
    - Simply averaging would be unfair - a user with 95% ETH and 5% WBTC shouldn't have their LTV heavily affected by the small WBTC position.
- Example
    - User collateral: Total collateral $30,000
        - 10 ETH (value: $20,000, LTV: 80%)
        - 5,000 USDC (value: $5,000, LTV: 75%) 
        - 0.25 WBTC (value: $5,000, LTV: 70%)
    - Weighted LTV
        - (20,000 × 0.80 + 5,000 × 0.75 + 5,000 × 0.70) / 30,000 = 0.775 (77.5%)
    - Borrowing power = 30,000 * 0.775 = $23,250

### 7. Oracle Integration - Real-time price feeds for risk assessment
- The price oracle converts all assets to a common denomination (ETH) for risk calculation.
- Oracle usage:
    ```solidity
    // GenericLogic.sol
    vars.reserveUnitPrice = IPriceOracleGetter(_oracle).getAssetPrice(
        vars.currentReserveAddress
    );

    // Calculate collateral value in ETH
    uint256 collateralValueInETH = vars.compoundedLiquidityBalance
        .mul(vars.reserveUnitPrice)
        .div(vars.tokenUnit);
    ```
- Example
    - User wants to borrow 5,000 USDC
    - **Get USDC price from oracle**
        - USDC price = 0.0005 ETH (when 1 ETH = $2,000)
    - **Convert amount to ETH value**
        - amountInETH = (price * amount) / 10^decimals = (0.005e18 * 5000e6) / 1e6 = 2.5 ETH worth
    - **Check collateral/ health factor**: User has 10 ETH, valued $20,000, liq. threshold 80%
        - health factor = (20,000 * 0.8) / (0 + 5000) = 3.2 ✅ Safe to borrow
- Why ETH denomination in Aave v2?
    - ETH is native to Ethereum
    - Simplifies calculation (all prices in one unit)
    - Reduce oracle dependency (don't need ETH/USDC price)
- Oracle failure risk
    - Stable price feeds -> incorrect health factor
    - Price manipulation -> unfair liquidation
    - Network congestion -> delayed updates
    - Mitigation: Aave uses Chainlink oracles with multiple data sources and freshness checks

## Step by Step Code Walkthrough
### Step 1: Entry Point - `borrow()`
```solidity
// LendingPool.sol
function borrow(
    address asset,              // the reserve asset to borrow
    uint256 amount,             // amount to borrow
    uint256 interestRateMode,   // 1 = stable, 2 = variable
    uint16 referralCode,        // referral code for integrators, 0 if none
    address onBehalfOf          // who receive the debt (usually msg.sender)
) external override whenNotPaused {
    // get reference of the asset's current reserve
    DataTypes.ReserveData storage reserve = _reserves[asset];

    _executeBorrow(
        ExecuteBorrowParams(
            asset,
            msg.sender,             // user = tx sender (receive borrowed fund)
            onBehalfOf,             // debt recipient
            amount,
            interestRateMode,
            reserve.aTokenAddress,
            referralCode,
            true                    // releaseUnderlying = true (transfer assets to user)
        )
    );
}
```
- **What happens here**
    - **Modifier check**: `whenNotPaused` ensures the protocol is operational
    - **Load reserve data**: Fetch storage pointer for the asset's reserve (incl. all config & state)
    - **Prepare params**: Package all parameters into `ExecuteBorrowParams` struct
    - **Key details**: `msg.sender` receives the borrowed funds, but `onBehalfOf` receives the debt tokens
        - Normal case: both are the same address
        - Credit delegation: `onBehalfOf` can be different in advanced use cases (require allowance)
    - **Delegate to internal function**: Call `_executeBorrow()` which contains the core logics

### Step 2: Main Borrow Logic - `_executeBorrow()`
```solidity
// LendingPool.sol
function _executeBorrow(ExecuteBorrowParams memory vars) internal {
    // Step 1: load storage references
    DataTypes.ReserveData storage reserve = _reserves[vars.asset];
    DataTypes.UserConfigurationMap storage userConfig = _usersConfig[vars.onBehalfOf];

    // Step 2: get price oracle
    address oracle = _addressesProvider.getPriceOracle();

    // Step 3: convert borrow amount to ETH value
    uint256 amountInETH =
      IPriceOracleGetter(oracle).getAssetPrice(vars.asset).mul(vars.amount).div(
        10**reserve.configuration.getDecimals()
      );

    // Step 4: validate the borrow operation
    ValidationLogic.validateBorrow(
      vars.asset,
      reserve,
      vars.onBehalfOf,
      vars.amount,
      amountInETH,
      vars.interestRateMode,
      _maxStableRateBorrowSizePercent,
      _reserves,
      userConfig,
      _reservesList,
      _reservesCount,
      oracle
    );

    // Step 5: update reserve state (indexes & rates)
    reserve.updateState();

    // Step 6: mint debt tokens (stable or variable)
    uint256 currentStableRate = 0;
    bool isFirstBorrowing = false;
    if (DataTypes.InterestRateMode(vars.interestRateMode) == DataTypes.InterestRateMode.STABLE) {
      currentStableRate = reserve.currentStableBorrowRate;

      isFirstBorrowing = IStableDebtToken(reserve.stableDebtTokenAddress).mint(
        vars.user,
        vars.onBehalfOf,
        vars.amount,
        currentStableRate
      );
    } else {
      isFirstBorrowing = IVariableDebtToken(reserve.variableDebtTokenAddress).mint(
        vars.user,
        vars.onBehalfOf,
        vars.amount,
        reserve.variableBorrowIndex
      );
    }

    // Step 7: update user config if first borrow
    if (isFirstBorrowing) {
      userConfig.setBorrowing(reserve.id, true);
    }

    // Step 8: update interest rates after borrow
    reserve.updateInterestRates(
      vars.asset,
      vars.aTokenAddress,
      0,
      vars.releaseUnderlying ? vars.amount : 0
    );

    // Step 9: transfer borrowed assets to user
    if (vars.releaseUnderlying) {
      IAToken(vars.aTokenAddress).transferUnderlyingTo(vars.user, vars.amount);
    }

    // Step 10: Emit borrow event
    emit Borrow(
      vars.asset,
      vars.user,
      vars.onBehalfOf,
      vars.amount,
      vars.interestRateMode,
      DataTypes.InterestRateMode(vars.interestRateMode) == DataTypes.InterestRateMode.STABLE
        ? currentStableRate
        : reserve.currentVariableBorrowRate,
      vars.referralCode
    );
}
```
Let's break down each step in details
#### 2.0 ExecuteBorrowParams Struct
```solidity
struct ExecuteBorrowParams {
    address asset;              // address of the reserve asset to borrow
    address user;               // the borrow transaction initiator
    address onBehalfOf;         // address that will receive the debt
    uint256 amount;             // amount to borrow
    uint256 interestRateMode;   // 1 = Stable, 2 = Variable
    address aTokenAddress;      // address of aToken for this reserve
    uint16 referralCode;        // referral code for integrators (for tracking/ incentives)
    bool releaseUnderlying;     // whether to transfer the borrowed asset to the user
}
```
- `asset`: The underlying token address being borrowed (eg. USDC, DAI, WETH)
- `user`: The msg.sender who initiates the borrow call - the transaction initiator
- `onBehalfOf`: The address that will incur the debt. This allows delegation - someone can borrow on behalf of another address (requires allowance)
- `amount`: The quantity of tokens to borrow, in the asset's native decimals
- `interestRateMode`:
    - 1 = Stable rate (fixed for a period)
    - 2 = Variable rate (changes with market conditions)
- `aTokenAddress`: Used for balance checks adn collateral validation
- `referralCode`: To track which integrator/ frontend referred the user (used for incentive distribution)
- `releaseUnderlying`:
    - `true`: Transfer borrowed tokens to user (normal borrow)
    - `false`: Don't transfer (used for certain internal operations like flash loans)

#### 2.1 Load Storage References
```solidity
DataTypes.ReserveData storage reserve = _reserves[vars.asset];
DataTypes.UserConfigurationMap storage userConfig = _usersConfig[vars.onBehalfOf];
```
- **Purpose**: Get direct storage pointers to avoid multiple lookups
- `reserve`: Contain all state & config for the asset, incl. interest rates, indexes, debt token addresses, aToken address, etc.
- `userConfig`: Bitmap tracking which reserves user is using as collateral/ borrowing

#### 2.2 Get Price Oracle
```solidity
address oracle = _addressesProvider.getPriceOracle();

// configuration/LendingPoolAddressesProvider.sol
function getPriceOracle() external view override returns (address) {
    return getAddress(PRICE_ORACLE);
}
```
- **Purpose**: Fetch the oracle contract address for price feeds
- **Why needed**: Must convert all assets to ETH for risk calculations
- **Oracle source**: Primarily use Chainlink price feeds with fallback mechanism
    - Details in misc/AaveOracle.sol and interface/IChainlinkAggregator.sol

#### 2.3 Convert Borrow Amount to ETH
```solidity
uint256 amountInETH =
    // type casting the oracle address to the interface
    IPriceOracleGetter(oracle).
        // call the oracle, return price of 1 unit of the asset in ETH
        getAssetPrice(vars.asset).
        // multiple by the amount being borrowed
        mul(vars.amount).
        // divide by the token's decimal to normalize
        div(10**reserve.configuration.getDecimals());
```
- **Purpose**: Calculate ETH-denominated value of borrowed amount
- **Why needed**: 
    - All health factor and collateral calculations use ETH as base unit
    - Simplifies multi-asset portfolio calculations

#### 2.4 Validate Borrow Operation
```solidity
ValidationLogic.validateBorrow(
    vars.asset,
    reserve,
    vars.onBehalfOf,
    vars.amount,
    amountInETH,
    vars.interestRateMode,
    _maxStableRateBorrowSizePercent,
    _reserves,
    userConfig,
    _reservesList,
    _reservesCount,
    oracle
);

// ValidationLogic.sol
struct ValidateBorrowLocalVars {
    uint256 currentLtv;                    // Loan-to-Value ratio
    uint256 currentLiquidationThreshold;   // Liquidation threshold
    uint256 amountOfCollateralNeededETH;   // Required collateral
    uint256 userCollateralBalanceETH;      // User's total collateral
    uint256 userBorrowBalanceETH;          // User's total borrows
    uint256 availableLiquidity;            // Available liquidity in pool
    uint256 healthFactor;                  // User's health factor
    bool isActive;                         // Is reserve active?
    bool isFrozen;                         // Is reserve frozen?
    bool borrowingEnabled;                 // Is borrowing enabled?
    bool stableRateBorrowingEnabled;       // Is stable rate enabled?
}

// ValidationLogic.sol
function validateBorrow(
    address asset, // asset being borrowed
    DataTypes.ReserveData storage reserve, // reserve info for this asset
    address userAddress, // borrower's address
    uint256 amount, // amount to borrow (in token units)
    uint256 amountInETH, // amount in ETH
    uint256 interestRateMode, // 1 = Stable, 2 = Variable
    uint256 maxStableLoanPercent, // max % of liquidity for stable borrow
    mapping(address => DataTypes.ReserveData) storage reservesData, // all reserves info
    DataTypes.UserConfigurationMap storage userConfig, // user config
    mapping(uint256 => address) storage reserves, // reserve list
    uint256 reservesCount, // total nb of reserves
    address oracle // price oracle address
) external view {
    ValidateBorrowLocalVars memory vars;

    // get the reserve's config flags from the bitmap
    (vars.isActive, vars.isFrozen, vars.borrowingEnabled, vars.stableRateBorrowingEnabled) = reserve
      .configuration
      .getFlags();

    // Step 1: reserve status checks
    // reserve must be active, not frozen, not 0, borrowing enabled
    require(vars.isActive, Errors.VL_NO_ACTIVE_RESERVE);
    require(!vars.isFrozen, Errors.VL_RESERVE_FROZEN);
    require(amount != 0, Errors.VL_INVALID_AMOUNT);
    require(vars.borrowingEnabled, Errors.VL_BORROWING_NOT_ENABLED);

    // Step 2: validate interest rate mode
    // must be 1 (STABLE) or 2 (VARIABLE)
    require(
      uint256(DataTypes.InterestRateMode.VARIABLE) == interestRateMode ||
        uint256(DataTypes.InterestRateMode.STABLE) == interestRateMode,
      Errors.VL_INVALID_INTEREST_RATE_MODE_SELECTED
    );

    // Step 3: user account health check
    (
      vars.userCollateralBalanceETH,            // total value of user's collateral in ETH
      vars.userBorrowBalanceETH,                // total value of user's existing borrowing in ETH
      vars.currentLtv,                          // weighted average LTV (max borrow ratio)
      vars.currentLiquidationThreshold,         // current liq. threshold
      vars.healthFactor                         // health factor (must > 1)
    ) = GenericLogic.calculateUserAccountData(
      userAddress,                              
      reservesData,                             // all reserves in protocol
      userConfig,                               // user's positions bitmap
      reserves,                                 // reserve list
      reservesCount,                            // total reserves count
      oracle                                    // price oracle
    );

    // Step 4: check collateral & health factor
    // user collateral balance in ETH must > 0
    require(vars.userCollateralBalanceETH > 0, Errors.VL_COLLATERAL_BALANCE_IS_0);
    // health factor must > 1
    require(
      vars.healthFactor > GenericLogic.HEALTH_FACTOR_LIQUIDATION_THRESHOLD,
      Errors.VL_HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD
    );

    // Step 5: check if user has enough collateral for new borrow
    // formula: collateral needed = (existing borrow + new borrow) / LTV
    // add the current already borrowed amount to the amount requested to calculate the total collateral needed.
    vars.amountOfCollateralNeededETH = vars.userBorrowBalanceETH.add(amountInETH).percentDiv(
      vars.currentLtv
    ); //LTV is calculated in percentage

    // amount of collateral needed should <= total value of user's collateral
    require(
      vars.amountOfCollateralNeededETH <= vars.userCollateralBalanceETH,
      Errors.VL_COLLATERAL_CANNOT_COVER_NEW_BORROW
    );

    // Step 6: stable rate specific checks
    /**
     * Following conditions need to be met if the user is borrowing at a stable rate:
     * 1. Reserve must be enabled for stable rate borrowing
     * 2. Users cannot borrow from the reserve if their collateral is (mostly) the same currency
     *    they are borrowing, to prevent abuses.
     * 3. Users will be able to borrow only a portion of the total available liquidity
     **/

    if (interestRateMode == uint256(DataTypes.InterestRateMode.STABLE)) {
      // check if the borrow mode is stable and if stable rate borrowing is enabled on this reserve
      require(vars.stableRateBorrowingEnabled, Errors.VL_STABLE_BORROWING_NOT_ENABLED);

      // prevent borrowing same asset the user is using as collateral
      require(
        !userConfig.isUsingAsCollateral(reserve.id) || // user is NOT using this asset as collateral
          reserve.configuration.getLtv() == 0 || // or the asset has LTV = 0 (can't be collateral)
          amount > IERC20(reserve.aTokenAddress).balanceOf(userAddress), // or borrow amount > user's aToken balance
        Errors.VL_COLLATERAL_SAME_AS_BORROWING_CURRENCY
      );

      // get the current available liquidity
      vars.availableLiquidity = IERC20(asset).balanceOf(reserve.aTokenAddress);

      // max stable loan size = available liquidity * max stable loan percent
      uint256 maxLoanSizeStable = vars.availableLiquidity.percentMul(maxStableLoanPercent);
      
      // limit stable rate borrow to % of liquidity
      require(amount <= maxLoanSizeStable, Errors.VL_AMOUNT_BIGGER_THAN_MAX_LOAN_SIZE_STABLE);
    }
}
```
- Summary of all checks
    | #   | Check                                | Purpose                           |
    |-----|--------------------------------------|-----------------------------------|
    | 1   | Reserve is active                    | Ensure reserve is operational     |
    | 2   | Reserve not frozen                   | Not in emergency mode             |
    | 3   | Amount > 0                           | Valid borrow amount               |
    | 4   | Borrowing enabled                    | Reserve allows borrowing          |
    | 5   | Valid interest rate mode             | 1 (stable) or 2 (variable)        |
    | 6   | User has collateral                  | Can't borrow without deposits     |
    | 7   | Health factor > 1                    | User not at liquidation risk      |
    | 8   | Enough collateral for total borrows  | LTV check                         |
    | 9   | (Stable only) Stable rate enabled    | Reserve permits stable borrowing  |
    | 10  | (Stable only) Not same asset         | Prevent collateral = borrow abuse |
    | 11  | (Stable only) Within liquidity limit | Max 25% of pool at stable rate    |
- Dive deeper into `calculateUserAccountData()` - Key of Aave's risk engine
    - It answers the fundamental question: "Can the user afford this borrow?"
    - What it does
        - Cross-asset analysis: Only function that looks at user's ENTIRE portfolio across ALL reserves
        - Real-time solvency: Calculate current collateral value and debt using live oracle prices
        - Risk metrics: Computes the 5 key metrics that determine if borrow is safe:
            1. Total collateral in ETH
            2. Total existing debt in ETH
            3. Weighted average LTV (max borrow capacity)
            4. Weighted liquidation threshold (liquidation point)
            5. Health factor (solvency measure, must > 1)
    - All subsequent validation checks depend on its output
    ```solidity
    function calculateUserAccountData(
        address user,
        mapping(address => DataTypes.ReserveData) storage reservesData,
        DataTypes.UserConfigurationMap memory userConfig,
        mapping(uint256 => address) storage reserves,
        uint256 reservesCount,
        address oracle
    )
        internal
        view
        returns (
        uint256,  // totalCollateralinETH
        uint256,  // totalDebtInETH
        uint256,  // avgLTM
        uint256,  // avgLiquidationThreshold
        uint256   // healthFactor
        )
    {
        // step 1: create a local variable struct in memory to hold intermediate calculation values
        CalculateUserAccountDataVars memory vars;

        // step 2: early exist for empty accounts
        if (userConfig.isEmpty()) {
        return (0, 0, 0, 0, uint256(-1));
        }

        // step 3: loop through all reserves
        for (vars.i = 0; vars.i < reservesCount; vars.i++) {
        // step 3.1: skip unsed reserves
        if (!userConfig.isUsingAsCollateralOrBorrowing(vars.i)) {
            continue;
        }

        // step 3.2: get info of THIS asset
        vars.currentReserveAddress = reserves[vars.i];
        DataTypes.ReserveData storage currentReserve = reservesData[vars.currentReserveAddress];

        (vars.ltv, vars.liquidationThreshold, , vars.decimals, ) = currentReserve
            .configuration
            .getParams();
        vars.tokenUnit = 10**vars.decimals;

        // step 3.3: get price for THIS asset
        vars.reserveUnitPrice = IPriceOracleGetter(oracle).getAssetPrice(vars.currentReserveAddress);
        
        // step 3.4: calculate collateral
        if (vars.liquidationThreshold != 0 && userConfig.isUsingAsCollateral(vars.i)) {
            // get user's aToken balance
            vars.compoundedLiquidityBalance = IERC20(currentReserve.aTokenAddress).balanceOf(user);
            // convert to ETH value
            uint256 liquidityBalanceETH =
            vars.reserveUnitPrice.mul(vars.compoundedLiquidityBalance).div(vars.tokenUnit);
            // accumulate total collateral amount
            vars.totalCollateralInETH = vars.totalCollateralInETH.add(liquidityBalanceETH);
            // weight the LTV by collateral value
            vars.avgLtv = vars.avgLtv.add(liquidityBalanceETH.mul(vars.ltv));
            // weight the liquidation threshold
            vars.avgLiquidationThreshold = vars.avgLiquidationThreshold.add(
            liquidityBalanceETH.mul(vars.liquidationThreshold)
            );
        }

        // step 3.5: calculate borrowing
        if (userConfig.isBorrowing(vars.i)) {
            // get stasble debt balance
            vars.compoundedBorrowBalance = IERC20(currentReserve.stableDebtTokenAddress).balanceOf(
            user
            );
            // add variable debt balance
            vars.compoundedBorrowBalance = vars.compoundedBorrowBalance.add(
            IERC20(currentReserve.variableDebtTokenAddress).balanceOf(user)
            );
            // convert to ETH value and accumulate
            vars.totalDebtInETH = vars.totalDebtInETH.add(
            vars.reserveUnitPrice.mul(vars.compoundedBorrowBalance).div(vars.tokenUnit)
            );
        }
        }

        // step 4: finalize weighted averages calculation
        // avgLtv = Σ(assetValueETH × assetLTV) / totalCollateralETH
        vars.avgLtv = vars.totalCollateralInETH > 0 ? vars.avgLtv.div(vars.totalCollateralInETH) : 0;
        // avgLiquidationThreshold = Σ(assetValueETH × liquidationThreshold) / totalCollateralETH
        vars.avgLiquidationThreshold = vars.totalCollateralInETH > 0
        ? vars.avgLiquidationThreshold.div(vars.totalCollateralInETH)
        : 0;

        // step 5: calculate health factor
        // HF = (collateral * liq. threshold) / total debt
        vars.healthFactor = calculateHealthFactorFromBalances(
        vars.totalCollateralInETH,
        vars.totalDebtInETH,
        vars.avgLiquidationThreshold
        );

        // return ad value
        return (
        vars.totalCollateralInETH,
        vars.totalDebtInETH,
        vars.avgLtv,
        vars.avgLiquidationThreshold,
        vars.healthFactor
        );
    }
    ```
- Why `amount > aToken balance` allows the stable rate borrowing?
    - The full check is an OR condition - the check allows stable rate borrow if ANY is true
    - It fails when ALL conditions are false
        - User IS using same asset as collateral, AND
        - Asset has LTV > 0, AND
        - Borrow amount <= user's aToken balance
    - The attack being prevented
        - User deposits 10,000 USDC -> Get 10,000 aUSDC ✅
        - LTV 80% > 0 ✅
        - User tries to borrow 8,000 USDC at stable rate, 8,000 < 10,000 aUSDC ❌
        - Key insight: This indicates a pure same-asset circular borrowing
    - Legitimate operation
        - User deposits 10,000 USDC -> Get 10,000 aUSDC ✅
        - LTV 80% > 0 ✅
        - User tries to borrow 20,000 USDC at stable rate, 20,000 > 10,000 aUSDC ✅
        - Key insight: If you're borrowing MORE of an asset than you deposited, you must have other collateral backing it. This indicates a legitimate diverse collateral strategy, not a same-asset rate arbitrage.
- Why stable rate borrowing needs to be cap at certin % of liquidity
    - It's a risk management strategy from the protocol perspective
    - Stable rate borrowing contains interest rate risk for the protocol
    - Example
        - Time T0:
            - Pool has 1,000,000 USDC
            - Market rate: 3% APR
            - User borrows 800,000 USDC at stable 3.5% APR
        - Time T1:
            - Market rate spikes to 15% APR (DeFi summer, high demand)
            - User still pays only 3.5% APR (stable rate locked in)
            - Protocol must pay depositors ~15% to keep liquidity
            - Protocol pays OUT 15% but receives IN 3.5% -> lose money! 💸
    - When cap at certain %, the risk is protected
- Why not just offer 100% variable rate borrowing in Aave v2?
    - User demand: Some users need predictability (businesses, long-term planning)
    - Competitive advantage: Offers something other protocols don't
    - Premium pricing: Stable rates are typically 0.5-2% higher than variable
    - Risk is manageable: At 25% cap for example, the risk is acceptable

#### 2.5 Update Reserve State
```solidity
reserve.updateState();
```
- **Purpose**: Updated liquidity & variable borrow index
- **What it does**: 
    - Calculate time elapsed since last update
    - Compound interest based on current rates
    - Update `liquidityIndex` for aToken balance growth
    - Update `variableBorrowIndex` for variable debt growth
- Detailed function walkthrough has been covered in the previous [deposit blog](https://hackmd.io/@ChloeIsCoding/r1XOmagfWg)

#### 2.6 Mint Debt Tokens
Two paths depending on interest rate mode:
- **Variable Rate Path**
    ```solidity
    isFirstBorrowing = IVariableDebtToken(reserve.variableDebtTokenAddress).mint(
        vars.user,
        vars.onBehalfOf,
        vars.amount,
        reserve.variableBorrowIndex
    );

    // tokenization/VariableDebtToken.sol
    function mint(
        address user,
        address onBehalfOf,
        uint256 amount,
        uint256 index
    ) external override onlyLendingPool returns (bool) { // return bool indicating if it's 1st borrow
        // delegation check
        if (user != onBehalfOf) {
            _decreaseBorrowAllowance(onBehalfOf, user, amount);
        }
        // check previous balance, used to determine if it's 1st borrow
        uint256 previousBalance = super.balanceOf(onBehalfOf);
        // calculate the scaled balance (principal)
        uint256 amountScaled = amount.rayDiv(index);
        require(amountScaled != 0, Errors.CT_INVALID_MINT_AMOUNT);
        // mint scaled amount to onBehalfOf
        _mint(onBehalfOf, amountScaled);
        // emit events
        emit Transfer(address(0), onBehalfOf, amount);
        emit Mint(user, onBehalfOf, amount, index);
        // return 1st borrow flag
        return previousBalance == 0;
    }
    ```
    - **Mint**: Variable debt tokens to `onBehalfOf`
    - **Scaled balance**: `scaledAmount = amount / variableBorrowIndex`
    - **Auto growth**: Balance increases as index grows

- **Stable Rate Path**
    ```solidity
    if (DataTypes.InterestRateMode(vars.interestRateMode) == DataTypes.InterestRateMode.STABLE) {
        currentStableRate = reserve.currentStableBorrowRate;

        isFirstBorrowing = IStableDebtToken(reserve.stableDebtTokenAddress).mint(
            vars.user,
            vars.onBehalfOf,
            vars.amount,
            currentStableRate
        );
    } 

    // tokenization/StableDebtToken.sol
    function mint(
        address user,
        address onBehalfOf,
        uint256 amount,
        uint256 rate
    ) external override onlyLendingPool returns (bool) {
        // create local variables 
        MintLocalVars memory vars;
        // check credit delegation
        if (user != onBehalfOf) {
        _decreaseBorrowAllowance(onBehalfOf, user, amount);
        }
        // calculate accrued interest
            // currentBalance: current debt incl. accrued interest
            // balanceIncrease: interest that has accumulated
        (, uint256 currentBalance, uint256 balanceIncrease) = _calculateBalanceIncrease(onBehalfOf);
        // update total debt supply
        vars.previousSupply = totalSupply(); // get current total debt across all users
        vars.currentAvgStableRate = _avgStableRate; // get protocol's avg stabe rate across all users
        vars.nextSupply = _totalSupply = vars.previousSupply.add(amount); // calculate new total supply AND update storage
        // convert to ray precision
        vars.amountInRay = amount.wadToRay();
        // calculate user's new weighted average stable rate
            // new rate = (old rate * old balance + new rate * new amount) / (old balance + new amount)
        vars.newStableRate = _usersStableRate[onBehalfOf]
        .rayMul(currentBalance.wadToRay())
        .add(vars.amountInRay.rayMul(rate))
        .rayDiv(currentBalance.add(amount).wadToRay());

        require(vars.newStableRate <= type(uint128).max, Errors.SDT_STABLE_DEBT_OVERFLOW);
        // update the user's stable rate
        _usersStableRate[onBehalfOf] = vars.newStableRate;
        // update timestamps
        //solium-disable-next-line
        _totalSupplyTimestamp = _timestamps[onBehalfOf] = uint40(block.timestamp);

        // Calculates the updated average stable rate for the protocol
            // new rate = (old rate * old balance + new rate * new amount) / new balance
        vars.currentAvgStableRate = _avgStableRate = vars
        .currentAvgStableRate
        .rayMul(vars.previousSupply.wadToRay())
        .add(rate.rayMul(vars.amountInRay))
        .rayDiv(vars.nextSupply.wadToRay());
        // mint the debt tokens: new borrow + accrued interest
        _mint(onBehalfOf, amount.add(balanceIncrease), vars.previousSupply);
        // emit events
        emit Transfer(address(0), onBehalfOf, amount);
        emit Mint(
        user,
        onBehalfOf,
        amount,
        currentBalance,
        balanceIncrease,
        vars.newStableRate,
        vars.currentAvgStableRate,
        vars.nextSupply
        );
        // return 1st borrow flag
        return currentBalance == 0;
    }
    ```
    - **Mint**: Stable debt tokens to `onBehalfOf`
    - **Actual balance**: Mints amount + accruedInterest to capture unpaid interest into balance (unlike variable which uses scaled balance)
    - **Per-User Rate**: Each user has own fixed rate stored on-chain (can't use global index like variable)
    - **Weighted average rate**: When user borrows more, new rate = weighted average of old rate × old balance + new rate × new amount
    - **Protocol tracking**: Maintain global avg. stable rate across all users for depositor yield calculation
- Key difference: Stable vs Variable Debt Token

    | Aspect       | VariableDebtToken                 | StableDebtToken                  |
    |--------------|-----------------------------------|----------------------------------|
    | Rate         | Uses index (changes)              | Uses fixed rate                  |
    | Balance      | Scaled by index                   | Grows by time × rate             |
    | Storged balance | Scaled balance (principal)     | Actual balance (principal + interest) |
    | User rate    | Same for all users                | Each user has own rate           |
    | Complexity   | Simple                            | Complex (weighted avg)           |
    | Mint params  | (user, onBehalfOf, amount, index) | (user, onBehalfOf, amount, rate) |
    | Rate updates | Automatic via index               | Manual recalculation             |
    | Timestamps   | Not tracked                       | Tracked per user                 |


#### 2.7 Update User Config (If 1st borrow)
```solidity
if (isFirstBorrowing) {
    userConfig.setBorrowing(reserve.id, true);
}

// UserConfiguration.sol
function setBorrowing(
    DataTypes.UserConfigurationMap storage self,
    uint256 reserveIndex, // which reserve
    bool borrowing // true = set bit, false = clear bit
) internal {
    // need to be < 128 as userconfig can only store max 128 reserves
    require(reserveIndex < 128, Errors.UL_INVALID_INDEX);
    // calculate bit position
    self.data =
        (self.data & ~(1 << (reserveIndex * 2))) |
        (uint256(borrowing ? 1 : 0) << (reserveIndex * 2));
}
```
- **Purpose**: Mark this reserve as "borrowed" in user's bitmap
- **Bitmap structure**: bit 0 = collateral flag, bit 1 = borrowing flag

#### 2.8 Update Interest Rates After Borrowing
```solidity
reserve.updateInterestRates(
    vars.asset, // reserve address
    vars.aTokenAddress, // aToken address
    0, // liquidity added
    vars.releaseUnderlying ? vars.amount : 0 // liquidity taken
);
```
- **Purpose**: Recalculate interest rates based on new utilization
- **What changes**
    - Available liquidity decreases by borrowed amount
    - Utilization rate increases due to borrowing
    - Variable & stable borrow rate adjusts based on new utilization
    - Deposit rate adjusts (depositors earn more as utilization increases)

#### 2.9 Transfer Borrowed Assets To User
```solidity
if (vars.releaseUnderlying) {
    IAToken(vars.aTokenAddress).transferUnderlyingTo(vars.user, vars.amount);
}

// AToken.sol
function transferUnderlyingTo(address target, uint256 amount)
    external
    override
    onlyLendingPool // only lendingPool can call
    returns (uint256)
{
    IERC20(_underlyingAsset).safeTransfer(target, amount);
    return amount; // the amount transferred
}
```
- **Purpose**: Send borrowed assets to the user address
- **How it works**:
    - aToken contract holds underlying assets (eg. USDC, DAI, etc.)
    - aToken transfers underlying from its balance to user address
    - aToken total supply doesn't change (as it represents deposits, not liquidity)

#### 2.10 Emit Borrow Event
```solidity
emit Borrow(
    vars.asset,             // borrowed asset (indexed)
    vars.user,              // who initiated the transaction
    vars.onBehalfOf,        // who received the debt (indexed)
    vars.amount,            // borrowed amount
    vars.interestRateMode,  // stable or variable
    DataTypes.InterestRateMode(vars.interestRateMode) == DataTypes.InterestRateMode.STABLE
    ? currentStableRate
    : reserve.currentVariableBorrowRate, // stable or variable current rate
    vars.referralCode       // partner referral code (indexed)
);
```
- **Purpose**: Log borrow for offchain indexing and analytics
- **Used by**: Aave UI, analytics platform, liquidation bots etc.

## Key Insights
### Risk Management Architecture
- **Defense in depth**: Multi-layered validation protects protocol at every stage
    - Pre-transaction validation (checks in `validateBorrow()` - fail fast before state changes)
    - Post-transaction health checks (health factor must remain > 1)
    - Continuous monitoring via debt tokens (balances auto-update without gas costs)
    - LTV/Threshold buffer zones (safety gap prevents instant liquidation)
- **Price oracle dependency**: All risk calculations flow through oracle prices
    - Every borrow converts amounts to ETH for unified risk assessment
    - Oracle failure = protocol risk (mitigated via Chainlink multi-source feeds)
    - Single point of failure for health factor calculations

### Gas-Efficient Design Patterns
- **Scaled balance architecture**: Core innovation enabling passive interest accrual for variable rate borrow
    - Only principal stored on-chain as `scaledBalance = amount / index`
    - Actual balance calculated on-demand: `balance = scaledBalance * currentIndex`
    - Zero gas cost for interest compounding across millions of users
    - Same pattern used in both aTokens (deposits) and variable debt tokens (borrows)

### Multi-Asset Complexity
- **Weighted average calculations**: Protocol handles diverse collateral portfolios
    - Each asset has different risk parameters (ETH: 80% LTV, WBTC: 70% LTV)
    - Weighted by collateral value, not simple arithmetic average
    - Formula: `avgLTV = Σ(assetValueETH × assetLTV) / totalCollateralETH`
    - Ensures fair treatment regardless of portfolio composition
- **Cross-reserve calculations**: `calculateUserAccountData()` is the key risk engine
    - Only function that analyzes user's ENTIRE portfolio
    - Loops through all reserves to compute 5 critical metrics
    - Called on every borrow to ensure global solvency

### Two-Rate System Trade-offs
- **Variable rate**: Simple, capital-efficient, protocol-favored
    - Shared index across all users (gas-efficient)
    - Risk born by borrowers (rate volatility)
    - Automatically adjusts to market conditions
- **Stable rate**: Complex, capital-inefficient, now deprecated
    - Per-user rate storage (expensive)
    - Risk borne by protocol (rate arbitrage exposure)
    - Required liquidity caps (max 25%) to limit protocol losses
    - **Historical note**: Fully deprecated in Jan 2024 after Nov 2023 security issue
- **Rate updates cascade**: Every borrow/repay recalculates rates
    - Changes utilization → triggers interest rate model
    - Affects ALL users in the pool (not just borrower)
    - Real-time supply/demand mechanism

### Implementation Insights
- **Credit delegation pattern**: `user` ≠ `onBehalfOf` enables advanced strategies
    - User initiates transaction and receives borrowed funds
    - OnBehalfOf incurs the debt (requires allowance)
    - Unlocks institutional use cases (treasuries, margin trading)
- **Reserve state updates**: Every operation follows strict sequence
    - Update state (compound indexes) BEFORE minting debt tokens
    - Mint debt tokens BEFORE updating rates
    - Update rates AFTER liquidity changes
    - Order matters for consistency and prevents manipulation
    
## Reference
- Aave v2 whitepaper: https://github.com/aave/protocol-v2/blob/master/aave-v2-whitepaper.pdf
- Aave v2 repo: https://github.com/aave/protocol-v2

## Discussion
- Twitter: [@chloe_zhuX](https://x.com/Chloe_zhuX)
- Telegram: [@Chloe_zhu](https://t.me/chloe_zhu)
- GitHub: [@Chloezhu010](https://github.com/Chloezhu010)

---

*Last updated: Dec 17th, 2025*
*Part of my #LearnInPublic Defi series*