Ensure the security of your smart contracts

Modern DeFi Lending Protocols, how it's made: Aave V3

Author: Sergey Boogerwooger, Dmitry Zakharov
Security researchers at MixBytes
Intro
The picture of modern lendings would be incomplete without one of the most significant projects - Aave. Aave is a top lending protocol with the highest TVL, with fully proven security and effectiveness. Aave V2 is a "must-study" in our DeFi educational courses, thanks to its innovative design of supply and debt tokens mechanics, as well as its lending pool architecture. Now, let’s explore the implementation details of Aave’s next evolution — Aave V3.
Higher-level design
The technical design of Aave's protocols centers around the main lending pool, managed by protocol governance, and the set of rebaseable supply and debt tokens used to store users' obligations to/from the protocol. The core lending procedures - borrow/repay/supply/withdraw - are handled by the mint/burn of protocol tokens, along with user configuration bitmaps.

The Aave DAO selects collateral and debt assets and defines their risk parameters, making it difficult to introduce high-risk or compromised tokens into the protocol. But the market demands a usage of many different assets, having different risk profiles. To address this, V3 introduces specialized supply/borrowing modes and combinations of assets with adjustable risk parameters. One key enhancement in V3 compared to V2, is the addition of these modes. The "isolation" mode restricts suppliers to a single asset and limits borrowing to a select group of tokens (mainly stablecoins). Another feature is the "efficiency mode", designed for assets with correlated prices, such as those with the same "underlying" asset as a price source. Examples include stablecoins pegged to USD or ETH derivatives. In these cases, risk parameters can be tuned to be more profitable for suppliers and borrowers as many risks of price manipulation are reduced.

These modes mitigate the risks of "mixing" risky and "safe" collateral assets in supply/borrowing schemes, allow to increase capital efficiency for "safe" markets, creating a clear distinction between markets with stable, predictable assets and those with volatile, high-risk assets.

Compared to Aave V2, V3 offers many additional features, making the protocol much more flexible. Aave V3 enables bridging protocol, alowing to bridge tokens across different networks, setting supply and borrow caps (protecting the protocol from large-scale attacks), delegating risk management to specialized agents and DAOs, implementing a variable liquidation close factor (allowing full liquidation of a position that is close to insolvency), offering multiple reward tokens, and many other features described in detail in the whitepaper.

Aave’s main procedures revolve around rebaseable protocol tokens, without direct "per-user" structures for positions. When a user supplies assets, they receive ATokens, and when borrowing, they get non-transferrable debt tokens. Additional "per-user" configuration includes enable/disable bits for collateral/debt assets and other configuration options.
Core
The main contract in Aave V3 is the Pool.sol contract, containing the entry points for borrow/supply/liquidation/flashloan and other functions. It is accompanied by a pack of contracts for ACLs (Access Control Lists), address management, protocol tokens, oracles contracts, rewards management controller, and others. The structure of these contracts is further explained in the docs.

The first stop in Aave's design is, of course, AToken. This token demonstrates a design that allows the interest to accumulate directly "in" the token balance. The key features of AToken are its balanceOf() and totalSupply() functions, which show the continuously increasing balances of each token holder and totalSupply. This approach is different, for example, from Compound, where the CToken balance remains constant and the interest is calculated separately. Using rebaseable supply and debt tokens allows easy calculation of protocol reserves and debt, avoiding excess multiplications and allowing supply tokens to move between different addresses without additional logic.

The same approach is applied to debt tokens: the balanceOf() function in both the StableDebtToken.sol and the VariableDebtToken.sol includes compounded interest. Studying these functions provides insight into how stable and variable borrow rates work in Aave.

[NOTE] StableDebt token is no longer supported by Aave, according to the governance decision.

Atoken contracts (for each underlying asset: WETH, DAI, etc.) also hold the balances of underlying assets (WETH, DAI, etc.). The procedures of minting/burning ATokens involve transferring the underlying tokens in/out of the protocol as well. This allows the underlying balances to be kept separate from each other and reduces the risk associated with compromising a single contract holding all the token balances.

The main logic parts of the Aave V3 protocol reside in multiple libraries. This approach enhances code composability and allows each logic component to be tested separately without needing a fully initialized protocol for each test, which is very beneficial for development and code analysis.
Borrow/repay
Borrowing logic is outlined in BorrowLogic.sol and executed through the executeBorrow() function. After a thorough validation of the borrowing possibility (we will return to this later), the debt tokens are minted to the user's address, and the underlying asset is transferred to the user. As with any function that alters protocol reserves, borrowing also updates market interest rates according to the new reserve amounts.

The repay logic in the executeRepay() function "mirrors" borrowing in many aspects but has an interesting feature: it allows the debt to be repaid using not only the underlying token but the supply AToken as well. If the borrower has already used the borrowed tokens, there's no need to acquire them elsewhere — they can use their supply to repay the debt.

V3 also implements a borrow cap (set by governance) restriction, which is critical for protecting the protocol from large-scale attacks that exploit oracle price manipulations, flashloans, and the use of massive amounts of funds.
Supply/withdraw
The supply logic is detailed in the supplyLogic.sol library by two functions: executeSupply() and executeWithdraw().

Supplying in Aave involves minting AToken and updating market interest rates according to the new reserve amounts. The withdrawal procedure is also a "mirrored" process for supply, burning ATokens instead of minting. However, there is an interesting implementation detail. The mint() and burn() functions of AToken.sol use the _mintScaled() and _burnScaled() functions, which calculate the target amounts to mint/burn by utilizing the liquidity index (used to scale balances of AToken and debt tokens). In the case of _burnScaled(), the final balance change can be positive (if the accumulated interest is greater than the amount being burned), meaning that instead of burning tokens, additional ATokens are minted. Such cases can occur in DeFi protocols when rebaseable tokens are used - this is a trade-off for significant simplification of reserve and interest rate calculations.

The final withdrawal steps, of course, include checking the position's health factor, which will be discussed below.

Similar to borrowing, V3 now includes a supply cap restriction to safeguard the protocol from attacks like the ones described in the borrowing section.
Protocol reserves
As mentioned earlier, Aave stores the underlying tokens using the balance of the corresponding AToken contracts. This separation helps mitigate the risk associated with storing all assets at a single address. AToken and debt tokens balances continuously increase, scaled by indexes that are adjusted accordingly to the current strategy. The updateInterestRates() function calculates the next supply/borrow rates using the strategy contract (set by protocol governance) here, using current debt, protocol reserves, and the newly added/removed liquidity as parameters. This function is present in all borrow/repay/supply/withdraw operations, ensuring the protocol state is updated whenever liquidity amounts change.

Two other functions that change the global state of the currently used reserve asset are _updateIndexes() and _accrueToTreasury() which are also called during each operation to adjust the protocol reserves.

These global updates are essential for applying different risk parameters to each asset and managing stable/variable debt rates and market strategies.
Oracles
For any lending protocol, price oracles are crucial, providing the means to compare different asset amounts based on market price. In Aave V2, ETH was the primary "measurement unit", and all asset amounts were converted into ETH for comparisons. In V3, Aave oracles support the usage of any base currency units in the oracle.

The oracles in Aave V3 reside in the AaveOracle.sol contract. The key function is getAssetPrice(), which utilizes Chainlink price feeds and refers to the _fallbackOracle if the price is unacceptable.

Asset prices are vital for validating the health of positions, as they are used to calculate collateral and debt values based on current market rates. The oracle is used in the calculation of user debt and collateral in "BASE_CURENCY" units here in the calculateUserAccountData() function, and later in validateBorrow(), ensuring the collateral matches the requested debt.

Asset prices are also crucial during the liquidation process, where the prices are used to determine the amounts of debt tokens needed and the collateral seized by the liquidator.

A notable feature in V3 is the PriceOracleSentinel, which addresses price oracle outages or downtimes. Such issues can occur, especially in L2 networks, when delays in sequencer can result in price oracle downtime. In these situations, Aave provides users with time to restore their positions to a healthy state by denying liquidations for a certain period of time.
Risk management and liquidations
Aave offers granular control over the risk parameters for different asset combinations across its networks. The live dashboard of risk parameters for each asset can be found here. V3 has a large set of these parameters, including not only standard LTVs and rates but also borrow/supply caps and

The liquidation procedure is a critical component of any lending protocol. In Aave, it's implemented in the executeLiquidationCall() function, where the first important step is the calculation of the health factor, applying the liquidation threshold limit set for the given collateral asset.

An important change in V3, compared to V2, is the adjustment in the available Liquidation Close Factor (how much of the debt can be repaid by the liquidator). Previously set at 50%, Aave now allows repaying up to 100% (MAX_LIQUIDATION_CLOSE_FACTOR) of the debt if the health factor of the position is below 0.95 (CLOSE_FACTOR_HF_THRESHOLD). This change enables faster liquidation of unhealthy positions, helping to remove potential bad debt from the protocol more efficiently.

Another notable feature of Aave's liquidation process (similar to repay procedure logic) is the option for liquidators to receive the seized collateral as ATokens. This feature allows the liquidator to automatically add the collateral to their supply without exiting the protocol.
Implementation details
Custom math libraries are a common practice for many lending protocols. Interaction between different assets, calculation of ratios, percentages, and asset amounts with different precision requires the unification of maths in the protocol. In Aave, the main library is WadRayMath.sol, which manages overflows, precision loss, and ensures correct ordering in multiplication/division operations. It operates with two main precision types: 18 and 27 digits. For example, the rayMul() or rayDiv() functions are used almost everywhere instead of regular multiplication/division, allowing for handling potential problems with precision and operations ordering.

While Aave uses protocol supply/debt tokens to track user supplies and debts, these balances alone are not enough to cover all aspects of the logic, especially for tracking the specific assets a user is borrowing or supplying. To resolve this, Aave employs an additional "per-user" value stored as single uint256 (struct UserConfigurationMap). Each time a user changes their position, the flags in the user configuration bitmap are changed (examples for "isBorrowing" here or here). This requires additional steps during operations (like here), but it helps to make the validation cheaper at many stages (example in validateBorrow here), especially with queries like "is user borrowing any assets", which would otherwise involve querying numerous token balances without these configuration maps.

Aave V3 supports operations with permits, featuring special versions of supplyWithPermit() and repayWithPermit() functions, which enable offchain signatures. This allows users to delegate these operations to external accounts, such as third-party providers, if they don't want to execute them directly.

Another important addition in Aave V3 is L2Pool.sol. It mirrors the functionality of the base Pool.sol but accepts arguments as packed bytes32 values. These values are internally decoded by L2Pool.sol and then forwarded to the corresponding functions in Pool.sol. This approach enables executing operations like supply(), borrow(), and others using a single bytes32 parameter, significantly reducing transaction costs in L2 rollups.

Aave V3 also introduces a second variant of flashloans. The executeFlashLoan() function works similarly to Aave V2, allowing users to borrow multiple assets and repay the flashloan debt along with fees by increasing their debt in the protocol. Additionally, V3 provides a simplified version of flashloan (the executeFlashLoanSimple() function) which enables borrowing a single asset without the option to pay with "debt increase", thus reducing gas costs. V3 also allows for setting authorized "flashborrowers" addresses that are exempt from flashloan fees.
Conclusion
Aave V3 is a prime example of a lending protocol with granular control over risk parameters for each type of asset across multiple networks, and featuring various modes of supplying/borrowing logic. This separation of risk profiles allows Aave to react to market conditions in high-risk areas without affecting the more stable parts of the protocol. Aave's supply modes ("efficiency" and "isolated") enable specialized risk profiles, optimizing interest rates, borrowing efficiency, and safety based on the nature of the assets involved, such as stablecoins, liquid staking assets, derivatives of the same basic asset, etc.

Aave’s use of rebaseable supply and debt tokens facilitates liquidity transfers through simple supply token transfers. This approach simplifies the calculations of accrued interest and allows for operations using supply tokens rather than underlying tokens, enhancing flexibility in liquidations and repayments. Users can pay their debts with supply tokens or convert seized collateral into supply tokens without leaving the protocol.

Aave V3 introduces supply/borrow caps, oracle sentinels, and delegated risk management to enhance the protocol’s responsiveness to security and financial incidents. At present, Aave stands out as one of the most robust lending project in the DeFi landscape. We look forward to seeing what the future holds with Aave V4.

See you in our next articles!
Who is MixBytes?
MixBytes is a team of expert blockchain auditors and security researchers specializing in providing comprehensive smart contract audits and technical advisory services for EVM-compatible and Substrate-based projects. Join us on Twitter to stay up-to-date with the latest industry trends and insights.
Disclaimer
The information contained in this Website is for educational and informational purposes only and shall not be understood or construed as financial or investment advice.
Other posts