mirror of
https://github.com/Instadapp/aave-protocol-v2.git
synced 2024-07-29 21:47:30 +00:00
465 lines
16 KiB
Solidity
465 lines
16 KiB
Solidity
// SPDX-License-Identifier: agpl-3.0
|
|
pragma solidity ^0.6.8;
|
|
|
|
import {ERC20} from './ERC20.sol';
|
|
import {LendingPool} from '../lendingpool/LendingPool.sol';
|
|
import {WadRayMath} from '../libraries/math/WadRayMath.sol';
|
|
import {Errors} from '../libraries/helpers/Errors.sol';
|
|
import {
|
|
VersionedInitializable
|
|
} from '../libraries/openzeppelin-upgradeability/VersionedInitializable.sol';
|
|
import {IAToken} from './interfaces/IAToken.sol';
|
|
import {IERC20} from '../interfaces/IERC20.sol';
|
|
import {SafeERC20} from "../misc/SafeERC20.sol";
|
|
|
|
/**
|
|
* @title Aave ERC20 AToken
|
|
*
|
|
* @dev Implementation of the interest bearing token for the DLP protocol.
|
|
* @author Aave
|
|
*/
|
|
contract AToken is VersionedInitializable, ERC20, IAToken {
|
|
using WadRayMath for uint256;
|
|
using SafeERC20 for ERC20;
|
|
|
|
uint256 public constant UINT_MAX_VALUE = uint256(-1);
|
|
|
|
address public immutable UNDERLYING_ASSET_ADDRESS;
|
|
|
|
mapping(address => address) private _interestRedirectionAddresses;
|
|
mapping(address => uint256) private _interestRedirectionIndexes;
|
|
|
|
mapping(address => uint256) private _redirectedBalances;
|
|
mapping(address => uint256) private _redirectedBalanceIndexes;
|
|
|
|
mapping(address => address) private _interestRedirectionAllowances;
|
|
|
|
LendingPool private immutable _pool;
|
|
|
|
uint256 public constant ATOKEN_REVISION = 0x1;
|
|
|
|
modifier onlyLendingPool {
|
|
require(msg.sender == address(_pool), Errors.CALLER_MUST_BE_LENDING_POOL);
|
|
_;
|
|
}
|
|
|
|
constructor(
|
|
LendingPool pool,
|
|
address underlyingAssetAddress,
|
|
string memory tokenName,
|
|
string memory tokenSymbol
|
|
) public ERC20(tokenName, tokenSymbol, 18) {
|
|
_pool = pool;
|
|
UNDERLYING_ASSET_ADDRESS = underlyingAssetAddress;
|
|
}
|
|
|
|
function getRevision() internal virtual override pure returns (uint256) {
|
|
return ATOKEN_REVISION;
|
|
}
|
|
|
|
function initialize(
|
|
uint8 underlyingAssetDecimals,
|
|
string calldata tokenName,
|
|
string calldata tokenSymbol
|
|
) external virtual initializer {
|
|
_setName(tokenName);
|
|
_setSymbol(tokenSymbol);
|
|
_setDecimals(underlyingAssetDecimals);
|
|
}
|
|
|
|
/**
|
|
* @dev redirects the interest generated to a target address.
|
|
* when the interest is redirected, the user balance is added to
|
|
* the recepient redirected balance.
|
|
* @param to the address to which the interest will be redirected
|
|
**/
|
|
function redirectInterestStream(address to) external override {
|
|
_redirectInterestStream(msg.sender, to);
|
|
}
|
|
|
|
/**
|
|
* @dev redirects the interest generated by from to a target address.
|
|
* when the interest is redirected, the user balance is added to
|
|
* the recepient redirected balance. The caller needs to have allowance on
|
|
* the interest redirection to be able to execute the function.
|
|
* @param from the address of the user whom interest is being redirected
|
|
* @param to the address to which the interest will be redirected
|
|
**/
|
|
function redirectInterestStreamOf(address from, address to) external override {
|
|
require(
|
|
msg.sender == _interestRedirectionAllowances[from],
|
|
Errors.INTEREST_REDIRECTION_NOT_ALLOWED
|
|
);
|
|
_redirectInterestStream(from, to);
|
|
}
|
|
|
|
/**
|
|
* @dev gives allowance to an address to execute the interest redirection
|
|
* on behalf of the caller.
|
|
* @param to the address to which the interest will be redirected. Pass address(0) to reset
|
|
* the allowance.
|
|
**/
|
|
function allowInterestRedirectionTo(address to) external override {
|
|
require(to != msg.sender, Errors.CANNOT_GIVE_ALLOWANCE_TO_HIMSELF);
|
|
_interestRedirectionAllowances[msg.sender] = to;
|
|
emit InterestRedirectionAllowanceChanged(msg.sender, to);
|
|
}
|
|
|
|
/**
|
|
* @dev burns the aTokens and sends the equivalent amount of underlying to the target.
|
|
* only lending pools can call this function
|
|
* @param amount the amount being burned
|
|
**/
|
|
function burn(
|
|
address user,
|
|
address receiverOfUnderlying,
|
|
uint256 amount
|
|
) external override onlyLendingPool {
|
|
|
|
uint256 currentBalance = balanceOf(user);
|
|
|
|
require(currentBalance <= amount, Errors.INVALID_ATOKEN_BALANCE);
|
|
|
|
uint256 index = _pool.getReserveNormalizedIncome(UNDERLYING_ASSET_ADDRESS);
|
|
|
|
uint256 scaledAmount = amount.rayDiv(index);
|
|
|
|
_burn(user, scaledAmount);
|
|
|
|
|
|
if(amount == currentBalance){
|
|
_resetDataOnZeroBalance(user);
|
|
}
|
|
|
|
//if the user is redirecting his interest towards someone else,
|
|
//we update the redirected balance of the redirection address by adding the accrued interest,
|
|
//and removing the amount to redeem
|
|
_updateRedirectedBalanceOfRedirectionAddress(user, user, scaledAmount, 0, index);
|
|
|
|
//transfers the underlying to the target
|
|
ERC20(UNDERLYING_ASSET_ADDRESS).safeTransfer(receiverOfUnderlying, amount);
|
|
|
|
|
|
emit Burn(msg.sender, receiverOfUnderlying, amount, index);
|
|
}
|
|
|
|
/**
|
|
* @dev mints aTokens to user
|
|
* only lending pools can call this function
|
|
* @param user the address receiving the minted tokens
|
|
* @param amount the amount of tokens to mint
|
|
*/
|
|
function mint(address user, uint256 amount) external override onlyLendingPool {
|
|
|
|
uint256 index = _pool.getReserveNormalizedIncome(UNDERLYING_ASSET_ADDRESS);
|
|
|
|
uint256 scaledAmount = amount.rayDiv(index);
|
|
|
|
//mint an equivalent amount of tokens to cover the new deposit
|
|
_mint(user,scaledAmount);
|
|
|
|
//if the user is redirecting his interest towards someone else,
|
|
//we update the redirected balance of the redirection address by adding the accrued interest
|
|
//and the amount deposited
|
|
_updateRedirectedBalanceOfRedirectionAddress(user, user, amount, 0, index);
|
|
|
|
emit Mint(user, amount, index);
|
|
}
|
|
|
|
/**
|
|
* @dev transfers tokens in the event of a borrow being liquidated, in case the liquidators reclaims the aToken
|
|
* only lending pools can call this function
|
|
* @param from the address from which transfer the aTokens
|
|
* @param to the destination address
|
|
* @param value the amount to transfer
|
|
**/
|
|
function transferOnLiquidation(
|
|
address from,
|
|
address to,
|
|
uint256 value
|
|
) external override onlyLendingPool {
|
|
//being a normal transfer, the Transfer() and BalanceTransfer() are emitted
|
|
//so no need to emit a specific event here
|
|
_transfer(from, to, value, false);
|
|
}
|
|
|
|
/**
|
|
* @dev calculates the balance of the user, which is the
|
|
* principal balance + interest generated by the principal balance + interest generated by the redirected balance
|
|
* @param user the user for which the balance is being calculated
|
|
* @return the total balance of the user
|
|
**/
|
|
function balanceOf(address user) public override(ERC20, IERC20) view returns (uint256) {
|
|
//current scaled balance of the user
|
|
uint256 currentScaledBalance = super.balanceOf(user);
|
|
|
|
//balance redirected by other users to user for interest rate accrual
|
|
uint256 redirectedBalance = _redirectedBalances[user];
|
|
|
|
if (currentScaledBalance == 0 && redirectedBalance == 0) {
|
|
return 0;
|
|
}
|
|
uint256 scaledRedirectedBalance = redirectedBalance > 0 ? redirectedBalance.rayDiv(_interestRedirectionIndexes[user]) : 0;
|
|
|
|
uint256 index = _pool.getReserveNormalizedIncome(UNDERLYING_ASSET_ADDRESS);
|
|
|
|
if(_interestRedirectionAddresses[user] == address(0)){
|
|
//if the user is not redirecting the interest, his balance is the result of
|
|
//the interest accrued by his current scaled balance and the interest accrued by his
|
|
//scaled redirected balance
|
|
return currentScaledBalance.add(scaledRedirectedBalance).rayMul(index).sub(scaledRedirectedBalance);
|
|
}
|
|
|
|
//if the user is redirecting, his balance only increases by the balance he is being redirected to
|
|
uint256 lastRedirectedBalance = currentScaledBalance.rayDiv(_interestRedirectionIndexes[user]);
|
|
|
|
return
|
|
lastRedirectedBalance.add(
|
|
scaledRedirectedBalance.rayMul(index)
|
|
).sub(scaledRedirectedBalance);
|
|
}
|
|
|
|
/**
|
|
* @dev returns the scaled balance of the user. The scaled balance is the sum of all the
|
|
* updated stored balance divided the reserve index at the moment of the update
|
|
* @param user the address of the user
|
|
* @return the scaled balance of the user
|
|
**/
|
|
function scaledBalanceOf(address user) external override view returns (uint256) {
|
|
return super.balanceOf(user);
|
|
}
|
|
|
|
|
|
/**
|
|
* @dev returns the scaled balance of the user. The scaled balance is the sum of all the
|
|
* updated stored balance divided the reserve index at the moment of the update
|
|
* @param user the address of the user
|
|
* @return the scaled balance of the user
|
|
**/
|
|
function getUserInterestRedirectionIndex(address user) external override view returns (uint256) {
|
|
return _interestRedirectionIndexes[user];
|
|
}
|
|
|
|
/**
|
|
* @dev calculates the total supply of the specific aToken
|
|
* since the balance of every single user increases over time, the total supply
|
|
* does that too.
|
|
* @return the current total supply
|
|
**/
|
|
function totalSupply() public override(ERC20, IERC20) view returns (uint256) {
|
|
uint256 currentSupplyScaled = super.totalSupply();
|
|
|
|
if (currentSupplyScaled == 0) {
|
|
return 0;
|
|
}
|
|
|
|
return
|
|
currentSupplyScaled
|
|
.wadToRay()
|
|
.rayMul(_pool.getReserveNormalizedIncome(UNDERLYING_ASSET_ADDRESS))
|
|
.rayToWad();
|
|
}
|
|
|
|
/**
|
|
* @dev Used to validate transfers before actually executing them.
|
|
* @param user address of the user to check
|
|
* @param amount the amount to check
|
|
* @return true if the user can transfer amount, false otherwise
|
|
**/
|
|
function isTransferAllowed(address user, uint256 amount) public override view returns (bool) {
|
|
return _pool.balanceDecreaseAllowed(UNDERLYING_ASSET_ADDRESS, user, amount);
|
|
}
|
|
|
|
/**
|
|
* @dev returns the address to which the interest is redirected
|
|
* @param user address of the user
|
|
* @return 0 if there is no redirection, an address otherwise
|
|
**/
|
|
function getInterestRedirectionAddress(address user) external override view returns (address) {
|
|
return _interestRedirectionAddresses[user];
|
|
}
|
|
|
|
/**
|
|
* @dev returns the redirected balance of the user. The redirected balance is the balance
|
|
* redirected by other accounts to the user, that is accrueing interest for him.
|
|
* @param user address of the user
|
|
* @return the total redirected balance
|
|
**/
|
|
function getRedirectedBalance(address user) external override view returns (uint256) {
|
|
return _redirectedBalances[user];
|
|
}
|
|
|
|
|
|
/**
|
|
* @dev updates the redirected balance of the user. If the user is not redirecting his
|
|
* interest, nothing is executed.
|
|
* @param user the address of the user for which the interest is being accumulated
|
|
* @param scaledBalanceToAdd the amount to add to the redirected balance
|
|
* @param scaledBalanceToRemove the amount to remove from the redirected balance
|
|
**/
|
|
function _updateRedirectedBalanceOfRedirectionAddress(
|
|
address origin,
|
|
address user,
|
|
uint256 scaledBalanceToAdd,
|
|
uint256 scaledBalanceToRemove,
|
|
uint256 index
|
|
) internal {
|
|
address redirectionAddress = _interestRedirectionAddresses[user];
|
|
//if there isn't any redirection, nothing to be done
|
|
if (redirectionAddress == address(0)) {
|
|
return;
|
|
}
|
|
|
|
//updating the interest redirection index of the user
|
|
_interestRedirectionIndexes[user] = index;
|
|
|
|
|
|
//updating the redirected balance
|
|
_redirectedBalances[redirectionAddress] = _redirectedBalances[redirectionAddress]
|
|
.add(scaledBalanceToAdd)
|
|
.sub(scaledBalanceToRemove);
|
|
|
|
//updating the redirected balance index of the redirection target
|
|
_redirectedBalanceIndexes[redirectionAddress] = index;
|
|
|
|
|
|
//if the interest of redirectionAddress is also being redirected, we need to update
|
|
//the redirected balance of the redirection target by adding the balance increase
|
|
address targetOfRedirectionAddress = _interestRedirectionAddresses[redirectionAddress];
|
|
|
|
|
|
// if the redirection address is also redirecting the interest, we update his index to
|
|
// accumulate the interest until now
|
|
// note: if the next address of redirection is the same as the one who originated the update,
|
|
// it means a loop of redirection has been formed: in this case, we break the recursion as no
|
|
// further updates are needed
|
|
if (targetOfRedirectionAddress != address(0) && targetOfRedirectionAddress != origin) {
|
|
_updateRedirectedBalanceOfRedirectionAddress(origin, redirectionAddress, 0, 0, index);
|
|
}
|
|
|
|
emit RedirectedBalanceUpdated(
|
|
redirectionAddress,
|
|
scaledBalanceToAdd,
|
|
scaledBalanceToRemove,
|
|
index
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @dev executes the redirection of the interest from one address to another.
|
|
* immediately after redirection, the destination address will start to accrue interest.
|
|
* @param from the address from which transfer the aTokens
|
|
* @param to the destination address
|
|
**/
|
|
function _redirectInterestStream(address from, address to) internal {
|
|
|
|
address currentRedirectionAddress = _interestRedirectionAddresses[from];
|
|
|
|
require(to != currentRedirectionAddress, Errors.INTEREST_ALREADY_REDIRECTED);
|
|
|
|
uint256 fromBalance = balanceOf(from);
|
|
|
|
require(fromBalance > 0, Errors.NO_VALID_BALANCE_FOR_REDIRECTION);
|
|
|
|
uint256 index = _pool.getReserveNormalizedIncome(UNDERLYING_ASSET_ADDRESS);
|
|
|
|
uint256 scaledBalance = fromBalance.rayDiv(index);
|
|
|
|
//if the user is already redirecting the interest to someone, before changing
|
|
//the redirection address we substract the redirected balance of the previous
|
|
//recipient
|
|
if (currentRedirectionAddress != address(0)) {
|
|
_updateRedirectedBalanceOfRedirectionAddress(from, from, 0, scaledBalance, index);
|
|
}
|
|
|
|
//if the user is redirecting the interest back to himself,
|
|
//we simply set to 0 the interest redirection address
|
|
if (to == from) {
|
|
_interestRedirectionAddresses[from] = address(0);
|
|
emit InterestStreamRedirected(from, address(0), scaledBalance, index);
|
|
return;
|
|
}
|
|
|
|
//first set the redirection address to the new recipient
|
|
_interestRedirectionAddresses[from] = to;
|
|
_interestRedirectionIndexes[from] = index;
|
|
|
|
//adds the user balance to the redirected balance of the destination
|
|
_updateRedirectedBalanceOfRedirectionAddress(from, from, fromBalance, 0, index);
|
|
|
|
emit InterestStreamRedirected(from, to, fromBalance, index);
|
|
}
|
|
|
|
/**
|
|
* @dev function to reset the interest stream redirection and the user index, if the
|
|
* user has no balance left.
|
|
* @param user the address of the user
|
|
* @return true if the user index has also been reset, false otherwise. useful to emit the proper user index value
|
|
**/
|
|
function _resetDataOnZeroBalance(address user) internal returns (bool) {
|
|
//if the user has 0 principal balance, the interest stream redirection gets reset
|
|
_interestRedirectionAddresses[user] = address(0);
|
|
|
|
//emits a InterestStreamRedirected event to notify that the redirection has been reset
|
|
emit InterestStreamRedirected(user, address(0), 0, 0);
|
|
}
|
|
|
|
/**
|
|
* @dev transfers the underlying asset to the target. Used by the lendingpool to transfer
|
|
* assets in borrow(), redeem() and flashLoan()
|
|
* @param target the target of the transfer
|
|
* @param amount the amount to transfer
|
|
* @return the amount transferred
|
|
**/
|
|
function transferUnderlyingTo(address target, uint256 amount)
|
|
external
|
|
override
|
|
onlyLendingPool
|
|
returns (uint256)
|
|
{
|
|
ERC20(UNDERLYING_ASSET_ADDRESS).safeTransfer(target, amount);
|
|
return amount;
|
|
}
|
|
|
|
|
|
/**
|
|
* @notice ERC20 implementation internal function backing transfer() and transferFrom()
|
|
**/
|
|
function _transfer(
|
|
address from,
|
|
address to,
|
|
uint256 amount,
|
|
bool validate
|
|
) internal {
|
|
if(validate){
|
|
require(isTransferAllowed(from, amount), Errors.TRANSFER_NOT_ALLOWED);
|
|
}
|
|
|
|
uint256 index = _pool.getReserveNormalizedIncome(UNDERLYING_ASSET_ADDRESS);
|
|
|
|
uint256 scaledAmount = amount.rayDiv(index);
|
|
|
|
super._transfer(from, to, scaledAmount);
|
|
|
|
//if the sender is redirecting his interest towards someone else,
|
|
//adds to the redirected balance the accrued interest and removes the amount
|
|
//being transferred
|
|
_updateRedirectedBalanceOfRedirectionAddress(from, from, 0, scaledAmount, index);
|
|
|
|
//if the receiver is redirecting his interest towards someone else,
|
|
//adds to the redirected balance the accrued interest and the amount
|
|
//being transferred
|
|
_updateRedirectedBalanceOfRedirectionAddress(to, to, scaledAmount, 0, index);
|
|
|
|
emit BalanceTransfer(from, to, amount, index);
|
|
|
|
}
|
|
|
|
/**
|
|
* @dev aTokens should not receive ETH
|
|
**/
|
|
receive() external payable {
|
|
revert();
|
|
}
|
|
}
|