# Understanding Aave V2 Code (2/n) - Deposit Mechanism ## TLDR - **Deposit Flow**: Deposit assets → receive aTokens → balances grow automatically without transactions - **Scaled Balance**: Stores normalized amount (÷ index), displays actual amount (× index) → scalable for millions of users - **Asymmetric Interest**: Depositors earn linear, borrowers pay compound → built-in solvency buffer prevents bank runs - **Dynamic Rates**: Self-adjusting based on utilization via two-slope interest rate model - **Gas Optimizations**: Bitmap storage + 27-decimal Ray math for precision without rounding errors ## What's Deposit in Aave V2 When you deposit assets into Aave v2: 1. Your tokens go into the lending pool 2. You receive **aTokens** (e.g., USDC → aUSDC) 3. Your aToken balance automatically grows as you earn interest ## High-Level Flow User deposits USDC → Receives aUSDC → Balance grows over time ``` ┌──────────┐ │ User │ └────┬─────┘ │ 1. deposit(USDC, 10000) ▼ ┌─────────────────────┐ │ LendingPool.sol │ │ │ │ 2. Validate deposit │ │ 3. Update state & │ │ interest │ │ 4. Transfer tokens │ │ 5. Mint aTokens │ └─────────────────────┘ │ ▼ ┌─────────────────────┐ │ AToken.sol │ │ │ │ 6. Mint scaled amt │ │ 7. Balance shows │ │ growing value │ └─────────────────────┘ ``` ## Data Structures & Storage Layout Before diving into the deposit code, we need to understand where and how Aave v2 stores its data. This will make the step-by-step walkthrough much clearer. ### Storage Architecture Overview Aave V2 uses a clean separation between storage and logic: ``` ┌─────────────────────────────────┐ │ LendingPoolStorage.sol │ ← Defines storage layout │ (State variables) │ └─────────────────────────────────┘ ↓ ┌─────────────────────────────────┐ │ DataTypes.sol │ ← Defines struct schemas │ (Data structures) │ └─────────────────────────────────┘ ↓ ┌─────────────────────────────────┐ │ LendingPool.sol │ ← Uses storage + types │ (Business logic) │ └─────────────────────────────────┘ ``` ### Key Data Structure #### 1. ReserveData - The Core Structure ```solidity // DataTypes.sol struct ReserveData { // configuration ReserveConfigurationMap configuration; // stores the reserve configuration // interest tracking uint128 liquidityIndex; // deposit multiplier uint128 variableBorrowIndex; // variable debt multiplier // current rates uint128 currentLiquidityRate; // supply APY (what depositors earn) uint128 currentVariableBorrowRate; // variable borrow APY (what borrowers pay) uint128 currentStableBorrowRate; // stable borrow APY (what borrowers pay) // timestamp uint40 lastUpdateTimestamp; // last update time // token addresses address aTokenAddress; // address of interest-bearing token address stableDebtTokenAddress; // address of stable debt token address variableDebtTokenAddress; // address of variable debt token // interest rate strategy address address interestRateStrategyAddress; // address of APY calculator contract // reserve ID uint8 id; // reserve position } ``` #### 2. ReserveConfigurationMap - Risk Parameters as Bitmap ```solidity // DataTypes.sol struct ReserveConfigurationMap { //bit 0-15: LTV //bit 16-31: Liq. threshold //bit 32-47: Liq. bonus //bit 48-55: Decimals //bit 56: Reserve is active //bit 57: reserve is frozen //bit 58: borrowing is enabled //bit 59: stable rate borrowing enabled //bit 60-63: reserved //bit 64-79: reserve factor uint256 data; } ``` ![reserveconfigmap](https://hackmd.io/_uploads/Hk4gr6xzZe.png) (*Source: Aave v2 whitepaper*) | Variable | Usage | |----------|----------| | LTV (Loan-to-Value) | Determine max borrow % of collateral | | Liquidation threshold | Determine when position can be liquidated | | Liquidation bonus | Incentive paid to liquidators | | Decimals | Token decimals | | Active flag | Need to be active to accept deposit/ borrow | | Frozen flag | Reserve frozen to prevent new deposits/ borrows in crisis | | Borrowing is enabled | Can turn off borrowing while keeping deposits open | | Stable rate borrowing enabled | Separates variable borrowing from stable borrowing | | Reserve factor | Percentage of interest that goes to Aave treasury | Why bitmap? - Gas efficient: All params in ONE storage slot, amazing compression - Bit manipulation is cheap - Can read entire config in one SLOAD #### 3. UserConfigurationMap - User's Asset Usage ```solidity // DataTypes.sol struct UserConfigurationMap { uint256 data; // bitmap tracking which assets user uses } ``` ![userconfigmap](https://hackmd.io/_uploads/BJYZBalG-l.png) (*Source: Aave v2 whitepaper*) Two bits per Reserve - Max 128 reserves - uint256 has 256 bits - So Aave v2 supports max 128 reserves - Each reserve gets 2 bits - Bit 0 (even): Whether use is using this reserve as collateral - Bit 1 (odd): Whether user is borrowing this reserve - State combinations - 00 : User has no interation with this reserve - 01 : Only borrowing (impossible) - 10 : Only collateral - 11 : Both borrowing and collateral ### Storage Layout in LendingPoolStorage ```solidity // LendingPoolStorage.sol contract LendingPoolStorage { // main storage // store all data for each reserve (incl. liquidity index, borrow index, current rate etc.) mapping(address => DataTypes.ReserveData) internal _reserves; // track which assets each user is using mapping(address => DataTypes.UserConfigurationMap) internal _usersConfig; // the list of the available reserves, structured as a mapping for gas savings reasons mapping(uint256 => address) internal _reservesList; } ``` ## Math & Key Concepts Aave's deposit mechanism uses several mathematical concepts: - **Ray Math (27 decimals)** - High-precision arithmetic - **Interest Calculation** - Linear interest for depositors - Compound interest for borrowers - **The Two Indexes** - Liquidity index (depositor growth) - Variable borrow index (borrower debt growth) - **Scaled Balance** - Storage-efficient balance representation - **Utilization Rate & Interest Rates** - How rates are determined ### 1. Ray Math - 27 Decimal Precision Ray = 27 decimal precision number system used by Aave ```solidity // WadRayMath.sol uint256 internal constant WAD = 1e18; // 18 decimals (standard ERC20) uint256 internal constant RAY = 1e27; // 27 decimals (Aave's precision) ``` Why 27 decimals? - Problem with 18 decimals - Interest rate: 0.05% per day - Over 365 days with compounding => Rounding errors accumulate - Can lose significant value - Solution with 27 decimals - Much more precise representation - Rounding errors 1 billion times smaller - Accurate compounding over years ### 2. Interest Calculation Aave v2 uses **two different** interest calculation methods: 1. **Linear Interest** - For depositors (liquidity) 2. **Compound Interest** - For borrowers (debt) **Why use two calculation methods?** - Solvency guarantee - Over time, compound interest always out-runs linear interest - Therefore the contract always owes depositors slightly less than what borrowers are paying - The pool silently accumulates a small surplus instead of a deficit, so a “bank-run” can never leave late withdrawers without funds - Gas optimization - Calculating linear interest onchain consumes less computation power and gas fee compared to compound interest calculation #### 2a. Linear Interest - Formula - `Linear Interest = 1 + (annualRate * timeElapsed / secondsPerYear)` - Math notation - `I = 1 + (r × Δt / T)` - I = interest multiplier - r = annual rate (eg. 0.03) - Δt = seconds elapsed - T = seconds per year (31,536,600) #### 2b. Compound Interest - Formula - `Compound Interest = (1 + annualRate) ^ (timeElapsed / secondsPerYear)` - Math notation - `I = (1 + r) ^ (Δt / T)` - Problem: Exponentiation is expensive in Solidity - Solution: Binomial approximation - `(1+x)^n = 1 + n*x + [n/2*(n-1)]*x^2 + [n/6*(n-1)*(n-2)*x^3...` - Aave v2 uses the first 3 terms for compound interest, which slighly underpays depositors and undercharge borrowers, with the advantages of great gas cost saving - Example - Initial value of 1,000 USDC - Over time period, compounded value grows faster than linear value - The "Dust" (difference between the two) stays in the pool as solvency buffer ![linear_compound](https://hackmd.io/_uploads/r1Kfrpef-l.png) ### 3. The Two Indexes Aave tracks growth using two separate indexes: - Liquidity Index - Tracks how much depositors have earned - Variable Borrow Index - Tracks how much variable debt borrowers owe Both start at 1.0 and grow over time, but at different rates #### 3a. Liquidity Index - Definition: A cumulative growth factor that tracks all interest earned by depositors since the pool was created. ```solidity // ReserveData struct uint128 liquidityIndex; // Starts at 1e27, grows using linear interest calculation ``` - Formula: `newIndex = oldIndex × (1 + rate × timeElapsed / secondsPerYear)` - Usage in deposit flow - Updated by `updateState()` - Used by `getReserveNormalizedIncome()` to calculate current aToken balance #### 3b. Variable Borrow Index - Definition: A cumulative growth factor that tracks how much borrowers' variable debt has grown since the pool was created. ```solidity // ReserveData struct uint128 variableBorrowIndex; // Starts at 1e27, grows using compound interest calculation ``` - Formula: `newIndex = oldIndex * (1 + rate)^ (timeElapsed / secondsPerYear)` - Usage in deposit flow - Updated by `updateState()` ### 4. Scaled Balance Scaled balance is a "normalized" balance that accounts for all interest accrued before a deposit/borrow, enabling automatic balance growth without storage updates #### The Two-Balance System Aave v2 uses two different balance concepts **1. Scaled Balance (Internal):** - What's stored in contract storage - Never changes unless user deposits/withdraws - Hidden from users **2. Actual Balance (External):** - What wallets display - Grow over time as interest accrues - Calculated on-demand (not stored) **Why This Design?** - WITHOUT this system: - Every block, update every user's balance - O(n) gas cost per block - Extremely expensive for thousands of users - Impossible to scale - WITH this system (smart): - Update only ONE index per block - O(1) gas cost - User's balance calculated on-read using: `scaledBalance × liquidity index` - Scale to millions of users - Gas efficient #### Usage in the deposit flow - Minting aToken for depositor: scaled balance - Formula: `scaled balance = actual balance / current index` - Code implementation ```solidity // AToken.sol - mint() uint256 amountScaled = amount.rayDiv(index); _mint(user, amountScaled); ``` - Reading balance: actual balance - Formula: `actual balance = scaled balance * current index` - Code implementation ```solidity // AToken.sol - balanceOf() function balanceOf(address user) public view returns (uint256) { return super.balanceOf(user).rayMul(_pool.getReserveNormalizedIncome(_underlyingAsset)); } ``` ### 5. Utilization Rate & Interest Rates Aave has **three interest rates** that all depend on **utilization rate**: 1. **Liquidity Rate** - What depositors earn 2. **Variable Borrow Rate** - What variable-rate debt borrowers pay 3. **Stable Borrow Rate** - What stable-rate debt borrowers pay #### Utilization rate - Definition: Percentage of deposited capital currently borrowed - Formula: - `Utilization rate = Total borrows / (Available liquidity + Total borrows)` - `Excess rate = 1 - Utilization rate` - Kinked interest rate model - Below optimal utilization rate: Rates increase gradually (encourage borrowing) - Above optimal utilization rate: Rates spike dramatically (discourage borrowing, encourage deposits) #### Variable / Stable borrow rate - Below optimal: `borrow rate = base rate + (utilization / optimal) * slope1` - Above optimal: `borrow rate = base rate + slope1 + ((utilization - optimal) / excess) * slope2` - Example - Optimal utilization rate: 80% - Base rate: 2% - Slope 1 rate: 4% - Slope 2 rate: 5% ![interestRate](https://hackmd.io/_uploads/By2XB6eGZx.png) #### Liquidity rate - Formula - `liquidity rate = weighted avg borrow rate * utilization rate * (1 - reserve factor)` - `weighted avg borrow rate = (total variable debt * variable borrow rate + total stable debt * stable borrow rate) / total debt` ## Step by Step Code Walkthrough Now that we understand the data structures and the relevant concepts & math, let's see how they're used in the deposit flow. ### Step 1: Entry Point - `deposit()` Main purpose - Deposit an amount of underlying asset into the reserve - In return get the correspondent aTokens ```solidity // LendingPool.sol function deposit( address asset, // asset to deposit uint256 amount, // amount to deposit address onBehalfOf, // the address that will receive the aTokens uint16 referralCode // code used to register the integrator originating the ops ) external override whenNotPaused { // get refernce of the asset's current reserve DataTypes.ReserveData storage reserve = _reserves[asset]; // validate a deposit action ValidationLogic.validateDeposit(reserve, amount); address aToken = reserve.aTokenAddress; // update 2 indexes (liquidity index, variable borrow index) reserve.updateState(); // update 3 rates (stable borrow rate, variable borrow rate, liquidity rate) reserve.updateInterestRates(asset, aToken, amount, 0); // transfer deposited asset to aToken address IERC20(asset).safeTransferFrom(msg.sender, aToken, amount); // mint aTokens bool isFirstDeposit = IAToken(aToken).mint(onBehalfOf, amount, reserve.liquidityIndex); // enable the deposit as collateral by default on first deposit if (isFirstDeposit) { _usersConfig[onBehalfOf].setUsingAsCollateral(reserve.id, true); emit ReserveUsedAsCollateralEnabled(asset, onBehalfOf); } // emit event emit Deposit(asset, msg.sender, onBehalfOf, amount, referralCode); } ``` ### Step 2: Get reserve of the asset ```solidity DataTypes.ReserveData storage reserve = _reserves[asset]; ``` - `DataTypes.ReserveData`, explained above, is a struct from DataTypes.sol that holds all info about a reserve - `storage` gets a reference to the actual data stored on chain - `_reserves[asset]` looks up the specific reserve for that asset, returns the `ReserveData` struct ### Step 3: Validate Deposit - `validateDeposit()` ```solidity // ValidationLogic.sol function validateDeposit( DataTypes.ReserveData storage reserve, // asset's current reserve uint256 amount // amount user want to deposit ) external view { // get reserve state floags from bit-packed config (bool isActive, bool isFrozen, , ) = reserve.configuration.getFlags(); // deposit amount should not be 0 require(amount != 0, Errors.VL_INVALID_AMOUNT); // reserve is active require(isActive, Errors.VL_NO_ACTIVE_RESERVE); // reserve is not frozen require(!isFrozen, Errors.VL_RESERVE_FROZEN); } // ReserveConfiguration.sol function getFlags(DataTypes.ReserveConfigurationMap storage self) internal view returns ( bool, // isActive bool, // isFrozen bool, // borrowingEnabled bool // stableRateBorrowingEnabled ) { uint256 dataLocal = self.data; return ( (dataLocal & ~ACTIVE_MASK) != 0, // bit 56 (dataLocal & ~FROZEN_MASK) != 0, // bit 57 (dataLocal & ~BORROWING_MASK) != 0, // bit 58 (dataLocal & ~STABLE_BORROWING_MASK) != 0 // bit 59 ); } ``` ### Step 4: Update Liquidity & Variable Borrow Index - `updateState()` `updateState()` updates the two indexes so depositors and borrowers' balances grow automatically ```solidity // ReserveLogic.sol function updateState(DataTypes.ReserveData storage reserve) internal { // get the total variable debt uint256 scaledVariableDebt = IVariableDebtToken(reserve.variableDebtTokenAddress).scaledTotalSupply(); // get previous borrow index uint256 previousVariableBorrowIndex = reserve.variableBorrowIndex; // get previous liquidity index uint256 previousLiquidityIndex = reserve.liquidityIndex; // get last updated time uint40 lastUpdatedTimestamp = reserve.lastUpdateTimestamp; // update the liquidity index & variable borrow index based on time passed and interest rates // liquidity index: linear interest 1 * (1 + x * n) // variable debt index: compound interest 1 * (1 + x)^n (uint256 newLiquidityIndex, uint256 newVariableBorrowIndex) = _updateIndexes( reserve, scaledVariableDebt, previousLiquidityIndex, previousVariableBorrowIndex, lastUpdatedTimestamp ); // send a portion of the earned interest to Aave treasury based on reserve factor _mintToTreasury( reserve, scaledVariableDebt, previousVariableBorrowIndex, newLiquidityIndex, newVariableBorrowIndex, lastUpdatedTimestamp ); } ``` What it does: 1. Calculate accrued interest using the time elapsed and current interest rates 2. Update **liquidity index** (depositors' balances scale by this) using **linear interest** 3. Update **variable borrow index** (variable rate borrowers' debt scales by this) using **compound interest** 4. Mint treasury share (Aave keeps a percentage of interest earned) Example: - Before `updateState()` - liquidityIndex = 1.00 (starting value) - variableBorrowIndex = 1.00 (starting value for variable debt) - Time passed: 2 year - currentLiquidityRate = 0.03 (3% APY for depositors) - currentVariableBorrowRate = 0.05 (5% APY for variable borrowers) - After `updateState()` - liquidityIndex = oldIndex × (1 + rate × time) = 1.00 * (1 + 0.03 * 2) = 1.06 - variableBorrowIndex = oldIndex × (1 + rate)^time = 1.00 * (1 + 0.05)^2 = 1.1025 - lastUpdateTimestamp = block.timestamp (updated) ### Step 5: Update Interest Rates - `updateInterestRates()` `updateInterestRates()` recalculates the three interest rates based on current market condition (supply/ demand), then store them in the reserve - current stable borrow rate - current variable borrow rate - current liquidity rate ```solidity // ReserveLogic.sol function updateInterestRates( DataTypes.ReserveData storage reserve, address reserveAddress, address aTokenAddress, uint256 liquidityAdded, uint256 liquidityTaken ) internal { // previous code ... (detailed will be explored in the later blog) // store the new rates reserve.currentLiquidityRate = uint128(vars.newLiquidityRate); reserve.currentStableBorrowRate = uint128(vars.newStableRate); reserve.currentVariableBorrowRate = uint128(vars.newVariableRate); // emit event } ``` What it does: 1. Gather market data: Collect current deposit and debt balances 2. Calculate utilization ratio: Determine how much of the pool is borrowed vs available 3. Apply two-slope model: Use different rate curves based on utilization level 4. Calculate three rates: Determine what depositors earn and what borrowers pay 5. Adjust for treasury fee: Reduce deposit rate by reserve factor percentage 6. Store new rates: Update reserve with updated three rates ### Step 6: Transfer Underlying Tokens to aToken contract - `safeTransferFrom()` ```solidity IERC20(asset).safeTransferFrom(msg.sender, aToken, amount); ``` What it does: - Transfer the actual underlying tokens (USDC, DAI, etc.) from the user to the aToken contract - aToken contract is the vault that holds all deposited asset. When user deposits: - The deposited tokens go into the aToken pool - The aToken holds them as collateral/ reserve - User's balance grows as the vault earns interest ### Step 7: Mint aToken - `IAToken.mint()` After tokens are transferred, Aave mints aTokens to represent your deposit. This is where the magic happens. ```solidity bool isFirstDeposit = IAToken(aToken).mint(onBehalfOf, amount, reserve.liquidityIndex); // tokenization/AToken.sol function mint( address user, // the address receiving the minted tokens uint256 amount, // amount of tokens getting minted uint256 index // the new liquidity index of the reserve ) external override onlyLendingPool returns (bool) { uint256 previousBalance = super.balanceOf(user); // amountScaled = amount / new index uint256 amountScaled = amount.rayDiv(index); require(amountScaled != 0, Errors.CT_INVALID_MINT_AMOUNT); // mint to the address normalized amount of aToken _mint(user, amountScaled); // emit event emit Transfer(address(0), user, amount); emit Mint(user, amount, index); // return true if previous balance was 0 return previousBalance == 0; } ``` What happens: 1. **Receive parameters from LendingPool:** - `user`: Alice's address - `amount`: 10,000 USDC (the deposit amount) - `index`: New liquidityIndex (e.g., 1.05e27) 2. **Calculate scaled amount:** - `amountScaled = amount / liquidityIndex` - Why divide? We're "deflating" the amount by all interest already accrued in the pool - This normalizes deposits made at different times 3. **Mint scaled aTokens:** - Stores `amountScaled` in the ERC20 `_balances` mapping - This is what lives in contract storage (not the full amount!) 4. **Emit events:** - Event shows `amount` (10,000) but stores `amountScaled` (9,523.81) - This is intentional - users see round numbers in events 5. **Return first deposit flag:** - Returns true if user's previous aToken balance was 0 - Used to enable collateral automatically for first-time depositors ### Step 8: Balance Growth - `balanceOf()` This is the most innovative part of Aave V2. User's aToken balance **grows automatically** without any transactions. **The Two-Balance System:** - **Scaled balance** (stored): Never changes unless you deposit/withdraw more - **Actual balance** (calculated): Grows as liquidityIndex increases - Formula: `actualBalance = scaledBalance × currentLiquidityIndex` ```solidity // tokenization/AToken.sol function balanceOf(address user) public view override(IncentivizedERC20, IERC20) returns (uint256) { // Displayed balance = scaled balance * liquidity index (rebase as the index changes) return super.balanceOf(user). // get the scaled balance rayMul( // multiple _pool.getReserveNormalizedIncome(_underlyingAsset) // get updated liquidity index ); } // LendingPool.sol function getReserveNormalizedIncome(address asset) external view virtual override returns (uint256) { return _reserves[asset].getNormalizedIncome(); } // ReserveLogic.sol function getNormalizedIncome(DataTypes.ReserveData storage reserve) internal view returns (uint256) { uint40 timestamp = reserve.lastUpdateTimestamp; // Check if the index already updated in this block by updateState() //solium-disable-next-line if (timestamp == uint40(block.timestamp)) { // if yes, just return the stored liquidityIndex return reserve.liquidityIndex; } // if not, calculate the latest liqudity index using linear interest calculation uint256 cumulated = MathUtils.calculateLinearInterest(reserve.currentLiquidityRate, timestamp).rayMul( reserve.liquidityIndex); return cumulated; } ``` **How it works:** 1. Get scaled balance from storage (what was minted during deposit) 2. Multiply by current liquidityIndex (which grows over time) 3. Result = actual balance that reflects all earned interest **Example:** ``` Alice deposits 10,000 USDC at index 1.05: - Scaled balance stored: 9,523.81 - Current index: 1.05 - Displayed balance: 9,523.81 × 1.05 = 10,000 ✅ One month later, index grows to 1.0526: - Scaled balance: 9,523.81 (unchanged!) - Current index: 1.0526 - Displayed balance: 9,523.81 × 1.0526 = 10,025 - Alice earned 25 USDC with zero transactions! ``` The `getNormalizedIncome()` function returns the up-to-date `liquidityIndex`: - If already updated this block → return stored index - If not updated yet → calculate latest index using linear interest - This ensures balance always reflects the most recent interest accrual ### Step 9: Enable Deposit as Collateral - `setUsingAsCollateral()` ```solidity // if it's user's first deposit of this reserve if (isFirstDeposit) { // enable to use this asset as collateral for borrowing _usersConfig[onBehalfOf].setUsingAsCollateral(reserve.id, true); emit ReserveUsedAsCollateralEnabled(asset, onBehalfOf); } // configuration/UserConfiguration.sol function setUsingAsCollateral( DataTypes.UserConfigurationMap storage self, uint256 reserveIndex, bool usingAsCollateral ) internal { // max number of reserve cap at 128 require(reserveIndex < 128, Errors.UL_INVALID_INDEX); // set the bits to match the usingAsCollateral parameter self.data = (self.data & ~(1 << (reserveIndex * 2 + 1))) | (uint256(usingAsCollateral ? 1 : 0) << (reserveIndex * 2 + 1)); } ``` ## Key Insights **1. Scaled Balance Architecture: Gas Efficiency at Scale** - **Storage**: Normalized amount (deposit ÷ liquidityIndex) - remains constant - **Display**: Actual amount (scaledBalance × currentIndex) - calculated on-demand - **Impact**: Single index update enables millions of balances to grow automatically without individual storage writes - **Innovation**: Transforms O(n) per-block update cost into O(1), making the protocol economically viable at scale **2. Asymmetric Interest Model: Built-in Solvency Guarantee** - **Depositors earn**: Linear interest (gas-efficient, slightly conservative) - **Borrowers pay**: Compound interest (economically fair, slightly higher) - **Result**: Compound growth always outpaces linear growth over time - **Safety mechanism**: The protocol accumulates a small surplus ("dust") instead of deficit, preventing bank-run scenarios where late withdrawers can't access funds **3. Dynamic Interest Rate Model: Self-Balancing Market Mechanism** - **Low utilization** → Lower rates → Incentivizes borrowing, discourages deposits - **High utilization** → Higher rates → Incentivizes deposits, discourages borrowing - **Two-slope (kinked) design**: - Below optimal utilization: Gradual rate increases maintain capital efficiency - Above optimal utilization: Steep rate increases protect liquidity availability - **Outcome**: Market naturally stabilizes around optimal utilization without governance intervention **4. Bitmap Storage Optimization: Maximum Information Density** - **Reserve configuration**: All parameters packed into single uint256 (one SLOAD operation) - **User configuration**: 2 bits per reserve × 128 reserves = 256 bits total - **Reserve factor**: Protocol treasury fee (typically 10%) extracted during index updates **5. Ray Math (27-Decimal Precision): Compounding Without Rounding Errors** - **Standard ERC20**: 18 decimals (1e18) - insufficient for long-term interest calculations - **Aave precision**: 27 decimals (1e27) - 1 billion times more precise - **Necessity**: Prevents cumulative rounding errors in compound interest over years - **Application**: All interest calculations, index updates, and rate computations use Ray math - **Result**: Accurate balance growth even after thousands of compounding periods ## 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 5th, 2025* *Part of my #LearnInPublic Defi series*