What is Aave
Aave is a decentralized non-custodial money market protocol where users can participate as depositors or borrowers. Depositors provide liquidity to the market to earn a passive income, while borrowers are able to borrow in an overcollateralized (perpetually) or undercollateralized (one-block liquidity) fashion.
The following documentation describes the fundamentals of the protocol and how to interact with it. Please join the #development room in the Aave community Discord server; our team and members of the community look forward to help you building an application on top of Aave. Your questions help us improve, so please don't hesitate to ask if you can't find what you are looking for here.
How it works
The protocol is implemented as a set of smart contracts on top of the Ethereum blockchain. Smart contracts guarantee safety, and avoid the need of a middleman.
The Aave protocol is developed with security as reference: the protocol has been audited by multiple auditors and formally verified. Please find more details about the security of the Aave protocol in the security section.
Smart Contracts
The LendingPoolAddressesProvider
The smart contracts in the Aave protocol are upgradeable through governance. The protocol keeps a global register of addresses that holds all the addresses of the components deployed on the particular network. Therefore, whenever it's required to access the LendingPool
contract (description below), it is recommended to fetch the correct address from the LendingPoolAddressesProvider
smart contract.
Retrieve contract instances
The following table sums up all the addresses of the protocol, grouped by network.
A description of the main contracts of the protocol follows.
LendingPoolAddressesProvider
/// Retrieve LendingPool address
LendingPoolAddressesProvider provider = LendingPoolAddressesProvider(
/*contract_address*/
);
LendingPool lendingPool = LendingPool(provider.getLendingPool());
Global addresses register of the protocol. This contract is immutable and his address will never change. Needs to be used whenever it is needed to fetch the address of the latest implementation of the LendingPool
contract.
LendingPool
The LendingPool
contract is the main contract of the protocol. It exposes all the user-oriented actions that can be invoked using either Solidity or web3 libraries.
deposit()
/**
* Deposit of 1000 DAI
*/
/// Retrieve LendingPool address
LendingPoolAddressesProvider provider = LendingPoolAddressesProvider(
/*contract_address*/
);
LendingPool lendingPool = LendingPool(provider.getLendingPool());
/// Input variables
address daiAddress = "0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359";
uint256 amount = 1000 * 1e18;
uint16 referral = 0;
/// Deposit method call
lendingPool.deposit(daiAddress, amount, referral);
function deposit( address _reserve, uint256 _amount, uint16 _referralCode)
Deposits a certain _amount
of an asset specified by the _reserve
parameter. The caller receives a certain amount of corresponding aTokens in exchange. The amount of aTokens received depends on the corresponding aToken exchange rate.
Parameter Name | Type | Description |
---|---|---|
_reserve |
address | address of the asset _reserve |
_amount |
uint256 | amount deposited, expressed in decimals units |
_referralCode |
uint256 | Referral code for affiliation programs |
ETH deposits
Our protocol doesn't use any EIP-20 wrapper such as wETH for ETH deposits, _amount
parameter of deposit()
method must match the Value
parameter of the transaction.
ETH reserve address is provided by LendingPoolAddressesProvider getEthereumAddress()
method
setUserUseReserveAsCollateral()
/**
* Enable usage of the DAI reserve as collateral for the user
*/
/// Retrieve LendingPoolAddress
LendingPoolAddressesProvider provider = LendingPoolAddressesProvider(
/*contract_address*/
);
LendingPool lendingPool = LendingPool(provider.getLendingPool());
/// Input variables
address daiAddress = "0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359";
bool useAsCollateral = true;
/// setUserUseReserveAsCollateral method call
lendingPool.setUserUseReserveAsCollateral(daiAddress, useAsCollateral);
function setUserUseReserveAsCollateral(address _reserve, bool _useAsCollateral)
Using this function users can decide whether or not use a specific deposit as collateral. Users will only be able to disable deposits that are not being used as collateral yet.
Parameter Name | Type | Description |
---|---|---|
_reserve |
address | address of the asset _reserve |
_useAsCollateral |
bool | if true, the asset is allowed as a collateral for borrow |
borrow()
/**
* Deposit of 1000 DAI
*/
/// Retrieve LendingPool address
LendingPoolAddressesProvider provider = LendingPoolAddressesProvider(
/*contract_address*/
);
LendingPool lendingPool = LendingPool(provider.getLendingPool());
/// Input variables
address daiAddress = "0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359";
uint256 amount = 1000 * 1e18;
/// 0 is stable rate, 1 is variable rate
bool variableRate = 1;
uint16 referral = 0;
/// Borrow method call
lendingPool.borrow(daiAddress, amount, variableRate, referral);
function borrow( address _reserve, uint256 _amount, uint256 _interestRateMode, uint16 _referralCode)
The borrow()
function transfer a specific amount of the asset identified by the _reserve
parameter to the msg.sender
, provided that the caller has preemptively deposited enough collateral to cover the borrow.
Every borrow can be opened with a stable or variable rate mode. Borrows have infinite duration and there is no repayment schedule. In case of price fluctuations, a borrow position is liquidated if if the price of the collateral drops below a certain threshold. Please refer to the White Paper to understand how the stable rate economy works.
NOTE:
stable terminology is replacing the deprecated fixed terminology. As the Aave contracts ecosystem is audited, fixed term remains in the code.
Parameter Name | Type | Description |
---|---|---|
_reserve |
address | address of the asset _reserve |
_amount |
uint256 | amount of to borrow, expressed in decimal units |
_interestRateMode |
uint256 | Stable or Variable rates borrow mode selection. take note that stable rate int is 0, variable rate is 1. |
_referralCode |
uint256 | Referral code for affiliation programs |
repay()
/// Retrieve the LendingPool address
LendingPoolAddressesProvider provider = LendingPoolAddressesProvider(
/*contract_address*/
);
LendingPool lendingPool = LendingPool(provider.getLendingPool());
/// Input variables
address daiAddress = "0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359";
uint256 amount = 1000 * 1e18;
address myAddress = /*repayer_address*/;
/// Repay method call
lendingPool.repay(daiAddress, amount, myAddress);
function repay( address _reserve, uint256 _amount, address payable _onBehalfOf)
This function allows the user to fully or partially repay a borrowed asset. The _onBehalfOf
parameter can be used to repay the debt of a different user. NOTE: When the user sending the transaction is repaying his own debt, _onBehalfOf
needs to be equal to msg.sender
.
Parameter Name | Type | Description |
---|---|---|
_reserve |
address | address of the asset _reserve |
_amount |
uint256 | amount to repay, expressed in decimal units. To repay the whole borrowed amount, the function accepts uint(-1) as value for _amount , ONLY when the repayment is not executed on behalf of a 3rd party. In case of repayments on behalf of another user, it's recommended to send an _amount slightly higher than the current borrowed amount. |
_onBehalfOf |
address payable | address to repay on behalf of. Needs to be equal to msg.sender when the caller is repaying for himself. |
rebalanceFixedBorrowRate()
/// Retrieve the LendingPool address
LendingPoolAddressesProvider provider = LendingPoolAddressesProvider(
/*contract_address*/
);
LendingPool lendingPool = LendingPool(provider.getLendingPool());
/// Input variables
address daiAddress = "0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359";
address rebalancedUser = /*address_to_rebalance*/;
/// rebalanceFixedBorrowRate method call
lendingPool.rebalanceFixedBorrowRate(daiAddress, rebalancedUser);
function rebalanceFixedBorrowRate(address _reserve, address _user)
This function rebalances the Stable rate of _user
. If the user is not borrowing at a Stable rate or the conditions for the rebalance are not satisfied, the transaction gets reverted. Please refer to the White Paper for details on how and when an user position can be rebalanced.
Parameter Name | Type | Description |
---|---|---|
_reserve |
address | address of the asset _reserve |
_user |
address | address of the user to rebalance |
liquidationCall()
/// Retrieve the LendingPool address
LendingPoolAddressesProvider provider = LendingPoolAddressesProvider(
/*contract_address*/
);
LendingPool lendingPool = LendingPool(provider.getLendingPool());
/// Input variables
address collateralAddress = /*collateral_address*/;
address linkAddress = "0x514910771af9ca656af840dff83e8264ecf986ca";
address userliquidated = /*user_address_being_liquidated*/;
uint256 purchaseAmount = 12 * 1e18;
address daiAddress = "0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359";
bool getATokens = true;
/// liquidationCall method call
lendingPool.liquidationCall(
collateralAddress,
linkAddress,
userliquidated,
purchaseAmount,
daiAddress,
getATokens
);
function liquidationCall(address _collateral, address _reserve, address _user, uint256 _purchaseAmount, bool _receiveAToken)
The liquidationCall()
function allows everybody to liquidate positions that present a health factor below 1. Whenever this happens, liquidators will repay part or all of the outstanding borrowed amount on behalf of the borrower, while receiving a discounted amount of collateral in return. Liquidators can decide if they want to receive an equivalent amount of collateral aTokens, or the underlying asset directly. When the liquidation happens, the health factor of the position is increased, bringing it above 1 again.
Liquidators can only close a certain amount of collateral defined by a close factor. Across all the protocol, the close factor is 0.5 (liquidators can liquidate 50% of every position at once).
Parameter Name | Type | Description |
---|---|---|
_collateral |
address | address of the liquidated collateral reserve |
_reserve |
address | address of the asset reserve of the loan principal |
_user |
address | address of the user borrowing |
_purchaseAmount |
uint256 | amount of the discounted purchase |
_receiveAToken |
bool | if true, the user receives the aTokens equivalent of the purchased collateral. If false, the user receives the underlying asset directly |
flashLoan()
/**
* Flash Loan of 1000 DAI
*/
/// Retrieve the LendingPool address
LendingPoolAddressesProvider provider = LendingPoolAddressesProvider(
/*contract_address*/
);
LendingPool lendingPool = LendingPool(provider.getLendingPool());
/// Input variables
/* the receiver is a contract that implements the IFLashLoanReceiver interface */
address receiver = /*contract_address*/;
address daiAddress = "0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359";
uint256 amount = 1000 * 1e18;
/// flashLoan method call
lendingPool.flashLoan(receiver, daiAddress, amount);
function flashLoan( address payable _receiver, address _reserve, uint _amount) external
Allows contracts to borrow in an undercollateralized fashion from the reserve specified by the parameter _reserve
a certain _amount
of liquidity, that must be returned within the same transaction. Please refer to the Tutorials section on how to create and implement on top of the Flash Loan functionality.
Parameter Name | Type | Description |
---|---|---|
_receiver |
address, payable | address of the receiver of the borrowed assets |
_reserve |
address | address of deposit asset _reserve |
_amount |
uint256 | amount to be received |
getReserveConfigurationData()
function getReserveConfigurationData(address _reserve)
Returns specific reserve's configuration parameters.
return name |
Type | Description |
---|---|---|
ltv | uint256 | Loan-to-value. Value in percentage |
liquidationThreshold | uint256 | liquidation threshold. Value in percentage |
liquidationDiscount | uint256 | liquidation discount. Value in percentage |
interestRateStrategyAddress | address | address of the contract defining the interest rate strategy |
usageasCollateralEnabled | bool | if true , reserve asset can be used as collateral for borrowing |
borrowingEnabled | bool | if true , reserve asset can be borrowed |
fixedBorrowRateEnabled | bool | if true , reserve asset can be borrowed with stable rate mode |
isActive | bool | if true , users can interact with reserve asset |
getReserveData()
function getReserveData(address _reserve)
Returns global information on any asset reserve
pool
return name |
Type | Description |
---|---|---|
totalLiquidity | uint256 | reserve total liquidity |
availableLiquidity | uint256 | reserve available liquidity for borrowing |
totalBorrowsFixed | uint256 | total amount of outstanding borrows at Stable rate |
totalBorrowsVariable | uint256 | total amount of outstanding borrows at Variable rate |
liquidityRate | uint256 | current yearly interest earned by the depositors. In Ray units. |
variableBorrowRate | uint256 | current Variable rate APR of the reserve pool |
fixedBorrowRate | uint256 | current stable rate APR of the reserve pool |
averageFixedBorrowRate | uint256 | current average stable borrow rate |
utilizationRate | uint256 | Espressed as total borrows/total liquidity. |
liquidityIndexRate | uint256 | cumulative liquidity index |
variableBorrowIndex | uint256 | cumulative variable borrow index |
aTokenAddress | address | aTokens contract address for the specific _reserve |
lastUpdateTimestamp | uint40 | timestamp of the last update of reserve data |
getUserAccountData()
function getUserAccountData(address _user)
Returns information of a reserve exclusively related with a particular user
address
return name |
Type | Description |
---|---|---|
totalLiquidityETH | uint256 | user aggregated deposits across all the reserves. In Wei |
totalCollateralETH | uint256 | user aggregated collateral across all the reserves. In Wei |
totalBorrowsETH | uint256 | user aggregated outstanding borrows across all the reserves. In Wei |
availableBorrowsETH | uint256 | user available amount to borrow in ETH |
currentLiquidationThreshold | uint256 | user current average liquidation threshold across all the collaterals deposited |
ltv | uint256 | user average Loan-to-Value between all the collaterals |
healthFactor | uint256 | user current Health Factor |
getUserReserveData()
function getUserReserveData(address _reserve, address _user)
Returns information related to the user
data on a specific reserve
return name |
Type | Description |
---|---|---|
currentATokenBalance | uint256 | user current reserve AToken balance |
currentUnderlyingBalance | uint256 | user current reserve deposit amount |
currentBorrowBalance | uint256 | user current reserve outstanding borrow balance |
principalBorrowBalance | uint256 | user balance of borrowed asset |
borrowRateMode | uint256 | user borrow rate mode either Stable or Variable |
borrowRate | uint256 | user current borrow rate APR |
liquidityRate | uint256 | user current earn rate on _reserve |
originationFee | uint256 | user outstanding loan origination fee |
variableBorrowIndex | uint256 | user variable cumulative index |
lastUpdateTimestamp | uint256 | Timestamp of the last data update |
getReserves()
function getReserves()
Returns an array of all the active reserves addresses.
aTokens
The aTokens are interest-bearing derivative tokens that are minted and burned upon deposit and redeem. The aTokens carry the underlying value of the deposited amount plus the accrued interest, and can be safely stored, transferred or traded. Since the contract interface is compatible with the ERC20 token interface, the documentation will only cover functions and behaviors that are not part of the ERC20 token standard.
redeem()
/// Instantiation of the AToken address
AToken aTokenInstance = AToken("/*aToken_address*/");
/// Input variables
uint256 amount = 1000 * 1e18;
/// redeem method call
aTokenInstance.redeem(amount)
function redeem(uint256 _amount)
Non-standard ERC20 function to redeem an _amount
of aTokens for the underlying asset, burning the aTokens during the process. NOTE: redeem()
will fail if the aTokens to be redeemed are being used as collateral. Please refer to the transferAllowed()
function to understand how to check if a specific redeem/transfer action can be performed.
Parameter Name | Type | Description |
---|---|---|
_amount |
uint256 | amount of ATokens to redeem |
transfer()
/// Instantiation of the AToken address
AToken aTokenInstance = AToken("/*aToken_address*/");
/// Input variables
address recipient = /*transfer_recipient_address*/;
uint256 amount = 1000 * 1e18;
/// transfer method call
aTokenInstance.transfer(recipient, amount)
function transfer(address recipient, uint256 amount) public
Standard ERC20 function to transfer tokens from msg.sender
to a specified recipient
. NOTE: transfer()
will fail if the aTokens to be redeemed are being used as collateral. Please refer to the transferAllowed()
function to understand how to check if a specific redeem/transfer action can be performed.
transferFrom()
/// Instantiation of the AToken address
AToken aTokenInstance = AToken("/*aToken_address*/");
/// Input variables
address from = /*transfer_source_address*/
address to = /*transfer_recipient_address*/;
uint256 amount = 1000 * 1e18;
/// transferFrom method call
aTokenInstance.transferFrom(from, to, amount)
function transferFrom(address from, address to, uint256 amount) public
Standard ERC20 function to transfer tokens from an address to another. NOTE: transferFrom()
will fail if the aTokens to be redeemed are being used as collateral. Please refer to the transferAllowed()
function to understand how to check if a specific redeem/transfer action can be performed.
transferAllowed()
function transferAllowed(address from, uint256 amount)
Non-standard ERC20 function that allows to check beforehand if a transfer or a redeem will fail. Specifically, a transfer/redeem will fail if the resulting Health Factor of the user performing the action will end up being below 1.
return name |
Type | Description |
---|---|---|
transferAllowed | bool | true if the transfer is allowed, otherwise false |
getExchangeRate()
function getExchangeRate()
Non-standard ERC20 function that returns the current exchange rate between the aToken and the corresponding underlying asset, in Ray.
return name |
Type | Description |
---|---|---|
initialExchangeRate | uint256 | initial exchange rate expressed in rays |
initialExchangeRate()
function initialExchangeRate()
Non-standard ERC20 functions that returns the initial exchange rate between the aToken and the corresponding underlying asset, in ray.
return name |
Type | Description |
---|---|---|
initialExchangeRate | uint256 | initial amount of aTokens per underlying token a depositor receives. In Ray units. |
underlyingAssetAddress()
function underlyingAssetAddress()
Non-standard ERC20 function that returns the address of the underlying asset.
return name |
Type | Description |
---|---|---|
underlyingAssetAddress | address | the ERC20 token contract address of the underlying asset |
balanceOfUnderlying()
function balanceOfUnderlying(address _user)
Non-standard ERC20 function that returns the current underlying balance of _user
.
return name |
Type | Description |
---|---|---|
balance | uint256 | balance of the user in the underlying asset |
aTokenAmountToUnderlyingAmount()
function aTokenAmountToUnderlyingAmount(uint256 _amount)
Non-standard ERC20 function that returns the underlying asset amount of a given aToken Amount in wei units.
return name |
Type | Description |
---|---|---|
amount | uint256 | corresponding uderlying asset amount of a given aToken amount, in wei units. |
aTokenAmountToUnderlyingAmount()
function underlyingAmountToATokenAmount(uint256 _amount)
Non-standard ERC20 function that returns the aToken amount of a given underlying asset Amount in wei units.
return name |
Type | Description |
---|---|---|
amount | uint256 | corresponding aToken amount of a given underlying asset, in wei units. |
Tutorials
The following section provides simple code examples on how to implement some specific functionalities of the protocol as well as the basic interactions.
Implementing a FlashLoanReceiver contract
pragma solidity ^0.5.0;
import "openzeppelin-solidity/contracts/math/SafeMath.sol";
import "../tokens/MintableERC20.sol";
import "../../flashloan/base/FlashLoanReceiverBase.sol";
import "../../configuration/LendingPoolAddressesProvider.sol";
import "../../configuration/NetworkMetadataProvider.sol";
contract FlashLoanReceiverExample is FlashLoanReceiverBase {
using SafeMath for uint256;
constructor(LendingPoolAddressesProvider _provider)
FlashLoanReceiverBase(_provider)
public {}
function executeOperation(
address _reserve,
uint256 _amount,
uint256 _fee) external returns(uint256 returnedAmount) {
//check the contract has the specified balance
require(_amount <= getBalanceInternal(address(this), _reserve),
"Invalid balance for the contract");
/**
CUSTOM ACTION TO PERFORM WITH THE BORROWED LIQUIDITY
*/
transferFundsBackToPoolInternal(_reserve, _amount.add(_fee));
return _amount.add(_fee);
}
}
To interact with the Flash Loan functionality of the Aave protocol, a basic understanding of Solidity and its inheritance techniques are needed. Specifically, in order to be able to access the protocol's liquidity in an undercollateralized way, it's necessary to implement a separate smart contract inheriting from the FlashLoanReceiverBase
contract which can be found here.
A basic implementation of the Flash Loan feature would need to keep into account the following conditions:
the
executeOperation()
function should include at any point atransferFundsBackToPoolInternal()
that returns the amount borrowed plus a fee to the protocol. If this action is not performed, the transaction is reverted.the
return
value ofexecuteOperation()
needs to be equal to_amount + _fee
. If this action is not performed, the transaction is reverted.
Security & Audits
Aave has been implemented with security as priority. The system has been designed to be safe and secure, and we have spent all the necessary resources in order to ensure that the protocol matches the highest security standards.
Currently, the code has already been audited by Trail of Bits. A second audit by OpenZeppelin and a Formal Verification procedure by ChainSecurity are in progress.
This section will be updated with the correspoding audit reports once all the code verification procedures are completed. As soon as the audit process is completed, the smart contracts code will be also open sourced and a bounty campaign will start.
Audits
Trail of Bits
The first audit by Trail of Bits can be found here.
Misc
Gas consumption
The gas consumption of the protocol depends on the specific action executed and the user. Specifically, it might be higher whenever an user executes an action for the first time, or depending on how many currencies the user has deposited/borrowed. Every currency added to the Aave protocol slightly increases the gas cost of all the operations that involve checking the account global liquidity.
Gas cost of the flashLoan()
function also depends on the specific flash loan implementation.
Function | Typical gas cost |
---|---|
Deposit | < 180K |
Redeem | < 300K |
Borrow | < 500K |
Redeem | < 150K |
liquidationCall | < 500K |
Ray Math
For internal calculations and to reduce the impact of rounding errors, the protocol uses the concept of Ray Math. A Ray is a unit with 27 decimals of precision. All the rates (liquidity/borrow/utilization rates) as well as the cumulative indexes and the aTokens exchange rates are expressed in Ray. Please refer to the following article for further details about this concept.
Glossary
Term | Description |
---|---|
Loan To Value | Expressed in percentage, represents the maximum borrowing power expressed by a specific collateral. If a collateral has a Loan to Value of 75%, for every 1 ETH worth of collateral the user will be able to borrow 0.75 ETH worth of principal currency. The Loan To Value is specified per collateral. |
Liquidation Threshold | Expressed in percentage, represents the threshold at which a borrow position will be considered undercollateralized and subjected to liquidation. If a collateral has a liquidation threshold of 80%, it means that the loan will be liquidated when the collateral is worth 80% of the principal amount. The liquidation threshold is specified per collateral. |
Liquidation Discount | Expressed in percentage, is the discount at which liquidators will be able to purchase a specific collateral. The Liquidation Discount is specified per collateral. |
Health factor | Represents the ratio between total collateral multiplied by the liquidation threshold, and the borrowed principal. When the Health Factor goes below 1, the loan is undercollateralized and can be liquidated. |
Stable rate | A loan with a stable rate behaves like a fixed rate loan in the short term, but the rates can be rebalanced following sudden market changes in the medium/long term. |