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 3 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
221 changes: 221 additions & 0 deletions contracts/incentives/BaseIncentivesController.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
// SPDX-License-Identifier: agpl-3.0
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 {IERC20} from '@aave/aave-stake/contracts/interfaces/IERC20.sol';
import {IScaledBalanceToken} from '../interfaces/IScaledBalanceToken.sol';
import {IAaveIncentivesController} from '../interfaces/IAaveIncentivesController.sol';

/**
* @title BaseIncentivesController
* @notice Distributor contract for ERC20 rewards to the Aave protocol participants
* @author Aave
**/
contract BaseIncentivesController is
IAaveIncentivesController,
VersionedInitializable,
DistributionManager
{
using SafeMath for uint256;
using SafeERC20 for IERC20;

uint256 public constant REVISION = 1;

address public immutable override REWARD_TOKEN;
address internal _rewardsVault;

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;

event RewardsVaultUpdated(address indexed vault);

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

constructor(IERC20 rewardToken, address emissionManager)
DistributionManager(emissionManager)
{
REWARD_TOKEN = address(rewardToken);
}

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

/// @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);
}

/// @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];
}

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

/**
* @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 {
_rewardsVault = rewardsVault;
emit RewardsVaultUpdated(rewardsVault);
}

/**
* @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

IERC20(REWARD_TOKEN).safeTransferFrom(_rewardsVault, to, amountToClaim);
emit RewardsClaimed(user, to, claimer, amountToClaim);

return amountToClaim;
}
}
10 changes: 9 additions & 1 deletion hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@ import fs from 'fs';
import { HardhatUserConfig } from 'hardhat/types';
// @ts-ignore
import { accounts } from './test-wallets';
import { eEthereumNetwork, eNetwork, ePolygonNetwork, eXDaiNetwork } from './helpers/types';
import {
eAvalancheNetwork,
eEthereumNetwork,
eNetwork,
ePolygonNetwork,
eXDaiNetwork,
} from './helpers/types';
import { BUIDLEREVM_CHAINID, COVERAGE_CHAINID } from './helpers/constants';
import { NETWORKS_RPC_URL, NETWORKS_DEFAULT_GAS } from './helper-hardhat-config';

Expand Down Expand Up @@ -117,6 +123,8 @@ const buidlerConfig: HardhatUserConfig = {
matic: getCommonNetworkConfig(ePolygonNetwork.matic, 137),
mumbai: getCommonNetworkConfig(ePolygonNetwork.mumbai, 80001),
xdai: getCommonNetworkConfig(eXDaiNetwork.xdai, 100),
fuji: getCommonNetworkConfig(eAvalancheNetwork.fuji, 43113),
avalanche: getCommonNetworkConfig(eAvalancheNetwork.avalanche, 43114),
hardhat: {
hardfork: 'istanbul',
blockGasLimit: DEFAULT_BLOCK_GAS_LIMIT,
Expand Down
5 changes: 5 additions & 0 deletions helper-hardhat-config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// @ts-ignore
import {
eAvalancheNetwork,
eEthereumNetwork,
ePolygonNetwork,
eXDaiNetwork,
Expand Down Expand Up @@ -31,6 +32,8 @@ export const NETWORKS_RPC_URL: iParamsPerNetwork<string> = {
[ePolygonNetwork.mumbai]: 'https://rpc-mumbai.maticvigil.com',
[ePolygonNetwork.matic]: 'https://rpc-mainnet.matic.network',
[eXDaiNetwork.xdai]: 'https://rpc.xdaichain.com/',
[eAvalancheNetwork.avalanche]: 'https://cchain.explorer.avax.network/',
[eAvalancheNetwork.fuji]: 'https://api.avax-test.network/ext/bc/C/rpc',
};

export const NETWORKS_DEFAULT_GAS: iParamsPerNetwork<number> = {
Expand All @@ -44,4 +47,6 @@ export const NETWORKS_DEFAULT_GAS: iParamsPerNetwork<number> = {
[ePolygonNetwork.mumbai]: 1 * GWEI,
[ePolygonNetwork.matic]: 2 * GWEI,
[eXDaiNetwork.xdai]: 1 * GWEI,
[eAvalancheNetwork.fuji]: 225 * GWEI,
[eAvalancheNetwork.avalanche]: 225 * GWEI,
};
21 changes: 20 additions & 1 deletion helpers/contracts-accessors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { IERC20Detailed } from '../types/IERC20Detailed';
import { verifyContract } from './etherscan-verification';
import { ATokenMock } from '../types/ATokenMock';
import {
BaseIncentivesController__factory,
InitializableAdminUpgradeabilityProxy__factory,
StakedTokenIncentivesController,
StakedTokenIncentivesController__factory,
Expand All @@ -24,7 +25,6 @@ export const deployAaveIncentivesController = async (
verify?: boolean,
signer?: Signer | DefenderRelaySigner
) => {
const id = eContractid.StakedTokenIncentivesController;
const args: [string, string] = [aavePsm, emissionManager];
const instance = await new StakedTokenIncentivesController__factory(
signer || (await getFirstSigner())
Expand All @@ -36,6 +36,22 @@ export const deployAaveIncentivesController = async (
return instance;
};

export const deployBaseIncentivesController = async (
[rewardToken, emissionManager]: [tEthereumAddress, tEthereumAddress],
verify?: boolean,
signer?: Signer | DefenderRelaySigner
) => {
const args: [string, string] = [rewardToken, emissionManager];
const instance = await new BaseIncentivesController__factory(
signer || (await getFirstSigner())
).deploy(...args);
await instance.deployTransaction.wait();
if (verify) {
await verifyContract(instance.address, args);
}
return instance;
};

export const deployInitializableAdminUpgradeabilityProxy = async (verify?: boolean) => {
const args: string[] = [];
const instance = await new InitializableAdminUpgradeabilityProxy__factory(
Expand Down Expand Up @@ -65,6 +81,9 @@ export const getAaveIncentivesController = getContractFactory<StakedTokenIncenti
export const getIncentivesController = async (address: tEthereumAddress) =>
StakedTokenIncentivesController__factory.connect(address, await getFirstSigner());

export const getBaseIncentivesController = async (address: tEthereumAddress) =>
BaseIncentivesController__factory.connect(address, await getFirstSigner());

export const getIErc20Detailed = getContractFactory<IERC20Detailed>(eContractid.IERC20Detailed);

export const getATokenMock = getContractFactory<ATokenMock>(eContractid.ATokenMock);
Expand Down
16 changes: 14 additions & 2 deletions helpers/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export interface SymbolMap<T> {
[symbol: string]: T;
}

export type eNetwork = eEthereumNetwork | ePolygonNetwork | eXDaiNetwork;
export type eNetwork = eEthereumNetwork | ePolygonNetwork | eXDaiNetwork | eAvalancheNetwork;

export enum eContractid {
DistributionManager = 'DistributionManager',
Expand All @@ -15,6 +15,7 @@ export enum eContractid {
StakedTokenIncentivesController = 'StakedTokenIncentivesController',
MockSelfDestruct = 'MockSelfDestruct',
StakedAaveV3 = 'StakedAaveV3',
BaseIncentivesController = 'BaseIncentivesController',
}

export enum eEthereumNetwork {
Expand All @@ -36,6 +37,11 @@ export enum eXDaiNetwork {
xdai = 'xdai',
}

export enum eAvalancheNetwork {
fuji = 'fuji',
avalanche = 'avalanche',
}

export enum EthereumNetworkNames {
kovan = 'kovan',
ropsten = 'ropsten',
Expand All @@ -48,7 +54,8 @@ export enum EthereumNetworkNames {
export type iParamsPerNetwork<T> =
| iEthereumParamsPerNetwork<T>
| iPolygonParamsPerNetwork<T>
| iXDaiParamsPerNetwork<T>;
| iXDaiParamsPerNetwork<T>
| iAvalancheParamsPerNetwork<T>;

export interface iEthereumParamsPerNetwork<T> {
[eEthereumNetwork.coverage]: T;
Expand All @@ -69,6 +76,11 @@ export interface iXDaiParamsPerNetwork<T> {
[eXDaiNetwork.xdai]: T;
}

export interface iAvalancheParamsPerNetwork<T> {
[eAvalancheNetwork.fuji]: T;
[eAvalancheNetwork.avalanche]: T;
}

export type tEthereumAddress = string;
export type tStringTokenBigUnits = string; // 1 ETH, or 10e6 USDC or 10e18 DAI
export type tBigNumberTokenBigUnits = BigNumber;
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@
"hardhat:mumbai": "hardhat --network mumbai",
"hardhat:matic": "hardhat --network matic",
"coverage": "hardhat coverage",
"test": "npm run test-incentives",
"test-incentives": "TS_NODE_TRANSPILE_ONLY=1 hardhat test ./test/__setup.spec.ts ./test/AaveIncentivesController/*.spec.ts",
"test": "npm run test-base-incentives && npm run test-staked-incentives",
"test-base-incentives": "TS_NODE_TRANSPILE_ONLY=1 hardhat test ./test/__setup.spec.ts ./test/BaseIncentivesController/*.spec.ts",
"test-staked-incentives": "TS_NODE_TRANSPILE_ONLY=1 hardhat test ./test/__setup.spec.ts ./test/StakedIncentivesController/*.spec.ts",
"test-proposal": "TS_NODE_TRANSPILE_ONLY=1 MAINNET_FORK=true hardhat test ./test-fork/incentivesProposal.spec.ts",
"test-proposal:tenderly": "TS_NODE_TRANSPILE_ONLY=1 TENDERLY=true npm run hardhat:tenderly -- test ./test-fork/incentivesProposal.spec.ts",
"test-proposal-skip": "TS_NODE_TRANSPILE_ONLY=1 MAINNET_FORK=true hardhat test ./test-fork/incentives-skip.spec.ts",
Expand Down
Loading