Aave V3 Tokens
At the heart of Aave's accounting are specialized ERC20-compliant tokens: aTokens (representing supplied liquidity) and Debt Tokens (representing borrowed funds). These tokens are more than simple balance keepers; they incorporate Aave's core interest accrual mechanisms and specific DeFi functionalities like EIP712 signatures and credit delegation.
The Core Mechanism: Scaled Balances
Before diving into the specifics of each token, it's crucial to understand the Scaled Balance mechanism employed by both aTokens and Debt Tokens.
- Problem: Continuously updating every user's balance as interest accrues would be incredibly gas-intensive.
- Solution: Instead of storing the actual balance, Aave stores a scaled balance for each user. Simultaneously, the protocol maintains a global index for each reserve:
- Liquidity Index (
liquidityIndex): Tracks the cumulative interest earned by suppliers. Starts at 1 Ray (1e27) and increases over time based on the supply rate. - Variable Borrow Index (
variableBorrowIndex): Tracks the cumulative interest accrued on variable-rate borrows. Starts at 1 Ray (1e27) and increases based on the variable borrow rate.
- Liquidity Index (
- Calculation:
- When a user interacts (supply, borrow, transfer), their current balance is calculated:
currentBalance = scaledBalance * currentIndex. - The amount corresponding to the action (e.g., amount supplied) is then converted to a scaled amount:
scaledAmount = amount / currentIndex. - This
scaledAmountis added to or subtracted from the user's storedscaledBalance. - The user's "last updated index" is also stored (implicitly within
ScaledBalanceTokenBase._userState'sadditionalDatafield) to calculate accrued interest correctly between actions.
- When a user interacts (supply, borrow, transfer), their current balance is calculated:
- Result: Interest accrual is reflected by simply updating the global index for the reserve. Individual user storage writes only happen when a user actively interacts with the protocol.
The base contract ScaledBalanceTokenBase.sol implements this core logic.
// File: /src/contracts/protocol/tokenization/base/ScaledBalanceTokenBase.sol
// Returns the internally stored scaled balance (balance / index)
function scaledBalanceOf(address user) external view override returns (uint256) {
return super.balanceOf(user); // Inherited balanceOf stores the scaled balance
}
// Returns the scaled total supply (totalSupply / index)
function scaledTotalSupply() public view virtual override returns (uint256) {
return super.totalSupply(); // Inherited totalSupply stores the scaled total supply
}
// Internal mint function using scaled math
function _mintScaled(
address caller,
address onBehalfOf,
uint256 amount, // Amount in underlying units
uint256 index // Current reserve index (liquidity or variable borrow)
) internal returns (bool) {
uint256 amountScaled = amount.rayDiv(index); // Convert to scaled amount
// ... calculate balanceIncrease based on previous index ...
_userState[onBehalfOf].additionalData = index.toUint128(); // Store current index for user
_mint(onBehalfOf, amountScaled.toUint128()); // Mint scaled amount (calls base _mint)
// ... emit events with actual amounts ...
}
// Internal burn function using scaled math
function _burnScaled(address user, address target, uint256 amount, uint256 index) internal {
uint256 amountScaled = amount.rayDiv(index); // Convert to scaled amount
// ... calculate balanceIncrease based on previous index ...
_userState[user].additionalData = index.toUint128(); // Store current index for user
_burn(user, amountScaled.toUint128()); // Burn scaled amount (calls base _burn)
// ... emit events with actual amounts ...
}
1. AToken (AToken.sol)
- Purpose: Represents a user's supplied liquidity in a specific reserve. It's an interest-bearing token; holding aTokens means your underlying deposit is earning the supply interest rate.
- Key Interfaces:
IAToken,IScaledBalanceToken,IInitializableAToken,IERC20,IERC20Detailed. - Core Logic: Inherits heavily from
ScaledBalanceTokenBasefor the core ERC20 and scaled balance logic, andEIP712Basefor permit functionality.
Key Functions & Mechanics:
initialize(...): Sets up the aToken, linking it to the Pool, underlying asset, treasury, incentives controller, and setting metadata (name, symbol, decimals). Called once by thePoolConfigurator.mint(caller, onBehalfOf, amount, index):- Called only by the
Poolcontract during asupplyoperation. - Delegates to
_mintScaled, providing the currentliquidityIndexfrom the Pool. - Increases the user's scaled balance and the token's scaled total supply.
- Called only by the
burn(from, receiverOfUnderlying, amount, index):- Called only by the
Poolcontract during awithdraworliquidationCall(ifreceiveATokenis false). - Delegates to
_burnScaled, providing the currentliquidityIndex. - Decreases the user's scaled balance and the token's scaled total supply.
- Crucially, it also calls
IERC20(_underlyingAsset).safeTransfer(receiverOfUnderlying, amount)to send the actual underlying asset back to the user/liquidator.
- Called only by the
balanceOf(user):- Overrides the base implementation to return the current, interest-accrued balance.
- Fetches the user's stored scaled balance (
super.balanceOf(user)). - Fetches the current
liquidityIndexfrom the Pool (POOL.getReserveNormalizedIncome(_underlyingAsset)). - Returns
scaledBalance.rayMul(currentIndex).
totalSupply():- Similar to
balanceOf, calculates the current total supply based on the stored scaled total supply and the currentliquidityIndex.
- Similar to
_transfer(from, to, amount, validate):- Handles internal transfer logic for both
transferandtransferFrom. - Fetches the current
liquidityIndex. - Updates the stored scaled balances of both sender and receiver using
super._transfer(from, to, amount.rayDiv(index).toUint128()). - Updates the
additionalData(last updated index) for both users. - If
validateis true (for user-initiated transfers): CallsPOOL.finalizeTransfer(...)to allow the Pool to perform a health factor check after the transfer, ensuring the sender doesn't become undercollateralized by transferring away collateral aTokens. - Emits relevant
TransferandBalanceTransfer(scaled amount) events.
- Handles internal transfer logic for both
permit(...):- Implements EIP-2612, allowing users to approve spending via an off-chain signature, saving a transaction compared to the standard
approve+transferFromflow. UsesEIP712Basefor domain separation and nonce tracking.
- Implements EIP-2612, allowing users to approve spending via an off-chain signature, saving a transaction compared to the standard
transferUnderlyingTo(target, amount):- A utility function callable only by the
Pool. Used duringborrowandflashLoanoperations to instruct the aToken contract (which holds the liquidity) to send underlying assets to the borrower/receiver.
- A utility function callable only by the
handleRepayment(user, onBehalfOf, amount):- Hook callable only by the
Poolafter a repayment using underlying assets or during a flash loan repayment. In the baseAToken, this is empty, but derived contracts could implement logic here (e.g., staking).
- Hook callable only by the
mintToTreasury(amount, index):- Called only by the
Poolto allocate accrued protocol fees (reserve factor) to the designated treasury address by minting new aTokens. Uses_mintScaled.
- Called only by the
2. VariableDebtToken (VariableDebtToken.sol)
- Purpose: Represents a user's borrowed amount at a variable interest rate for a specific reserve. The balance accrues interest over time based on the reserve's variable borrow rate.
- Key Interfaces:
IVariableDebtToken,IScaledBalanceToken,IInitializableDebtToken,ICreditDelegationToken. - Core Logic: Inherits from
DebtTokenBase(for credit delegation) andScaledBalanceTokenBase(for ERC20 and scaled balance logic).
Key Functions & Mechanics:
initialize(...): Sets up the debt token, similar to the aToken, linking it to the Pool, underlying asset, incentives controller, and setting metadata.mint(user, onBehalfOf, amount, index):- Called only by the
Poolduring aborrowoperation or aflashLoanwith Mode 2 (debt opening). - Credit Delegation Check: If
user != onBehalfOf, it means theuser(caller ofPool.borrow) is borrowing on behalf ofonBehalfOfusing delegated credit. It calls_decreaseBorrowAllowance(onBehalfOf, user, amount)(fromDebtTokenBase) to reduce the borrowing allowanceonBehalfOfpreviously granted touser. - Delegates to
_mintScaled, providing the currentvariableBorrowIndexfrom the Pool. - Increases the
onBehalfOfuser's scaled debt balance and the token's scaled total supply. - Returns
isFirstBorrowingflag and the newscaledTotalSupply.
- Called only by the
burn(from, amount, index):- Called only by the
Poolduring arepayorliquidationCall. - Delegates to
_burnScaled, providing the currentvariableBorrowIndex. - Decreases the
fromuser's scaled balance and the token's scaled total supply. - Returns the new
scaledTotalSupply.
- Called only by the
balanceOf(user):- Overrides the base implementation.
- Fetches the user's stored scaled debt balance (
super.balanceOf(user)). - Fetches the current
variableBorrowIndexfrom the Pool (POOL.getReserveNormalizedVariableDebt(_underlyingAsset)). - Returns
scaledBalance.rayMul(currentVariableBorrowIndex).
totalSupply():- Similar to
balanceOf, calculates the current total variable debt based on the stored scaled total supply and the currentvariableBorrowIndex.
- Similar to
approveDelegation(delegatee, amount):- Implements
ICreditDelegationToken. Allows a user (msg.sender) to grant another user (delegatee) the permission to borrow up toamounton their behalf (i.e., increasing the delegator's debt). Stores allowance in_borrowAllowances.
- Implements
delegationWithSig(...):- Implements EIP-712 based signature approval for
approveDelegation, saving gas. UsesDELEGATION_WITH_SIG_TYPEHASHandEIP712Base.
- Implements EIP-712 based signature approval for
- Disabled ERC20 Functions:
transfer,approve,transferFrom,increaseAllowance,decreaseAllowanceall explicitlyrevert(Errors.OPERATION_NOT_SUPPORTED). Debt tokens are non-transferable to maintain the integrity of individual user health factors and liquidation logic.
Base Contracts (IncentivizedERC20, MintableIncentivizedERC20, DebtTokenBase, EIP712Base)
IncentivizedERC20.sol: Provides the basic ERC20 storage (_userState,_allowances,_totalSupply) and implements standard view functions (name,symbol,decimals,allowance). Crucially, it introduces the_incentivesControllerstorage slot and modifies_transfer(and requires_mint/_burnin derived contracts) to include calls toincentivesControllerLocal.handleAction(user, totalSupply, oldBalance)whenever a user's balance changes, allowing rewards tracking.MintableIncentivizedERC20.sol: ExtendsIncentivizedERC20by adding the internal_mintand_burnfunctions, which update total supply and user balances while including thehandleActionhook for incentives.DebtTokenBase.sol: Provides the logic and storage (_borrowAllowances) for credit delegation (approveDelegation,delegationWithSig,borrowAllowance,_decreaseBorrowAllowance). It also inherits fromEIP712Basefor the delegation signature functionality.EIP712Base.sol: Provides the foundational logic for EIP-712 domain separation and nonce management, used by bothAToken(forpermit) andDebtTokenBase(fordelegationWithSig).
Conclusion
Aave V3's AToken and VariableDebtToken are sophisticated ERC20 tokens central to the protocol's operation. They leverage the Scaled Balance mechanism for gas-efficient interest accrual. While AToken represents yield-bearing supplied assets and supports standard ERC20 transfers (with Pool validation) and permit, VariableDebtToken represents variable-rate debt, is non-transferable, and includes unique Credit Delegation features. Both integrate seamlessly with the Pool for minting/burning and optionally with an IAaveIncentivesController for rewards. Understanding their internal scaled math and specific functionalities is key to interacting correctly with the Aave protocol.

