Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Abstract IncentivesContract logic, adapt Staked Incentives Controller and Pull Reward Incentives Controller #6

Merged
merged 12 commits into from
Nov 16, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions contracts/incentives/PullRewardsIncentivesController.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// SPDX-License-Identifier: agpl-3.0
pragma solidity 0.7.5;
pragma experimental ABIEncoderV2;

import {IERC20} from '@aave/aave-stake/contracts/interfaces/IERC20.sol';
import {SafeERC20} from '@aave/aave-stake/contracts/lib/SafeERC20.sol';

import {BaseIncentivesController} from './base/BaseIncentivesController.sol';

/**
* @title PullRewardsIncentivesController
* @notice Distributor contract for ERC20 rewards to the Aave protocol participants that pulls ERC20 from external account
* @author Aave
**/
contract PullRewardsIncentivesController is
BaseIncentivesController
{
using SafeERC20 for IERC20;

address internal _rewardsVault;

event RewardsVaultUpdated(address indexed vault);

constructor(IERC20 rewardToken, address emissionManager)
BaseIncentivesController(rewardToken, emissionManager)
{}

/**
* @dev Initialize AaveIncentivesController
* @param rewardsVault rewards vault to pull ERC20 funds
**/
function initialize(address rewardsVault) external initializer {
_rewardsVault = rewardsVault;
emit RewardsVaultUpdated(_rewardsVault);
}

/**
* @dev returns the current rewards vault contract
* @return address
*/
function getRewardsVault() external view returns (address) {
return _rewardsVault;
}

/**
* @dev update the rewards vault address, only allowed by the Rewards admin
* @param rewardsVault The address of the rewards vault
**/
function setRewardsVault(address rewardsVault) external onlyEmissionManager {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a risk of updating the rewardsVault, but leaving existing token emissions? For token contract updates, would be fine, but if switching tokens could be an issue. Probably can just be handled with clear documentation but can consider include smart contract options.

Potential options
Clearly document that existing emissions will remain for the new token
Clear all emmissions on setRewardsVault
Add a parameters to setRewardsVault and include a call to update emissions

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the EmissionManager changes the rewards vault, it will not change the current token as is inmmutable, just the source where "pull" and transfer the reward token. In my opinion emissions could still be the same and if the "reward vault" has sufficient tokens the incentives will not stop.

The rewards vault can be an EOA or smart contract with the sole purpose of pulling the reward tokens from this contract to the incentivized accounts.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense +1

_rewardsVault = rewardsVault;
emit RewardsVaultUpdated(rewardsVault);
}


/// @inheritdoc BaseIncentivesController
function _transferRewards(address to, uint256 amount) internal override {
IERC20(REWARD_TOKEN).safeTransferFrom(_rewardsVault, to, amount);
}
}
194 changes: 8 additions & 186 deletions contracts/incentives/StakedTokenIncentivesController.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,9 @@ pragma solidity 0.7.5;
pragma experimental ABIEncoderV2;

import {SafeERC20} from '@aave/aave-stake/contracts/lib/SafeERC20.sol';
import {SafeMath} from '../lib/SafeMath.sol';
import {DistributionTypes} from '../lib/DistributionTypes.sol';
import {VersionedInitializable} from '@aave/aave-stake/contracts/utils/VersionedInitializable.sol';
import {DistributionManager} from './DistributionManager.sol';
import {IStakedTokenWithConfig} from '../interfaces/IStakedTokenWithConfig.sol';
import {IERC20} from '@aave/aave-stake/contracts/interfaces/IERC20.sol';
import {IScaledBalanceToken} from '../interfaces/IScaledBalanceToken.sol';
import {IAaveIncentivesController} from '../interfaces/IAaveIncentivesController.sol';
import {BaseIncentivesController} from './base/BaseIncentivesController.sol';
import {IStakedTokenWithConfig} from '../interfaces/IStakedTokenWithConfig.sol';

/**
* @title StakedTokenIncentivesController
Expand All @@ -19,200 +14,27 @@ import {IAaveIncentivesController} from '../interfaces/IAaveIncentivesController
* The reference staked token implementation is at https://github.com/aave/aave-stake-v2
* @author Aave
**/
contract StakedTokenIncentivesController is
IAaveIncentivesController,
VersionedInitializable,
DistributionManager
{
using SafeMath for uint256;
contract StakedTokenIncentivesController is BaseIncentivesController {
using SafeERC20 for IERC20;

uint256 public constant REVISION = 1;

IStakedTokenWithConfig public immutable STAKE_TOKEN;

mapping(address => uint256) internal _usersUnclaimedRewards;

// this mapping allows whitelisted addresses to claim on behalf of others
// useful for contracts that hold tokens to be rewarded but don't have any native logic to claim Liquidity Mining rewards
mapping(address => address) internal _authorizedClaimers;

modifier onlyAuthorizedClaimers(address claimer, address user) {
require(_authorizedClaimers[user] == claimer, 'CLAIMER_UNAUTHORIZED');
_;
}

constructor(IStakedTokenWithConfig stakeToken, address emissionManager)
DistributionManager(emissionManager)
BaseIncentivesController(IERC20(address(stakeToken)), emissionManager)
{
STAKE_TOKEN = stakeToken;
}

/**
* @dev Initialize IStakedTokenIncentivesController
* @param addressesProvider the address of the corresponding addresses provider
**/
function initialize(address addressesProvider) external initializer {
function initialize() external initializer {
//approves the safety module to allow staking
IERC20(STAKE_TOKEN.STAKED_TOKEN()).safeApprove(address(STAKE_TOKEN), type(uint256).max);
}

/// @inheritdoc IAaveIncentivesController
function configureAssets(address[] calldata assets, uint256[] calldata emissionsPerSecond)
external
override
onlyEmissionManager
{
require(assets.length == emissionsPerSecond.length, 'INVALID_CONFIGURATION');

DistributionTypes.AssetConfigInput[] memory assetsConfig =
new DistributionTypes.AssetConfigInput[](assets.length);

for (uint256 i = 0; i < assets.length; i++) {
assetsConfig[i].underlyingAsset = assets[i];
assetsConfig[i].emissionPerSecond = uint104(emissionsPerSecond[i]);

require(assetsConfig[i].emissionPerSecond == emissionsPerSecond[i], 'INVALID_CONFIGURATION');

assetsConfig[i].totalStaked = IScaledBalanceToken(assets[i]).scaledTotalSupply();
}
_configureAssets(assetsConfig);
}

/// @inheritdoc IAaveIncentivesController
function handleAction(
address user,
uint256 totalSupply,
uint256 userBalance
) external override {
uint256 accruedRewards = _updateUserAssetInternal(user, msg.sender, userBalance, totalSupply);
if (accruedRewards != 0) {
_usersUnclaimedRewards[user] = _usersUnclaimedRewards[user].add(accruedRewards);
emit RewardsAccrued(user, accruedRewards);
}
}

/// @inheritdoc IAaveIncentivesController
function getRewardsBalance(address[] calldata assets, address user)
external
view
override
returns (uint256)
{
uint256 unclaimedRewards = _usersUnclaimedRewards[user];

DistributionTypes.UserStakeInput[] memory userState =
new DistributionTypes.UserStakeInput[](assets.length);
for (uint256 i = 0; i < assets.length; i++) {
userState[i].underlyingAsset = assets[i];
(userState[i].stakedByUser, userState[i].totalStaked) = IScaledBalanceToken(assets[i])
.getScaledUserBalanceAndSupply(user);
}
unclaimedRewards = unclaimedRewards.add(_getUnclaimedRewards(user, userState));
return unclaimedRewards;
}

/// @inheritdoc IAaveIncentivesController
function claimRewards(
address[] calldata assets,
uint256 amount,
address to
) external override returns (uint256) {
require(to != address(0), 'INVALID_TO_ADDRESS');
return _claimRewards(assets, amount, msg.sender, msg.sender, to);
}

/// @inheritdoc IAaveIncentivesController
function claimRewardsOnBehalf(
address[] calldata assets,
uint256 amount,
address user,
address to
) external override onlyAuthorizedClaimers(msg.sender, user) returns (uint256) {
require(user != address(0), 'INVALID_USER_ADDRESS');
require(to != address(0), 'INVALID_TO_ADDRESS');
return _claimRewards(assets, amount, msg.sender, user, to);
}

/**
* @dev Claims reward for an user on behalf, on all the assets of the lending pool, accumulating the pending rewards.
* @param amount Amount of rewards to claim
* @param user Address to check and claim rewards
* @param to Address that will be receiving the rewards
* @return Rewards claimed
**/

/// @inheritdoc IAaveIncentivesController
function setClaimer(address user, address caller) external override onlyEmissionManager {
_authorizedClaimers[user] = caller;
emit ClaimerSet(user, caller);
}

/// @inheritdoc IAaveIncentivesController
function getClaimer(address user) external view override returns (address) {
return _authorizedClaimers[user];
}

/// @inheritdoc IAaveIncentivesController
function getUserUnclaimedRewards(address _user) external view override returns (uint256) {
return _usersUnclaimedRewards[_user];
}

/// @inheritdoc IAaveIncentivesController
function REWARD_TOKEN() external view override returns (address) {
return address(STAKE_TOKEN);
}

/**
* @dev returns the revision of the implementation contract
*/
function getRevision() internal pure override returns (uint256) {
return REVISION;
}

/**
* @dev Claims reward for an user on behalf, on all the assets of the lending pool, accumulating the pending rewards.
* @param amount Amount of rewards to claim
* @param user Address to check and claim rewards
* @param to Address that will be receiving the rewards
* @return Rewards claimed
**/
function _claimRewards(
address[] calldata assets,
uint256 amount,
address claimer,
address user,
address to
) internal returns (uint256) {
if (amount == 0) {
return 0;
}
uint256 unclaimedRewards = _usersUnclaimedRewards[user];

DistributionTypes.UserStakeInput[] memory userState =
new DistributionTypes.UserStakeInput[](assets.length);
for (uint256 i = 0; i < assets.length; i++) {
userState[i].underlyingAsset = assets[i];
(userState[i].stakedByUser, userState[i].totalStaked) = IScaledBalanceToken(assets[i])
.getScaledUserBalanceAndSupply(user);
}

uint256 accruedRewards = _claimRewards(user, userState);
if (accruedRewards != 0) {
unclaimedRewards = unclaimedRewards.add(accruedRewards);
emit RewardsAccrued(user, accruedRewards);
}

if (unclaimedRewards == 0) {
return 0;
}

uint256 amountToClaim = amount > unclaimedRewards ? unclaimedRewards : amount;
_usersUnclaimedRewards[user] = unclaimedRewards - amountToClaim; // Safe due to the previous line

STAKE_TOKEN.stake(to, amountToClaim);
emit RewardsClaimed(user, to, claimer, amountToClaim);

return amountToClaim;
/// @inheritdoc BaseIncentivesController
function _transferRewards(address to, uint256 amount) internal override {
STAKE_TOKEN.stake(to, amount);
}
}
Loading