diff --git a/contracts/incentives-v2/DistributionManagerV2.sol b/contracts/incentives-v2/DistributionManagerV2.sol
index 8cc2d493..672796b0 100644
--- a/contracts/incentives-v2/DistributionManagerV2.sol
+++ b/contracts/incentives-v2/DistributionManagerV2.sol
@@ -2,6 +2,7 @@ pragma solidity 0.8.10;
import {IAaveDistributionManagerV2} from './interfaces/IAaveDistributionManagerV2.sol';
import {DistributionTypesV2} from './libraries/DistributionTypesV2.sol';
+import {IERC20Detailed} from '@aave/core-v3/contracts/dependencies/openzeppelin/contracts/IERC20Detailed.sol';
/**
* @title DistributionManagerV2
@@ -20,13 +21,12 @@ abstract contract DistributionManagerV2 is IAaveDistributionManagerV2 {
struct AssetData {
mapping(address => RewardData) rewards;
address[] availableRewards;
+ uint8 decimals;
}
// manager of incentives
address public immutable EMISSION_MANAGER;
- uint8 public constant PRECISION = 18;
-
// asset => AssetData
mapping(address => AssetData) internal _assets;
@@ -148,6 +148,8 @@ abstract contract DistributionManagerV2 is IAaveDistributionManagerV2 {
**/
function _configureAssets(DistributionTypesV2.RewardsConfigInput[] memory rewardsInput) internal {
for (uint256 i = 0; i < rewardsInput.length; i++) {
+ _assets[rewardsInput[i].asset].decimals = IERC20Detailed(rewardsInput[i].asset).decimals();
+
RewardData storage rewardConfig = _assets[rewardsInput[i].asset].rewards[
rewardsInput[i].reward
];
@@ -168,7 +170,8 @@ abstract contract DistributionManagerV2 is IAaveDistributionManagerV2 {
rewardsInput[i].asset,
rewardsInput[i].reward,
rewardConfig,
- rewardsInput[i].totalSupply
+ rewardsInput[i].totalSupply,
+ _assets[rewardsInput[i].asset].decimals
);
// Configure emission and distribution end of the reward per asset
@@ -189,14 +192,16 @@ abstract contract DistributionManagerV2 is IAaveDistributionManagerV2 {
* @param asset The address of the asset being updated
* @param reward The address of the reward being updated
* @param rewardConfig Storage pointer to the distribution's reward config
- * @param totalSupply Current total of staked _assets for this distribution
+ * @param totalSupply Current total of underlying assets for this distribution
+ * @param decimals The decimals of the underlying asset
* @return The new distribution index
**/
function _updateAssetStateInternal(
address asset,
address reward,
RewardData storage rewardConfig,
- uint256 totalSupply
+ uint256 totalSupply,
+ uint8 decimals
) internal returns (uint256) {
uint256 oldIndex = rewardConfig.index;
@@ -209,7 +214,8 @@ abstract contract DistributionManagerV2 is IAaveDistributionManagerV2 {
rewardConfig.emissionPerSecond,
rewardConfig.lastUpdateTimestamp,
rewardConfig.distributionEnd,
- totalSupply
+ totalSupply,
+ decimals
);
if (newIndex != oldIndex) {
@@ -245,11 +251,17 @@ abstract contract DistributionManagerV2 is IAaveDistributionManagerV2 {
uint256 userIndex = rewardData.usersIndex[user];
uint256 accruedRewards = 0;
- uint256 newIndex = _updateAssetStateInternal(asset, reward, rewardData, totalSupply);
+ uint256 newIndex = _updateAssetStateInternal(
+ asset,
+ reward,
+ rewardData,
+ totalSupply,
+ _assets[asset].decimals
+ );
if (userIndex != newIndex) {
if (userBalance != 0) {
- accruedRewards = _getRewards(userBalance, newIndex, userIndex);
+ accruedRewards = _getRewards(userBalance, newIndex, userIndex, _assets[asset].decimals);
}
rewardData.usersIndex[user] = newIndex;
@@ -259,6 +271,13 @@ abstract contract DistributionManagerV2 is IAaveDistributionManagerV2 {
return accruedRewards;
}
+ /**
+ * @dev Iterates and updates all rewards of an asset that belongs to an user
+ * @param asset The address of the reference asset of the distribution
+ * @param user The user address
+ * @param userBalance The current user asset balance
+ * @param totalSupply Total supply of the asset
+ **/
function _updateUserRewardsPerAssetInternal(
address asset,
address user,
@@ -370,31 +389,34 @@ abstract contract DistributionManagerV2 is IAaveDistributionManagerV2 {
DistributionTypesV2.UserAssetStatsInput memory stake
) internal view returns (uint256) {
RewardData storage rewardData = _assets[stake.underlyingAsset].rewards[reward];
-
+ uint8 assetDecimals = _assets[stake.underlyingAsset].decimals;
uint256 assetIndex = _getAssetIndex(
rewardData.index,
rewardData.emissionPerSecond,
rewardData.lastUpdateTimestamp,
rewardData.distributionEnd,
- stake.totalSupply
+ stake.totalSupply,
+ assetDecimals
);
- return _getRewards(stake.userBalance, assetIndex, rewardData.usersIndex[user]);
+ return _getRewards(stake.userBalance, assetIndex, rewardData.usersIndex[user], assetDecimals);
}
/**
* @dev Internal function for the calculation of user's rewards on a distribution
- * @param principalUserBalance Amount staked by the user on a distribution
+ * @param principalUserBalance Balance of the user asset on a distribution
* @param reserveIndex Current index of the distribution
* @param userIndex Index stored for the user, representation his staking moment
+ * @param decimals The decimals of the underlying asset
* @return The rewards
**/
function _getRewards(
uint256 principalUserBalance,
uint256 reserveIndex,
- uint256 userIndex
+ uint256 userIndex,
+ uint8 decimals
) internal pure returns (uint256) {
- return (principalUserBalance * (reserveIndex - userIndex)) / 10**uint256(PRECISION);
+ return (principalUserBalance * (reserveIndex - userIndex)) / 10**decimals;
}
/**
@@ -403,6 +425,7 @@ abstract contract DistributionManagerV2 is IAaveDistributionManagerV2 {
* @param emissionPerSecond Representing the total rewards distributed per second per asset unit, on the distribution
* @param lastUpdateTimestamp Last moment this distribution was updated
* @param totalBalance of tokens considered for the distribution
+ * @param decimals The decimals of the underlying asset
* @return The new index.
**/
function _getAssetIndex(
@@ -410,7 +433,8 @@ abstract contract DistributionManagerV2 is IAaveDistributionManagerV2 {
uint256 emissionPerSecond,
uint128 lastUpdateTimestamp,
uint256 distributionEnd,
- uint256 totalBalance
+ uint256 totalBalance,
+ uint8 decimals
) internal view returns (uint256) {
if (
emissionPerSecond == 0 ||
@@ -425,7 +449,7 @@ abstract contract DistributionManagerV2 is IAaveDistributionManagerV2 {
? distributionEnd
: block.timestamp;
uint256 timeDelta = currentTimestamp - lastUpdateTimestamp;
- return (emissionPerSecond * timeDelta * (10**uint256(PRECISION))) / totalBalance + currentIndex;
+ return (emissionPerSecond * timeDelta * (10**decimals)) / totalBalance + currentIndex;
}
/**
@@ -439,4 +463,9 @@ abstract contract DistributionManagerV2 is IAaveDistributionManagerV2 {
view
virtual
returns (DistributionTypesV2.UserAssetStatsInput[] memory userState);
+
+ /// @inheritdoc IAaveDistributionManagerV2
+ function getAssetDecimals(address asset) external view returns (uint8) {
+ return _assets[asset].decimals;
+ }
}
diff --git a/contracts/incentives-v2/interfaces/IAaveDistributionManagerV2.sol b/contracts/incentives-v2/interfaces/IAaveDistributionManagerV2.sol
index d4536daf..f54d9e62 100644
--- a/contracts/incentives-v2/interfaces/IAaveDistributionManagerV2.sol
+++ b/contracts/incentives-v2/interfaces/IAaveDistributionManagerV2.sol
@@ -118,8 +118,9 @@ interface IAaveDistributionManagerV2 {
returns (address[] memory, uint256[] memory);
/**
- * @dev Returns the precision to calculate the distribution delta
- * @return The precision of the calculation
+ * @dev Returns the decimals of an asset to calculate the distribution delta
+ * @param asset The address to retrieve decimals saved at storage
+ * @return The decimals of an underlying asset
*/
- function PRECISION() external view returns (uint8);
+ function getAssetDecimals(address asset) external view returns (uint8);
}
diff --git a/contracts/misc/UiIncentiveDataProviderV3.sol b/contracts/misc/UiIncentiveDataProviderV3.sol
index dfb2f4b8..4956e229 100644
--- a/contracts/misc/UiIncentiveDataProviderV3.sol
+++ b/contracts/misc/UiIncentiveDataProviderV3.sol
@@ -2,18 +2,19 @@
pragma solidity 0.8.10;
import {IPoolAddressesProvider} from '@aave/core-v3/contracts/interfaces/IPoolAddressesProvider.sol';
-import {IAaveIncentivesController} from '@aave/core-v3/contracts/interfaces/IAaveIncentivesController.sol';
-import {IUiIncentiveDataProvider} from './interfaces/IUiIncentiveDataProvider.sol';
+import {IAaveIncentivesControllerV2} from '../incentives-v2/interfaces/IAaveIncentivesControllerV2.sol';
+import {IUiIncentiveDataProviderV3} from './interfaces/IUiIncentiveDataProviderV3.sol';
import {IPool} from '@aave/core-v3/contracts/interfaces/IPool.sol';
import {IncentivizedERC20} from '@aave/core-v3/contracts/protocol/tokenization/IncentivizedERC20.sol';
import {UserConfiguration} from '@aave/core-v3/contracts/protocol/libraries/configuration/UserConfiguration.sol';
import {DataTypes} from '@aave/core-v3/contracts/protocol/libraries/types/DataTypes.sol';
import {IERC20Detailed} from '@aave/core-v3/contracts/dependencies/openzeppelin/contracts/IERC20Detailed.sol';
+import {IEACAggregatorProxy} from './interfaces/IEACAggregatorProxy.sol';
-contract UiIncentiveDataProviderV3 is IUiIncentiveDataProvider {
+contract UiIncentiveDataProviderV3 is IUiIncentiveDataProviderV3 {
using UserConfiguration for DataTypes.UserConfigurationMap;
- constructor() public {}
+ constructor() {}
function getFullReservesIncentiveData(IPoolAddressesProvider provider, address user)
external
@@ -42,162 +43,174 @@ contract UiIncentiveDataProviderV3 is IUiIncentiveDataProvider {
address[] memory reserves = lendingPool.getReservesList();
AggregatedReserveIncentiveData[]
memory reservesIncentiveData = new AggregatedReserveIncentiveData[](reserves.length);
-
+ // Iterate through the reserves to get all the information from the (a/s/v) Tokens
for (uint256 i = 0; i < reserves.length; i++) {
AggregatedReserveIncentiveData memory reserveIncentiveData = reservesIncentiveData[i];
reserveIncentiveData.underlyingAsset = reserves[i];
DataTypes.ReserveData memory baseData = lendingPool.getReserveData(reserves[i]);
- try IncentivizedERC20(baseData.aTokenAddress).getIncentivesController() returns (
- IAaveIncentivesController aTokenIncentiveController
- ) {
- if (address(aTokenIncentiveController) != address(0)) {
- address aRewardToken = aTokenIncentiveController.REWARD_TOKEN();
-
- try aTokenIncentiveController.getAssetData(baseData.aTokenAddress) returns (
- uint256 aTokenIncentivesIndex,
- uint256 aEmissionPerSecond,
- uint256 aIncentivesLastUpdateTimestamp
- ) {
- reserveIncentiveData.aIncentiveData = IncentiveData(
- aEmissionPerSecond,
- aIncentivesLastUpdateTimestamp,
- aTokenIncentivesIndex,
- aTokenIncentiveController.DISTRIBUTION_END(),
- baseData.aTokenAddress,
- aRewardToken,
- address(aTokenIncentiveController),
- IERC20Detailed(aRewardToken).decimals(),
- aTokenIncentiveController.PRECISION()
- );
- } catch (
- bytes memory /*lowLevelData*/
- ) {
- (
- uint256 aEmissionPerSecond,
- uint256 aIncentivesLastUpdateTimestamp,
- uint256 aTokenIncentivesIndex
- ) = aTokenIncentiveController.assets(baseData.aTokenAddress);
-
- reserveIncentiveData.aIncentiveData = IncentiveData(
- aEmissionPerSecond,
- aIncentivesLastUpdateTimestamp,
- aTokenIncentivesIndex,
- aTokenIncentiveController.DISTRIBUTION_END(),
- baseData.aTokenAddress,
- aRewardToken,
- address(aTokenIncentiveController),
- IERC20Detailed(aRewardToken).decimals(),
- aTokenIncentiveController.PRECISION()
- );
- }
+ // Get aTokens rewards information
+ // TODO: check that this is deployed correctly on contract and remove casting
+ IAaveIncentivesControllerV2 aTokenIncentiveController = IAaveIncentivesControllerV2(
+ address(IncentivizedERC20(baseData.aTokenAddress).getIncentivesController())
+ );
+ RewardInfo[] memory aRewardsInformation;
+ if (address(aTokenIncentiveController) != address(0)) {
+ address[] memory aTokenRewardAddresses = aTokenIncentiveController.getRewardsByAsset(
+ baseData.aTokenAddress
+ );
+
+ aRewardsInformation = new RewardInfo[](aTokenRewardAddresses.length);
+ for (uint256 j = 0; j < aTokenRewardAddresses.length; ++j) {
+ RewardInfo memory rewardInformation;
+ rewardInformation.rewardTokenAddress = aTokenRewardAddresses[j];
+
+ (
+ rewardInformation.tokenIncentivesIndex,
+ rewardInformation.emissionPerSecond,
+ rewardInformation.incentivesLastUpdateTimestamp,
+ rewardInformation.emissionEndTimestamp
+ ) = aTokenIncentiveController.getRewardsData(
+ baseData.aTokenAddress,
+ rewardInformation.rewardTokenAddress
+ );
+
+ rewardInformation.precision = aTokenIncentiveController.getAssetDecimals(baseData.aTokenAddress);
+ rewardInformation.rewardTokenDecimals = IERC20Detailed(
+ rewardInformation.rewardTokenAddress
+ ).decimals();
+ rewardInformation.rewardTokenSymbol = IERC20Detailed(rewardInformation.rewardTokenAddress)
+ .symbol();
+
+ // Get price of reward token from Chainlink Proxy Oracle
+ rewardInformation.rewardOracleAddress = aTokenIncentiveController.getRewardOracle(
+ rewardInformation.rewardTokenAddress
+ );
+ rewardInformation.priceFeedDecimals = IEACAggregatorProxy(
+ rewardInformation.rewardOracleAddress
+ ).decimals();
+ rewardInformation.rewardPriceFeed = IEACAggregatorProxy(
+ rewardInformation.rewardOracleAddress
+ ).latestAnswer();
+
+ aRewardsInformation[j] = rewardInformation;
}
- } catch (
- bytes memory /*lowLevelData*/
- ) {
- // Will not get here
}
- try IncentivizedERC20(baseData.stableDebtTokenAddress).getIncentivesController() returns (
- IAaveIncentivesController sTokenIncentiveController
- ) {
- if (address(sTokenIncentiveController) != address(0)) {
- address sRewardToken = sTokenIncentiveController.REWARD_TOKEN();
- try sTokenIncentiveController.getAssetData(baseData.stableDebtTokenAddress) returns (
- uint256 sTokenIncentivesIndex,
- uint256 sEmissionPerSecond,
- uint256 sIncentivesLastUpdateTimestamp
- ) {
- reserveIncentiveData.sIncentiveData = IncentiveData(
- sEmissionPerSecond,
- sIncentivesLastUpdateTimestamp,
- sTokenIncentivesIndex,
- sTokenIncentiveController.DISTRIBUTION_END(),
- baseData.stableDebtTokenAddress,
- sRewardToken,
- address(sTokenIncentiveController),
- IERC20Detailed(sRewardToken).decimals(),
- sTokenIncentiveController.PRECISION()
- );
- } catch (
- bytes memory /*lowLevelData*/
- ) {
- (
- uint256 sEmissionPerSecond,
- uint256 sIncentivesLastUpdateTimestamp,
- uint256 sTokenIncentivesIndex
- ) = sTokenIncentiveController.assets(baseData.stableDebtTokenAddress);
-
- reserveIncentiveData.sIncentiveData = IncentiveData(
- sEmissionPerSecond,
- sIncentivesLastUpdateTimestamp,
- sTokenIncentivesIndex,
- sTokenIncentiveController.DISTRIBUTION_END(),
- baseData.stableDebtTokenAddress,
- sRewardToken,
- address(sTokenIncentiveController),
- IERC20Detailed(sRewardToken).decimals(),
- sTokenIncentiveController.PRECISION()
- );
- }
+ reserveIncentiveData.aIncentiveData = IncentiveData(
+ baseData.aTokenAddress,
+ address(aTokenIncentiveController),
+ aRewardsInformation
+ );
+
+ // Get vTokens rewards information
+ IAaveIncentivesControllerV2 vTokenIncentiveController = IAaveIncentivesControllerV2(
+ address(IncentivizedERC20(baseData.variableDebtTokenAddress).getIncentivesController())
+ );
+ address[] memory vTokenRewardAddresses = vTokenIncentiveController.getRewardsByAsset(
+ baseData.variableDebtTokenAddress
+ );
+ RewardInfo[] memory vRewardsInformation;
+
+ if (address(vTokenIncentiveController) != address(0)) {
+ vRewardsInformation = new RewardInfo[](vTokenRewardAddresses.length);
+ for (uint256 j = 0; j < vTokenRewardAddresses.length; ++j) {
+ RewardInfo memory rewardInformation;
+ rewardInformation.rewardTokenAddress = vTokenRewardAddresses[j];
+
+ (
+ rewardInformation.tokenIncentivesIndex,
+ rewardInformation.emissionPerSecond,
+ rewardInformation.incentivesLastUpdateTimestamp,
+ rewardInformation.emissionEndTimestamp
+ ) = vTokenIncentiveController.getRewardsData(
+ baseData.variableDebtTokenAddress,
+ rewardInformation.rewardTokenAddress
+ );
+
+ rewardInformation.precision = vTokenIncentiveController.getAssetDecimals(baseData.variableDebtTokenAddress);
+ rewardInformation.rewardTokenDecimals = IERC20Detailed(
+ rewardInformation.rewardTokenAddress
+ ).decimals();
+ rewardInformation.rewardTokenSymbol = IERC20Detailed(rewardInformation.rewardTokenAddress)
+ .symbol();
+
+ // Get price of reward token from Chainlink Proxy Oracle
+ rewardInformation.rewardOracleAddress = vTokenIncentiveController.getRewardOracle(
+ rewardInformation.rewardTokenAddress
+ );
+ rewardInformation.priceFeedDecimals = IEACAggregatorProxy(
+ rewardInformation.rewardOracleAddress
+ ).decimals();
+ rewardInformation.rewardPriceFeed = IEACAggregatorProxy(
+ rewardInformation.rewardOracleAddress
+ ).latestAnswer();
+
+ vRewardsInformation[j] = rewardInformation;
}
- } catch (
- bytes memory /*lowLevelData*/
- ) {
- // Will not get here
}
- try IncentivizedERC20(baseData.variableDebtTokenAddress).getIncentivesController() returns (
- IAaveIncentivesController vTokenIncentiveController
- ) {
- if (address(vTokenIncentiveController) != address(0)) {
- address vRewardToken = vTokenIncentiveController.REWARD_TOKEN();
-
- try vTokenIncentiveController.getAssetData(baseData.variableDebtTokenAddress) returns (
- uint256 vTokenIncentivesIndex,
- uint256 vEmissionPerSecond,
- uint256 vIncentivesLastUpdateTimestamp
- ) {
- reserveIncentiveData.vIncentiveData = IncentiveData(
- vEmissionPerSecond,
- vIncentivesLastUpdateTimestamp,
- vTokenIncentivesIndex,
- vTokenIncentiveController.DISTRIBUTION_END(),
- baseData.variableDebtTokenAddress,
- vRewardToken,
- address(vTokenIncentiveController),
- IERC20Detailed(vRewardToken).decimals(),
- vTokenIncentiveController.PRECISION()
- );
- } catch (
- bytes memory /*lowLevelData*/
- ) {
- (
- uint256 vEmissionPerSecond,
- uint256 vIncentivesLastUpdateTimestamp,
- uint256 vTokenIncentivesIndex
- ) = vTokenIncentiveController.assets(baseData.variableDebtTokenAddress);
-
- reserveIncentiveData.vIncentiveData = IncentiveData(
- vEmissionPerSecond,
- vIncentivesLastUpdateTimestamp,
- vTokenIncentivesIndex,
- vTokenIncentiveController.DISTRIBUTION_END(),
- baseData.variableDebtTokenAddress,
- vRewardToken,
- address(vTokenIncentiveController),
- IERC20Detailed(vRewardToken).decimals(),
- vTokenIncentiveController.PRECISION()
- );
- }
+ reserveIncentiveData.vIncentiveData = IncentiveData(
+ baseData.variableDebtTokenAddress,
+ address(vTokenIncentiveController),
+ vRewardsInformation
+ );
+
+ // Get sTokens rewards information
+ IAaveIncentivesControllerV2 sTokenIncentiveController = IAaveIncentivesControllerV2(
+ address(IncentivizedERC20(baseData.stableDebtTokenAddress).getIncentivesController())
+ );
+ address[] memory sTokenRewardAddresses = sTokenIncentiveController.getRewardsByAsset(
+ baseData.stableDebtTokenAddress
+ );
+ RewardInfo[] memory sRewardsInformation;
+
+ if (address(sTokenIncentiveController) != address(0)) {
+ sRewardsInformation = new RewardInfo[](sTokenRewardAddresses.length);
+ for (uint256 j = 0; j < sTokenRewardAddresses.length; ++j) {
+ RewardInfo memory rewardInformation;
+ rewardInformation.rewardTokenAddress = sTokenRewardAddresses[j];
+
+ (
+ rewardInformation.tokenIncentivesIndex,
+ rewardInformation.emissionPerSecond,
+ rewardInformation.incentivesLastUpdateTimestamp,
+ rewardInformation.emissionEndTimestamp
+ ) = sTokenIncentiveController.getRewardsData(
+ baseData.stableDebtTokenAddress,
+ rewardInformation.rewardTokenAddress
+ );
+
+ rewardInformation.precision = sTokenIncentiveController.getAssetDecimals(baseData.stableDebtTokenAddress);
+ rewardInformation.rewardTokenDecimals = IERC20Detailed(
+ rewardInformation.rewardTokenAddress
+ ).decimals();
+ rewardInformation.rewardTokenSymbol = IERC20Detailed(rewardInformation.rewardTokenAddress)
+ .symbol();
+
+ // Get price of reward token from Chainlink Proxy Oracle
+ rewardInformation.rewardOracleAddress = sTokenIncentiveController.getRewardOracle(
+ rewardInformation.rewardTokenAddress
+ );
+ rewardInformation.priceFeedDecimals = IEACAggregatorProxy(
+ rewardInformation.rewardOracleAddress
+ ).decimals();
+ rewardInformation.rewardPriceFeed = IEACAggregatorProxy(
+ rewardInformation.rewardOracleAddress
+ ).latestAnswer();
+
+ sRewardsInformation[j] = rewardInformation;
}
- } catch (
- bytes memory /*lowLevelData*/
- ) {
- // Will not get here
}
+
+ reserveIncentiveData.sIncentiveData = IncentiveData(
+ baseData.stableDebtTokenAddress,
+ address(sTokenIncentiveController),
+ sRewardsInformation
+ );
}
+
return (reservesIncentiveData);
}
@@ -228,79 +241,165 @@ contract UiIncentiveDataProviderV3 is IUiIncentiveDataProvider {
// user reserve data
userReservesIncentivesData[i].underlyingAsset = reserves[i];
- IUiIncentiveDataProvider.UserIncentiveData memory aUserIncentiveData;
+ IAaveIncentivesControllerV2 aTokenIncentiveController = IAaveIncentivesControllerV2(
+ address(IncentivizedERC20(baseData.aTokenAddress).getIncentivesController())
+ );
+ if (address(aTokenIncentiveController) != address(0)) {
+ // get all rewards information from the asset
+ address[] memory aTokenRewardAddresses = aTokenIncentiveController.getRewardsByAsset(
+ baseData.aTokenAddress
+ );
+ UserRewardInfo[] memory aUserRewardsInformation = new UserRewardInfo[](
+ aTokenRewardAddresses.length
+ );
+ for (uint256 j = 0; j < aTokenRewardAddresses.length; ++j) {
+ UserRewardInfo memory userRewardInformation;
+ userRewardInformation.rewardTokenAddress = aTokenRewardAddresses[j];
+
+ userRewardInformation.tokenIncentivesUserIndex = aTokenIncentiveController
+ .getUserAssetData(
+ user,
+ baseData.aTokenAddress,
+ userRewardInformation.rewardTokenAddress
+ );
- try IncentivizedERC20(baseData.aTokenAddress).getIncentivesController() returns (
- IAaveIncentivesController aTokenIncentiveController
- ) {
- if (address(aTokenIncentiveController) != address(0)) {
- address aRewardToken = aTokenIncentiveController.REWARD_TOKEN();
- aUserIncentiveData.tokenincentivesUserIndex = aTokenIncentiveController.getUserAssetData(
- user,
- baseData.aTokenAddress
+ userRewardInformation.userUnclaimedRewards = aTokenIncentiveController
+ .getUserUnclaimedRewardsFromStorage(user, userRewardInformation.rewardTokenAddress);
+ userRewardInformation.rewardTokenDecimals = IERC20Detailed(
+ userRewardInformation.rewardTokenAddress
+ ).decimals();
+ userRewardInformation.rewardTokenSymbol = IERC20Detailed(
+ userRewardInformation.rewardTokenAddress
+ ).symbol();
+
+ // Get price of reward token from Chainlink Proxy Oracle
+ userRewardInformation.rewardOracleAddress = aTokenIncentiveController.getRewardOracle(
+ userRewardInformation.rewardTokenAddress
);
- aUserIncentiveData.userUnclaimedRewards = aTokenIncentiveController
- .getUserUnclaimedRewards(user);
- aUserIncentiveData.tokenAddress = baseData.aTokenAddress;
- aUserIncentiveData.rewardTokenAddress = aRewardToken;
- aUserIncentiveData.incentiveControllerAddress = address(aTokenIncentiveController);
- aUserIncentiveData.rewardTokenDecimals = IERC20Detailed(aRewardToken).decimals();
+ userRewardInformation.priceFeedDecimals = IEACAggregatorProxy(
+ userRewardInformation.rewardOracleAddress
+ ).decimals();
+ userRewardInformation.rewardPriceFeed = IEACAggregatorProxy(
+ userRewardInformation.rewardOracleAddress
+ ).latestAnswer();
+
+ aUserRewardsInformation[j] = userRewardInformation;
}
- } catch (
- bytes memory /*lowLevelData*/
- ) {}
-
- userReservesIncentivesData[i].aTokenIncentivesUserData = aUserIncentiveData;
-
- UserIncentiveData memory vUserIncentiveData;
-
- try IncentivizedERC20(baseData.variableDebtTokenAddress).getIncentivesController() returns (
- IAaveIncentivesController vTokenIncentiveController
- ) {
- if (address(vTokenIncentiveController) != address(0)) {
- address vRewardToken = vTokenIncentiveController.REWARD_TOKEN();
- vUserIncentiveData.tokenincentivesUserIndex = vTokenIncentiveController.getUserAssetData(
- user,
- baseData.variableDebtTokenAddress
+
+ userReservesIncentivesData[i].aTokenIncentivesUserData = UserIncentiveData(
+ baseData.aTokenAddress,
+ address(aTokenIncentiveController),
+ aUserRewardsInformation
+ );
+ }
+
+ // variable debt token
+ IAaveIncentivesControllerV2 vTokenIncentiveController = IAaveIncentivesControllerV2(
+ address(IncentivizedERC20(baseData.variableDebtTokenAddress).getIncentivesController())
+ );
+ if (address(vTokenIncentiveController) != address(0)) {
+ // get all rewards information from the asset
+ address[] memory vTokenRewardAddresses = vTokenIncentiveController.getRewardsByAsset(
+ baseData.variableDebtTokenAddress
+ );
+ UserRewardInfo[] memory vUserRewardsInformation = new UserRewardInfo[](
+ vTokenRewardAddresses.length
+ );
+ for (uint256 j = 0; j < vTokenRewardAddresses.length; ++j) {
+ UserRewardInfo memory userRewardInformation;
+ userRewardInformation.rewardTokenAddress = vTokenRewardAddresses[j];
+
+ userRewardInformation.tokenIncentivesUserIndex = vTokenIncentiveController
+ .getUserAssetData(
+ user,
+ baseData.variableDebtTokenAddress,
+ userRewardInformation.rewardTokenAddress
+ );
+
+ userRewardInformation.userUnclaimedRewards = vTokenIncentiveController
+ .getUserUnclaimedRewardsFromStorage(user, userRewardInformation.rewardTokenAddress);
+ userRewardInformation.rewardTokenDecimals = IERC20Detailed(
+ userRewardInformation.rewardTokenAddress
+ ).decimals();
+ userRewardInformation.rewardTokenSymbol = IERC20Detailed(
+ userRewardInformation.rewardTokenAddress
+ ).symbol();
+
+ // Get price of reward token from Chainlink Proxy Oracle
+ userRewardInformation.rewardOracleAddress = vTokenIncentiveController.getRewardOracle(
+ userRewardInformation.rewardTokenAddress
);
- vUserIncentiveData.userUnclaimedRewards = vTokenIncentiveController
- .getUserUnclaimedRewards(user);
- vUserIncentiveData.tokenAddress = baseData.variableDebtTokenAddress;
- vUserIncentiveData.rewardTokenAddress = vRewardToken;
- vUserIncentiveData.incentiveControllerAddress = address(vTokenIncentiveController);
- vUserIncentiveData.rewardTokenDecimals = IERC20Detailed(vRewardToken).decimals();
+ userRewardInformation.priceFeedDecimals = IEACAggregatorProxy(
+ userRewardInformation.rewardOracleAddress
+ ).decimals();
+ userRewardInformation.rewardPriceFeed = IEACAggregatorProxy(
+ userRewardInformation.rewardOracleAddress
+ ).latestAnswer();
+
+ vUserRewardsInformation[j] = userRewardInformation;
}
- } catch (
- bytes memory /*lowLevelData*/
- ) {}
-
- userReservesIncentivesData[i].vTokenIncentivesUserData = vUserIncentiveData;
-
- UserIncentiveData memory sUserIncentiveData;
-
- try IncentivizedERC20(baseData.stableDebtTokenAddress).getIncentivesController() returns (
- IAaveIncentivesController sTokenIncentiveController
- ) {
- if (address(sTokenIncentiveController) != address(0)) {
- address sRewardToken = sTokenIncentiveController.REWARD_TOKEN();
- sUserIncentiveData.tokenincentivesUserIndex = sTokenIncentiveController.getUserAssetData(
- user,
- baseData.stableDebtTokenAddress
+
+ userReservesIncentivesData[i].vTokenIncentivesUserData = UserIncentiveData(
+ baseData.variableDebtTokenAddress,
+ address(aTokenIncentiveController),
+ vUserRewardsInformation
+ );
+ }
+
+ // stable debt toekn
+ IAaveIncentivesControllerV2 sTokenIncentiveController = IAaveIncentivesControllerV2(
+ address(IncentivizedERC20(baseData.stableDebtTokenAddress).getIncentivesController())
+ );
+ if (address(sTokenIncentiveController) != address(0)) {
+ // get all rewards information from the asset
+ address[] memory sTokenRewardAddresses = sTokenIncentiveController.getRewardsByAsset(
+ baseData.stableDebtTokenAddress
+ );
+ UserRewardInfo[] memory sUserRewardsInformation = new UserRewardInfo[](
+ sTokenRewardAddresses.length
+ );
+ for (uint256 j = 0; j < sTokenRewardAddresses.length; ++j) {
+ UserRewardInfo memory userRewardInformation;
+ userRewardInformation.rewardTokenAddress = sTokenRewardAddresses[j];
+
+ userRewardInformation.tokenIncentivesUserIndex = sTokenIncentiveController
+ .getUserAssetData(
+ user,
+ baseData.stableDebtTokenAddress,
+ userRewardInformation.rewardTokenAddress
+ );
+
+ userRewardInformation.userUnclaimedRewards = sTokenIncentiveController
+ .getUserUnclaimedRewardsFromStorage(user, userRewardInformation.rewardTokenAddress);
+ userRewardInformation.rewardTokenDecimals = IERC20Detailed(
+ userRewardInformation.rewardTokenAddress
+ ).decimals();
+ userRewardInformation.rewardTokenSymbol = IERC20Detailed(
+ userRewardInformation.rewardTokenAddress
+ ).symbol();
+
+ // Get price of reward token from Chainlink Proxy Oracle
+ userRewardInformation.rewardOracleAddress = sTokenIncentiveController.getRewardOracle(
+ userRewardInformation.rewardTokenAddress
);
- sUserIncentiveData.userUnclaimedRewards = sTokenIncentiveController
- .getUserUnclaimedRewards(user);
- sUserIncentiveData.tokenAddress = baseData.stableDebtTokenAddress;
- sUserIncentiveData.rewardTokenAddress = sRewardToken;
- sUserIncentiveData.incentiveControllerAddress = address(sTokenIncentiveController);
- sUserIncentiveData.rewardTokenDecimals = IERC20Detailed(sRewardToken).decimals();
+ userRewardInformation.priceFeedDecimals = IEACAggregatorProxy(
+ userRewardInformation.rewardOracleAddress
+ ).decimals();
+ userRewardInformation.rewardPriceFeed = IEACAggregatorProxy(
+ userRewardInformation.rewardOracleAddress
+ ).latestAnswer();
+
+ sUserRewardsInformation[j] = userRewardInformation;
}
- } catch (
- bytes memory /*lowLevelData*/
- ) {}
- userReservesIncentivesData[i].sTokenIncentivesUserData = sUserIncentiveData;
+ userReservesIncentivesData[i].sTokenIncentivesUserData = UserIncentiveData(
+ baseData.stableDebtTokenAddress,
+ address(aTokenIncentiveController),
+ sUserRewardsInformation
+ );
+ }
}
return (userReservesIncentivesData);
}
-}
+}
\ No newline at end of file
diff --git a/contracts/misc/UiPoolDataProvider.sol b/contracts/misc/UiPoolDataProviderV3.sol
similarity index 98%
rename from contracts/misc/UiPoolDataProvider.sol
rename to contracts/misc/UiPoolDataProviderV3.sol
index 59ae64c0..e247739a 100644
--- a/contracts/misc/UiPoolDataProvider.sol
+++ b/contracts/misc/UiPoolDataProviderV3.sol
@@ -3,7 +3,7 @@ pragma solidity 0.8.10;
import {IERC20Detailed} from '@aave/core-v3/contracts/dependencies/openzeppelin/contracts/IERC20Detailed.sol';
import {IPoolAddressesProvider} from '@aave/core-v3/contracts/interfaces/IPoolAddressesProvider.sol';
-import {IUiPoolDataProvider} from './interfaces/IUiPoolDataProvider.sol';
+import {IUiPoolDataProviderV3} from './interfaces/IUiPoolDataProviderV3.sol';
import {IPool} from '@aave/core-v3/contracts/interfaces/IPool.sol';
import {IPriceOracleGetter} from '@aave/core-v3/contracts/interfaces/IPriceOracleGetter.sol';
import {IAToken} from '@aave/core-v3/contracts/interfaces/IAToken.sol';
@@ -20,7 +20,7 @@ import {IEACAggregatorProxy} from './interfaces/IEACAggregatorProxy.sol';
import {IERC20DetailedBytes} from './interfaces/IERC20DetailedBytes.sol';
import {AaveProtocolDataProvider} from '@aave/core-v3/contracts/misc/AaveProtocolDataProvider.sol';
-contract UiPoolDataProvider is IUiPoolDataProvider {
+contract UiPoolDataProviderV3 is IUiPoolDataProviderV3 {
using WadRayMath for uint256;
using ReserveConfiguration for DataTypes.ReserveConfigurationMap;
using UserConfiguration for DataTypes.UserConfigurationMap;
diff --git a/contracts/misc/interfaces/IUiIncentiveDataProvider.sol b/contracts/misc/interfaces/IUiIncentiveDataProviderV3.sol
similarity index 77%
rename from contracts/misc/interfaces/IUiIncentiveDataProvider.sol
rename to contracts/misc/interfaces/IUiIncentiveDataProviderV3.sol
index c0685a17..ec3832f3 100644
--- a/contracts/misc/interfaces/IUiIncentiveDataProvider.sol
+++ b/contracts/misc/interfaces/IUiIncentiveDataProviderV3.sol
@@ -3,7 +3,7 @@ pragma solidity 0.8.10;
import {IPoolAddressesProvider} from '@aave/core-v3/contracts/interfaces/IPoolAddressesProvider.sol';
-interface IUiIncentiveDataProvider {
+interface IUiIncentiveDataProviderV3 {
struct AggregatedReserveIncentiveData {
address underlyingAsset;
IncentiveData aIncentiveData;
@@ -12,15 +12,23 @@ interface IUiIncentiveDataProvider {
}
struct IncentiveData {
+ address tokenAddress;
+ address incentiveControllerAddress;
+ RewardInfo[] rewardsTokenInformation;
+ }
+
+ struct RewardInfo {
+ string rewardTokenSymbol;
+ address rewardTokenAddress;
+ address rewardOracleAddress;
uint256 emissionPerSecond;
uint256 incentivesLastUpdateTimestamp;
uint256 tokenIncentivesIndex;
uint256 emissionEndTimestamp;
- address tokenAddress;
- address rewardTokenAddress;
- address incentiveControllerAddress;
+ int256 rewardPriceFeed;
uint8 rewardTokenDecimals;
uint8 precision;
+ uint8 priceFeedDecimals;
}
struct UserReserveIncentiveData {
@@ -29,14 +37,23 @@ interface IUiIncentiveDataProvider {
UserIncentiveData vTokenIncentivesUserData;
UserIncentiveData sTokenIncentivesUserData;
}
-
+
struct UserIncentiveData {
- uint256 tokenincentivesUserIndex;
- uint256 userUnclaimedRewards;
address tokenAddress;
- address rewardTokenAddress;
address incentiveControllerAddress;
+ UserRewardInfo[] userRewardsInformation;
+ }
+
+ struct UserRewardInfo {
+ string rewardTokenSymbol;
+ address rewardOracleAddress;
+ address rewardTokenAddress;
+ uint256 userUnclaimedRewards;
+ uint256 tokenIncentivesUserIndex;
+ int256 rewardPriceFeed;
+ uint8 priceFeedDecimals;
uint8 rewardTokenDecimals;
+
}
function getReservesIncentivesData(IPoolAddressesProvider provider)
@@ -54,4 +71,4 @@ interface IUiIncentiveDataProvider {
external
view
returns (AggregatedReserveIncentiveData[] memory, UserReserveIncentiveData[] memory);
-}
+}
\ No newline at end of file
diff --git a/contracts/misc/interfaces/IUiPoolDataProvider.sol b/contracts/misc/interfaces/IUiPoolDataProviderV3.sol
similarity index 98%
rename from contracts/misc/interfaces/IUiPoolDataProvider.sol
rename to contracts/misc/interfaces/IUiPoolDataProviderV3.sol
index 0859d967..c123bbfd 100644
--- a/contracts/misc/interfaces/IUiPoolDataProvider.sol
+++ b/contracts/misc/interfaces/IUiPoolDataProviderV3.sol
@@ -4,7 +4,7 @@ pragma solidity 0.8.10;
import {IPoolAddressesProvider} from '@aave/core-v3/contracts/interfaces/IPoolAddressesProvider.sol';
import {DataTypes} from '@aave/core-v3/contracts/protocol/libraries/types/DataTypes.sol';
-interface IUiPoolDataProvider {
+interface IUiPoolDataProviderV3 {
struct AggregatedReserveData {
address underlyingAsset;
string name;
diff --git a/contracts/mocks/ATokenMock.sol b/contracts/mocks/ATokenMock.sol
index 67e28c8a..a2286505 100644
--- a/contracts/mocks/ATokenMock.sol
+++ b/contracts/mocks/ATokenMock.sol
@@ -7,6 +7,7 @@ contract ATokenMock {
IAaveIncentivesControllerV2 public _aic;
uint256 internal _userBalance;
uint256 internal _totalSupply;
+ uint256 internal immutable _decimals;
// hack to be able to test event from EI properly
event RewardsAccrued(address indexed user, uint256 amount);
@@ -16,8 +17,9 @@ contract ATokenMock {
event AssetIndexUpdated(address indexed asset, uint256 index);
event UserIndexUpdated(address indexed user, address indexed asset, uint256 index);
- constructor(IAaveIncentivesControllerV2 aic) {
+ constructor(IAaveIncentivesControllerV2 aic, uint256 decimals) {
_aic = aic;
+ _decimals = decimals;
}
function handleActionOnAic(
@@ -54,4 +56,8 @@ contract ATokenMock {
_userBalance = 0;
_totalSupply = 0;
}
+
+ function decimals() external view returns (uint256) {
+ return _decimals;
+ }
}
diff --git a/package.json b/package.json
index d22c8ae5..bf8f9ee5 100644
--- a/package.json
+++ b/package.json
@@ -19,8 +19,8 @@
"hardhat:matic": "hardhat --network matic",
"compile": "SKIP_LOAD=true hardhat compile",
"console:fork": "MAINNET_FORK=true hardhat console",
- "test": ". ./setup-test-env.sh && TS_NODE_TRANSPILE_ONLY=1 hardhat test test/__setup.spec.ts ./test/weth-gateway.spec.ts ./test/incentives-v2/**/*.spec.ts",
- "test-incentives": ". ./setup-test-env.sh && TS_NODE_TRANSPILE_ONLY=1 hardhat test test/__setup.spec.ts ./test/incentives-v2/**/.spec.ts",
+ "test": ". ./setup-test-env.sh && TS_NODE_TRANSPILE_ONLY=1 hardhat test test/__setup.spec.ts ./test/weth-gateway.spec.ts ./test/incentives-v2/*.spec.ts ./test/incentives-v2/**/*.spec.ts",
+ "test-incentives": ". ./setup-test-env.sh && TS_NODE_TRANSPILE_ONLY=1 hardhat test test/__setup.spec.ts ./test/incentives-v2/*.spec.ts ./test/incentives-v2/**/*.spec.ts",
"test-strategies": ". ./setup-test-env.sh && TS_NODE_TRANSPILE_ONLY=1 hardhat test test/__setup.spec.ts ./test/incentives-v2/strategies/*.spec.ts",
"coverage": ". ./setup-test-env.sh && TS_NODE_TRANSPILE_ONLY=1 COVERAGE=true EMPTY_RUN=true npx hardhat coverage --temp temp-artifacts --testfiles test/emptyrun.coverage.ts && rm -rf coverage.json coverage/ && TS_NODE_TRANSPILE_ONLY=1 COVERAGE=true npx hardhat coverage --temp temp-artifacts --testfiles 'test/**/*.spec.ts'",
"clean": "rm -rf artifacts cache",
diff --git a/test/helpers/make-suite.ts b/test/helpers/make-suite.ts
index 9f95b872..2067fc22 100644
--- a/test/helpers/make-suite.ts
+++ b/test/helpers/make-suite.ts
@@ -49,6 +49,7 @@ import {
waitForTx,
MAX_UINT_AMOUNT,
TESTNET_PRICE_AGGR_PREFIX,
+ deployMintableERC20,
} from '@aave/deploy-v3';
import { deployATokenMock } from '../incentives-v2/helpers/deploy';
import { parseEther } from 'ethers/lib/utils';
@@ -90,6 +91,7 @@ export interface TestEnv {
aDaiMockV2: ATokenMock;
aWethMockV2: ATokenMock;
aAaveMockV2: ATokenMock;
+ aEursMockV2: ATokenMock;
pullRewardsStrategy: PullRewardsTransferStrategy;
stakedTokenStrategy: StakedTokenTransferStrategy;
rewardToken: MintableERC20;
@@ -134,6 +136,7 @@ const testEnv: TestEnv = {
aDaiMockV2: {} as ATokenMock,
aWethMockV2: {} as ATokenMock,
aAaveMockV2: {} as ATokenMock,
+ aEursMockV2: {} as ATokenMock,
pullRewardsStrategy: {} as PullRewardsTransferStrategy,
stakedTokenStrategy: {} as StakedTokenTransferStrategy,
rewardToken: {} as MintableERC20,
@@ -215,7 +218,20 @@ export async function initializeMakeSuite() {
testEnv.weth = await getWETHMocked(wethAddress);
testEnv.wethGateway = await getWETHGateway();
- // incentives-v2 setup
+ // Added extra reward token
+ await hre.deployments.deploy(`EXTRA${TESTNET_REWARD_TOKEN_PREFIX}`, {
+ from: await _deployer.getAddress(),
+ contract: 'MintableERC20',
+ args: ['EXTRA', 'EXTRA', 18],
+ log: true,
+ });
+ await hre.deployments.deploy(`EXTRA${TESTNET_PRICE_AGGR_PREFIX}`, {
+ args: [parseEther('2')],
+ from: await _deployer.getAddress(),
+ log: true,
+ contract: 'MockAggregator',
+ });
+ // Setup Incentives V2 environment
const rewardTokens = await getSubTokensByPrefix(TESTNET_REWARD_TOKEN_PREFIX);
const incentivesControllerV2 = ((await getIncentivesV2()) as any) as IncentivesControllerV2;
testEnv.incentivesControllerV2 = incentivesControllerV2;
@@ -225,6 +241,7 @@ export async function initializeMakeSuite() {
testEnv.aDaiMockV2 = await deployATokenMock(incentivesControllerV2.address, 'aDaiV2');
testEnv.aWethMockV2 = await deployATokenMock(incentivesControllerV2.address, 'aWethV2');
testEnv.aAaveMockV2 = await deployATokenMock(incentivesControllerV2.address, 'aAaveV2');
+ testEnv.aEursMockV2 = await deployATokenMock(incentivesControllerV2.address, 'aEursV2', 2);
testEnv.pullRewardsStrategy = (await getPullRewardsStrategy()) as PullRewardsTransferStrategy;
testEnv.stakedTokenStrategy = ((await getStakedRewardsStrategy()) as any) as StakedTokenTransferStrategy;
testEnv.rewardToken = await getMintableERC20(rewardTokens[0].artifact.address);
@@ -246,18 +263,18 @@ export async function initializeMakeSuite() {
await waitForTx(
await testEnv.aaveToken
.connect(rewardsVault.signer)
- ['mint(address,uint256)'](rewardsVault.address, parseEther('3000000'))
+ ['mint(address,uint256)'](rewardsVault.address, parseEther('60000000000'))
);
await waitForTx(
await testEnv.rewardToken
.connect(rewardsVault.signer)
- ['mint(address,uint256)'](rewardsVault.address, parseEther('2000000'))
+ ['mint(address,uint256)'](rewardsVault.address, parseEther('200000000'))
);
await waitForTx(
await testEnv.aaveToken
.connect(rewardsVault.signer)
- .transfer(testEnv.stakedTokenStrategy.address, parseEther('1000000'))
+ .transfer(testEnv.stakedTokenStrategy.address, parseEther('30000000000'))
);
}
diff --git a/test/incentives-v2/claim-all-rewards-to-self.spec.ts b/test/incentives-v2/claim-all-rewards-to-self.spec.ts
index 0ab4933a..c7bbcb05 100644
--- a/test/incentives-v2/claim-all-rewards-to-self.spec.ts
+++ b/test/incentives-v2/claim-all-rewards-to-self.spec.ts
@@ -4,9 +4,9 @@ import { BigNumber } from 'ethers';
import {
waitForTx,
getBlockTimestamp,
- increaseTime,
MAX_UINT_AMOUNT,
ERC20__factory,
+ advanceTimeAndBlock,
} from '@aave/deploy-v3';
import {
assetDataComparator,
@@ -17,6 +17,7 @@ import { getUserIndex } from './helpers/DistributionManagerV2/data-helpers/asset
import { parseEther } from '@ethersproject/units';
import Bluebird from 'bluebird';
import { ATokenMock__factory } from '../../types';
+import hre from 'hardhat';
type ScenarioAction = {
caseName: string;
@@ -73,10 +74,10 @@ const getRewardsBalanceScenarios: ScenarioAction[] = [
},
];
-makeSuite('Incentives Controller V2 claimRewards tests', (testEnv) => {
+makeSuite('Incentives Controller V2 claimRewards to self tests', (testEnv) => {
before(async () => {
const { rewardTokens, rewardsVault, pullRewardsStrategy } = testEnv;
- const rewards = rewardTokens.slice(0, 4);
+ const rewards = rewardTokens.slice(0, 3);
await Bluebird.each(rewards, async (reward, index) => {
await reward.connect(rewardsVault.signer)['mint(uint256)'](parseEther('1000000000'));
@@ -87,14 +88,16 @@ makeSuite('Incentives Controller V2 claimRewards tests', (testEnv) => {
});
for (const { caseName, emissionsPerSecond, zeroBalance } of getRewardsBalanceScenarios) {
it(caseName, async () => {
- await increaseTime(100);
+ const { timestamp } = await hre.ethers.provider.getBlock('latest');
+ const timePerTest = 31536000;
+ const distributionEnd = timestamp + timePerTest * getRewardsBalanceScenarios.length;
+ await advanceTimeAndBlock(timePerTest);
const {
incentivesControllerV2,
aDaiMockV2,
aAaveMockV2,
aWethMockV2,
pullRewardsStrategy,
- distributionEnd,
rewardTokens,
deployer,
} = testEnv;
@@ -110,7 +113,7 @@ makeSuite('Incentives Controller V2 claimRewards tests', (testEnv) => {
const totalSupply = assets.map((_, index) =>
BigNumber.from(parseEther('100000')).mul(caseName.length).mul(index)
);
- const rewards = rewardTokens.slice(0, 4).map(({ address }) => address);
+ const rewards = rewardTokens.slice(0, 3).map(({ address }) => address);
await Bluebird.each(assets, async (asset, index) => {
await ATokenMock__factory.connect(asset, deployer.signer).setUserBalanceAndSupply(
diff --git a/test/incentives-v2/claim-all-rewards.spec.ts b/test/incentives-v2/claim-all-rewards.spec.ts
index 016e3914..37b10968 100644
--- a/test/incentives-v2/claim-all-rewards.spec.ts
+++ b/test/incentives-v2/claim-all-rewards.spec.ts
@@ -7,6 +7,7 @@ import {
increaseTime,
MAX_UINT_AMOUNT,
ERC20__factory,
+ advanceTimeAndBlock,
} from '@aave/deploy-v3';
import { RANDOM_ADDRESSES } from '../helpers/constants';
import {
@@ -15,9 +16,10 @@ import {
getRewardsData,
} from './helpers/DistributionManagerV2/data-helpers/asset-data';
import { getUserIndex } from './helpers/DistributionManagerV2/data-helpers/asset-user-data';
-import { parseEther } from '@ethersproject/units';
+import { parseEther, parseUnits } from '@ethersproject/units';
import Bluebird from 'bluebird';
import { ATokenMock__factory } from '../../types';
+import hre from 'hardhat';
type ScenarioAction = {
caseName: string;
@@ -30,55 +32,55 @@ type ScenarioAction = {
const getRewardsBalanceScenarios: ScenarioAction[] = [
{
caseName: 'Accrued rewards are 0',
- emissionsPerSecond: ['0', '0', '0'],
- zeroBalance: [false, false, false],
+ emissionsPerSecond: ['0', '0', '0', '0'],
+ zeroBalance: [false, false, false, false],
},
{
caseName: 'Accrued rewards are not 0',
- emissionsPerSecond: ['2432424', '4432424', '1234'],
- zeroBalance: [false, false, false],
+ emissionsPerSecond: ['2432424', '4432424', '1234', '124231210000'],
+ zeroBalance: [false, false, false, false],
},
{
caseName: 'Some rewards are not 0',
- emissionsPerSecond: ['2432424', '0', '1234'],
- zeroBalance: [false, false, false],
+ emissionsPerSecond: ['2432424', '0', '1234', '1242312100000'],
+ zeroBalance: [false, false, false, false],
},
{
caseName: 'Some rewards are not 0',
- emissionsPerSecond: ['0', '2432424', '1234'],
- zeroBalance: [false, false, false],
+ emissionsPerSecond: ['0', '2432424', '1234', '1242312100000'],
+ zeroBalance: [false, false, false, false],
},
{
caseName: 'Some user balances are not 0',
- emissionsPerSecond: ['13412', '2432424', '0'],
- zeroBalance: [false, true, true],
+ emissionsPerSecond: ['13412', '2432424', '0', '12423121000000'],
+ zeroBalance: [false, true, true, true],
},
{
caseName: 'Some user balances are not 0',
- emissionsPerSecond: ['13412', '2432424', '0'],
- zeroBalance: [true, false, true],
+ emissionsPerSecond: ['13412', '2432424', '0', '12423121000000'],
+ zeroBalance: [true, false, true, true],
},
{
caseName: 'Should withdraw to another user',
- emissionsPerSecond: ['2314', '3331', '421512'],
+ emissionsPerSecond: ['2314', '3331', '421512', '42152'],
to: RANDOM_ADDRESSES[5],
- zeroBalance: [false, false, false],
+ zeroBalance: [false, false, false, false],
},
{
caseName: 'Should withdraw to another user',
- emissionsPerSecond: ['2314', '3331', '421512'],
+ emissionsPerSecond: ['2314', '3331', '421512', '2123'],
to: RANDOM_ADDRESSES[3],
- zeroBalance: [false, false, false],
+ zeroBalance: [false, false, false, false],
},
{
caseName: 'Should not claim due emissions are zero',
- emissionsPerSecond: ['0', '0', '0'],
+ emissionsPerSecond: ['0', '0', '0', '0'],
to: RANDOM_ADDRESSES[3],
- zeroBalance: [false, false, false],
+ zeroBalance: [false, false, false, false],
},
];
-makeSuite('Incentives Controller V2 claimRewards tests', (testEnv) => {
+makeSuite('Incentives Controller V2 claimAllRewards tests', (testEnv) => {
before(async () => {
const { rewardTokens, rewardsVault, pullRewardsStrategy } = testEnv;
const rewards = rewardTokens.slice(0, 4);
@@ -90,30 +92,43 @@ makeSuite('Incentives Controller V2 claimRewards tests', (testEnv) => {
.approve(pullRewardsStrategy.address, MAX_UINT_AMOUNT);
});
});
- for (const { caseName, to, emissionsPerSecond, zeroBalance } of getRewardsBalanceScenarios) {
+ for (const [
+ caseIndex,
+ { caseName, to, emissionsPerSecond, zeroBalance },
+ ] of getRewardsBalanceScenarios.entries()) {
it(caseName, async () => {
- await increaseTime(100);
+ const { timestamp } = await hre.ethers.provider.getBlock('latest');
+ const timePerTest = 31536000;
+ const distributionEnd = timestamp + timePerTest * getRewardsBalanceScenarios.length;
+ await advanceTimeAndBlock(timePerTest);
const {
incentivesControllerV2,
aDaiMockV2,
aAaveMockV2,
aWethMockV2,
+ aEursMockV2,
pullRewardsStrategy,
- distributionEnd,
rewardTokens,
deployer,
} = testEnv;
const userAddress = await incentivesControllerV2.signer.getAddress();
- const assets = [aDaiMockV2, aAaveMockV2, aWethMockV2].map(({ address }) => address);
+ const assets = [aDaiMockV2, aAaveMockV2, aWethMockV2, aEursMockV2].map(
+ ({ address }) => address
+ );
+
const stakedByUser = assets.map((_, index) =>
zeroBalance[index]
? BigNumber.from('0')
- : BigNumber.from(parseEther('20000')).mul(caseName.length).mul(index)
+ : BigNumber.from(parseUnits('10000', index > 2 ? 2 : 18))
+ .mul(index)
+ .mul(caseIndex)
);
const totalSupply = assets.map((_, index) =>
- BigNumber.from(parseEther('100000')).mul(caseName.length).mul(index)
+ BigNumber.from(parseUnits('10000', index > 2 ? 2 : 18))
+ .mul(index)
+ .mul(caseIndex)
);
const rewards = rewardTokens.slice(0, 4).map(({ address }) => address);
@@ -125,7 +140,6 @@ makeSuite('Incentives Controller V2 claimRewards tests', (testEnv) => {
});
// update emissionPerSecond in advance to not affect user calculations
-
await waitForTx(
await incentivesControllerV2.configureAssets(
emissionsPerSecond.map((emissionPerSecond, index) => ({
@@ -203,14 +217,12 @@ makeSuite('Incentives Controller V2 claimRewards tests', (testEnv) => {
const claimedAmounts = await Bluebird.map(destinationAddressBalanceAfter, (balance, index) =>
balance.sub(destinationAddressBalanceBefore[index])
);
-
- await aDaiMockV2.cleanUserState();
-
const expectedAccruedRewards = await Bluebird.map(rewards, (_, index) =>
getRewards(
stakedByUser[index],
userIndexesAfter[index],
- userIndexesBefore[index]
+ userIndexesBefore[index],
+ index > 2 ? 2 : 18
).toString()
);
@@ -221,7 +233,8 @@ makeSuite('Incentives Controller V2 claimRewards tests', (testEnv) => {
assetDataAfter[i],
actionBlockTimestamp,
distributionEnd,
- {}
+ {},
+ i > 2 ? 2 : 18
);
expect(userIndexesAfter[i].toString()).to.be.equal(
diff --git a/test/incentives-v2/claim-rewards-low-decimals.spec.ts b/test/incentives-v2/claim-rewards-low-decimals.spec.ts
new file mode 100644
index 00000000..9676125c
--- /dev/null
+++ b/test/incentives-v2/claim-rewards-low-decimals.spec.ts
@@ -0,0 +1,267 @@
+const { expect } = require('chai');
+import { makeSuite } from '../helpers/make-suite';
+import { BigNumber } from 'ethers';
+import {
+ waitForTx,
+ getBlockTimestamp,
+ increaseTime,
+ MAX_UINT_AMOUNT,
+ advanceTimeAndBlock,
+} from '@aave/deploy-v3';
+import { RANDOM_ADDRESSES } from '../helpers/constants';
+import { comparatorEngine } from './helpers/comparator-engine';
+import {
+ assetDataComparator,
+ getRewards,
+ getRewardsData,
+} from './helpers/DistributionManagerV2/data-helpers/asset-data';
+import { getUserIndex } from './helpers/DistributionManagerV2/data-helpers/asset-user-data';
+import hre from 'hardhat';
+
+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: '317097919837645865',
+ amountToClaim: '10',
+ },
+ {
+ caseName: 'Should allow -1',
+ emissionPerSecond: '317097919837645865',
+ amountToClaim: MAX_UINT_AMOUNT,
+ },
+ {
+ caseName: 'Should withdraw everything if amountToClaim more then rewards balance',
+ emissionPerSecond: '317097919837645865',
+ amountToClaim: '1034',
+ },
+ {
+ caseName: 'Should withdraw to another user',
+ emissionPerSecond: '317097919837645865',
+ amountToClaim: '1034',
+ to: RANDOM_ADDRESSES[5],
+ },
+ {
+ caseName: 'Should withdraw to another user and stake',
+ emissionPerSecond: '317097919837645865',
+ amountToClaim: '1034',
+ to: RANDOM_ADDRESSES[5],
+ },
+];
+
+makeSuite('Incentives Controller V2 claimRewards with 2 decimals', (testEnv) => {
+ for (const {
+ caseName,
+ amountToClaim: _amountToClaim,
+ to,
+ emissionPerSecond,
+ } of getRewardsBalanceScenarios) {
+ let amountToClaim = _amountToClaim;
+ it(caseName, async () => {
+ const { timestamp } = await hre.ethers.provider.getBlock('latest');
+ const timePerTest = 31536000;
+ const distributionEnd = timestamp + timePerTest * getRewardsBalanceScenarios.length;
+ await advanceTimeAndBlock(timePerTest);
+ const { incentivesControllerV2, stakedAave, aEursMockV2, stakedTokenStrategy } = testEnv;
+
+ const userAddress = await incentivesControllerV2.signer.getAddress();
+
+ const underlyingAsset = aEursMockV2.address;
+ const stakedByUser = 22 * caseName.length;
+ const totalSupply = 33 * caseName.length;
+ const reward = stakedAave.address;
+
+ await aEursMockV2.setUserBalanceAndSupply(stakedByUser, totalSupply);
+
+ // update emissionPerSecond in advance to not affect user calculations
+ if (emissionPerSecond) {
+ await waitForTx(
+ await incentivesControllerV2.configureAssets([
+ {
+ asset: underlyingAsset,
+ reward,
+ rewardOracle: testEnv.aavePriceAggregator,
+ emissionPerSecond,
+ distributionEnd,
+ totalSupply,
+ transferStrategy: stakedTokenStrategy.address,
+ },
+ ])
+ );
+ }
+
+ const destinationAddress = to || userAddress;
+
+ const destinationAddressBalanceBefore = await stakedAave.balanceOf(destinationAddress);
+ await aEursMockV2.handleActionOnAic(userAddress, totalSupply, stakedByUser);
+
+ const unclaimedRewardsBefore = await incentivesControllerV2.getUserRewardsBalance(
+ [underlyingAsset],
+ userAddress,
+ reward
+ );
+ const unclaimedRewardsStorageBefore = await incentivesControllerV2.getUserUnclaimedRewardsFromStorage(
+ userAddress,
+ reward
+ );
+
+ const userIndexBefore = await getUserIndex(
+ incentivesControllerV2,
+ userAddress,
+ underlyingAsset,
+ reward
+ );
+ const assetDataBefore = (
+ await getRewardsData(incentivesControllerV2, [underlyingAsset], [reward])
+ )[0];
+
+ const action = await incentivesControllerV2.claimRewards(
+ [underlyingAsset],
+ amountToClaim,
+ destinationAddress,
+ reward
+ );
+ const claimRewardsReceipt = await waitForTx(action);
+ const eventsEmitted = claimRewardsReceipt.events || [];
+
+ const actionBlockTimestamp = await getBlockTimestamp(claimRewardsReceipt.blockNumber);
+
+ const userIndexAfter = await getUserIndex(
+ incentivesControllerV2,
+ userAddress,
+ underlyingAsset,
+ reward
+ );
+ const assetDataAfter = (
+ await getRewardsData(incentivesControllerV2, [underlyingAsset], [reward])
+ )[0];
+
+ const unclaimedRewardsAfter = await incentivesControllerV2.getUserRewardsBalance(
+ [underlyingAsset],
+ userAddress,
+ reward
+ );
+ const unclaimedRewardsStorageAfter = await incentivesControllerV2.getUserUnclaimedRewardsFromStorage(
+ userAddress,
+ reward
+ );
+
+ const destinationAddressBalanceAfter = await stakedAave.balanceOf(destinationAddress);
+
+ const claimedAmount = destinationAddressBalanceAfter.sub(destinationAddressBalanceBefore);
+
+ // Only calculate expected accrued rewards if unclaimedRewards is below the amount to claim due gas optimization
+ const expectedAccruedRewards = unclaimedRewardsStorageBefore.lt(amountToClaim)
+ ? getRewards(stakedByUser, userIndexAfter, userIndexBefore, 2).toString()
+ : '0';
+
+ await aEursMockV2.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, totalSupply },
+ assetDataBefore,
+ assetDataAfter,
+ actionBlockTimestamp,
+ {}
+ );
+ expect(eventsEmitted.length).to.be.equal(0, 'no events should be emitted');
+ return;
+ }
+
+ // ------- Distribution Manager tests START -----
+ await assetDataComparator(
+ { underlyingAsset, totalSupply },
+ assetDataBefore,
+ assetDataAfter,
+ unclaimedRewardsStorageBefore.gte(amountToClaim)
+ ? Number(assetDataBefore.lastUpdateTimestamp.toString())
+ : actionBlockTimestamp,
+ distributionEnd,
+ {},
+ 2
+ );
+ expect(userIndexAfter.toString()).to.be.equal(
+ unclaimedRewardsStorageBefore.gte(amountToClaim)
+ ? userIndexBefore.toString()
+ : assetDataAfter.index.toString(),
+ 'user index are not correctly updated'
+ );
+ if (!assetDataAfter.index.eq(assetDataBefore.index)) {
+ await expect(action)
+ .to.emit(incentivesControllerV2, 'AssetIndexUpdated')
+ .withArgs(assetDataAfter.underlyingAsset, reward, assetDataAfter.index);
+ await expect(action)
+ .to.emit(incentivesControllerV2, 'UserIndexUpdated')
+ .withArgs(userAddress, assetDataAfter.underlyingAsset, reward, assetDataAfter.index);
+ }
+ // ------- Distribution Manager tests END -----
+
+ let unclaimedRewardsCalc = unclaimedRewardsStorageBefore.add(expectedAccruedRewards);
+
+ let expectedClaimedAmount: BigNumber;
+ if (unclaimedRewardsCalc.lte(amountToClaim)) {
+ expectedClaimedAmount = unclaimedRewardsCalc;
+ expect(unclaimedRewardsStorageAfter.toString()).to.be.equal(
+ '0',
+ 'unclaimed amount after should go to 0'
+ );
+ } else {
+ expectedClaimedAmount = BigNumber.from(amountToClaim);
+ expect(unclaimedRewardsStorageAfter.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') {
+ await expect(action)
+ .to.emit(incentivesControllerV2, 'RewardsAccrued')
+ .withArgs(userAddress, reward, expectedAccruedRewards);
+ await expect(action)
+ .to.emit(incentivesControllerV2, 'UserIndexUpdated')
+ .withArgs(userAddress, assetDataAfter.underlyingAsset, reward, assetDataAfter.index);
+ }
+ if (expectedClaimedAmount.gt(0)) {
+ await expect(action)
+ .to.emit(incentivesControllerV2, 'RewardsClaimed')
+ .withArgs(userAddress, reward, destinationAddress, userAddress, expectedClaimedAmount);
+ }
+ });
+ }
+});
diff --git a/test/incentives-v2/claim-rewards-to-self.spec.ts b/test/incentives-v2/claim-rewards-to-self.spec.ts
index 7ba05feb..4cb7a909 100644
--- a/test/incentives-v2/claim-rewards-to-self.spec.ts
+++ b/test/incentives-v2/claim-rewards-to-self.spec.ts
@@ -1,4 +1,10 @@
-import { getBlockTimestamp, increaseTime, waitForTx, MAX_UINT_AMOUNT } from '@aave/deploy-v3';
+import {
+ getBlockTimestamp,
+ increaseTime,
+ waitForTx,
+ MAX_UINT_AMOUNT,
+ advanceTimeAndBlock,
+} from '@aave/deploy-v3';
import { BigNumber } from 'ethers';
import { makeSuite } from '../helpers/make-suite';
import { comparatorEngine } from './helpers/comparator-engine';
@@ -8,6 +14,7 @@ import {
getRewardsData,
} from './helpers/DistributionManagerV2/data-helpers/asset-data';
import { getUserIndex } from './helpers/DistributionManagerV2/data-helpers/asset-user-data';
+import hre from 'hardhat';
const { expect } = require('chai');
@@ -53,14 +60,11 @@ makeSuite('AaveIncentivesController claimRewardsToSelf tests', (testEnv) => {
} of getRewardsBalanceScenarios) {
let amountToClaim = _amountToClaim;
it(caseName, async () => {
- await increaseTime(100);
- const {
- incentivesControllerV2,
- stakedAave,
- aDaiMockV2,
- distributionEnd,
- stakedTokenStrategy,
- } = testEnv;
+ const { timestamp } = await hre.ethers.provider.getBlock('latest');
+ const timePerTest = 31536000;
+ const distributionEnd = timestamp + timePerTest * getRewardsBalanceScenarios.length;
+ await advanceTimeAndBlock(timePerTest);
+ const { incentivesControllerV2, stakedAave, aDaiMockV2, stakedTokenStrategy } = testEnv;
const userAddress = await incentivesControllerV2.signer.getAddress();
diff --git a/test/incentives-v2/claim-rewards.spec.ts b/test/incentives-v2/claim-rewards.spec.ts
index cba37fac..de6e7dda 100644
--- a/test/incentives-v2/claim-rewards.spec.ts
+++ b/test/incentives-v2/claim-rewards.spec.ts
@@ -1,7 +1,13 @@
const { expect } = require('chai');
import { makeSuite } from '../helpers/make-suite';
import { BigNumber } from 'ethers';
-import { waitForTx, getBlockTimestamp, increaseTime, MAX_UINT_AMOUNT } from '@aave/deploy-v3';
+import {
+ waitForTx,
+ getBlockTimestamp,
+ increaseTime,
+ MAX_UINT_AMOUNT,
+ advanceTimeAndBlock,
+} from '@aave/deploy-v3';
import { RANDOM_ADDRESSES } from '../helpers/constants';
import { comparatorEngine } from './helpers/comparator-engine';
import {
@@ -10,6 +16,7 @@ import {
getRewardsData,
} from './helpers/DistributionManagerV2/data-helpers/asset-data';
import { getUserIndex } from './helpers/DistributionManagerV2/data-helpers/asset-user-data';
+import hre from 'hardhat';
type ScenarioAction = {
caseName: string;
@@ -68,14 +75,11 @@ makeSuite('Incentives Controller V2 claimRewards tests', (testEnv) => {
} of getRewardsBalanceScenarios) {
let amountToClaim = _amountToClaim;
it(caseName, async () => {
- await increaseTime(100);
- const {
- incentivesControllerV2,
- stakedAave,
- aDaiMockV2,
- stakedTokenStrategy,
- distributionEnd,
- } = testEnv;
+ const { timestamp } = await hre.ethers.provider.getBlock('latest');
+ const timePerTest = 31536000;
+ const distributionEnd = timestamp + timePerTest * getRewardsBalanceScenarios.length;
+ await advanceTimeAndBlock(timePerTest);
+ const { incentivesControllerV2, stakedAave, aDaiMockV2, stakedTokenStrategy } = testEnv;
const userAddress = await incentivesControllerV2.signer.getAddress();
diff --git a/test/incentives-v2/helpers/DistributionManagerV2/data-helpers/asset-data.ts b/test/incentives-v2/helpers/DistributionManagerV2/data-helpers/asset-data.ts
index 0fe4db67..e46e028e 100644
--- a/test/incentives-v2/helpers/DistributionManagerV2/data-helpers/asset-data.ts
+++ b/test/incentives-v2/helpers/DistributionManagerV2/data-helpers/asset-data.ts
@@ -109,7 +109,8 @@ export function assetDataComparator<
assetConfigAfter: State,
actionBlockTimestamp: number,
emissionEndTimestamp: number,
- compareRules: CompareRules
+ compareRules: CompareRules,
+ decimals = 18
) {
return comparatorEngine(
['emissionPerSecond', 'index', 'lastUpdateTimestamp'],
@@ -134,7 +135,8 @@ export function assetDataComparator<
stateBefore.emissionPerSecond,
stateBefore.lastUpdateTimestamp,
txTimestamp,
- emissionEndTimestamp
+ emissionEndTimestamp,
+ decimals
).toString(10);
},
},
diff --git a/test/incentives-v2/helpers/deploy.ts b/test/incentives-v2/helpers/deploy.ts
index ac933cd2..9e557ebf 100644
--- a/test/incentives-v2/helpers/deploy.ts
+++ b/test/incentives-v2/helpers/deploy.ts
@@ -5,13 +5,14 @@ declare var hre: HardhatRuntimeEnvironment;
export const deployATokenMock = async (
incentivesController: string,
- slug: string
+ slug: string,
+ decimals = 18
): Promise => {
const { deployer } = await hre.getNamedAccounts();
const artifact = await hre.deployments.deploy(`${slug}-ATokenMock`, {
contract: 'ATokenMock',
from: deployer,
- args: [incentivesController],
+ args: [incentivesController, decimals],
log: true,
});
return hre.ethers.getContractAt(artifact.abi, artifact.address);