mirror of
https://github.com/Instadapp/aave-protocol-v2.git
synced 2024-07-29 21:47:30 +00:00
516 lines
19 KiB
Solidity
516 lines
19 KiB
Solidity
// SPDX-License-Identifier: agpl-3.0
|
|
pragma solidity 0.6.12;
|
|
|
|
import {ILendingPool} from '../../interfaces/ILendingPool.sol';
|
|
import {ILendingPoolAddressesProvider} from '../../interfaces/ILendingPoolAddressesProvider.sol';
|
|
import {IDelegationToken} from '../../interfaces/IDelegationToken.sol';
|
|
import {Errors} from '../libraries/helpers/Errors.sol';
|
|
import {AToken} from './AToken.sol';
|
|
import {SafeERC20} from '../../dependencies/openzeppelin/contracts/SafeERC20.sol';
|
|
import {IERC20} from '../../dependencies/openzeppelin/contracts/IERC20.sol';
|
|
import {IRewardsAwareAToken} from '../../interfaces/IRewardsAwareAToken.sol';
|
|
import {IAToken} from '../../interfaces/IRewardsAwareAToken.sol';
|
|
import {WadRayMath} from '../../protocol/libraries/math/WadRayMath.sol';
|
|
import {PercentageMath} from '../../protocol/libraries/math/PercentageMath.sol';
|
|
|
|
/**
|
|
* @title Rewards Aware AToken
|
|
* @notice AToken aware to claim and distribute rewards from an external rewards controller.
|
|
* @author Aave
|
|
*/
|
|
abstract contract RewardsAwareAToken is AToken, IRewardsAwareAToken {
|
|
using SafeERC20 for IERC20;
|
|
using WadRayMath for uint256;
|
|
using PercentageMath for uint256;
|
|
|
|
// Precision of the multiplier for calculating distribution percentages
|
|
uint256 public constant PRECISION = 24;
|
|
// Max rewards allowed
|
|
uint256 public constant MAX_REWARD_TOKENS = 9;
|
|
// Multiplier for calculating share indexes
|
|
uint256 public constant MULTIPLIER = 10**PRECISION;
|
|
// Max rewards reserve factor
|
|
uint256 public constant MAX_VALID_RESERVE_FACTOR = 10000;
|
|
// Rewards Reserve Factor
|
|
uint256 internal rewardsReserveFactor;
|
|
|
|
// token => user => userIndex
|
|
mapping(address => mapping(address => uint256)) private _userIndex;
|
|
// token => user => userRewardsAccrued
|
|
mapping(address => mapping(address => uint256)) private _userRewardsAccrued;
|
|
// token => user => userRewardsClaimed
|
|
mapping(address => mapping(address => uint256)) private _userRewardsClaimed;
|
|
|
|
// token => rewardIndex
|
|
mapping(address => uint256) private _rewardIndex;
|
|
// reward address => lifetime rewards
|
|
mapping(address => uint256) private _lifetimeRewards;
|
|
// token => lifetimeMinted
|
|
mapping(address => uint256) private _lifetimeClaimed;
|
|
// reward tokens
|
|
address[MAX_REWARD_TOKENS] internal _rewardTokens;
|
|
|
|
modifier onlyPoolAdmin {
|
|
address poolAdmin =
|
|
ILendingPoolAddressesProvider(ILendingPool(_pool).getAddressesProvider()).getPoolAdmin();
|
|
require(_msgSender() == poolAdmin, Errors.AT_CALLER_MUST_BE_POOL_ADMIN);
|
|
_;
|
|
}
|
|
|
|
/**
|
|
* @dev Get the current claimable rewards dinamically by calling the external rewards contract and simulate the rewards without storage
|
|
* @param token Address of the rewards token
|
|
* @param user Address of the account to get current claimable rewards
|
|
* @return The claimable rewards of the address passed at the "user" argument
|
|
*/
|
|
function getClaimableRewards(address token, address user) public view override returns (uint256) {
|
|
return _getPendingRewards(token, user);
|
|
}
|
|
|
|
/**
|
|
* @dev Get the total lifetime rewards of an address from contract storage
|
|
* @param token Address of the rewards token
|
|
* @param user Address of the account to get the total lifetime rewards
|
|
* @return The total lifetime rewards of an address, this includes claimed and pending rewards
|
|
*/
|
|
function getUserRewardsAccrued(address token, address user)
|
|
external
|
|
view
|
|
override
|
|
returns (uint256)
|
|
{
|
|
return _userRewardsAccrued[token][user];
|
|
}
|
|
|
|
/**
|
|
* @dev Get the claimed rewards of an address from contract storage
|
|
* @param token Address of the rewards token
|
|
* @param user Address of the account to get the claimed rewards
|
|
* @return The claimed rewards of an address
|
|
*/
|
|
function getUserClaimedRewards(address token, address user)
|
|
external
|
|
view
|
|
override
|
|
returns (uint256)
|
|
{
|
|
return _userRewardsClaimed[token][user];
|
|
}
|
|
|
|
/**
|
|
* @dev Get lifetime rewards of a rewards token
|
|
* @param token Address of the rewards token
|
|
* @return The total of lifetime rewards
|
|
*/
|
|
function getLifetimeRewards(address token) external view override returns (uint256) {
|
|
return _getLifetimeRewards(token);
|
|
}
|
|
|
|
/**
|
|
* @dev Get lifetime minted rewards of a rewards token
|
|
* @param token Address of the rewards token
|
|
* @return The lifetime rewards claimed
|
|
*/
|
|
function getLifetimeClaimed(address token) external view override returns (uint256) {
|
|
return _lifetimeClaimed[token];
|
|
}
|
|
|
|
/**
|
|
* @dev Get the user checkpoint of the aToken contract itself, from contract storage
|
|
* @param token Address of the rewards token
|
|
* @param user Address of the account to get the claimed rewards
|
|
* @return The total lifetime rewards, this includes claimed and pending rewards from the aToken contract
|
|
*/
|
|
function getUserIndex(address token, address user) external view override returns (uint256) {
|
|
return _userIndex[token][user];
|
|
}
|
|
|
|
/**
|
|
* @dev Get the rewards ERC20 token address by index position
|
|
* @param index The position of the rewards, starting from zero up
|
|
* @return The rewards ERC20 token address
|
|
*/
|
|
function getRewardsTokenAddress(uint256 index) external view override returns (address) {
|
|
return _getRewardsTokenAddress(index);
|
|
}
|
|
|
|
/**
|
|
* @dev Get all the rewards token addresses
|
|
* @return The list of rewards token addresseses
|
|
*/
|
|
function getRewardsTokenAddressList()
|
|
external
|
|
view
|
|
override
|
|
returns (address[MAX_REWARD_TOKENS] memory)
|
|
{
|
|
return _rewardTokens;
|
|
}
|
|
|
|
/**
|
|
* @dev Claim the available token rewards from the caller and transfers to `msg.sender`
|
|
*/
|
|
function claim(address token) external override {
|
|
_claim(token);
|
|
}
|
|
|
|
/**
|
|
* @dev Burns aTokens from `user` and sends the equivalent amount of underlying to `receiverOfUnderlying`
|
|
* - Only callable by the LendingPool, as extra state updates there need to be managed
|
|
* @param user The owner of the aTokens, getting them burned
|
|
* @param receiverOfUnderlying The address that will receive the underlying
|
|
* @param amount The amount being burned
|
|
* @param index The new liquidity index of the reserve
|
|
**/
|
|
function burn(
|
|
address user,
|
|
address receiverOfUnderlying,
|
|
uint256 amount,
|
|
uint256 index
|
|
) public virtual override(AToken, IAToken) onlyLendingPool {
|
|
// Unstake
|
|
_unstake(UNDERLYING_ASSET_ADDRESS(), amount);
|
|
|
|
// Update distribution of rewards
|
|
_updateDistribution(user);
|
|
|
|
// burns aTokens
|
|
return super.burn(user, receiverOfUnderlying, amount, index);
|
|
}
|
|
|
|
/**
|
|
* @dev Mints `amount` aTokens to `user`
|
|
* - Only callable by the LendingPool, as extra state updates there need to be managed
|
|
* @param user The address receiving the minted tokens
|
|
* @param amount The amount of tokens getting minted
|
|
* @param index The new liquidity index of the reserve
|
|
* @return `true` if the the previous balance of the user was 0
|
|
*/
|
|
function mint(
|
|
address user,
|
|
uint256 amount,
|
|
uint256 index
|
|
) public virtual override(AToken, IAToken) onlyLendingPool returns (bool) {
|
|
// Stake
|
|
_stake(UNDERLYING_ASSET_ADDRESS(), amount);
|
|
|
|
// Update distribution of rewards
|
|
_updateDistribution(user);
|
|
|
|
// mint aTokens
|
|
return super.mint(user, amount, index);
|
|
}
|
|
|
|
/**
|
|
* @dev Transfers the underlying asset to `target`. Used by the LendingPool to transfer
|
|
* assets in borrow(), withdraw() and flashLoan()
|
|
* @param target The recipient of the aTokens
|
|
* @param amount The amount getting transferred
|
|
* @return The amount transferred
|
|
**/
|
|
function transferUnderlyingTo(address target, uint256 amount)
|
|
external
|
|
virtual
|
|
override(AToken, IAToken)
|
|
onlyLendingPool
|
|
returns (uint256)
|
|
{
|
|
_unstake(UNDERLYING_ASSET_ADDRESS(), amount);
|
|
_updateDistribution(target);
|
|
|
|
IERC20(UNDERLYING_ASSET_ADDRESS()).safeTransfer(target, amount);
|
|
return amount;
|
|
}
|
|
|
|
/**
|
|
* @dev Invoked to execute actions on the aToken side after a repayment.
|
|
* @param user The user executing the repayment
|
|
* @param amount The amount getting repaid
|
|
**/
|
|
function handleRepayment(address user, uint256 amount)
|
|
external
|
|
virtual
|
|
override(AToken, IAToken)
|
|
onlyLendingPool
|
|
{
|
|
_stake(UNDERLYING_ASSET_ADDRESS(), amount);
|
|
_updateDistribution(user);
|
|
}
|
|
|
|
/**
|
|
* @dev Set the rewards reserve factor to the treasury, only allowed by LendingPoolAddressesProvider pool admin
|
|
* @param reserveFactor reserve factor in percent value
|
|
*/
|
|
function setRewardsReserveFactor(uint256 reserveFactor) external override onlyPoolAdmin {
|
|
_setRewardsReserveFactor(reserveFactor);
|
|
}
|
|
|
|
/**
|
|
* @dev Return the rewards reserve factor to the treasury
|
|
* @return reserve factor in percent value
|
|
*/
|
|
function getRewardsReserveFactor() external view override returns (uint256) {
|
|
return rewardsReserveFactor;
|
|
}
|
|
|
|
/**
|
|
* @dev Hook to update distributions before token transfer. Called at "burn" and "transferUnderlyingTo".
|
|
* @param from address of the `from`
|
|
* @param to address of the `to`
|
|
*/
|
|
function _beforeTokenTransfer(
|
|
address from,
|
|
address to,
|
|
uint256
|
|
) internal override {
|
|
_updateDistribution(from);
|
|
_updateDistribution(to);
|
|
}
|
|
|
|
/**
|
|
* @dev Get lifetime rewards of a rewards token
|
|
* @param token Address of the rewards token
|
|
* @return The total of lifetime rewards
|
|
*/
|
|
function _getLifetimeRewards(address token) internal view returns (uint256) {
|
|
return _lifetimeRewards[token];
|
|
}
|
|
|
|
/**
|
|
* @dev Get the rewards ERC20 token address by index position
|
|
* @param index The position of the rewards, starting from zero up
|
|
* @return The rewards ERC20 token address
|
|
*/
|
|
function _getRewardsTokenAddress(uint256 index) internal view returns (address) {
|
|
if (index > MAX_REWARD_TOKENS) return address(0);
|
|
|
|
return _rewardTokens[index];
|
|
}
|
|
|
|
/**
|
|
* @dev Calculate rewards distribution in proportion of aTokens balance during a timeframe.
|
|
* @param aTokensBalance The amounts of aTokens of the user
|
|
* @param lastRewardIndex The last reward token index of the distribution percentage
|
|
* @param lastUserIndex The last user index of the distribution percentage
|
|
* @return The proportional rewards between two distribution percentages indexes
|
|
*/
|
|
function _getRewards(
|
|
uint256 aTokensBalance,
|
|
uint256 lastRewardIndex,
|
|
uint256 lastUserIndex
|
|
) internal pure returns (uint256) {
|
|
return aTokensBalance.mul(lastRewardIndex.sub(lastUserIndex)).div(MULTIPLIER);
|
|
}
|
|
|
|
/**
|
|
* @dev Calculate the next reward token index, that contains the sums of the percentages of the distribution of the token rewards.
|
|
* @param latestRewardIndex The latest reward token index
|
|
* @param accruedRewards The accrued rewards during the current distribution
|
|
* @param aTokenSupply The total supply of this aToken contract
|
|
* @return The sums of the reward indexes plus the latest percentage distribution index of the reward
|
|
*/
|
|
function _getRewardIndex(
|
|
uint256 latestRewardIndex,
|
|
uint256 accruedRewards,
|
|
uint256 aTokenSupply
|
|
) internal pure returns (uint256) {
|
|
return latestRewardIndex.add(accruedRewards.mul(MULTIPLIER).div(aTokenSupply));
|
|
}
|
|
|
|
/**
|
|
* @dev Getter for virtually calculating the pending rewards of an user without altering the storage of the contract
|
|
* @param user Address of the user to lookup for rewards
|
|
* @param token The rewards token to lookup for user rewards
|
|
* @return Expected token rewards for an user
|
|
*/
|
|
function _getPendingRewards(address token, address user) internal view returns (uint256) {
|
|
uint256 aTokenTotalSupply = totalSupply();
|
|
uint256 externalLifetimeRewards = _getExternalLifetimeRewards(token);
|
|
uint256 rewardAccrued = externalLifetimeRewards.sub(_lifetimeRewards[token]);
|
|
uint256 rewardAssetIndex = _rewardIndex[token];
|
|
|
|
if (aTokenTotalSupply != 0 && rewardAccrued != 0) {
|
|
rewardAssetIndex = _getRewardIndex(_rewardIndex[token], rewardAccrued, aTokenTotalSupply);
|
|
}
|
|
|
|
uint256 userLatestRewards =
|
|
_getRewards(balanceOf(user), rewardAssetIndex, _userIndex[token][user]);
|
|
|
|
return
|
|
_userRewardsAccrued[token][user].add(userLatestRewards).sub(_userRewardsClaimed[token][user]);
|
|
}
|
|
|
|
/**
|
|
* @dev Retrieve the latest rewards distribution to an user and update the distribution of a token rewards at storage
|
|
* @param user The `user` address to update the rewards index and retrieve latest reward distribution
|
|
* @param token The reward token to lookup the distribution
|
|
* @param aTokenBalance The aToken balance of the user determines his share during the distribution
|
|
* @param aTokenSupply The current total supply of this aToken
|
|
*/
|
|
function _updateRewardDistribution(
|
|
address user,
|
|
address token,
|
|
uint256 aTokenBalance,
|
|
uint256 aTokenSupply
|
|
) private {
|
|
uint256 userRewardsIndex = _userIndex[token][user];
|
|
uint256 externalLifetimeRewards = _computeExternalLifetimeRewards(token);
|
|
uint256 rewardAccrued = externalLifetimeRewards.sub(_lifetimeRewards[token]);
|
|
uint256 previousRewardIndex = _rewardIndex[token];
|
|
uint256 rewardIndex = _getRewardIndex(previousRewardIndex, rewardAccrued, aTokenSupply);
|
|
|
|
// Check reward index
|
|
if (rewardIndex != previousRewardIndex) {
|
|
_rewardIndex[token] = rewardIndex;
|
|
_setLifetimeRewards(token, externalLifetimeRewards);
|
|
emit RewardIndexUpdate(token, rewardIndex);
|
|
}
|
|
|
|
// Check user reward index
|
|
if (userRewardsIndex != rewardIndex) {
|
|
_userIndex[token][user] = rewardIndex;
|
|
emit UserIndexUpdate(user, token, rewardIndex);
|
|
|
|
uint256 userRewardAccrued = _getRewards(aTokenBalance, rewardIndex, userRewardsIndex);
|
|
if (userRewardAccrued != 0) {
|
|
_userRewardsAccrued[token][user] = _userRewardsAccrued[token][user].add(userRewardAccrued);
|
|
emit UserRewardSnapshot(user, token, userRewardAccrued);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @dev Update the distribution of each rewards configured at this aToken
|
|
* @param user The `user` address to update the rewards index and retrieve latest reward distribution
|
|
*/
|
|
function _updateDistribution(address user) internal {
|
|
uint256 aTokenBalance = balanceOf(user);
|
|
if (aTokenBalance == 0) {
|
|
return;
|
|
}
|
|
|
|
uint256 aTokenSupply = totalSupply();
|
|
if (aTokenSupply == 0) {
|
|
return;
|
|
}
|
|
|
|
for (uint256 index; index < MAX_REWARD_TOKENS; index++) {
|
|
address rewardToken = _rewardTokens[index];
|
|
if (rewardToken == address(0)) break;
|
|
|
|
_updateRewardDistribution(user, rewardToken, aTokenBalance, aTokenSupply);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @dev Set token rewards claimed from an `user` address
|
|
* @param user The `user` address to sum and set the claimed rewards
|
|
* @param token The claimed reward `token` address
|
|
* @param claimed The amount of `claimed` rewards by the `user`
|
|
*/
|
|
function _setUserRewardsClaimed(
|
|
address user,
|
|
address token,
|
|
uint256 claimed
|
|
) private {
|
|
_userRewardsClaimed[token][user] = _userRewardsClaimed[token][user].add(claimed);
|
|
_lifetimeClaimed[token] = _lifetimeClaimed[token].add(claimed);
|
|
}
|
|
|
|
/**
|
|
* @dev Claim the available rewards from the caller and transfers to `msg.sender`
|
|
* @param token Reward adresss to lookup rewards and claim
|
|
*/
|
|
function _claim(address token) private {
|
|
// Mint the available rewards from the external rewards controller to this aToken contract implementation
|
|
_claimRewardsFromController();
|
|
|
|
// Update aToken and user rewards
|
|
_updateDistribution(msg.sender);
|
|
|
|
// Get the remaining tokens to claim
|
|
uint256 accruedRewards =
|
|
_userRewardsAccrued[token][msg.sender].sub(_userRewardsClaimed[token][msg.sender]);
|
|
|
|
if (accruedRewards > 0) {
|
|
// Track the claimed rewards
|
|
_setUserRewardsClaimed(msg.sender, token, accruedRewards);
|
|
// Unstake reward token, if needed
|
|
uint256 unstaked = _unstake(token, accruedRewards);
|
|
uint256 userRewards = unstaked;
|
|
|
|
// Transfer rewards to treasury
|
|
if (_treasury != address(0)) {
|
|
uint256 reserveRewards = unstaked.percentMul(rewardsReserveFactor);
|
|
if (reserveRewards > 0) {
|
|
userRewards = unstaked.sub(reserveRewards);
|
|
IERC20(token).transfer(_treasury, reserveRewards);
|
|
}
|
|
}
|
|
|
|
// Transfer rewards to user
|
|
IERC20(token).transfer(msg.sender, userRewards);
|
|
emit Claim(msg.sender, token, accruedRewards, userRewards);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @dev Set the lifetime token lifetime rewards
|
|
* @param token The reward token address
|
|
* @param amount The amount of lifetime rewards
|
|
*/
|
|
function _setLifetimeRewards(address token, uint256 amount) private {
|
|
_lifetimeRewards[token] = amount;
|
|
}
|
|
|
|
/**
|
|
* @dev External call to retrieve the lifetime rewards of the aToken contract to the external Rewards Controller contract
|
|
* @notice Should only mutate the state outside RewardsAwareAToken abstract contract
|
|
* @param token Reward adresss to lookup available rewards
|
|
* @notice To be implemented by the contract that inherits this abstract contract RewardsAwareAToken
|
|
*/
|
|
function _computeExternalLifetimeRewards(address token) internal virtual returns (uint256);
|
|
|
|
/**
|
|
* @dev External view call to retrieve the lifetime rewards of the aToken contract to the external Rewards Controller contract
|
|
* @param token Reward adresss to lookup available rewards
|
|
* @notice To be implemented by the contract that inherits this abstract contract RewardsAwareAToken
|
|
*/
|
|
function _getExternalLifetimeRewards(address token) internal view virtual returns (uint256);
|
|
|
|
/**
|
|
* @dev External call to claim the lifetime accrued rewards of the aToken contract to the external Rewards Controller contract
|
|
* @notice To be implemented by the contract that inherits this abstract contract RewardsAwareAToken
|
|
* @notice WIP pending to check virtual balance versus claimed
|
|
*/
|
|
function _claimRewardsFromController() internal virtual;
|
|
|
|
/**
|
|
* @dev External call to stake a token of the aToken contract
|
|
* @notice Optional, to be implemented by the contract that inherits this abstract contract RewardsAwareAToken,if needed.
|
|
* @param amount to stake
|
|
*/
|
|
function _stake(address, uint256 amount) internal virtual returns (uint256) {
|
|
return amount;
|
|
}
|
|
|
|
/**
|
|
* @dev External call to unstake a token of the aToken contract
|
|
* @notice Optional, to be implemented by the contract that inherits this abstract contract RewardsAwareAToken, if needed.
|
|
* @param amount to unstake
|
|
*/
|
|
function _unstake(address, uint256 amount) internal virtual returns (uint256) {
|
|
return amount;
|
|
}
|
|
|
|
/**
|
|
* @dev Set the rewards reserve factor to the treasury
|
|
* @param reserveFactor reserve factor in percent value
|
|
*/
|
|
function _setRewardsReserveFactor(uint256 reserveFactor) internal {
|
|
require(reserveFactor <= MAX_VALID_RESERVE_FACTOR, Errors.RC_INVALID_RESERVE_FACTOR);
|
|
rewardsReserveFactor = reserveFactor;
|
|
emit RewardsReserveFactorUpdate(rewardsReserveFactor);
|
|
}
|
|
}
|