From eacb14fb43e375c34ebaa5f3f11feaa1ab0b4861 Mon Sep 17 00:00:00 2001 From: karotjal Date: Mon, 23 Aug 2021 13:37:09 +0200 Subject: [PATCH 01/12] feat: Added base ERC20 incentives controller contract and deploy scripts. --- .../incentives/BaseIncentivesController.sol | 220 ++++++++++++++++++ hardhat.config.ts | 10 +- helper-hardhat-config.ts | 5 + helpers/contracts-accessors.ts | 21 +- helpers/types.ts | 16 +- tasks/migrations/deploy-base-incentives.ts | 59 +++++ 6 files changed, 327 insertions(+), 4 deletions(-) create mode 100644 contracts/incentives/BaseIncentivesController.sol create mode 100644 tasks/migrations/deploy-base-incentives.ts diff --git a/contracts/incentives/BaseIncentivesController.sol b/contracts/incentives/BaseIncentivesController.sol new file mode 100644 index 0000000..4b9ded4 --- /dev/null +++ b/contracts/incentives/BaseIncentivesController.sol @@ -0,0 +1,220 @@ +// 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; + 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).transferFrom(_rewardsVault, to, amountToClaim); + + emit RewardsClaimed(claimer, user, to, amountToClaim); + + return amountToClaim; + } +} \ No newline at end of file diff --git a/hardhat.config.ts b/hardhat.config.ts index c670f45..7306930 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -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'; @@ -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, diff --git a/helper-hardhat-config.ts b/helper-hardhat-config.ts index 7c9b76d..02db92b 100644 --- a/helper-hardhat-config.ts +++ b/helper-hardhat-config.ts @@ -1,5 +1,6 @@ // @ts-ignore import { + eAvalancheNetwork, eEthereumNetwork, ePolygonNetwork, eXDaiNetwork, @@ -31,6 +32,8 @@ export const NETWORKS_RPC_URL: iParamsPerNetwork = { [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 = { @@ -44,4 +47,6 @@ export const NETWORKS_DEFAULT_GAS: iParamsPerNetwork = { [ePolygonNetwork.mumbai]: 1 * GWEI, [ePolygonNetwork.matic]: 2 * GWEI, [eXDaiNetwork.xdai]: 1 * GWEI, + [eAvalancheNetwork.fuji]: 225 * GWEI, + [eAvalancheNetwork.avalanche]: 225 * GWEI, }; diff --git a/helpers/contracts-accessors.ts b/helpers/contracts-accessors.ts index ae556f5..db893d9 100644 --- a/helpers/contracts-accessors.ts +++ b/helpers/contracts-accessors.ts @@ -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, @@ -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()) @@ -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( @@ -65,6 +81,9 @@ export const getAaveIncentivesController = getContractFactory StakedTokenIncentivesController__factory.connect(address, await getFirstSigner()); +export const getBaseIncentivesController = async (address: tEthereumAddress) => + BaseIncentivesController__factory.connect(address, await getFirstSigner()); + export const getIErc20Detailed = getContractFactory(eContractid.IERC20Detailed); export const getATokenMock = getContractFactory(eContractid.ATokenMock); diff --git a/helpers/types.ts b/helpers/types.ts index 1343587..b6ec2ca 100644 --- a/helpers/types.ts +++ b/helpers/types.ts @@ -4,7 +4,7 @@ export interface SymbolMap { [symbol: string]: T; } -export type eNetwork = eEthereumNetwork | ePolygonNetwork | eXDaiNetwork; +export type eNetwork = eEthereumNetwork | ePolygonNetwork | eXDaiNetwork | eAvalancheNetwork; export enum eContractid { DistributionManager = 'DistributionManager', @@ -15,6 +15,7 @@ export enum eContractid { StakedTokenIncentivesController = 'StakedTokenIncentivesController', MockSelfDestruct = 'MockSelfDestruct', StakedAaveV3 = 'StakedAaveV3', + BaseIncentivesController = 'BaseIncentivesController', } export enum eEthereumNetwork { @@ -36,6 +37,11 @@ export enum eXDaiNetwork { xdai = 'xdai', } +export enum eAvalancheNetwork { + fuji = 'fuji', + avalanche = 'avalanche', +} + export enum EthereumNetworkNames { kovan = 'kovan', ropsten = 'ropsten', @@ -48,7 +54,8 @@ export enum EthereumNetworkNames { export type iParamsPerNetwork = | iEthereumParamsPerNetwork | iPolygonParamsPerNetwork - | iXDaiParamsPerNetwork; + | iXDaiParamsPerNetwork + | iAvalancheParamsPerNetwork; export interface iEthereumParamsPerNetwork { [eEthereumNetwork.coverage]: T; @@ -69,6 +76,11 @@ export interface iXDaiParamsPerNetwork { [eXDaiNetwork.xdai]: T; } +export interface iAvalancheParamsPerNetwork { + [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; diff --git a/tasks/migrations/deploy-base-incentives.ts b/tasks/migrations/deploy-base-incentives.ts new file mode 100644 index 0000000..a177650 --- /dev/null +++ b/tasks/migrations/deploy-base-incentives.ts @@ -0,0 +1,59 @@ +import { isAddress } from 'ethers/lib/utils'; +import { task } from 'hardhat/config'; +import { ZERO_ADDRESS } from '../../helpers/constants'; +import { + deployBaseIncentivesController, + deployInitializableAdminUpgradeabilityProxy, +} from '../../helpers/contracts-accessors'; +import { waitForTx } from '../../helpers/misc-utils'; + +task(`deploy-base-incentives`, `Deploy and initializes the BaseIncentivesController contract`) + .addFlag('verify') + .addParam('rewardToken') + .addParam('rewardsVault') + .addParam('emissionManager') + .addParam('proxyAdmin', `The address to be added as an Admin role at the Transparent Proxy.`) + .setAction( + async ({ verify, rewardToken, rewardsVault, emissionManager, proxyAdmin }, localBRE) => { + await localBRE.run('set-DRE'); + if (!isAddress(proxyAdmin)) { + throw Error('Missing or incorrect admin param'); + } + if (!isAddress(rewardToken)) { + throw Error('Missing or incorrect rewardToken param'); + } + if (!isAddress(rewardsVault)) { + throw Error('Missing or incorrect rewardsVault param'); + } + emissionManager = isAddress(emissionManager) ? emissionManager : ZERO_ADDRESS; + + console.log(`[BaseIncentivesController] Starting deployment:`); + + const aaveIncentivesControllerImpl = await deployBaseIncentivesController( + [rewardToken, emissionManager], + verify + ); + console.log(` - Deployed implementation of BaseIncentivesController`); + + const aaveIncentivesProxy = await deployInitializableAdminUpgradeabilityProxy(verify); + console.log(` - Deployed proxy of BaseIncentivesController`); + + const encodedParams = aaveIncentivesControllerImpl.interface.encodeFunctionData( + 'initialize', + [rewardsVault] + ); + + await waitForTx( + await aaveIncentivesProxy.functions['initialize(address,address,bytes)']( + aaveIncentivesControllerImpl.address, + proxyAdmin, + encodedParams + ) + ); + console.log(` - Initialized BaseIncentivesController Proxy`); + + console.log(` - Finished BaseIncentivesController deployment and initialization`); + console.log(` - Proxy: ${aaveIncentivesProxy.address}`); + console.log(` - Impl: ${aaveIncentivesControllerImpl.address}`); + } + ); From 6a80c15f10543e9f834e265efa750e4354f3958d Mon Sep 17 00:00:00 2001 From: karotjal Date: Mon, 23 Aug 2021 16:45:51 +0200 Subject: [PATCH 02/12] feat: Added tests for base incentives controller --- .../incentives/BaseIncentivesController.sol | 7 +- package-lock.json | 2 +- package.json | 3 +- tasks/migrations/deploy-base-incentives.ts | 24 +- .../claim-on-behalf.spec.ts | 139 ++++++++++ .../claim-rewards.spec.ts | 243 ++++++++++++++++++ .../configure-assets.spec.ts | 215 ++++++++++++++++ .../get-rewards-balance.spec.ts | 91 +++++++ .../handle-action.spec.ts | 166 ++++++++++++ .../initialize.spec.ts | 12 + test/BaseIncentivesController/misc.spec.ts | 81 ++++++ .../data-helpers/asset-data.ts | 4 +- .../data-helpers/asset-user-data.ts | 7 +- .../claim-on-behalf.spec.ts | 0 .../claim-rewards.spec.ts | 0 .../configure-assets.spec.ts | 0 .../get-rewards-balance.spec.ts | 0 .../handle-action.spec.ts | 0 .../initialize.spec.ts | 0 .../misc.spec.ts | 0 test/__setup.spec.ts | 51 +++- test/helpers/deploy.ts | 20 +- test/helpers/make-suite.ts | 19 +- 23 files changed, 1046 insertions(+), 38 deletions(-) create mode 100644 test/BaseIncentivesController/claim-on-behalf.spec.ts create mode 100644 test/BaseIncentivesController/claim-rewards.spec.ts create mode 100644 test/BaseIncentivesController/configure-assets.spec.ts create mode 100644 test/BaseIncentivesController/get-rewards-balance.spec.ts create mode 100644 test/BaseIncentivesController/handle-action.spec.ts create mode 100644 test/BaseIncentivesController/initialize.spec.ts create mode 100644 test/BaseIncentivesController/misc.spec.ts rename test/{AaveIncentivesController => StakedIncentivesController}/claim-on-behalf.spec.ts (100%) rename test/{AaveIncentivesController => StakedIncentivesController}/claim-rewards.spec.ts (100%) rename test/{AaveIncentivesController => StakedIncentivesController}/configure-assets.spec.ts (100%) rename test/{AaveIncentivesController => StakedIncentivesController}/get-rewards-balance.spec.ts (100%) rename test/{AaveIncentivesController => StakedIncentivesController}/handle-action.spec.ts (100%) rename test/{AaveIncentivesController => StakedIncentivesController}/initialize.spec.ts (100%) rename test/{AaveIncentivesController => StakedIncentivesController}/misc.spec.ts (100%) diff --git a/contracts/incentives/BaseIncentivesController.sol b/contracts/incentives/BaseIncentivesController.sol index 4b9ded4..2222b6b 100644 --- a/contracts/incentives/BaseIncentivesController.sol +++ b/contracts/incentives/BaseIncentivesController.sol @@ -22,6 +22,8 @@ contract BaseIncentivesController is DistributionManager { using SafeMath for uint256; + using SafeERC20 for IERC20; + uint256 public constant REVISION = 1; address public immutable override REWARD_TOKEN; @@ -211,9 +213,8 @@ contract BaseIncentivesController is uint256 amountToClaim = amount > unclaimedRewards ? unclaimedRewards : amount; _usersUnclaimedRewards[user] = unclaimedRewards - amountToClaim; // Safe due to the previous line - IERC20(REWARD_TOKEN).transferFrom(_rewardsVault, to, amountToClaim); - - emit RewardsClaimed(claimer, user, to, amountToClaim); + IERC20(REWARD_TOKEN).safeTransferFrom(_rewardsVault, to, amountToClaim); + emit RewardsClaimed(user, to, claimer, amountToClaim); return amountToClaim; } diff --git a/package-lock.json b/package-lock.json index cb3c6d0..4f10b87 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13399,7 +13399,7 @@ } }, "ethereumjs-abi": { - "version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#1a27c59c15ab1e95ee8e5c4ed6ad814c49cc439e", + "version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#ee3994657fa7a427238e6ba92a84d0b529bbcde0", "from": "git+https://github.com/ethereumjs/ethereumjs-abi.git", "dev": true, "requires": { diff --git a/package.json b/package.json index c898e78..13c4112 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,8 @@ "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-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", diff --git a/tasks/migrations/deploy-base-incentives.ts b/tasks/migrations/deploy-base-incentives.ts index a177650..063fcb2 100644 --- a/tasks/migrations/deploy-base-incentives.ts +++ b/tasks/migrations/deploy-base-incentives.ts @@ -29,23 +29,22 @@ task(`deploy-base-incentives`, `Deploy and initializes the BaseIncentivesControl console.log(`[BaseIncentivesController] Starting deployment:`); - const aaveIncentivesControllerImpl = await deployBaseIncentivesController( + const incentivesControllerImpl = await deployBaseIncentivesController( [rewardToken, emissionManager], verify ); console.log(` - Deployed implementation of BaseIncentivesController`); - const aaveIncentivesProxy = await deployInitializableAdminUpgradeabilityProxy(verify); + const incentivesProxy = await deployInitializableAdminUpgradeabilityProxy(verify); console.log(` - Deployed proxy of BaseIncentivesController`); - const encodedParams = aaveIncentivesControllerImpl.interface.encodeFunctionData( - 'initialize', - [rewardsVault] - ); + const encodedParams = incentivesControllerImpl.interface.encodeFunctionData('initialize', [ + rewardsVault, + ]); await waitForTx( - await aaveIncentivesProxy.functions['initialize(address,address,bytes)']( - aaveIncentivesControllerImpl.address, + await incentivesProxy.functions['initialize(address,address,bytes)']( + incentivesControllerImpl.address, proxyAdmin, encodedParams ) @@ -53,7 +52,12 @@ task(`deploy-base-incentives`, `Deploy and initializes the BaseIncentivesControl console.log(` - Initialized BaseIncentivesController Proxy`); console.log(` - Finished BaseIncentivesController deployment and initialization`); - console.log(` - Proxy: ${aaveIncentivesProxy.address}`); - console.log(` - Impl: ${aaveIncentivesControllerImpl.address}`); + console.log(` - Proxy: ${incentivesProxy.address}`); + console.log(` - Impl: ${incentivesControllerImpl.address}`); + + return { + proxy: incentivesProxy.address, + implementation: incentivesControllerImpl.address, + }; } ); diff --git a/test/BaseIncentivesController/claim-on-behalf.spec.ts b/test/BaseIncentivesController/claim-on-behalf.spec.ts new file mode 100644 index 0000000..73875b8 --- /dev/null +++ b/test/BaseIncentivesController/claim-on-behalf.spec.ts @@ -0,0 +1,139 @@ +import { expect } from 'chai'; +import { MAX_UINT_AMOUNT, ZERO_ADDRESS } from '../../helpers/constants'; +import { waitForTx } from '../../helpers/misc-utils'; + +import { makeSuite, TestEnv } from '../helpers/make-suite'; + +makeSuite('baseIncentivesController - Claim rewards on behalf', (testEnv: TestEnv) => { + it('Should setClaimer revert if not called by emission manager', async () => { + const { baseIncentivesController, users } = testEnv; + const [userWithRewards, thirdClaimer] = users; + await expect( + baseIncentivesController + .connect(userWithRewards.signer) + .setClaimer(userWithRewards.address, thirdClaimer.address) + ).to.be.revertedWith('ONLY_EMISSION_MANAGER'); + }); + it('Should claimRewardsOnBehalf revert if called claimer is not authorized', async () => { + const { baseIncentivesController, users, aDaiBaseMock, aaveToken } = testEnv; + const [userWithRewards, thirdClaimer] = users; + + await waitForTx( + await baseIncentivesController.configureAssets([aDaiBaseMock.address], ['20000']) + ); + await waitForTx(await aDaiBaseMock.setUserBalanceAndSupply('300000', '300000')); + + // Claim from third party claimer + const priorStkBalance = await aaveToken.balanceOf(thirdClaimer.address); + + await expect( + baseIncentivesController + .connect(thirdClaimer.signer) + .claimRewardsOnBehalf( + [aDaiBaseMock.address], + MAX_UINT_AMOUNT, + userWithRewards.address, + thirdClaimer.address + ) + ).to.be.revertedWith('CLAIMER_UNAUTHORIZED'); + + const afterStkBalance = await aaveToken.balanceOf(thirdClaimer.address); + expect(afterStkBalance).to.be.eq(priorStkBalance); + }); + it('Should setClaimer pass if called by emission manager', async () => { + const { baseIncentivesController, users, rewardsVault } = testEnv; + const [userWithRewards, thirdClaimer] = users; + const emissionManager = rewardsVault; + + await expect( + baseIncentivesController + .connect(emissionManager.signer) + .setClaimer(userWithRewards.address, thirdClaimer.address) + ) + .to.emit(baseIncentivesController, 'ClaimerSet') + .withArgs(userWithRewards.address, thirdClaimer.address); + await expect(await baseIncentivesController.getClaimer(userWithRewards.address)).to.be.equal( + thirdClaimer.address + ); + }); + it('Should claimRewardsOnBehalf pass if called by the assigned claimer', async () => { + const { baseIncentivesController, users, aDaiBaseMock, aaveToken } = testEnv; + const [userWithRewards, thirdClaimer] = users; + + await waitForTx( + await baseIncentivesController.configureAssets([aDaiBaseMock.address], ['2000']) + ); + await waitForTx(await aDaiBaseMock.setUserBalanceAndSupply('300000', '30000')); + + // Claim from third party claimer + const priorBalance = await aaveToken.balanceOf(thirdClaimer.address); + + await expect( + baseIncentivesController + .connect(thirdClaimer.signer) + .claimRewardsOnBehalf( + [aDaiBaseMock.address], + MAX_UINT_AMOUNT, + userWithRewards.address, + thirdClaimer.address + ) + ) + .to.emit(baseIncentivesController, 'RewardsClaimed') + .withArgs(userWithRewards.address, thirdClaimer.address, thirdClaimer.address, '99999'); + const afterStkBalance = await aaveToken.balanceOf(thirdClaimer.address); + console.log('adt', afterStkBalance.toString()); + expect(afterStkBalance).to.be.gt(priorBalance); + }); + + it('Should claimRewardsOnBehalf revert if to argument address is ZERO_ADDRESS', async () => { + const { baseIncentivesController, users, aDaiBaseMock, aaveToken } = testEnv; + const [userWithRewards, thirdClaimer] = users; + + await waitForTx( + await baseIncentivesController.configureAssets([aDaiBaseMock.address], ['2000']) + ); + await waitForTx(await aDaiBaseMock.setUserBalanceAndSupply('300000', '30000')); + + await expect( + baseIncentivesController + .connect(thirdClaimer.signer) + .claimRewardsOnBehalf( + [aDaiBaseMock.address], + MAX_UINT_AMOUNT, + userWithRewards.address, + ZERO_ADDRESS + ) + ).to.be.revertedWith('INVALID_TO_ADDRESS'); + }); + + it('Should claimRewardsOnBehalf revert if user argument is ZERO_ADDRESS', async () => { + const { baseIncentivesController, users, aDaiBaseMock, rewardsVault } = testEnv; + const [, thirdClaimer] = users; + + const emissionManager = rewardsVault; + + await waitForTx( + await baseIncentivesController.configureAssets([aDaiBaseMock.address], ['2000']) + ); + await waitForTx(await aDaiBaseMock.setUserBalanceAndSupply('300000', '30000')); + + await expect( + baseIncentivesController + .connect(emissionManager.signer) + .setClaimer(ZERO_ADDRESS, thirdClaimer.address) + ) + .to.emit(baseIncentivesController, 'ClaimerSet') + .withArgs(ZERO_ADDRESS, thirdClaimer.address); + + await expect( + baseIncentivesController + .connect(thirdClaimer.signer) + .claimRewardsOnBehalf( + [aDaiBaseMock.address], + MAX_UINT_AMOUNT, + ZERO_ADDRESS, + thirdClaimer.address + ) + ).to.be.revertedWith('INVALID_USER_ADDRESS'); + }); +}); diff --git a/test/BaseIncentivesController/claim-rewards.spec.ts b/test/BaseIncentivesController/claim-rewards.spec.ts new file mode 100644 index 0000000..83ce3bf --- /dev/null +++ b/test/BaseIncentivesController/claim-rewards.spec.ts @@ -0,0 +1,243 @@ +import { MAX_UINT_AMOUNT, RANDOM_ADDRESSES } from '../../helpers/constants'; + +const { expect } = require('chai'); + +import { makeSuite } from '../helpers/make-suite'; +import { BigNumber } from 'ethers'; +import { waitForTx, increaseTime } from '../../helpers/misc-utils'; +import { comparatorEngine, eventChecker } from '../helpers/comparator-engine'; +import { getUserIndex } from '../DistributionManager/data-helpers/asset-user-data'; +import { assetDataComparator, getAssetsData } from '../DistributionManager/data-helpers/asset-data'; +import { getBlockTimestamp } from '../../helpers/contracts-helpers'; +import { getRewards } from '../DistributionManager/data-helpers/base-math'; +import { fail } from 'assert'; + +type ScenarioAction = { + caseName: string; + emissionPerSecond?: string; + amountToClaim: string; + to?: string; + toStake?: boolean; +}; + +const getRewardsBalanceScenarios: ScenarioAction[] = [ + { + caseName: 'Accrued rewards are 0, claim 0', + emissionPerSecond: '0', + amountToClaim: '0', + }, + { + caseName: 'Accrued rewards are 0, claim not 0', + emissionPerSecond: '0', + amountToClaim: '100', + }, + { + caseName: 'Accrued rewards are not 0', + emissionPerSecond: '2432424', + amountToClaim: '10', + }, + { + caseName: 'Should allow -1', + emissionPerSecond: '2432424', + amountToClaim: MAX_UINT_AMOUNT, + }, + { + caseName: 'Should withdraw everything if amountToClaim more then rewards balance', + emissionPerSecond: '100', + amountToClaim: '1034', + }, + { + caseName: 'Should withdraw to another user', + emissionPerSecond: '100', + amountToClaim: '1034', + to: RANDOM_ADDRESSES[5], + }, + { + caseName: 'Should withdraw to another user and stake', + emissionPerSecond: '100', + amountToClaim: '1034', + to: RANDOM_ADDRESSES[5], + }, +]; + +makeSuite('baseIncentivesController claimRewards tests', (testEnv) => { + for (const { + caseName, + amountToClaim: _amountToClaim, + to, + emissionPerSecond, + } of getRewardsBalanceScenarios) { + let amountToClaim = _amountToClaim; + it(caseName, async () => { + await increaseTime(100); + const { baseIncentivesController, aaveToken, aDaiBaseMock } = testEnv; + + const distributionEndTimestamp = await baseIncentivesController.getDistributionEnd(); + const userAddress = await baseIncentivesController.signer.getAddress(); + + const underlyingAsset = aDaiBaseMock.address; + const stakedByUser = 22 * caseName.length; + const totalStaked = 33 * caseName.length; + + // update emissionPerSecond in advance to not affect user calculations + if (emissionPerSecond) { + await baseIncentivesController.configureAssets([underlyingAsset], [emissionPerSecond]); + } + + const destinationAddress = to || userAddress; + + const destinationAddressBalanceBefore = await aaveToken.balanceOf(destinationAddress); + await aDaiBaseMock.setUserBalanceAndSupply(stakedByUser, totalStaked); + await aDaiBaseMock.handleActionOnAic(userAddress, totalStaked, stakedByUser); + + const unclaimedRewardsBefore = await baseIncentivesController.getRewardsBalance( + [underlyingAsset], + userAddress + ); + + const userIndexBefore = await getUserIndex( + baseIncentivesController, + userAddress, + underlyingAsset + ); + const assetDataBefore = (await getAssetsData(baseIncentivesController, [underlyingAsset]))[0]; + + const claimRewardsReceipt = await waitForTx( + await baseIncentivesController.claimRewards( + [underlyingAsset], + amountToClaim, + destinationAddress + ) + ); + const eventsEmitted = claimRewardsReceipt.events || []; + + const actionBlockTimestamp = await getBlockTimestamp(claimRewardsReceipt.blockNumber); + + const userIndexAfter = await getUserIndex( + baseIncentivesController, + userAddress, + underlyingAsset + ); + const assetDataAfter = (await getAssetsData(baseIncentivesController, [underlyingAsset]))[0]; + + const unclaimedRewardsAfter = await baseIncentivesController.getRewardsBalance( + [underlyingAsset], + userAddress + ); + + const destinationAddressBalanceAfter = await aaveToken.balanceOf(destinationAddress); + + const claimedAmount = destinationAddressBalanceAfter.sub(destinationAddressBalanceBefore); + + const expectedAccruedRewards = getRewards( + stakedByUser, + userIndexAfter, + userIndexBefore + ).toString(); + + await aDaiBaseMock.cleanUserState(); + + if (amountToClaim === '0') { + // state should not change + expect(userIndexBefore.toString()).to.be.equal( + userIndexAfter.toString(), + 'userIndexAfter should not change' + ); + expect(unclaimedRewardsBefore.toString()).to.be.equal( + unclaimedRewardsAfter.toString(), + 'unclaimedRewards should not change' + ); + expect(destinationAddressBalanceBefore.toString()).to.be.equal( + destinationAddressBalanceAfter.toString(), + 'destinationAddressBalance should not change' + ); + await comparatorEngine( + ['emissionPerSecond', 'index', 'lastUpdateTimestamp'], + { underlyingAsset, totalStaked }, + assetDataBefore, + assetDataAfter, + actionBlockTimestamp, + {} + ); + expect(eventsEmitted.length).to.be.equal(0, 'no events should be emitted'); + return; + } + + // ------- Distribution Manager tests START ----- + await assetDataComparator( + { underlyingAsset, totalStaked }, + assetDataBefore, + assetDataAfter, + actionBlockTimestamp, + distributionEndTimestamp.toNumber(), + {} + ); + expect(userIndexAfter.toString()).to.be.equal( + assetDataAfter.index.toString(), + 'user index are not correctly updated' + ); + if (!assetDataAfter.index.eq(assetDataBefore.index)) { + eventChecker(eventsEmitted[0], 'AssetIndexUpdated', [ + assetDataAfter.underlyingAsset, + assetDataAfter.index, + ]); + eventChecker(eventsEmitted[1], 'UserIndexUpdated', [ + userAddress, + assetDataAfter.underlyingAsset, + assetDataAfter.index, + ]); + } + // ------- Distribution Manager tests END ----- + + let unclaimedRewardsCalc = unclaimedRewardsBefore.add(expectedAccruedRewards); + + let expectedClaimedAmount: BigNumber; + if (unclaimedRewardsCalc.lte(amountToClaim)) { + expectedClaimedAmount = unclaimedRewardsCalc; + expect(unclaimedRewardsAfter.toString()).to.be.equal( + '0', + 'unclaimed amount after should go to 0' + ); + } else { + expectedClaimedAmount = BigNumber.from(amountToClaim); + expect(unclaimedRewardsAfter.toString()).to.be.equal( + unclaimedRewardsCalc.sub(amountToClaim).toString(), + 'unclaimed rewards after are wrong' + ); + } + + expect(claimedAmount.toString()).to.be.equal( + expectedClaimedAmount.toString(), + 'claimed amount are wrong' + ); + if (expectedAccruedRewards !== '0') { + const rewardsAccruedEvent = eventsEmitted.find(({ event }) => event === 'RewardsAccrued'); + // Expect event to exist + expect(rewardsAccruedEvent).to.be.ok; + if (rewardsAccruedEvent) { + eventChecker(rewardsAccruedEvent, 'RewardsAccrued', [ + userAddress, + expectedAccruedRewards, + ]); + } else { + fail('missing accrued event'); + } + } + if (expectedClaimedAmount.gt(0)) { + const rewardsClaimedEvent = eventsEmitted.find(({ event }) => event === 'RewardsClaimed'); + // Expect event to exist + expect(rewardsClaimedEvent).to.be.ok; + if (rewardsClaimedEvent) { + eventChecker(rewardsClaimedEvent, 'RewardsClaimed', [ + userAddress, + destinationAddress, + userAddress, + expectedClaimedAmount, + ]); + } else { + fail('missing reward event'); + } + } + }); + } +}); diff --git a/test/BaseIncentivesController/configure-assets.spec.ts b/test/BaseIncentivesController/configure-assets.spec.ts new file mode 100644 index 0000000..24cbd22 --- /dev/null +++ b/test/BaseIncentivesController/configure-assets.spec.ts @@ -0,0 +1,215 @@ +const { expect } = require('chai'); + +import { makeSuite, TestEnv } from '../helpers/make-suite'; +import { RANDOM_ADDRESSES } from '../../helpers/constants'; +import { increaseTime, waitForTx } from '../../helpers/misc-utils'; +import { getBlockTimestamp } from '../../helpers/contracts-helpers'; +import { CompareRules, eventChecker } from '../helpers/comparator-engine'; +import { + AssetData, + assetDataComparator, + AssetUpdateData, + getAssetsData, +} from '../DistributionManager/data-helpers/asset-data'; +import { BigNumberish } from '@ethersproject/bignumber'; + +type ScenarioAction = { + caseName: string; + customTimeMovement?: number; + assets: Omit[]; + compareRules?: CompareRules; +}; + +const configureAssetScenarios: ScenarioAction[] = [ + { + caseName: 'Submit initial config for the assets', + assets: [ + { + emissionPerSecond: '11', + totalStaked: '0', + }, + ], + compareRules: { + fieldsEqualToInput: ['emissionPerSecond'], + }, + }, + { + caseName: 'Submit updated config for the assets', + assets: [ + { + emissionPerSecond: '33', + totalStaked: '0', + }, + { + emissionPerSecond: '22', + totalStaked: '0', + }, + ], + compareRules: { + fieldsEqualToInput: ['emissionPerSecond'], + }, + }, + { + caseName: + 'Indexes should change if emission are set not to 0, and pool has deposited and borrowed funds', + assets: [ + { + emissionPerSecond: '33', + totalStaked: '100000', + }, + { + emissionPerSecond: '22', + totalStaked: '123123123', + }, + ], + compareRules: { + fieldsEqualToInput: ['emissionPerSecond'], + }, + }, + { + caseName: 'Indexes should cumulate rewards if next emission is 0', + assets: [ + { + emissionPerSecond: '0', + totalStaked: '100000', + }, + ], + compareRules: { + fieldsEqualToInput: ['emissionPerSecond'], + }, + }, + { + caseName: 'Indexes should not change if no emission', + assets: [ + { + emissionPerSecond: '222', + totalStaked: '213213213213', + }, + ], + compareRules: { + fieldsEqualToInput: ['emissionPerSecond'], + }, + }, + { + caseName: 'Should go to the limit if distribution ended', + customTimeMovement: 1000 * 60 * 100, + assets: [ + { + emissionPerSecond: '222', + totalStaked: '213213213213', + }, + ], + compareRules: { + fieldsEqualToInput: ['emissionPerSecond'], + }, + }, + { + caseName: 'Should not accrue any rewards after end or distribution', + customTimeMovement: 1000, + assets: [ + { + emissionPerSecond: '222', + totalStaked: '213213213213', + }, + ], + compareRules: { + fieldsEqualToInput: ['emissionPerSecond'], + }, + }, +]; + +makeSuite('baseIncentivesController configureAssets', (testEnv: TestEnv) => { + let deployedAssets; + + before(async () => { + deployedAssets = [testEnv.aDaiBaseMock, testEnv.aWethBaseMock]; + }); + + // custom checks + it('Tries to submit config updates not from emission manager', async () => { + const { baseIncentivesController, users } = testEnv; + await expect( + baseIncentivesController.connect(users[2].signer).configureAssets([], []) + ).to.be.revertedWith('ONLY_EMISSION_MANAGER'); + }); + + for (const { + assets: assetsConfig, + caseName, + compareRules, + customTimeMovement, + } of configureAssetScenarios) { + it(caseName, async () => { + const { baseIncentivesController } = testEnv; + const distributionEndTimestamp = await baseIncentivesController.DISTRIBUTION_END(); + + const assets: string[] = []; + const assetsEmissions: BigNumberish[] = []; + const assetConfigsUpdate: AssetUpdateData[] = []; + + for (let i = 0; i < assetsConfig.length; i++) { + const { emissionPerSecond, totalStaked } = assetsConfig[i]; + if (i > deployedAssets.length) { + throw new Error('to many assets to test'); + } + + // Change current supply + await deployedAssets[i].setUserBalanceAndSupply('0', totalStaked); + + // Push configs + assets.push(deployedAssets[i].address); + assetsEmissions.push(emissionPerSecond); + assetConfigsUpdate.push({ + emissionPerSecond, + totalStaked, + underlyingAsset: deployedAssets[i].address, + }); + } + + const assetsConfigBefore = await getAssetsData(baseIncentivesController, assets); + + if (customTimeMovement) { + await increaseTime(customTimeMovement); + } + + const txReceipt = await waitForTx( + await baseIncentivesController.configureAssets(assets, assetsEmissions) + ); + const configsUpdateBlockTimestamp = await getBlockTimestamp(txReceipt.blockNumber); + const assetsConfigAfter = await getAssetsData(baseIncentivesController, assets); + + const eventsEmitted = txReceipt.events || []; + + let eventArrayIndex = 0; + for (let i = 0; i < assetsConfigBefore.length; i++) { + const assetConfigBefore = assetsConfigBefore[i]; + const assetConfigUpdateInput = assetConfigsUpdate[i]; + const assetConfigAfter = assetsConfigAfter[i]; + + if (!assetConfigAfter.index.eq(assetConfigBefore.index)) { + eventChecker(eventsEmitted[eventArrayIndex], 'AssetIndexUpdated', [ + assetConfigAfter.underlyingAsset, + assetConfigAfter.index, + ]); + eventArrayIndex += 1; + } + + eventChecker(eventsEmitted[eventArrayIndex], 'AssetConfigUpdated', [ + assetConfigAfter.underlyingAsset, + assetConfigAfter.emissionPerSecond, + ]); + eventArrayIndex += 1; + + await assetDataComparator( + assetConfigUpdateInput, + assetConfigBefore, + assetConfigAfter, + configsUpdateBlockTimestamp, + distributionEndTimestamp.toNumber(), + compareRules || {} + ); + } + expect(eventsEmitted[eventArrayIndex]).to.be.equal(undefined, 'Too many events emitted'); + }); + } +}); diff --git a/test/BaseIncentivesController/get-rewards-balance.spec.ts b/test/BaseIncentivesController/get-rewards-balance.spec.ts new file mode 100644 index 0000000..78a9b8c --- /dev/null +++ b/test/BaseIncentivesController/get-rewards-balance.spec.ts @@ -0,0 +1,91 @@ +const { expect } = require('chai'); + +import { makeSuite } from '../helpers/make-suite'; +import { getRewards } from '../DistributionManager/data-helpers/base-math'; +import { getUserIndex } from '../DistributionManager/data-helpers/asset-user-data'; +import { getAssetsData } from '../DistributionManager/data-helpers/asset-data'; +import { advanceBlock, timeLatest, waitForTx, increaseTime } from '../../helpers/misc-utils'; +import { getNormalizedDistribution } from '../helpers/ray-math'; +import { getBlockTimestamp } from '../../helpers/contracts-helpers'; + +type ScenarioAction = { + caseName: string; + emissionPerSecond: string; +}; + +const getRewardsBalanceScenarios: ScenarioAction[] = [ + { + caseName: 'Accrued rewards are 0', + emissionPerSecond: '0', + }, + { + caseName: 'Accrued rewards are not 0', + emissionPerSecond: '2432424', + }, + { + caseName: 'Accrued rewards are not 0', + emissionPerSecond: '2432424', + }, +]; + +makeSuite('baseIncentivesController getRewardsBalance tests', (testEnv) => { + for (const { caseName, emissionPerSecond } of getRewardsBalanceScenarios) { + it(caseName, async () => { + await increaseTime(100); + + const { baseIncentivesController, users, aDaiBaseMock } = testEnv; + + const distributionEndTimestamp = await baseIncentivesController.DISTRIBUTION_END(); + const userAddress = users[1].address; + const stakedByUser = 22 * caseName.length; + const totalStaked = 33 * caseName.length; + const underlyingAsset = aDaiBaseMock.address; + + // update emissionPerSecond in advance to not affect user calculations + await advanceBlock((await timeLatest()).plus(100).toNumber()); + if (emissionPerSecond) { + await aDaiBaseMock.setUserBalanceAndSupply('0', totalStaked); + await baseIncentivesController.configureAssets([underlyingAsset], [emissionPerSecond]); + } + await aDaiBaseMock.handleActionOnAic(userAddress, totalStaked, stakedByUser); + await advanceBlock((await timeLatest()).plus(100).toNumber()); + + const lastTxReceipt = await waitForTx( + await aDaiBaseMock.setUserBalanceAndSupply(stakedByUser, totalStaked) + ); + const lastTxTimestamp = await getBlockTimestamp(lastTxReceipt.blockNumber); + + const unclaimedRewardsBefore = await baseIncentivesController.getUserUnclaimedRewards( + userAddress + ); + + const unclaimedRewards = await baseIncentivesController.getRewardsBalance( + [underlyingAsset], + userAddress + ); + + const userIndex = await getUserIndex(baseIncentivesController, userAddress, underlyingAsset); + const assetData = (await getAssetsData(baseIncentivesController, [underlyingAsset]))[0]; + + await aDaiBaseMock.cleanUserState(); + + const expectedAssetIndex = getNormalizedDistribution( + totalStaked, + assetData.index, + assetData.emissionPerSecond, + assetData.lastUpdateTimestamp, + lastTxTimestamp, + distributionEndTimestamp + ); + const expectedAccruedRewards = getRewards( + stakedByUser, + expectedAssetIndex, + userIndex + ).toString(); + + expect(unclaimedRewards.toString()).to.be.equal( + unclaimedRewardsBefore.add(expectedAccruedRewards).toString() + ); + }); + } +}); diff --git a/test/BaseIncentivesController/handle-action.spec.ts b/test/BaseIncentivesController/handle-action.spec.ts new file mode 100644 index 0000000..c1eafab --- /dev/null +++ b/test/BaseIncentivesController/handle-action.spec.ts @@ -0,0 +1,166 @@ +import { fail } from 'assert'; +const { expect } = require('chai'); + +import { waitForTx, increaseTime } from '../../helpers/misc-utils'; +import { makeSuite } from '../helpers/make-suite'; +import { eventChecker } from '../helpers/comparator-engine'; +import { getBlockTimestamp } from '../../helpers/contracts-helpers'; + +import { getUserIndex } from '../DistributionManager/data-helpers/asset-user-data'; +import { assetDataComparator, getAssetsData } from '../DistributionManager/data-helpers/asset-data'; +import { getRewards } from '../DistributionManager/data-helpers/base-math'; + +type ScenarioAction = { + caseName: string; + emissionPerSecond?: string; + userBalance: string; + totalSupply: string; + customTimeMovement?: number; +}; + +const handleActionScenarios: ScenarioAction[] = [ + { + caseName: 'All 0', + emissionPerSecond: '0', + userBalance: '0', + totalSupply: '0', + }, + { + caseName: 'Accrued rewards are 0, 0 emission', + emissionPerSecond: '0', + userBalance: '22', + totalSupply: '22', + }, + { + caseName: 'Accrued rewards are 0, 0 user balance', + emissionPerSecond: '100', + userBalance: '0', + totalSupply: '22', + }, + { + caseName: '1. Accrued rewards are not 0', + userBalance: '22', + totalSupply: '22', + }, + { + caseName: '2. Accrued rewards are not 0', + emissionPerSecond: '1000', + userBalance: '2332', + totalSupply: '3232', + }, +]; + +makeSuite('baseIncentivesController handleAction tests', (testEnv) => { + for (const { + caseName, + totalSupply, + userBalance, + customTimeMovement, + emissionPerSecond, + } of handleActionScenarios) { + it(caseName, async () => { + await increaseTime(100); + + const { baseIncentivesController, users, aDaiBaseMock } = testEnv; + const userAddress = users[1].address; + const underlyingAsset = aDaiBaseMock.address; + + // update emissionPerSecond in advance to not affect user calculations + if (emissionPerSecond) { + await baseIncentivesController.configureAssets([underlyingAsset], [emissionPerSecond]); + } + + const distributionEndTimestamp = await baseIncentivesController.DISTRIBUTION_END(); + + const rewardsBalanceBefore = await baseIncentivesController.getUserUnclaimedRewards( + userAddress + ); + const userIndexBefore = await getUserIndex( + baseIncentivesController, + userAddress, + underlyingAsset + ); + const assetDataBefore = (await getAssetsData(baseIncentivesController, [underlyingAsset]))[0]; + + if (customTimeMovement) { + await increaseTime(customTimeMovement); + } + + await waitForTx(await aDaiBaseMock.setUserBalanceAndSupply(userBalance, totalSupply)); + const handleActionReceipt = await waitForTx( + await aDaiBaseMock.handleActionOnAic(userAddress, totalSupply, userBalance) + ); + const eventsEmitted = handleActionReceipt.events || []; + const actionBlockTimestamp = await getBlockTimestamp(handleActionReceipt.blockNumber); + + const userIndexAfter = await getUserIndex( + baseIncentivesController, + userAddress, + underlyingAsset + ); + + const assetDataAfter = (await getAssetsData(baseIncentivesController, [underlyingAsset]))[0]; + + const expectedAccruedRewards = getRewards( + userBalance, + userIndexAfter, + userIndexBefore + ).toString(); + + const rewardsBalanceAfter = await baseIncentivesController.getUserUnclaimedRewards( + userAddress + ); + + // ------- Distribution Manager tests START ----- + await assetDataComparator( + { underlyingAsset, totalStaked: totalSupply }, + assetDataBefore, + assetDataAfter, + actionBlockTimestamp, + distributionEndTimestamp.toNumber(), + {} + ); + expect(userIndexAfter.toString()).to.be.equal( + assetDataAfter.index.toString(), + 'user index are not correctly updated' + ); + if (!assetDataAfter.index.eq(assetDataBefore.index)) { + const eventAssetUpdated = eventsEmitted.find(({ event }) => event === 'AssetIndexUpdated'); + const eventUserIndexUpdated = eventsEmitted.find( + ({ event }) => event === 'UserIndexUpdated' + ); + + if (!eventAssetUpdated) { + fail('missing AssetIndexUpdated event'); + } + if (!eventUserIndexUpdated) { + fail('missing UserIndexUpdated event'); + } + eventChecker(eventAssetUpdated, 'AssetIndexUpdated', [ + assetDataAfter.underlyingAsset, + assetDataAfter.index, + ]); + eventChecker(eventUserIndexUpdated, 'UserIndexUpdated', [ + userAddress, + assetDataAfter.underlyingAsset, + assetDataAfter.index, + ]); + } + // ------- Distribution Manager tests END ----- + + // ------- PEI tests START ----- + expect(rewardsBalanceAfter.toString()).to.be.equal( + rewardsBalanceBefore.add(expectedAccruedRewards).toString(), + 'rewards balance are incorrect' + ); + if (expectedAccruedRewards !== '0') { + const eventAssetUpdated = eventsEmitted.find(({ event }) => event === 'RewardsAccrued'); + if (!eventAssetUpdated) { + fail('missing RewardsAccrued event'); + } + eventChecker(eventAssetUpdated, 'RewardsAccrued', [userAddress, expectedAccruedRewards]); + } + // ------- PEI tests END ----- + }); + } +}); diff --git a/test/BaseIncentivesController/initialize.spec.ts b/test/BaseIncentivesController/initialize.spec.ts new file mode 100644 index 0000000..2c380a5 --- /dev/null +++ b/test/BaseIncentivesController/initialize.spec.ts @@ -0,0 +1,12 @@ +import { makeSuite, TestEnv } from '../helpers/make-suite'; +import { MAX_UINT_AMOUNT, ZERO_ADDRESS } from '../../helpers/constants'; + +const { expect } = require('chai'); + +makeSuite('baseIncentivesController initialize', (testEnv: TestEnv) => { + // TODO: useless or not? + it('Tries to call initialize second time, should be reverted', async () => { + const { baseIncentivesController } = testEnv; + await expect(baseIncentivesController.initialize(ZERO_ADDRESS)).to.be.reverted; + }); +}); diff --git a/test/BaseIncentivesController/misc.spec.ts b/test/BaseIncentivesController/misc.spec.ts new file mode 100644 index 0000000..3559807 --- /dev/null +++ b/test/BaseIncentivesController/misc.spec.ts @@ -0,0 +1,81 @@ +import { timeLatest, waitForTx } from '../../helpers/misc-utils'; + +import { expect } from 'chai'; + +import { makeSuite } from '../helpers/make-suite'; +import { deployBaseIncentivesController } from '../../helpers/contracts-accessors'; +import { MAX_UINT_AMOUNT, RANDOM_ADDRESSES, ZERO_ADDRESS } from '../../helpers/constants'; + +makeSuite('baseIncentivesController misc tests', (testEnv) => { + it('constructor should assign correct params', async () => { + const peiEmissionManager = RANDOM_ADDRESSES[1]; + const fakeToken = RANDOM_ADDRESSES[5]; + + const baseIncentivesController = await deployBaseIncentivesController([ + fakeToken, + peiEmissionManager, + ]); + await expect(await baseIncentivesController.REWARD_TOKEN()).to.be.equal(fakeToken); + await expect((await baseIncentivesController.EMISSION_MANAGER()).toString()).to.be.equal( + peiEmissionManager + ); + }); + + it('Should return same index while multiple asset index updates', async () => { + const { aDaiBaseMock, baseIncentivesController, users } = testEnv; + await waitForTx( + await baseIncentivesController.configureAssets([aDaiBaseMock.address], ['100']) + ); + await waitForTx(await aDaiBaseMock.doubleHandleActionOnAic(users[1].address, '2000', '100')); + }); + + it('Should overflow index if passed a large emission', async () => { + const { aDaiBaseMock, baseIncentivesController, users } = testEnv; + const MAX_104_UINT = '20282409603651670423947251286015'; + + await waitForTx( + await baseIncentivesController.configureAssets([aDaiBaseMock.address], [MAX_104_UINT]) + ); + await expect( + aDaiBaseMock.doubleHandleActionOnAic(users[1].address, '2000', '100') + ).to.be.revertedWith('Index overflow'); + }); + + it('Should configureAssets revert if parameters length does not match', async () => { + const { aDaiBaseMock, baseIncentivesController } = testEnv; + + await expect( + baseIncentivesController.configureAssets([aDaiBaseMock.address], ['1', '2']) + ).to.be.revertedWith('INVALID_CONFIGURATION'); + }); + + it('Should configureAssets revert if emission parameter overflows uin104', async () => { + const { aDaiBaseMock, baseIncentivesController } = testEnv; + + await expect( + baseIncentivesController.configureAssets([aDaiBaseMock.address], [MAX_UINT_AMOUNT]) + ).to.be.revertedWith('INVALID_CONFIGURATION'); + }); + + it('Should REWARD_TOKEN getter returns the stake token address to keep old interface compatibility', async () => { + const { baseIncentivesController, aaveToken } = testEnv; + await expect(await baseIncentivesController.REWARD_TOKEN()).to.be.equal(aaveToken.address); + }); + + it('Should claimRewards revert if to argument is ZERO_ADDRESS', async () => { + const { baseIncentivesController, users, aDaiBaseMock } = testEnv; + const [userWithRewards] = users; + + await waitForTx( + await baseIncentivesController.configureAssets([aDaiBaseMock.address], ['2000']) + ); + await waitForTx(await aDaiBaseMock.setUserBalanceAndSupply('300000', '30000')); + + // Claim from third party claimer + await expect( + baseIncentivesController + .connect(userWithRewards.signer) + .claimRewards([aDaiBaseMock.address], MAX_UINT_AMOUNT, ZERO_ADDRESS) + ).to.be.revertedWith('INVALID_TO_ADDRESS'); + }); +}); diff --git a/test/DistributionManager/data-helpers/asset-data.ts b/test/DistributionManager/data-helpers/asset-data.ts index 0cddca7..c265e6c 100644 --- a/test/DistributionManager/data-helpers/asset-data.ts +++ b/test/DistributionManager/data-helpers/asset-data.ts @@ -2,7 +2,7 @@ import { BigNumber, BigNumberish } from 'ethers'; import { comparatorEngine, CompareRules } from '../../helpers/comparator-engine'; import { getNormalizedDistribution } from '../../helpers/ray-math'; import { AaveDistributionManager } from '../../../types/AaveDistributionManager'; -import { StakedTokenIncentivesController } from '../../../types'; +import { BaseIncentivesController, StakedTokenIncentivesController } from '../../../types'; export type AssetUpdateData = { emissionPerSecond: BigNumberish; @@ -16,7 +16,7 @@ export type AssetData = { }; export async function getAssetsData( - peiContract: AaveDistributionManager | StakedTokenIncentivesController, + peiContract: AaveDistributionManager | StakedTokenIncentivesController | BaseIncentivesController, assets: string[] ) { return await Promise.all( diff --git a/test/DistributionManager/data-helpers/asset-user-data.ts b/test/DistributionManager/data-helpers/asset-user-data.ts index 73d8d27..cfd9ede 100644 --- a/test/DistributionManager/data-helpers/asset-user-data.ts +++ b/test/DistributionManager/data-helpers/asset-user-data.ts @@ -1,5 +1,5 @@ import { BigNumber } from 'ethers'; -import { StakedAaveV3, StakedTokenIncentivesController } from '../../../types'; +import { BaseIncentivesController, StakedTokenIncentivesController } from '../../../types'; import { AaveDistributionManager } from '../../../types/AaveDistributionManager'; export type UserStakeInput = { @@ -12,7 +12,10 @@ export type UserPositionUpdate = UserStakeInput & { user: string; }; export async function getUserIndex( - distributionManager: AaveDistributionManager | StakedTokenIncentivesController, + distributionManager: + | AaveDistributionManager + | StakedTokenIncentivesController + | BaseIncentivesController, user: string, asset: string ): Promise { diff --git a/test/AaveIncentivesController/claim-on-behalf.spec.ts b/test/StakedIncentivesController/claim-on-behalf.spec.ts similarity index 100% rename from test/AaveIncentivesController/claim-on-behalf.spec.ts rename to test/StakedIncentivesController/claim-on-behalf.spec.ts diff --git a/test/AaveIncentivesController/claim-rewards.spec.ts b/test/StakedIncentivesController/claim-rewards.spec.ts similarity index 100% rename from test/AaveIncentivesController/claim-rewards.spec.ts rename to test/StakedIncentivesController/claim-rewards.spec.ts diff --git a/test/AaveIncentivesController/configure-assets.spec.ts b/test/StakedIncentivesController/configure-assets.spec.ts similarity index 100% rename from test/AaveIncentivesController/configure-assets.spec.ts rename to test/StakedIncentivesController/configure-assets.spec.ts diff --git a/test/AaveIncentivesController/get-rewards-balance.spec.ts b/test/StakedIncentivesController/get-rewards-balance.spec.ts similarity index 100% rename from test/AaveIncentivesController/get-rewards-balance.spec.ts rename to test/StakedIncentivesController/get-rewards-balance.spec.ts diff --git a/test/AaveIncentivesController/handle-action.spec.ts b/test/StakedIncentivesController/handle-action.spec.ts similarity index 100% rename from test/AaveIncentivesController/handle-action.spec.ts rename to test/StakedIncentivesController/handle-action.spec.ts diff --git a/test/AaveIncentivesController/initialize.spec.ts b/test/StakedIncentivesController/initialize.spec.ts similarity index 100% rename from test/AaveIncentivesController/initialize.spec.ts rename to test/StakedIncentivesController/initialize.spec.ts diff --git a/test/AaveIncentivesController/misc.spec.ts b/test/StakedIncentivesController/misc.spec.ts similarity index 100% rename from test/AaveIncentivesController/misc.spec.ts rename to test/StakedIncentivesController/misc.spec.ts diff --git a/test/__setup.spec.ts b/test/__setup.spec.ts index a0bc188..e2358d4 100644 --- a/test/__setup.spec.ts +++ b/test/__setup.spec.ts @@ -3,11 +3,18 @@ import { Signer, ethers } from 'ethers'; import { getBlockTimestamp, getEthersSigners } from '../helpers/contracts-helpers'; import { initializeMakeSuite } from './helpers/make-suite'; import { deployMintableErc20, deployATokenMock } from '../helpers/contracts-accessors'; -import { waitForTx } from '../helpers/misc-utils'; +import { DRE, waitForTx } from '../helpers/misc-utils'; import { MintableErc20 } from '../types/MintableErc20'; import { testDeployIncentivesController } from './helpers/deploy'; -import { StakedAaveV3__factory, StakedTokenIncentivesController__factory } from '../types'; +import { + BaseIncentivesController, + BaseIncentivesController__factory, + StakedAaveV3__factory, + StakedTokenIncentivesController__factory, +} from '../types'; import { parseEther } from '@ethersproject/units'; +import { hrtime } from 'process'; +import { MAX_UINT_AMOUNT } from '../helpers/constants'; const topUpWalletsWithAave = async ( wallets: Signer[], @@ -29,7 +36,7 @@ const buildTestEnv = async ( const aaveToken = await deployMintableErc20(['Aave', 'aave']); - await waitForTx(await aaveToken.connect(vaultOfRewards).mint(ethers.utils.parseEther('1000000'))); + await waitForTx(await aaveToken.connect(vaultOfRewards).mint(ethers.utils.parseEther('2000000'))); await topUpWalletsWithAave( [restWallets[0], restWallets[1], restWallets[2], restWallets[3], restWallets[4]], aaveToken, @@ -37,19 +44,40 @@ const buildTestEnv = async ( ); const { incentivesProxy, stakeProxy } = await testDeployIncentivesController( + deployer, vaultOfRewards, proxyAdmin, aaveToken ); + const { proxy: baseIncentivesProxy } = await DRE.run('deploy-base-incentives', { + emissionManager: await deployer.getAddress(), + rewardToken: aaveToken.address, + rewardsVault: await vaultOfRewards.getAddress(), + proxyAdmin: await proxyAdmin.getAddress(), + }); + + await waitForTx( + await aaveToken.connect(vaultOfRewards).approve(baseIncentivesProxy, MAX_UINT_AMOUNT) + ); + const distributionDuration = ((await getBlockTimestamp()) + 1000 * 60 * 60).toString(); await deployATokenMock(incentivesProxy.address, 'aDai'); await deployATokenMock(incentivesProxy.address, 'aWeth'); + await deployATokenMock(baseIncentivesProxy, 'aDaiBase'); + await deployATokenMock(baseIncentivesProxy, 'aWethBase'); + const incentivesController = StakedTokenIncentivesController__factory.connect( incentivesProxy.address, deployer ); + const baseIncentivesController = BaseIncentivesController__factory.connect( + baseIncentivesProxy, + deployer + ); + await incentivesController.setDistributionEnd(distributionDuration); + await baseIncentivesController.setDistributionEnd(distributionDuration); await waitForTx( await aaveToken .connect(vaultOfRewards) @@ -61,20 +89,21 @@ const buildTestEnv = async ( return { aaveToken, incentivesController, + baseIncentivesController, aaveStake: StakedAaveV3__factory.connect(stakeProxy.address, deployer), }; }; before(async () => { await rawBRE.run('set-DRE'); - const [deployer, proxyAdmin, ...restWallets] = await getEthersSigners(); - const { aaveToken, aaveStake, incentivesController } = await buildTestEnv( - deployer, - deployer, - proxyAdmin, - restWallets - ); - await initializeMakeSuite(aaveToken, aaveStake, incentivesController); + const [deployer, proxyAdmin, rewardsVault, ...restWallets] = await getEthersSigners(); + const { + aaveToken, + aaveStake, + incentivesController, + baseIncentivesController, + } = await buildTestEnv(deployer, rewardsVault, proxyAdmin, restWallets); + await initializeMakeSuite(aaveToken, aaveStake, incentivesController, baseIncentivesController); console.log('\n***************'); console.log('Setup and snapshot finished'); console.log('***************\n'); diff --git a/test/helpers/deploy.ts b/test/helpers/deploy.ts index 6cef69a..4e90e39 100644 --- a/test/helpers/deploy.ts +++ b/test/helpers/deploy.ts @@ -14,12 +14,12 @@ export const COOLDOWN_SECONDS = '3600'; // 1 hour in seconds export const UNSTAKE_WINDOW = '1800'; // 30 min in second export const testDeployIncentivesController = async ( + emissionManager: Signer, vaultOfRewards: Signer, proxyAdmin: Signer, aaveToken: MintableErc20 ) => { - const emissionManager = await vaultOfRewards.getAddress(); - + const emissionManagerAddress = await emissionManager.getAddress(); // Deploy proxies and implementations const stakeProxy = await deployInitializableAdminUpgradeabilityProxy(); const incentivesProxy = await deployInitializableAdminUpgradeabilityProxy(); @@ -29,21 +29,29 @@ export const testDeployIncentivesController = async ( aaveToken.address, COOLDOWN_SECONDS, UNSTAKE_WINDOW, - emissionManager, - emissionManager, + await vaultOfRewards.getAddress(), + emissionManagerAddress, (1000 * 60 * 60).toString(), ]); const incentivesImplementation = await deployAaveIncentivesController([ stakeProxy.address, - emissionManager, + emissionManagerAddress, ]); // Initialize proxies const aaveStakeInit = aaveStakeV3.interface.encodeFunctionData( // @ts-ignore 'initialize(address,address,address,uint256,string,string,uint8)', - [emissionManager, emissionManager, emissionManager, '2000', 'Staked AAVE', 'stkAAVE', '18'] + [ + emissionManagerAddress, + emissionManagerAddress, + emissionManagerAddress, + '2000', + 'Staked AAVE', + 'stkAAVE', + '18', + ] ); const incentivesInit = incentivesImplementation.interface.encodeFunctionData('initialize', [ ZERO_ADDRESS, diff --git a/test/helpers/make-suite.ts b/test/helpers/make-suite.ts index 5a045df..e4618c3 100644 --- a/test/helpers/make-suite.ts +++ b/test/helpers/make-suite.ts @@ -9,7 +9,12 @@ import bignumberChai from 'chai-bignumber'; import { getATokenMock } from '../../helpers/contracts-accessors'; import { MintableErc20 } from '../../types/MintableErc20'; import { ATokenMock } from '../../types/ATokenMock'; -import { StakedAaveV3, StakedTokenIncentivesController } from '../../types'; +import { + BaseIncentivesController, + BaseIncentivesController__factory, + StakedAaveV3, + StakedTokenIncentivesController, +} from '../../types'; chai.use(bignumberChai()); @@ -28,9 +33,12 @@ export interface TestEnv { users: SignerWithAddress[]; aaveToken: MintableErc20; aaveIncentivesController: StakedTokenIncentivesController; + baseIncentivesController: BaseIncentivesController; stakedAave: StakedAaveV3; aDaiMock: ATokenMock; aWethMock: ATokenMock; + aDaiBaseMock: ATokenMock; + aWethBaseMock: ATokenMock; } let buidlerevmSnapshotId: string = '0x1'; @@ -46,14 +54,18 @@ const testEnv: TestEnv = { aaveToken: {} as MintableErc20, stakedAave: {} as StakedAaveV3, aaveIncentivesController: {} as StakedTokenIncentivesController, + baseIncentivesController: {} as BaseIncentivesController, aDaiMock: {} as ATokenMock, aWethMock: {} as ATokenMock, + aDaiBaseMock: {} as ATokenMock, + aWethBaseMock: {} as ATokenMock, } as TestEnv; export async function initializeMakeSuite( aaveToken: MintableErc20, stakedAave: StakedAaveV3, - aaveIncentivesController: StakedTokenIncentivesController + aaveIncentivesController: StakedTokenIncentivesController, + baseIncentivesController: BaseIncentivesController ) { const [_deployer, _proxyAdmin, ...restSigners] = await getEthersSigners(); const deployer: SignerWithAddress = { @@ -76,9 +88,12 @@ export async function initializeMakeSuite( testEnv.rewardsVault = rewardsVault; testEnv.stakedAave = stakedAave; testEnv.aaveIncentivesController = aaveIncentivesController; + testEnv.baseIncentivesController = baseIncentivesController; testEnv.aaveToken = aaveToken; testEnv.aDaiMock = await getATokenMock({ slug: 'aDai' }); testEnv.aWethMock = await getATokenMock({ slug: 'aWeth' }); + testEnv.aDaiBaseMock = await getATokenMock({ slug: 'aDaiBase' }); + testEnv.aWethBaseMock = await getATokenMock({ slug: 'aWethBase' }); } export function makeSuite(name: string, tests: (testEnv: TestEnv) => void) { From 78f2c15aad94752ebe2355284fd1c07c2993ef94 Mon Sep 17 00:00:00 2001 From: karotjal Date: Mon, 23 Aug 2021 16:48:59 +0200 Subject: [PATCH 03/12] fix: fix npm run test script --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 13c4112..f819e4a 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "hardhat:mumbai": "hardhat --network mumbai", "hardhat:matic": "hardhat --network matic", "coverage": "hardhat coverage", - "test": "npm run test-incentives", + "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", From 26ff2e65519321d275ef5f9f9e45892946638082 Mon Sep 17 00:00:00 2001 From: kartojal Date: Mon, 18 Oct 2021 14:01:50 +0200 Subject: [PATCH 04/12] feat: Use an abstract contract to hold all the IncentivesController logic. Adapt Stake and PullRewards contracts to use the abstracted incentives controller. --- .../PullRewardsIncentivesController.sol | 59 ++++++ .../StakedTokenIncentivesController.sol | 191 +----------------- .../{ => base}/BaseIncentivesController.sol | 51 ++--- .../{ => base}/DistributionManager.sol | 6 +- helpers/contracts-accessors.ts | 10 +- helpers/types.ts | 2 +- package.json | 2 +- ...s.ts => deploy-pull-rewards-incentives.ts} | 19 +- .../data-helpers/asset-data.ts | 7 +- .../data-helpers/asset-user-data.ts | 4 +- .../claim-on-behalf.spec.ts | 48 ++--- .../claim-rewards.spec.ts | 31 +-- .../configure-assets.spec.ts | 16 +- .../get-rewards-balance.spec.ts | 25 ++- .../handle-action.spec.ts | 27 ++- .../initialize.spec.ts | 6 +- .../misc.spec.ts | 38 ++-- test/__setup.spec.ts | 21 +- test/helpers/make-suite.ts | 12 +- 19 files changed, 235 insertions(+), 340 deletions(-) create mode 100644 contracts/incentives/PullRewardsIncentivesController.sol rename contracts/incentives/{ => base}/BaseIncentivesController.sol (81%) rename contracts/incentives/{ => base}/DistributionManager.sol (97%) rename tasks/migrations/{deploy-base-incentives.ts => deploy-pull-rewards-incentives.ts} (73%) rename test/{BaseIncentivesController => PullRewardsIncentivesController}/claim-on-behalf.spec.ts (70%) rename test/{BaseIncentivesController => PullRewardsIncentivesController}/claim-rewards.spec.ts (87%) rename test/{BaseIncentivesController => PullRewardsIncentivesController}/configure-assets.spec.ts (89%) rename test/{BaseIncentivesController => PullRewardsIncentivesController}/get-rewards-balance.spec.ts (75%) rename test/{BaseIncentivesController => PullRewardsIncentivesController}/handle-action.spec.ts (83%) rename test/{BaseIncentivesController => PullRewardsIncentivesController}/initialize.spec.ts (55%) rename test/{BaseIncentivesController => PullRewardsIncentivesController}/misc.spec.ts (56%) diff --git a/contracts/incentives/PullRewardsIncentivesController.sol b/contracts/incentives/PullRewardsIncentivesController.sol new file mode 100644 index 0000000..f571898 --- /dev/null +++ b/contracts/incentives/PullRewardsIncentivesController.sol @@ -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 { + _rewardsVault = rewardsVault; + emit RewardsVaultUpdated(rewardsVault); + } + + + /// @inheritdoc BaseIncentivesController + function _transferRewards(address to, uint256 amount) internal override { + IERC20(REWARD_TOKEN).safeTransferFrom(_rewardsVault, to, amount); + } +} \ No newline at end of file diff --git a/contracts/incentives/StakedTokenIncentivesController.sol b/contracts/incentives/StakedTokenIncentivesController.sol index 905989a..7c1e76e 100644 --- a/contracts/incentives/StakedTokenIncentivesController.sol +++ b/contracts/incentives/StakedTokenIncentivesController.sol @@ -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 @@ -19,31 +14,13 @@ 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; } @@ -57,162 +34,8 @@ contract StakedTokenIncentivesController is 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); } } diff --git a/contracts/incentives/BaseIncentivesController.sol b/contracts/incentives/base/BaseIncentivesController.sol similarity index 81% rename from contracts/incentives/BaseIncentivesController.sol rename to contracts/incentives/base/BaseIncentivesController.sol index 2222b6b..e61fd29 100644 --- a/contracts/incentives/BaseIncentivesController.sol +++ b/contracts/incentives/base/BaseIncentivesController.sol @@ -2,32 +2,29 @@ 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 {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'; +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 + * @notice Abstract contract template to build Distributors contracts for ERC20 rewards to protocol participants * @author Aave **/ -contract BaseIncentivesController is +abstract 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; @@ -35,8 +32,6 @@ contract BaseIncentivesController is // 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'); _; @@ -48,14 +43,6 @@ contract BaseIncentivesController is 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 @@ -156,23 +143,6 @@ contract BaseIncentivesController is 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 @@ -213,9 +183,16 @@ contract BaseIncentivesController is uint256 amountToClaim = amount > unclaimedRewards ? unclaimedRewards : amount; _usersUnclaimedRewards[user] = unclaimedRewards - amountToClaim; // Safe due to the previous line - IERC20(REWARD_TOKEN).safeTransferFrom(_rewardsVault, to, amountToClaim); + _transferRewards(to, amountToClaim); emit RewardsClaimed(user, to, claimer, amountToClaim); return amountToClaim; } + + /** + * @dev Abstract function to transfer rewards to the desired account + * @param to Account address to send the rewards + * @param amount Amount of rewards to transfer + */ + function _transferRewards(address to, uint256 amount) internal virtual; } \ No newline at end of file diff --git a/contracts/incentives/DistributionManager.sol b/contracts/incentives/base/DistributionManager.sol similarity index 97% rename from contracts/incentives/DistributionManager.sol rename to contracts/incentives/base/DistributionManager.sol index bd8cf90..905d343 100644 --- a/contracts/incentives/DistributionManager.sol +++ b/contracts/incentives/base/DistributionManager.sol @@ -2,9 +2,9 @@ pragma solidity 0.7.5; pragma experimental ABIEncoderV2; -import {IAaveDistributionManager} from '../interfaces/IAaveDistributionManager.sol'; -import {SafeMath} from '../lib/SafeMath.sol'; -import {DistributionTypes} from '../lib/DistributionTypes.sol'; +import {IAaveDistributionManager} from '../../interfaces/IAaveDistributionManager.sol'; +import {SafeMath} from '../../lib/SafeMath.sol'; +import {DistributionTypes} from '../../lib/DistributionTypes.sol'; /** * @title DistributionManager diff --git a/helpers/contracts-accessors.ts b/helpers/contracts-accessors.ts index db893d9..dd58481 100644 --- a/helpers/contracts-accessors.ts +++ b/helpers/contracts-accessors.ts @@ -12,7 +12,7 @@ import { IERC20Detailed } from '../types/IERC20Detailed'; import { verifyContract } from './etherscan-verification'; import { ATokenMock } from '../types/ATokenMock'; import { - BaseIncentivesController__factory, + PullRewardsIncentivesController__factory, InitializableAdminUpgradeabilityProxy__factory, StakedTokenIncentivesController, StakedTokenIncentivesController__factory, @@ -36,13 +36,13 @@ export const deployAaveIncentivesController = async ( return instance; }; -export const deployBaseIncentivesController = async ( +export const deployPullRewardsIncentivesController = async ( [rewardToken, emissionManager]: [tEthereumAddress, tEthereumAddress], verify?: boolean, signer?: Signer | DefenderRelaySigner ) => { const args: [string, string] = [rewardToken, emissionManager]; - const instance = await new BaseIncentivesController__factory( + const instance = await new PullRewardsIncentivesController__factory( signer || (await getFirstSigner()) ).deploy(...args); await instance.deployTransaction.wait(); @@ -81,8 +81,8 @@ export const getAaveIncentivesController = getContractFactory StakedTokenIncentivesController__factory.connect(address, await getFirstSigner()); -export const getBaseIncentivesController = async (address: tEthereumAddress) => - BaseIncentivesController__factory.connect(address, await getFirstSigner()); +export const getPullRewardsIncentivesController = async (address: tEthereumAddress) => + PullRewardsIncentivesController__factory.connect(address, await getFirstSigner()); export const getIErc20Detailed = getContractFactory(eContractid.IERC20Detailed); diff --git a/helpers/types.ts b/helpers/types.ts index b6ec2ca..4694f82 100644 --- a/helpers/types.ts +++ b/helpers/types.ts @@ -15,7 +15,7 @@ export enum eContractid { StakedTokenIncentivesController = 'StakedTokenIncentivesController', MockSelfDestruct = 'MockSelfDestruct', StakedAaveV3 = 'StakedAaveV3', - BaseIncentivesController = 'BaseIncentivesController', + PullRewardsIncentivesController = 'PullRewardsIncentivesController', } export enum eEthereumNetwork { diff --git a/package.json b/package.json index f819e4a..28ff544 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "hardhat:matic": "hardhat --network matic", "coverage": "hardhat coverage", "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-base-incentives": "TS_NODE_TRANSPILE_ONLY=1 hardhat test ./test/__setup.spec.ts ./test/PullRewardsIncentivesController/*.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", diff --git a/tasks/migrations/deploy-base-incentives.ts b/tasks/migrations/deploy-pull-rewards-incentives.ts similarity index 73% rename from tasks/migrations/deploy-base-incentives.ts rename to tasks/migrations/deploy-pull-rewards-incentives.ts index 063fcb2..0267e28 100644 --- a/tasks/migrations/deploy-base-incentives.ts +++ b/tasks/migrations/deploy-pull-rewards-incentives.ts @@ -2,12 +2,15 @@ import { isAddress } from 'ethers/lib/utils'; import { task } from 'hardhat/config'; import { ZERO_ADDRESS } from '../../helpers/constants'; import { - deployBaseIncentivesController, + deployPullRewardsIncentivesController, deployInitializableAdminUpgradeabilityProxy, } from '../../helpers/contracts-accessors'; import { waitForTx } from '../../helpers/misc-utils'; -task(`deploy-base-incentives`, `Deploy and initializes the BaseIncentivesController contract`) +task( + `deploy-pull-rewards-incentives`, + `Deploy and initializes the PullRewardsIncentivesController contract` +) .addFlag('verify') .addParam('rewardToken') .addParam('rewardsVault') @@ -27,16 +30,16 @@ task(`deploy-base-incentives`, `Deploy and initializes the BaseIncentivesControl } emissionManager = isAddress(emissionManager) ? emissionManager : ZERO_ADDRESS; - console.log(`[BaseIncentivesController] Starting deployment:`); + console.log(`[PullRewardsIncentivesController] Starting deployment:`); - const incentivesControllerImpl = await deployBaseIncentivesController( + const incentivesControllerImpl = await deployPullRewardsIncentivesController( [rewardToken, emissionManager], verify ); - console.log(` - Deployed implementation of BaseIncentivesController`); + console.log(` - Deployed implementation of PullRewardsIncentivesController`); const incentivesProxy = await deployInitializableAdminUpgradeabilityProxy(verify); - console.log(` - Deployed proxy of BaseIncentivesController`); + console.log(` - Deployed proxy of PullRewardsIncentivesController`); const encodedParams = incentivesControllerImpl.interface.encodeFunctionData('initialize', [ rewardsVault, @@ -49,9 +52,9 @@ task(`deploy-base-incentives`, `Deploy and initializes the BaseIncentivesControl encodedParams ) ); - console.log(` - Initialized BaseIncentivesController Proxy`); + console.log(` - Initialized PullRewardsIncentivesController Proxy`); - console.log(` - Finished BaseIncentivesController deployment and initialization`); + console.log(` - Finished PullRewardsIncentivesController deployment and initialization`); console.log(` - Proxy: ${incentivesProxy.address}`); console.log(` - Impl: ${incentivesControllerImpl.address}`); diff --git a/test/DistributionManager/data-helpers/asset-data.ts b/test/DistributionManager/data-helpers/asset-data.ts index c265e6c..ca6c0c7 100644 --- a/test/DistributionManager/data-helpers/asset-data.ts +++ b/test/DistributionManager/data-helpers/asset-data.ts @@ -2,7 +2,7 @@ import { BigNumber, BigNumberish } from 'ethers'; import { comparatorEngine, CompareRules } from '../../helpers/comparator-engine'; import { getNormalizedDistribution } from '../../helpers/ray-math'; import { AaveDistributionManager } from '../../../types/AaveDistributionManager'; -import { BaseIncentivesController, StakedTokenIncentivesController } from '../../../types'; +import { PullRewardsIncentivesController, StakedTokenIncentivesController } from '../../../types'; export type AssetUpdateData = { emissionPerSecond: BigNumberish; @@ -16,7 +16,10 @@ export type AssetData = { }; export async function getAssetsData( - peiContract: AaveDistributionManager | StakedTokenIncentivesController | BaseIncentivesController, + peiContract: + | AaveDistributionManager + | StakedTokenIncentivesController + | PullRewardsIncentivesController, assets: string[] ) { return await Promise.all( diff --git a/test/DistributionManager/data-helpers/asset-user-data.ts b/test/DistributionManager/data-helpers/asset-user-data.ts index cfd9ede..c1de6fd 100644 --- a/test/DistributionManager/data-helpers/asset-user-data.ts +++ b/test/DistributionManager/data-helpers/asset-user-data.ts @@ -1,5 +1,5 @@ import { BigNumber } from 'ethers'; -import { BaseIncentivesController, StakedTokenIncentivesController } from '../../../types'; +import { PullRewardsIncentivesController, StakedTokenIncentivesController } from '../../../types'; import { AaveDistributionManager } from '../../../types/AaveDistributionManager'; export type UserStakeInput = { @@ -15,7 +15,7 @@ export async function getUserIndex( distributionManager: | AaveDistributionManager | StakedTokenIncentivesController - | BaseIncentivesController, + | PullRewardsIncentivesController, user: string, asset: string ): Promise { diff --git a/test/BaseIncentivesController/claim-on-behalf.spec.ts b/test/PullRewardsIncentivesController/claim-on-behalf.spec.ts similarity index 70% rename from test/BaseIncentivesController/claim-on-behalf.spec.ts rename to test/PullRewardsIncentivesController/claim-on-behalf.spec.ts index 73875b8..7212314 100644 --- a/test/BaseIncentivesController/claim-on-behalf.spec.ts +++ b/test/PullRewardsIncentivesController/claim-on-behalf.spec.ts @@ -4,22 +4,22 @@ import { waitForTx } from '../../helpers/misc-utils'; import { makeSuite, TestEnv } from '../helpers/make-suite'; -makeSuite('baseIncentivesController - Claim rewards on behalf', (testEnv: TestEnv) => { +makeSuite('PullRewardsIncentivesController - Claim rewards on behalf', (testEnv: TestEnv) => { it('Should setClaimer revert if not called by emission manager', async () => { - const { baseIncentivesController, users } = testEnv; + const { pullRewardsIncentivesController, users } = testEnv; const [userWithRewards, thirdClaimer] = users; await expect( - baseIncentivesController + pullRewardsIncentivesController .connect(userWithRewards.signer) .setClaimer(userWithRewards.address, thirdClaimer.address) ).to.be.revertedWith('ONLY_EMISSION_MANAGER'); }); it('Should claimRewardsOnBehalf revert if called claimer is not authorized', async () => { - const { baseIncentivesController, users, aDaiBaseMock, aaveToken } = testEnv; + const { pullRewardsIncentivesController, users, aDaiBaseMock, aaveToken } = testEnv; const [userWithRewards, thirdClaimer] = users; await waitForTx( - await baseIncentivesController.configureAssets([aDaiBaseMock.address], ['20000']) + await pullRewardsIncentivesController.configureAssets([aDaiBaseMock.address], ['20000']) ); await waitForTx(await aDaiBaseMock.setUserBalanceAndSupply('300000', '300000')); @@ -27,7 +27,7 @@ makeSuite('baseIncentivesController - Claim rewards on behalf', (testEnv: TestEn const priorStkBalance = await aaveToken.balanceOf(thirdClaimer.address); await expect( - baseIncentivesController + pullRewardsIncentivesController .connect(thirdClaimer.signer) .claimRewardsOnBehalf( [aDaiBaseMock.address], @@ -41,27 +41,27 @@ makeSuite('baseIncentivesController - Claim rewards on behalf', (testEnv: TestEn expect(afterStkBalance).to.be.eq(priorStkBalance); }); it('Should setClaimer pass if called by emission manager', async () => { - const { baseIncentivesController, users, rewardsVault } = testEnv; + const { pullRewardsIncentivesController, users, rewardsVault } = testEnv; const [userWithRewards, thirdClaimer] = users; const emissionManager = rewardsVault; await expect( - baseIncentivesController + pullRewardsIncentivesController .connect(emissionManager.signer) .setClaimer(userWithRewards.address, thirdClaimer.address) ) - .to.emit(baseIncentivesController, 'ClaimerSet') + .to.emit(pullRewardsIncentivesController, 'ClaimerSet') .withArgs(userWithRewards.address, thirdClaimer.address); - await expect(await baseIncentivesController.getClaimer(userWithRewards.address)).to.be.equal( - thirdClaimer.address - ); + await expect( + await pullRewardsIncentivesController.getClaimer(userWithRewards.address) + ).to.be.equal(thirdClaimer.address); }); it('Should claimRewardsOnBehalf pass if called by the assigned claimer', async () => { - const { baseIncentivesController, users, aDaiBaseMock, aaveToken } = testEnv; + const { pullRewardsIncentivesController, users, aDaiBaseMock, aaveToken } = testEnv; const [userWithRewards, thirdClaimer] = users; await waitForTx( - await baseIncentivesController.configureAssets([aDaiBaseMock.address], ['2000']) + await pullRewardsIncentivesController.configureAssets([aDaiBaseMock.address], ['2000']) ); await waitForTx(await aDaiBaseMock.setUserBalanceAndSupply('300000', '30000')); @@ -69,7 +69,7 @@ makeSuite('baseIncentivesController - Claim rewards on behalf', (testEnv: TestEn const priorBalance = await aaveToken.balanceOf(thirdClaimer.address); await expect( - baseIncentivesController + pullRewardsIncentivesController .connect(thirdClaimer.signer) .claimRewardsOnBehalf( [aDaiBaseMock.address], @@ -78,7 +78,7 @@ makeSuite('baseIncentivesController - Claim rewards on behalf', (testEnv: TestEn thirdClaimer.address ) ) - .to.emit(baseIncentivesController, 'RewardsClaimed') + .to.emit(pullRewardsIncentivesController, 'RewardsClaimed') .withArgs(userWithRewards.address, thirdClaimer.address, thirdClaimer.address, '99999'); const afterStkBalance = await aaveToken.balanceOf(thirdClaimer.address); console.log('adt', afterStkBalance.toString()); @@ -86,16 +86,16 @@ makeSuite('baseIncentivesController - Claim rewards on behalf', (testEnv: TestEn }); it('Should claimRewardsOnBehalf revert if to argument address is ZERO_ADDRESS', async () => { - const { baseIncentivesController, users, aDaiBaseMock, aaveToken } = testEnv; + const { pullRewardsIncentivesController, users, aDaiBaseMock, aaveToken } = testEnv; const [userWithRewards, thirdClaimer] = users; await waitForTx( - await baseIncentivesController.configureAssets([aDaiBaseMock.address], ['2000']) + await pullRewardsIncentivesController.configureAssets([aDaiBaseMock.address], ['2000']) ); await waitForTx(await aDaiBaseMock.setUserBalanceAndSupply('300000', '30000')); await expect( - baseIncentivesController + pullRewardsIncentivesController .connect(thirdClaimer.signer) .claimRewardsOnBehalf( [aDaiBaseMock.address], @@ -107,26 +107,26 @@ makeSuite('baseIncentivesController - Claim rewards on behalf', (testEnv: TestEn }); it('Should claimRewardsOnBehalf revert if user argument is ZERO_ADDRESS', async () => { - const { baseIncentivesController, users, aDaiBaseMock, rewardsVault } = testEnv; + const { pullRewardsIncentivesController, users, aDaiBaseMock, rewardsVault } = testEnv; const [, thirdClaimer] = users; const emissionManager = rewardsVault; await waitForTx( - await baseIncentivesController.configureAssets([aDaiBaseMock.address], ['2000']) + await pullRewardsIncentivesController.configureAssets([aDaiBaseMock.address], ['2000']) ); await waitForTx(await aDaiBaseMock.setUserBalanceAndSupply('300000', '30000')); await expect( - baseIncentivesController + pullRewardsIncentivesController .connect(emissionManager.signer) .setClaimer(ZERO_ADDRESS, thirdClaimer.address) ) - .to.emit(baseIncentivesController, 'ClaimerSet') + .to.emit(pullRewardsIncentivesController, 'ClaimerSet') .withArgs(ZERO_ADDRESS, thirdClaimer.address); await expect( - baseIncentivesController + pullRewardsIncentivesController .connect(thirdClaimer.signer) .claimRewardsOnBehalf( [aDaiBaseMock.address], diff --git a/test/BaseIncentivesController/claim-rewards.spec.ts b/test/PullRewardsIncentivesController/claim-rewards.spec.ts similarity index 87% rename from test/BaseIncentivesController/claim-rewards.spec.ts rename to test/PullRewardsIncentivesController/claim-rewards.spec.ts index 83ce3bf..719e03f 100644 --- a/test/BaseIncentivesController/claim-rewards.spec.ts +++ b/test/PullRewardsIncentivesController/claim-rewards.spec.ts @@ -60,7 +60,7 @@ const getRewardsBalanceScenarios: ScenarioAction[] = [ }, ]; -makeSuite('baseIncentivesController claimRewards tests', (testEnv) => { +makeSuite('pullRewardsIncentivesController claimRewards tests', (testEnv) => { for (const { caseName, amountToClaim: _amountToClaim, @@ -70,10 +70,10 @@ makeSuite('baseIncentivesController claimRewards tests', (testEnv) => { let amountToClaim = _amountToClaim; it(caseName, async () => { await increaseTime(100); - const { baseIncentivesController, aaveToken, aDaiBaseMock } = testEnv; + const { pullRewardsIncentivesController, aaveToken, aDaiBaseMock } = testEnv; - const distributionEndTimestamp = await baseIncentivesController.getDistributionEnd(); - const userAddress = await baseIncentivesController.signer.getAddress(); + const distributionEndTimestamp = await pullRewardsIncentivesController.getDistributionEnd(); + const userAddress = await pullRewardsIncentivesController.signer.getAddress(); const underlyingAsset = aDaiBaseMock.address; const stakedByUser = 22 * caseName.length; @@ -81,7 +81,10 @@ makeSuite('baseIncentivesController claimRewards tests', (testEnv) => { // update emissionPerSecond in advance to not affect user calculations if (emissionPerSecond) { - await baseIncentivesController.configureAssets([underlyingAsset], [emissionPerSecond]); + await pullRewardsIncentivesController.configureAssets( + [underlyingAsset], + [emissionPerSecond] + ); } const destinationAddress = to || userAddress; @@ -90,20 +93,22 @@ makeSuite('baseIncentivesController claimRewards tests', (testEnv) => { await aDaiBaseMock.setUserBalanceAndSupply(stakedByUser, totalStaked); await aDaiBaseMock.handleActionOnAic(userAddress, totalStaked, stakedByUser); - const unclaimedRewardsBefore = await baseIncentivesController.getRewardsBalance( + const unclaimedRewardsBefore = await pullRewardsIncentivesController.getRewardsBalance( [underlyingAsset], userAddress ); const userIndexBefore = await getUserIndex( - baseIncentivesController, + pullRewardsIncentivesController, userAddress, underlyingAsset ); - const assetDataBefore = (await getAssetsData(baseIncentivesController, [underlyingAsset]))[0]; + const assetDataBefore = ( + await getAssetsData(pullRewardsIncentivesController, [underlyingAsset]) + )[0]; const claimRewardsReceipt = await waitForTx( - await baseIncentivesController.claimRewards( + await pullRewardsIncentivesController.claimRewards( [underlyingAsset], amountToClaim, destinationAddress @@ -114,13 +119,15 @@ makeSuite('baseIncentivesController claimRewards tests', (testEnv) => { const actionBlockTimestamp = await getBlockTimestamp(claimRewardsReceipt.blockNumber); const userIndexAfter = await getUserIndex( - baseIncentivesController, + pullRewardsIncentivesController, userAddress, underlyingAsset ); - const assetDataAfter = (await getAssetsData(baseIncentivesController, [underlyingAsset]))[0]; + const assetDataAfter = ( + await getAssetsData(pullRewardsIncentivesController, [underlyingAsset]) + )[0]; - const unclaimedRewardsAfter = await baseIncentivesController.getRewardsBalance( + const unclaimedRewardsAfter = await pullRewardsIncentivesController.getRewardsBalance( [underlyingAsset], userAddress ); diff --git a/test/BaseIncentivesController/configure-assets.spec.ts b/test/PullRewardsIncentivesController/configure-assets.spec.ts similarity index 89% rename from test/BaseIncentivesController/configure-assets.spec.ts rename to test/PullRewardsIncentivesController/configure-assets.spec.ts index 24cbd22..8dc7db8 100644 --- a/test/BaseIncentivesController/configure-assets.spec.ts +++ b/test/PullRewardsIncentivesController/configure-assets.spec.ts @@ -118,7 +118,7 @@ const configureAssetScenarios: ScenarioAction[] = [ }, ]; -makeSuite('baseIncentivesController configureAssets', (testEnv: TestEnv) => { +makeSuite('pullRewardsIncentivesController configureAssets', (testEnv: TestEnv) => { let deployedAssets; before(async () => { @@ -127,9 +127,9 @@ makeSuite('baseIncentivesController configureAssets', (testEnv: TestEnv) => { // custom checks it('Tries to submit config updates not from emission manager', async () => { - const { baseIncentivesController, users } = testEnv; + const { pullRewardsIncentivesController, users } = testEnv; await expect( - baseIncentivesController.connect(users[2].signer).configureAssets([], []) + pullRewardsIncentivesController.connect(users[2].signer).configureAssets([], []) ).to.be.revertedWith('ONLY_EMISSION_MANAGER'); }); @@ -140,8 +140,8 @@ makeSuite('baseIncentivesController configureAssets', (testEnv: TestEnv) => { customTimeMovement, } of configureAssetScenarios) { it(caseName, async () => { - const { baseIncentivesController } = testEnv; - const distributionEndTimestamp = await baseIncentivesController.DISTRIBUTION_END(); + const { pullRewardsIncentivesController } = testEnv; + const distributionEndTimestamp = await pullRewardsIncentivesController.DISTRIBUTION_END(); const assets: string[] = []; const assetsEmissions: BigNumberish[] = []; @@ -166,17 +166,17 @@ makeSuite('baseIncentivesController configureAssets', (testEnv: TestEnv) => { }); } - const assetsConfigBefore = await getAssetsData(baseIncentivesController, assets); + const assetsConfigBefore = await getAssetsData(pullRewardsIncentivesController, assets); if (customTimeMovement) { await increaseTime(customTimeMovement); } const txReceipt = await waitForTx( - await baseIncentivesController.configureAssets(assets, assetsEmissions) + await pullRewardsIncentivesController.configureAssets(assets, assetsEmissions) ); const configsUpdateBlockTimestamp = await getBlockTimestamp(txReceipt.blockNumber); - const assetsConfigAfter = await getAssetsData(baseIncentivesController, assets); + const assetsConfigAfter = await getAssetsData(pullRewardsIncentivesController, assets); const eventsEmitted = txReceipt.events || []; diff --git a/test/BaseIncentivesController/get-rewards-balance.spec.ts b/test/PullRewardsIncentivesController/get-rewards-balance.spec.ts similarity index 75% rename from test/BaseIncentivesController/get-rewards-balance.spec.ts rename to test/PullRewardsIncentivesController/get-rewards-balance.spec.ts index 78a9b8c..464dd8c 100644 --- a/test/BaseIncentivesController/get-rewards-balance.spec.ts +++ b/test/PullRewardsIncentivesController/get-rewards-balance.spec.ts @@ -28,14 +28,14 @@ const getRewardsBalanceScenarios: ScenarioAction[] = [ }, ]; -makeSuite('baseIncentivesController getRewardsBalance tests', (testEnv) => { +makeSuite('pullRewardsIncentivesController getRewardsBalance tests', (testEnv) => { for (const { caseName, emissionPerSecond } of getRewardsBalanceScenarios) { it(caseName, async () => { await increaseTime(100); - const { baseIncentivesController, users, aDaiBaseMock } = testEnv; + const { pullRewardsIncentivesController, users, aDaiBaseMock } = testEnv; - const distributionEndTimestamp = await baseIncentivesController.DISTRIBUTION_END(); + const distributionEndTimestamp = await pullRewardsIncentivesController.DISTRIBUTION_END(); const userAddress = users[1].address; const stakedByUser = 22 * caseName.length; const totalStaked = 33 * caseName.length; @@ -45,7 +45,10 @@ makeSuite('baseIncentivesController getRewardsBalance tests', (testEnv) => { await advanceBlock((await timeLatest()).plus(100).toNumber()); if (emissionPerSecond) { await aDaiBaseMock.setUserBalanceAndSupply('0', totalStaked); - await baseIncentivesController.configureAssets([underlyingAsset], [emissionPerSecond]); + await pullRewardsIncentivesController.configureAssets( + [underlyingAsset], + [emissionPerSecond] + ); } await aDaiBaseMock.handleActionOnAic(userAddress, totalStaked, stakedByUser); await advanceBlock((await timeLatest()).plus(100).toNumber()); @@ -55,17 +58,23 @@ makeSuite('baseIncentivesController getRewardsBalance tests', (testEnv) => { ); const lastTxTimestamp = await getBlockTimestamp(lastTxReceipt.blockNumber); - const unclaimedRewardsBefore = await baseIncentivesController.getUserUnclaimedRewards( + const unclaimedRewardsBefore = await pullRewardsIncentivesController.getUserUnclaimedRewards( userAddress ); - const unclaimedRewards = await baseIncentivesController.getRewardsBalance( + const unclaimedRewards = await pullRewardsIncentivesController.getRewardsBalance( [underlyingAsset], userAddress ); - const userIndex = await getUserIndex(baseIncentivesController, userAddress, underlyingAsset); - const assetData = (await getAssetsData(baseIncentivesController, [underlyingAsset]))[0]; + const userIndex = await getUserIndex( + pullRewardsIncentivesController, + userAddress, + underlyingAsset + ); + const assetData = ( + await getAssetsData(pullRewardsIncentivesController, [underlyingAsset]) + )[0]; await aDaiBaseMock.cleanUserState(); diff --git a/test/BaseIncentivesController/handle-action.spec.ts b/test/PullRewardsIncentivesController/handle-action.spec.ts similarity index 83% rename from test/BaseIncentivesController/handle-action.spec.ts rename to test/PullRewardsIncentivesController/handle-action.spec.ts index c1eafab..39dead6 100644 --- a/test/BaseIncentivesController/handle-action.spec.ts +++ b/test/PullRewardsIncentivesController/handle-action.spec.ts @@ -50,7 +50,7 @@ const handleActionScenarios: ScenarioAction[] = [ }, ]; -makeSuite('baseIncentivesController handleAction tests', (testEnv) => { +makeSuite('pullRewardsIncentivesController handleAction tests', (testEnv) => { for (const { caseName, totalSupply, @@ -61,26 +61,31 @@ makeSuite('baseIncentivesController handleAction tests', (testEnv) => { it(caseName, async () => { await increaseTime(100); - const { baseIncentivesController, users, aDaiBaseMock } = testEnv; + const { pullRewardsIncentivesController, users, aDaiBaseMock } = testEnv; const userAddress = users[1].address; const underlyingAsset = aDaiBaseMock.address; // update emissionPerSecond in advance to not affect user calculations if (emissionPerSecond) { - await baseIncentivesController.configureAssets([underlyingAsset], [emissionPerSecond]); + await pullRewardsIncentivesController.configureAssets( + [underlyingAsset], + [emissionPerSecond] + ); } - const distributionEndTimestamp = await baseIncentivesController.DISTRIBUTION_END(); + const distributionEndTimestamp = await pullRewardsIncentivesController.DISTRIBUTION_END(); - const rewardsBalanceBefore = await baseIncentivesController.getUserUnclaimedRewards( + const rewardsBalanceBefore = await pullRewardsIncentivesController.getUserUnclaimedRewards( userAddress ); const userIndexBefore = await getUserIndex( - baseIncentivesController, + pullRewardsIncentivesController, userAddress, underlyingAsset ); - const assetDataBefore = (await getAssetsData(baseIncentivesController, [underlyingAsset]))[0]; + const assetDataBefore = ( + await getAssetsData(pullRewardsIncentivesController, [underlyingAsset]) + )[0]; if (customTimeMovement) { await increaseTime(customTimeMovement); @@ -94,12 +99,14 @@ makeSuite('baseIncentivesController handleAction tests', (testEnv) => { const actionBlockTimestamp = await getBlockTimestamp(handleActionReceipt.blockNumber); const userIndexAfter = await getUserIndex( - baseIncentivesController, + pullRewardsIncentivesController, userAddress, underlyingAsset ); - const assetDataAfter = (await getAssetsData(baseIncentivesController, [underlyingAsset]))[0]; + const assetDataAfter = ( + await getAssetsData(pullRewardsIncentivesController, [underlyingAsset]) + )[0]; const expectedAccruedRewards = getRewards( userBalance, @@ -107,7 +114,7 @@ makeSuite('baseIncentivesController handleAction tests', (testEnv) => { userIndexBefore ).toString(); - const rewardsBalanceAfter = await baseIncentivesController.getUserUnclaimedRewards( + const rewardsBalanceAfter = await pullRewardsIncentivesController.getUserUnclaimedRewards( userAddress ); diff --git a/test/BaseIncentivesController/initialize.spec.ts b/test/PullRewardsIncentivesController/initialize.spec.ts similarity index 55% rename from test/BaseIncentivesController/initialize.spec.ts rename to test/PullRewardsIncentivesController/initialize.spec.ts index 2c380a5..5870cb5 100644 --- a/test/BaseIncentivesController/initialize.spec.ts +++ b/test/PullRewardsIncentivesController/initialize.spec.ts @@ -3,10 +3,10 @@ import { MAX_UINT_AMOUNT, ZERO_ADDRESS } from '../../helpers/constants'; const { expect } = require('chai'); -makeSuite('baseIncentivesController initialize', (testEnv: TestEnv) => { +makeSuite('pullRewardsIncentivesController initialize', (testEnv: TestEnv) => { // TODO: useless or not? it('Tries to call initialize second time, should be reverted', async () => { - const { baseIncentivesController } = testEnv; - await expect(baseIncentivesController.initialize(ZERO_ADDRESS)).to.be.reverted; + const { pullRewardsIncentivesController } = testEnv; + await expect(pullRewardsIncentivesController.initialize(ZERO_ADDRESS)).to.be.reverted; }); }); diff --git a/test/BaseIncentivesController/misc.spec.ts b/test/PullRewardsIncentivesController/misc.spec.ts similarity index 56% rename from test/BaseIncentivesController/misc.spec.ts rename to test/PullRewardsIncentivesController/misc.spec.ts index 3559807..b210d2b 100644 --- a/test/BaseIncentivesController/misc.spec.ts +++ b/test/PullRewardsIncentivesController/misc.spec.ts @@ -3,38 +3,38 @@ import { timeLatest, waitForTx } from '../../helpers/misc-utils'; import { expect } from 'chai'; import { makeSuite } from '../helpers/make-suite'; -import { deployBaseIncentivesController } from '../../helpers/contracts-accessors'; +import { deployPullRewardsIncentivesController } from '../../helpers/contracts-accessors'; import { MAX_UINT_AMOUNT, RANDOM_ADDRESSES, ZERO_ADDRESS } from '../../helpers/constants'; -makeSuite('baseIncentivesController misc tests', (testEnv) => { +makeSuite('pullRewardsIncentivesController misc tests', (testEnv) => { it('constructor should assign correct params', async () => { const peiEmissionManager = RANDOM_ADDRESSES[1]; const fakeToken = RANDOM_ADDRESSES[5]; - const baseIncentivesController = await deployBaseIncentivesController([ + const pullRewardsIncentivesController = await deployPullRewardsIncentivesController([ fakeToken, peiEmissionManager, ]); - await expect(await baseIncentivesController.REWARD_TOKEN()).to.be.equal(fakeToken); - await expect((await baseIncentivesController.EMISSION_MANAGER()).toString()).to.be.equal( + await expect(await pullRewardsIncentivesController.REWARD_TOKEN()).to.be.equal(fakeToken); + await expect((await pullRewardsIncentivesController.EMISSION_MANAGER()).toString()).to.be.equal( peiEmissionManager ); }); it('Should return same index while multiple asset index updates', async () => { - const { aDaiBaseMock, baseIncentivesController, users } = testEnv; + const { aDaiBaseMock, pullRewardsIncentivesController, users } = testEnv; await waitForTx( - await baseIncentivesController.configureAssets([aDaiBaseMock.address], ['100']) + await pullRewardsIncentivesController.configureAssets([aDaiBaseMock.address], ['100']) ); await waitForTx(await aDaiBaseMock.doubleHandleActionOnAic(users[1].address, '2000', '100')); }); it('Should overflow index if passed a large emission', async () => { - const { aDaiBaseMock, baseIncentivesController, users } = testEnv; + const { aDaiBaseMock, pullRewardsIncentivesController, users } = testEnv; const MAX_104_UINT = '20282409603651670423947251286015'; await waitForTx( - await baseIncentivesController.configureAssets([aDaiBaseMock.address], [MAX_104_UINT]) + await pullRewardsIncentivesController.configureAssets([aDaiBaseMock.address], [MAX_104_UINT]) ); await expect( aDaiBaseMock.doubleHandleActionOnAic(users[1].address, '2000', '100') @@ -42,38 +42,40 @@ makeSuite('baseIncentivesController misc tests', (testEnv) => { }); it('Should configureAssets revert if parameters length does not match', async () => { - const { aDaiBaseMock, baseIncentivesController } = testEnv; + const { aDaiBaseMock, pullRewardsIncentivesController } = testEnv; await expect( - baseIncentivesController.configureAssets([aDaiBaseMock.address], ['1', '2']) + pullRewardsIncentivesController.configureAssets([aDaiBaseMock.address], ['1', '2']) ).to.be.revertedWith('INVALID_CONFIGURATION'); }); it('Should configureAssets revert if emission parameter overflows uin104', async () => { - const { aDaiBaseMock, baseIncentivesController } = testEnv; + const { aDaiBaseMock, pullRewardsIncentivesController } = testEnv; await expect( - baseIncentivesController.configureAssets([aDaiBaseMock.address], [MAX_UINT_AMOUNT]) + pullRewardsIncentivesController.configureAssets([aDaiBaseMock.address], [MAX_UINT_AMOUNT]) ).to.be.revertedWith('INVALID_CONFIGURATION'); }); it('Should REWARD_TOKEN getter returns the stake token address to keep old interface compatibility', async () => { - const { baseIncentivesController, aaveToken } = testEnv; - await expect(await baseIncentivesController.REWARD_TOKEN()).to.be.equal(aaveToken.address); + const { pullRewardsIncentivesController, aaveToken } = testEnv; + await expect(await pullRewardsIncentivesController.REWARD_TOKEN()).to.be.equal( + aaveToken.address + ); }); it('Should claimRewards revert if to argument is ZERO_ADDRESS', async () => { - const { baseIncentivesController, users, aDaiBaseMock } = testEnv; + const { pullRewardsIncentivesController, users, aDaiBaseMock } = testEnv; const [userWithRewards] = users; await waitForTx( - await baseIncentivesController.configureAssets([aDaiBaseMock.address], ['2000']) + await pullRewardsIncentivesController.configureAssets([aDaiBaseMock.address], ['2000']) ); await waitForTx(await aDaiBaseMock.setUserBalanceAndSupply('300000', '30000')); // Claim from third party claimer await expect( - baseIncentivesController + pullRewardsIncentivesController .connect(userWithRewards.signer) .claimRewards([aDaiBaseMock.address], MAX_UINT_AMOUNT, ZERO_ADDRESS) ).to.be.revertedWith('INVALID_TO_ADDRESS'); diff --git a/test/__setup.spec.ts b/test/__setup.spec.ts index e2358d4..47cbbcc 100644 --- a/test/__setup.spec.ts +++ b/test/__setup.spec.ts @@ -7,8 +7,8 @@ import { DRE, waitForTx } from '../helpers/misc-utils'; import { MintableErc20 } from '../types/MintableErc20'; import { testDeployIncentivesController } from './helpers/deploy'; import { - BaseIncentivesController, - BaseIncentivesController__factory, + PullRewardsIncentivesController, + PullRewardsIncentivesController__factory, StakedAaveV3__factory, StakedTokenIncentivesController__factory, } from '../types'; @@ -49,7 +49,7 @@ const buildTestEnv = async ( proxyAdmin, aaveToken ); - const { proxy: baseIncentivesProxy } = await DRE.run('deploy-base-incentives', { + const { proxy: baseIncentivesProxy } = await DRE.run('deploy-pull-rewards-incentives', { emissionManager: await deployer.getAddress(), rewardToken: aaveToken.address, rewardsVault: await vaultOfRewards.getAddress(), @@ -71,13 +71,13 @@ const buildTestEnv = async ( incentivesProxy.address, deployer ); - const baseIncentivesController = BaseIncentivesController__factory.connect( + const pullRewardsIncentivesController = PullRewardsIncentivesController__factory.connect( baseIncentivesProxy, deployer ); await incentivesController.setDistributionEnd(distributionDuration); - await baseIncentivesController.setDistributionEnd(distributionDuration); + await pullRewardsIncentivesController.setDistributionEnd(distributionDuration); await waitForTx( await aaveToken .connect(vaultOfRewards) @@ -89,7 +89,7 @@ const buildTestEnv = async ( return { aaveToken, incentivesController, - baseIncentivesController, + pullRewardsIncentivesController, aaveStake: StakedAaveV3__factory.connect(stakeProxy.address, deployer), }; }; @@ -101,9 +101,14 @@ before(async () => { aaveToken, aaveStake, incentivesController, - baseIncentivesController, + pullRewardsIncentivesController, } = await buildTestEnv(deployer, rewardsVault, proxyAdmin, restWallets); - await initializeMakeSuite(aaveToken, aaveStake, incentivesController, baseIncentivesController); + await initializeMakeSuite( + aaveToken, + aaveStake, + incentivesController, + pullRewardsIncentivesController + ); console.log('\n***************'); console.log('Setup and snapshot finished'); console.log('***************\n'); diff --git a/test/helpers/make-suite.ts b/test/helpers/make-suite.ts index e4618c3..0b8c2e4 100644 --- a/test/helpers/make-suite.ts +++ b/test/helpers/make-suite.ts @@ -10,8 +10,8 @@ import { getATokenMock } from '../../helpers/contracts-accessors'; import { MintableErc20 } from '../../types/MintableErc20'; import { ATokenMock } from '../../types/ATokenMock'; import { - BaseIncentivesController, - BaseIncentivesController__factory, + PullRewardsIncentivesController, + PullRewardsIncentivesController__factory, StakedAaveV3, StakedTokenIncentivesController, } from '../../types'; @@ -33,7 +33,7 @@ export interface TestEnv { users: SignerWithAddress[]; aaveToken: MintableErc20; aaveIncentivesController: StakedTokenIncentivesController; - baseIncentivesController: BaseIncentivesController; + pullRewardsIncentivesController: PullRewardsIncentivesController; stakedAave: StakedAaveV3; aDaiMock: ATokenMock; aWethMock: ATokenMock; @@ -54,7 +54,7 @@ const testEnv: TestEnv = { aaveToken: {} as MintableErc20, stakedAave: {} as StakedAaveV3, aaveIncentivesController: {} as StakedTokenIncentivesController, - baseIncentivesController: {} as BaseIncentivesController, + pullRewardsIncentivesController: {} as PullRewardsIncentivesController, aDaiMock: {} as ATokenMock, aWethMock: {} as ATokenMock, aDaiBaseMock: {} as ATokenMock, @@ -65,7 +65,7 @@ export async function initializeMakeSuite( aaveToken: MintableErc20, stakedAave: StakedAaveV3, aaveIncentivesController: StakedTokenIncentivesController, - baseIncentivesController: BaseIncentivesController + pullRewardsIncentivesController: PullRewardsIncentivesController ) { const [_deployer, _proxyAdmin, ...restSigners] = await getEthersSigners(); const deployer: SignerWithAddress = { @@ -88,7 +88,7 @@ export async function initializeMakeSuite( testEnv.rewardsVault = rewardsVault; testEnv.stakedAave = stakedAave; testEnv.aaveIncentivesController = aaveIncentivesController; - testEnv.baseIncentivesController = baseIncentivesController; + testEnv.pullRewardsIncentivesController = pullRewardsIncentivesController; testEnv.aaveToken = aaveToken; testEnv.aDaiMock = await getATokenMock({ slug: 'aDai' }); testEnv.aWethMock = await getATokenMock({ slug: 'aWeth' }); From 9e1653a1d716bf1bffe61c8a31034bb52108c455 Mon Sep 17 00:00:00 2001 From: kartojal Date: Mon, 18 Oct 2021 14:20:41 +0200 Subject: [PATCH 05/12] rename: rename incentives proposal to incentives controller in package json and readme.md --- package.json | 12 ++++++------ readme.md | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 28ff544..091a7b3 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "incentives-proposal", + "name": "incentives-controller", "version": "1.0.0", - "description": "Incentives proposal for Aave protocol markets", + "description": "Incentives controller for Aave protocol", "files": [ "contracts", "artifacts" @@ -18,7 +18,7 @@ "hardhat:matic": "hardhat --network matic", "coverage": "hardhat coverage", "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/PullRewardsIncentivesController/*.spec.ts", + "test-pull-rewards-incentives": "TS_NODE_TRANSPILE_ONLY=1 hardhat test ./test/__setup.spec.ts ./test/PullRewardsIncentivesController/*.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", @@ -33,14 +33,14 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/aave/incentives-proposal.git" + "url": "git+https://github.com/aave/incentives-controller.git" }, "author": "David Racero", "license": "AGPLv3", "bugs": { - "url": "https://github.com/aave/incentives-proposal/issues" + "url": "https://github.com/aave/incentives-controller/issues" }, - "homepage": "https://github.com/aave/incentives-proposal#readme", + "homepage": "https://github.com/aave/incentives-controller#readme", "devDependencies": { "@aave/protocol-v2": "^1.0.2-fat.3", "@nomiclabs/hardhat-ethers": "^2.0.0", diff --git a/readme.md b/readme.md index 1264f12..caf3e1d 100644 --- a/readme.md +++ b/readme.md @@ -1,8 +1,8 @@ [![License: AGPL v3](https://img.shields.io/badge/License-AGPL%20v3-blue.svg)](https://www.gnu.org/licenses/agpl-3.0) -[![Build pass](https://github.com/aave/incentives-proposal/actions/workflows/node.js.yml/badge.svg)](https://github.com/aave/incentives-proposal/actions/workflows/node.js.yml) -[![codecov](https://codecov.io/gh/aave/incentives-proposal/branch/master/graph/badge.svg?token=DRFNLw506C)](https://codecov.io/gh/aave/incentives-proposal) +[![Build pass](https://github.com/aave/incentives-controller/actions/workflows/node.js.yml/badge.svg)](https://github.com/aave/incentives-controller/actions/workflows/node.js.yml) +[![codecov](https://codecov.io/gh/aave/incentives-controller/branch/master/graph/badge.svg?token=DRFNLw506C)](https://codecov.io/gh/aave/incentives-controller) -# Aave incentives proposal +# Aave incentives ## Introduction From ad5274c00fc20d9eada78424d256ea8f3180e0a3 Mon Sep 17 00:00:00 2001 From: kartojal Date: Mon, 18 Oct 2021 15:04:56 +0200 Subject: [PATCH 06/12] scripts: fix task name at tests script --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 091a7b3..3018e24 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "hardhat:mumbai": "hardhat --network mumbai", "hardhat:matic": "hardhat --network matic", "coverage": "hardhat coverage", - "test": "npm run test-base-incentives && npm run test-staked-incentives", + "test": "npm run test-pull-rewards-incentives && npm run test-staked-incentives", "test-pull-rewards-incentives": "TS_NODE_TRANSPILE_ONLY=1 hardhat test ./test/__setup.spec.ts ./test/PullRewardsIncentivesController/*.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", From 77b7c65f8a8ef47de06747d29ffb5da627b188e9 Mon Sep 17 00:00:00 2001 From: kartojal Date: Tue, 26 Oct 2021 12:07:17 +0200 Subject: [PATCH 07/12] feat: Added claimRewardsToSelf function to BaseIncentivesController to allow users to claim rewards directly to msg.sender --- .../base/BaseIncentivesController.sol | 9 +++++++++ .../interfaces/IAaveIncentivesController.sol | 17 +++++++++++------ 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/contracts/incentives/base/BaseIncentivesController.sol b/contracts/incentives/base/BaseIncentivesController.sol index e61fd29..94a0a8e 100644 --- a/contracts/incentives/base/BaseIncentivesController.sol +++ b/contracts/incentives/base/BaseIncentivesController.sol @@ -120,6 +120,15 @@ abstract contract BaseIncentivesController is return _claimRewards(assets, amount, msg.sender, user, to); } + /// @inheritdoc IAaveIncentivesController + function claimRewardsToSelf(address[] calldata assets, uint256 amount) + external + override + returns (uint256) + { + return _claimRewards(assets, amount, msg.sender, msg.sender, msg.sender); + } + /// @inheritdoc IAaveIncentivesController function setClaimer(address user, address caller) external override onlyEmissionManager { _authorizedClaimers[user] = caller; diff --git a/contracts/interfaces/IAaveIncentivesController.sol b/contracts/interfaces/IAaveIncentivesController.sol index c81232f..48813d6 100644 --- a/contracts/interfaces/IAaveIncentivesController.sol +++ b/contracts/interfaces/IAaveIncentivesController.sol @@ -6,9 +6,8 @@ pragma experimental ABIEncoderV2; import {IAaveDistributionManager} from '../interfaces/IAaveDistributionManager.sol'; interface IAaveIncentivesController is IAaveDistributionManager { - event RewardsAccrued(address indexed user, uint256 amount); - + event RewardsClaimed( address indexed user, address indexed to, @@ -40,7 +39,6 @@ interface IAaveIncentivesController is IAaveDistributionManager { function configureAssets(address[] calldata assets, uint256[] calldata emissionsPerSecond) external; - /** * @dev Called by the corresponding asset on any update that affects the rewards distribution * @param asset The address of the user @@ -64,7 +62,7 @@ interface IAaveIncentivesController is IAaveDistributionManager { returns (uint256); /** - * @dev Claims reward for an user, on all the assets of the lending pool, accumulating the pending rewards + * @dev Claims reward for an user to the desired address, on all the assets of the lending pool, accumulating the pending rewards * @param amount Amount of rewards to claim * @param to Address that will be receiving the rewards * @return Rewards claimed @@ -90,6 +88,13 @@ interface IAaveIncentivesController is IAaveDistributionManager { address to ) external returns (uint256); + /** + * @dev Claims reward for msg.sender, on all the assets of the lending pool, accumulating the pending rewards + * @param amount Amount of rewards to claim + * @return Rewards claimed + **/ + function claimRewardsToSelf(address[] calldata assets, uint256 amount) external returns (uint256); + /** * @dev returns the unclaimed rewards of the user * @param user the address of the user @@ -98,7 +103,7 @@ interface IAaveIncentivesController is IAaveDistributionManager { function getUserUnclaimedRewards(address user) external view returns (uint256); /** - * @dev for backward compatibility with previous implementation of the Incentives controller - */ + * @dev for backward compatibility with previous implementation of the Incentives controller + */ function REWARD_TOKEN() external view returns (address); } From 93950336d0e8877a21b023edc5807a8268fd8f86 Mon Sep 17 00:00:00 2001 From: kartojal Date: Tue, 26 Oct 2021 12:52:52 +0200 Subject: [PATCH 08/12] tests: added test case for claimRewardsToSelf --- .../claim-on-behalf.spec.ts | 1 - .../claim-rewards-to-self.spec.ts | 231 ++++++++++++++++++ .../claim-rewards-to-self.spec.ts | 223 +++++++++++++++++ 3 files changed, 454 insertions(+), 1 deletion(-) create mode 100644 test/PullRewardsIncentivesController/claim-rewards-to-self.spec.ts create mode 100644 test/StakedIncentivesController/claim-rewards-to-self.spec.ts diff --git a/test/PullRewardsIncentivesController/claim-on-behalf.spec.ts b/test/PullRewardsIncentivesController/claim-on-behalf.spec.ts index 7212314..06f73d9 100644 --- a/test/PullRewardsIncentivesController/claim-on-behalf.spec.ts +++ b/test/PullRewardsIncentivesController/claim-on-behalf.spec.ts @@ -81,7 +81,6 @@ makeSuite('PullRewardsIncentivesController - Claim rewards on behalf', (testEnv: .to.emit(pullRewardsIncentivesController, 'RewardsClaimed') .withArgs(userWithRewards.address, thirdClaimer.address, thirdClaimer.address, '99999'); const afterStkBalance = await aaveToken.balanceOf(thirdClaimer.address); - console.log('adt', afterStkBalance.toString()); expect(afterStkBalance).to.be.gt(priorBalance); }); diff --git a/test/PullRewardsIncentivesController/claim-rewards-to-self.spec.ts b/test/PullRewardsIncentivesController/claim-rewards-to-self.spec.ts new file mode 100644 index 0000000..3049102 --- /dev/null +++ b/test/PullRewardsIncentivesController/claim-rewards-to-self.spec.ts @@ -0,0 +1,231 @@ +import { MAX_UINT_AMOUNT, RANDOM_ADDRESSES } from '../../helpers/constants'; + +const { expect } = require('chai'); + +import { makeSuite } from '../helpers/make-suite'; +import { BigNumber } from 'ethers'; +import { waitForTx, increaseTime } from '../../helpers/misc-utils'; +import { comparatorEngine, eventChecker } from '../helpers/comparator-engine'; +import { getUserIndex } from '../DistributionManager/data-helpers/asset-user-data'; +import { assetDataComparator, getAssetsData } from '../DistributionManager/data-helpers/asset-data'; +import { getBlockTimestamp } from '../../helpers/contracts-helpers'; +import { getRewards } from '../DistributionManager/data-helpers/base-math'; +import { fail } from 'assert'; + +type ScenarioAction = { + caseName: string; + emissionPerSecond?: string; + amountToClaim: string; +}; + +const getRewardsBalanceScenarios: ScenarioAction[] = [ + { + caseName: 'Accrued rewards are 0, claim 0', + emissionPerSecond: '0', + amountToClaim: '0', + }, + { + caseName: 'Accrued rewards are 0, claim not 0', + emissionPerSecond: '0', + amountToClaim: '100', + }, + { + caseName: 'Accrued rewards are not 0', + emissionPerSecond: '2432424', + amountToClaim: '10', + }, + { + caseName: 'Should allow -1', + emissionPerSecond: '2432424', + amountToClaim: MAX_UINT_AMOUNT, + }, + { + caseName: 'Should withdraw everything if amountToClaim more then rewards balance', + emissionPerSecond: '100', + amountToClaim: '1034', + }, +]; + +makeSuite('pullRewardsIncentivesController claimRewardsToSelf tests', (testEnv) => { + for (const { + caseName, + amountToClaim: _amountToClaim, + emissionPerSecond, + } of getRewardsBalanceScenarios) { + let amountToClaim = _amountToClaim; + it(caseName, async () => { + await increaseTime(100); + const { pullRewardsIncentivesController, aaveToken, aDaiBaseMock } = testEnv; + + const distributionEndTimestamp = await pullRewardsIncentivesController.getDistributionEnd(); + const userAddress = await pullRewardsIncentivesController.signer.getAddress(); + + const underlyingAsset = aDaiBaseMock.address; + const stakedByUser = 22 * caseName.length; + const totalStaked = 33 * caseName.length; + + // update emissionPerSecond in advance to not affect user calculations + if (emissionPerSecond) { + await pullRewardsIncentivesController.configureAssets( + [underlyingAsset], + [emissionPerSecond] + ); + } + + const destinationAddress = userAddress; + + const destinationAddressBalanceBefore = await aaveToken.balanceOf(destinationAddress); + await aDaiBaseMock.setUserBalanceAndSupply(stakedByUser, totalStaked); + await aDaiBaseMock.handleActionOnAic(userAddress, totalStaked, stakedByUser); + + const unclaimedRewardsBefore = await pullRewardsIncentivesController.getRewardsBalance( + [underlyingAsset], + userAddress + ); + + const userIndexBefore = await getUserIndex( + pullRewardsIncentivesController, + userAddress, + underlyingAsset + ); + const assetDataBefore = ( + await getAssetsData(pullRewardsIncentivesController, [underlyingAsset]) + )[0]; + + const claimRewardsReceipt = await waitForTx( + await pullRewardsIncentivesController.claimRewardsToSelf([underlyingAsset], amountToClaim) + ); + const eventsEmitted = claimRewardsReceipt.events || []; + + const actionBlockTimestamp = await getBlockTimestamp(claimRewardsReceipt.blockNumber); + + const userIndexAfter = await getUserIndex( + pullRewardsIncentivesController, + userAddress, + underlyingAsset + ); + const assetDataAfter = ( + await getAssetsData(pullRewardsIncentivesController, [underlyingAsset]) + )[0]; + + const unclaimedRewardsAfter = await pullRewardsIncentivesController.getRewardsBalance( + [underlyingAsset], + userAddress + ); + + const destinationAddressBalanceAfter = await aaveToken.balanceOf(destinationAddress); + + const claimedAmount = destinationAddressBalanceAfter.sub(destinationAddressBalanceBefore); + + const expectedAccruedRewards = getRewards( + stakedByUser, + userIndexAfter, + userIndexBefore + ).toString(); + + await aDaiBaseMock.cleanUserState(); + + if (amountToClaim === '0') { + // state should not change + expect(userIndexBefore.toString()).to.be.equal( + userIndexAfter.toString(), + 'userIndexAfter should not change' + ); + expect(unclaimedRewardsBefore.toString()).to.be.equal( + unclaimedRewardsAfter.toString(), + 'unclaimedRewards should not change' + ); + expect(destinationAddressBalanceBefore.toString()).to.be.equal( + destinationAddressBalanceAfter.toString(), + 'destinationAddressBalance should not change' + ); + await comparatorEngine( + ['emissionPerSecond', 'index', 'lastUpdateTimestamp'], + { underlyingAsset, totalStaked }, + assetDataBefore, + assetDataAfter, + actionBlockTimestamp, + {} + ); + expect(eventsEmitted.length).to.be.equal(0, 'no events should be emitted'); + return; + } + + // ------- Distribution Manager tests START ----- + await assetDataComparator( + { underlyingAsset, totalStaked }, + assetDataBefore, + assetDataAfter, + actionBlockTimestamp, + distributionEndTimestamp.toNumber(), + {} + ); + expect(userIndexAfter.toString()).to.be.equal( + assetDataAfter.index.toString(), + 'user index are not correctly updated' + ); + if (!assetDataAfter.index.eq(assetDataBefore.index)) { + eventChecker(eventsEmitted[0], 'AssetIndexUpdated', [ + assetDataAfter.underlyingAsset, + assetDataAfter.index, + ]); + eventChecker(eventsEmitted[1], 'UserIndexUpdated', [ + userAddress, + assetDataAfter.underlyingAsset, + assetDataAfter.index, + ]); + } + // ------- Distribution Manager tests END ----- + + let unclaimedRewardsCalc = unclaimedRewardsBefore.add(expectedAccruedRewards); + + let expectedClaimedAmount: BigNumber; + if (unclaimedRewardsCalc.lte(amountToClaim)) { + expectedClaimedAmount = unclaimedRewardsCalc; + expect(unclaimedRewardsAfter.toString()).to.be.equal( + '0', + 'unclaimed amount after should go to 0' + ); + } else { + expectedClaimedAmount = BigNumber.from(amountToClaim); + expect(unclaimedRewardsAfter.toString()).to.be.equal( + unclaimedRewardsCalc.sub(amountToClaim).toString(), + 'unclaimed rewards after are wrong' + ); + } + + expect(claimedAmount.toString()).to.be.equal( + expectedClaimedAmount.toString(), + 'claimed amount are wrong' + ); + if (expectedAccruedRewards !== '0') { + const rewardsAccruedEvent = eventsEmitted.find(({ event }) => event === 'RewardsAccrued'); + // Expect event to exist + expect(rewardsAccruedEvent).to.be.ok; + if (rewardsAccruedEvent) { + eventChecker(rewardsAccruedEvent, 'RewardsAccrued', [ + userAddress, + expectedAccruedRewards, + ]); + } else { + fail('missing accrued event'); + } + } + if (expectedClaimedAmount.gt(0)) { + const rewardsClaimedEvent = eventsEmitted.find(({ event }) => event === 'RewardsClaimed'); + // Expect event to exist + expect(rewardsClaimedEvent).to.be.ok; + if (rewardsClaimedEvent) { + eventChecker(rewardsClaimedEvent, 'RewardsClaimed', [ + userAddress, + destinationAddress, + userAddress, + expectedClaimedAmount, + ]); + } else { + fail('missing reward event'); + } + } + }); + } +}); diff --git a/test/StakedIncentivesController/claim-rewards-to-self.spec.ts b/test/StakedIncentivesController/claim-rewards-to-self.spec.ts new file mode 100644 index 0000000..cee3b43 --- /dev/null +++ b/test/StakedIncentivesController/claim-rewards-to-self.spec.ts @@ -0,0 +1,223 @@ +import { MAX_UINT_AMOUNT, RANDOM_ADDRESSES } from '../../helpers/constants'; + +const { expect } = require('chai'); + +import { makeSuite } from '../helpers/make-suite'; +import { BigNumber } from 'ethers'; +import { waitForTx, increaseTime } from '../../helpers/misc-utils'; +import { comparatorEngine, eventChecker } from '../helpers/comparator-engine'; +import { getUserIndex } from '../DistributionManager/data-helpers/asset-user-data'; +import { assetDataComparator, getAssetsData } from '../DistributionManager/data-helpers/asset-data'; +import { getBlockTimestamp } from '../../helpers/contracts-helpers'; +import { getRewards } from '../DistributionManager/data-helpers/base-math'; +import { fail } from 'assert'; + +type ScenarioAction = { + caseName: string; + emissionPerSecond?: string; + amountToClaim: string; +}; + +const getRewardsBalanceScenarios: ScenarioAction[] = [ + { + caseName: 'Accrued rewards are 0, claim 0', + emissionPerSecond: '0', + amountToClaim: '0', + }, + { + caseName: 'Accrued rewards are 0, claim not 0', + emissionPerSecond: '0', + amountToClaim: '100', + }, + { + caseName: 'Accrued rewards are not 0', + emissionPerSecond: '2432424', + amountToClaim: '10', + }, + { + caseName: 'Should allow -1', + emissionPerSecond: '2432424', + amountToClaim: MAX_UINT_AMOUNT, + }, + { + caseName: 'Should withdraw everything if amountToClaim more then rewards balance', + emissionPerSecond: '100', + amountToClaim: '1034', + }, +]; + +makeSuite('AaveIncentivesController claimRewardsToSelf tests', (testEnv) => { + for (const { + caseName, + amountToClaim: _amountToClaim, + emissionPerSecond, + } of getRewardsBalanceScenarios) { + let amountToClaim = _amountToClaim; + it(caseName, async () => { + await increaseTime(100); + const { aaveIncentivesController, stakedAave, aDaiMock } = testEnv; + + const distributionEndTimestamp = await aaveIncentivesController.getDistributionEnd(); + const userAddress = await aaveIncentivesController.signer.getAddress(); + + const underlyingAsset = aDaiMock.address; + const stakedByUser = 22 * caseName.length; + const totalStaked = 33 * caseName.length; + + // update emissionPerSecond in advance to not affect user calculations + if (emissionPerSecond) { + await aaveIncentivesController.configureAssets([underlyingAsset], [emissionPerSecond]); + } + + const destinationAddress = userAddress; + + const destinationAddressBalanceBefore = await stakedAave.balanceOf(destinationAddress); + await aDaiMock.setUserBalanceAndSupply(stakedByUser, totalStaked); + await aDaiMock.handleActionOnAic(userAddress, totalStaked, stakedByUser); + + const unclaimedRewardsBefore = await aaveIncentivesController.getRewardsBalance( + [underlyingAsset], + userAddress + ); + const userIndexBefore = await getUserIndex( + aaveIncentivesController, + userAddress, + underlyingAsset + ); + const assetDataBefore = (await getAssetsData(aaveIncentivesController, [underlyingAsset]))[0]; + + const claimRewardsToSelfReceipt = await waitForTx( + await aaveIncentivesController.claimRewardsToSelf([underlyingAsset], amountToClaim) + ); + const eventsEmitted = claimRewardsToSelfReceipt.events || []; + + const actionBlockTimestamp = await getBlockTimestamp(claimRewardsToSelfReceipt.blockNumber); + + const userIndexAfter = await getUserIndex( + aaveIncentivesController, + userAddress, + underlyingAsset + ); + const assetDataAfter = (await getAssetsData(aaveIncentivesController, [underlyingAsset]))[0]; + + const unclaimedRewardsAfter = await aaveIncentivesController.getRewardsBalance( + [underlyingAsset], + userAddress + ); + + const destinationAddressBalanceAfter = await stakedAave.balanceOf(destinationAddress); + + const claimedAmount = destinationAddressBalanceAfter.sub(destinationAddressBalanceBefore); + + const expectedAccruedRewards = getRewards( + stakedByUser, + userIndexAfter, + userIndexBefore + ).toString(); + + await aDaiMock.cleanUserState(); + + if (amountToClaim === '0') { + // state should not change + expect(userIndexBefore.toString()).to.be.equal( + userIndexAfter.toString(), + 'userIndexAfter should not change' + ); + expect(unclaimedRewardsBefore.toString()).to.be.equal( + unclaimedRewardsAfter.toString(), + 'unclaimedRewards should not change' + ); + expect(destinationAddressBalanceBefore.toString()).to.be.equal( + destinationAddressBalanceAfter.toString(), + 'destinationAddressBalance should not change' + ); + await comparatorEngine( + ['emissionPerSecond', 'index', 'lastUpdateTimestamp'], + { underlyingAsset, totalStaked }, + assetDataBefore, + assetDataAfter, + actionBlockTimestamp, + {} + ); + expect(eventsEmitted.length).to.be.equal(0, 'no events should be emitted'); + return; + } + + // ------- Distribution Manager tests START ----- + await assetDataComparator( + { underlyingAsset, totalStaked }, + assetDataBefore, + assetDataAfter, + actionBlockTimestamp, + distributionEndTimestamp.toNumber(), + {} + ); + expect(userIndexAfter.toString()).to.be.equal( + assetDataAfter.index.toString(), + 'user index are not correctly updated' + ); + if (!assetDataAfter.index.eq(assetDataBefore.index)) { + eventChecker(eventsEmitted[0], 'AssetIndexUpdated', [ + assetDataAfter.underlyingAsset, + assetDataAfter.index, + ]); + eventChecker(eventsEmitted[1], 'UserIndexUpdated', [ + userAddress, + assetDataAfter.underlyingAsset, + assetDataAfter.index, + ]); + } + // ------- Distribution Manager tests END ----- + + let unclaimedRewardsCalc = unclaimedRewardsBefore.add(expectedAccruedRewards); + + let expectedClaimedAmount: BigNumber; + if (unclaimedRewardsCalc.lte(amountToClaim)) { + expectedClaimedAmount = unclaimedRewardsCalc; + expect(unclaimedRewardsAfter.toString()).to.be.equal( + '0', + 'unclaimed amount after should go to 0' + ); + } else { + expectedClaimedAmount = BigNumber.from(amountToClaim); + expect(unclaimedRewardsAfter.toString()).to.be.equal( + unclaimedRewardsCalc.sub(amountToClaim).toString(), + 'unclaimed rewards after are wrong' + ); + } + + expect(claimedAmount.toString()).to.be.equal( + expectedClaimedAmount.toString(), + 'claimed amount are wrong' + ); + if (expectedAccruedRewards !== '0') { + const rewardsAccruedEvent = eventsEmitted.find(({ event }) => event === 'RewardsAccrued'); + // Expect event to exist + expect(rewardsAccruedEvent).to.be.ok; + if (rewardsAccruedEvent) { + eventChecker(rewardsAccruedEvent, 'RewardsAccrued', [ + userAddress, + expectedAccruedRewards, + ]); + } else { + fail('missing accrued event'); + } + } + if (expectedClaimedAmount.gt(0)) { + const rewardsClaimedEvent = eventsEmitted.find(({ event }) => event === 'RewardsClaimed'); + // Expect event to exist + expect(rewardsClaimedEvent).to.be.ok; + if (rewardsClaimedEvent) { + eventChecker(rewardsClaimedEvent, 'RewardsClaimed', [ + userAddress, + destinationAddress, + userAddress, + expectedClaimedAmount, + ]); + } else { + fail('missing reward event'); + } + } + }); + } +}); From 8c0c65780a51964c5f336d51bf673ccbf07f724a Mon Sep 17 00:00:00 2001 From: David K Date: Tue, 16 Nov 2021 09:36:25 +0100 Subject: [PATCH 09/12] feat: perform similar overflow check as DistributionManager Perform similar overflow check at BaseIncentivesController:58 as DistributionManager::_updateAssetStateInternal() --- contracts/incentives/base/BaseIncentivesController.sol | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/contracts/incentives/base/BaseIncentivesController.sol b/contracts/incentives/base/BaseIncentivesController.sol index 94a0a8e..8455a35 100644 --- a/contracts/incentives/base/BaseIncentivesController.sol +++ b/contracts/incentives/base/BaseIncentivesController.sol @@ -55,11 +55,9 @@ abstract contract BaseIncentivesController is new DistributionTypes.AssetConfigInput[](assets.length); for (uint256 i = 0; i < assets.length; i++) { + require(uint104(emissionsPerSecond[i]) == emissionsPerSecond[i], 'Index overflow at emissionsPerSecond'); 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); @@ -204,4 +202,4 @@ abstract contract BaseIncentivesController is * @param amount Amount of rewards to transfer */ function _transferRewards(address to, uint256 amount) internal virtual; -} \ No newline at end of file +} From 13b78bb6b05a7c4e51a44c2b07bbb602badccb76 Mon Sep 17 00:00:00 2001 From: David K Date: Tue, 16 Nov 2021 09:56:17 +0100 Subject: [PATCH 10/12] feat: bypass accrue extra rewards at claim if amount is less than unclaimed rewards --- .../base/BaseIncentivesController.sol | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/contracts/incentives/base/BaseIncentivesController.sol b/contracts/incentives/base/BaseIncentivesController.sol index 8455a35..55096e4 100644 --- a/contracts/incentives/base/BaseIncentivesController.sol +++ b/contracts/incentives/base/BaseIncentivesController.sol @@ -169,18 +169,20 @@ abstract contract BaseIncentivesController is } 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 (amount > unclaimedRewards) { + 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) { From fc0b5fdb02b88f3f6206a3290a367be15cf9360c Mon Sep 17 00:00:00 2001 From: kartojal Date: Tue, 16 Nov 2021 10:14:39 +0100 Subject: [PATCH 11/12] feat: fix tests, remove unused initialize parameter --- contracts/incentives/StakedTokenIncentivesController.sol | 3 +-- .../claim-rewards-to-self.spec.ts | 2 +- test/PullRewardsIncentivesController/claim-rewards.spec.ts | 2 +- test/PullRewardsIncentivesController/misc.spec.ts | 2 +- test/StakedIncentivesController/claim-rewards-to-self.spec.ts | 2 +- test/StakedIncentivesController/claim-rewards.spec.ts | 2 +- test/StakedIncentivesController/initialize.spec.ts | 2 +- test/StakedIncentivesController/misc.spec.ts | 2 +- test/helpers/deploy.ts | 4 +--- 9 files changed, 9 insertions(+), 12 deletions(-) diff --git a/contracts/incentives/StakedTokenIncentivesController.sol b/contracts/incentives/StakedTokenIncentivesController.sol index 7c1e76e..148da4b 100644 --- a/contracts/incentives/StakedTokenIncentivesController.sol +++ b/contracts/incentives/StakedTokenIncentivesController.sol @@ -27,9 +27,8 @@ contract StakedTokenIncentivesController is BaseIncentivesController { /** * @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); } diff --git a/test/PullRewardsIncentivesController/claim-rewards-to-self.spec.ts b/test/PullRewardsIncentivesController/claim-rewards-to-self.spec.ts index 3049102..2b0e213 100644 --- a/test/PullRewardsIncentivesController/claim-rewards-to-self.spec.ts +++ b/test/PullRewardsIncentivesController/claim-rewards-to-self.spec.ts @@ -32,7 +32,7 @@ const getRewardsBalanceScenarios: ScenarioAction[] = [ { caseName: 'Accrued rewards are not 0', emissionPerSecond: '2432424', - amountToClaim: '10', + amountToClaim: MAX_UINT_AMOUNT, }, { caseName: 'Should allow -1', diff --git a/test/PullRewardsIncentivesController/claim-rewards.spec.ts b/test/PullRewardsIncentivesController/claim-rewards.spec.ts index 719e03f..c154084 100644 --- a/test/PullRewardsIncentivesController/claim-rewards.spec.ts +++ b/test/PullRewardsIncentivesController/claim-rewards.spec.ts @@ -34,7 +34,7 @@ const getRewardsBalanceScenarios: ScenarioAction[] = [ { caseName: 'Accrued rewards are not 0', emissionPerSecond: '2432424', - amountToClaim: '10', + amountToClaim: MAX_UINT_AMOUNT, }, { caseName: 'Should allow -1', diff --git a/test/PullRewardsIncentivesController/misc.spec.ts b/test/PullRewardsIncentivesController/misc.spec.ts index b210d2b..505669c 100644 --- a/test/PullRewardsIncentivesController/misc.spec.ts +++ b/test/PullRewardsIncentivesController/misc.spec.ts @@ -54,7 +54,7 @@ makeSuite('pullRewardsIncentivesController misc tests', (testEnv) => { await expect( pullRewardsIncentivesController.configureAssets([aDaiBaseMock.address], [MAX_UINT_AMOUNT]) - ).to.be.revertedWith('INVALID_CONFIGURATION'); + ).to.be.revertedWith('Index overflow at emissionsPerSecond'); }); it('Should REWARD_TOKEN getter returns the stake token address to keep old interface compatibility', async () => { diff --git a/test/StakedIncentivesController/claim-rewards-to-self.spec.ts b/test/StakedIncentivesController/claim-rewards-to-self.spec.ts index cee3b43..41c3275 100644 --- a/test/StakedIncentivesController/claim-rewards-to-self.spec.ts +++ b/test/StakedIncentivesController/claim-rewards-to-self.spec.ts @@ -32,7 +32,7 @@ const getRewardsBalanceScenarios: ScenarioAction[] = [ { caseName: 'Accrued rewards are not 0', emissionPerSecond: '2432424', - amountToClaim: '10', + amountToClaim: MAX_UINT_AMOUNT, }, { caseName: 'Should allow -1', diff --git a/test/StakedIncentivesController/claim-rewards.spec.ts b/test/StakedIncentivesController/claim-rewards.spec.ts index 3bee11c..5d8493e 100644 --- a/test/StakedIncentivesController/claim-rewards.spec.ts +++ b/test/StakedIncentivesController/claim-rewards.spec.ts @@ -34,7 +34,7 @@ const getRewardsBalanceScenarios: ScenarioAction[] = [ { caseName: 'Accrued rewards are not 0', emissionPerSecond: '2432424', - amountToClaim: '10', + amountToClaim: MAX_UINT_AMOUNT, }, { caseName: 'Should allow -1', diff --git a/test/StakedIncentivesController/initialize.spec.ts b/test/StakedIncentivesController/initialize.spec.ts index 6abef9f..894c189 100644 --- a/test/StakedIncentivesController/initialize.spec.ts +++ b/test/StakedIncentivesController/initialize.spec.ts @@ -7,7 +7,7 @@ makeSuite('AaveIncentivesController initialize', (testEnv: TestEnv) => { // TODO: useless or not? it('Tries to call initialize second time, should be reverted', async () => { const { aaveIncentivesController } = testEnv; - await expect(aaveIncentivesController.initialize(ZERO_ADDRESS)).to.be.reverted; + await expect(aaveIncentivesController.initialize()).to.be.reverted; }); it('allowance on aave token should be granted to psm contract for pei', async () => { const { aaveIncentivesController, stakedAave, aaveToken } = testEnv; diff --git a/test/StakedIncentivesController/misc.spec.ts b/test/StakedIncentivesController/misc.spec.ts index 7663cbf..f6f9480 100644 --- a/test/StakedIncentivesController/misc.spec.ts +++ b/test/StakedIncentivesController/misc.spec.ts @@ -52,7 +52,7 @@ makeSuite('AaveIncentivesController misc tests', (testEnv) => { await expect( aaveIncentivesController.configureAssets([aDaiMock.address], [MAX_UINT_AMOUNT]) - ).to.be.revertedWith('INVALID_CONFIGURATION'); + ).to.be.revertedWith('Index overflow at emissionsPerSecond'); }); it('Should REWARD_TOKEN getter returns the stake token address to keep old interface compatibility', async () => { diff --git a/test/helpers/deploy.ts b/test/helpers/deploy.ts index 4e90e39..5b944c5 100644 --- a/test/helpers/deploy.ts +++ b/test/helpers/deploy.ts @@ -53,9 +53,7 @@ export const testDeployIncentivesController = async ( '18', ] ); - const incentivesInit = incentivesImplementation.interface.encodeFunctionData('initialize', [ - ZERO_ADDRESS, - ]); + const incentivesInit = incentivesImplementation.interface.encodeFunctionData('initialize'); await ( await stakeProxy['initialize(address,address,bytes)']( From c02a737eca5ffafbc84dcdee37aa2feca0e2cfaf Mon Sep 17 00:00:00 2001 From: kartojal Date: Tue, 16 Nov 2021 10:35:18 +0100 Subject: [PATCH 12/12] feat: remove large commented block --- test-fork/incentives-skip.spec.ts | 62 ------------------------------- 1 file changed, 62 deletions(-) diff --git a/test-fork/incentives-skip.spec.ts b/test-fork/incentives-skip.spec.ts index 6c3104a..86a0203 100644 --- a/test-fork/incentives-skip.spec.ts +++ b/test-fork/incentives-skip.spec.ts @@ -302,68 +302,6 @@ describe('Enable incentives in target assets', () => { } throw error; } - /* Other way via impersonating gov - - const executor = IExecutorWithTimelockFactory.connect( - AAVE_SHORT_EXECUTOR, - impersonatedGovernance - ); - - // Calldata - const callData = ethers.utils.defaultAbiCoder.encode( - ['address', 'address[6]', 'address[6]'], - [incentivesProxy, aTokensImpl, variableDebtTokensImpl] - ); - const test = await DRE.ethers.provider._getBlock(await latestBlock()); - - const { timestamp } = await DRE.ethers.provider.getBlock(await latestBlock()); - const executionTime = BigNumber.from(timestamp.toString()).add('86415'); - try { - // Queue payload - await ( - await executor.queueTransaction( - proposalExecutionPayload, - '0', - 'execute(address,address[6],address[6])', - callData, - executionTime, - true - ) - ).wait(); - - const { timestamp: time2 } = await DRE.ethers.provider.getBlock(await latestBlock()); - console.log('time2', time2, executionTime.toString()); - const neededTime = executionTime.sub(time2.toString()); - // Pass time - await increaseTime(Number(neededTime.add('1000').toString())); - const { timestamp: tim32 } = await DRE.ethers.provider.getBlock(await latestBlock()); - console.log('current', tim32, executionTime.toString()); - expect(tim32).to.be.gte(Number(executionTime.toString()), 'chain.timestamp below execution'); - // Execute payload - await ( - await executor.executeTransaction( - proposalExecutionPayload, - '0', - 'execute(address,address[6],address[6])', - callData, - executionTime, - true, - { gasLimit: 3000000 } - ) - ).wait(); - } catch (error) { - if (DRE.network.name.includes('tenderly')) { - const transactionLink = `https://dashboard.tenderly.co/${DRE.config.tenderly.username}/${ - DRE.config.tenderly.project - }/fork/${DRE.tenderly.network().getFork()}/simulation/${DRE.tenderly.network().getHead()}`; - console.error( - '[TENDERLY] Transaction Reverted. Check TX simulation error at:', - transactionLink - ); - } - throw error; - } - */ }); it('Check emission rate', async () => {