# 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;
}
```

(*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
}
```

(*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

### 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%

#### 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*