Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions contracts/interfaces/IClaimStakingRewardsHelper.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
pragma solidity ^0.7.5;

interface IClaimStakingRewardsHelper {
function claimAllRewards(address to) external returns (uint256);

function claimAllRewardsAndStake(address to) external;

function claimAndStake(address to, address stakeToken) external;
}
8 changes: 8 additions & 0 deletions contracts/interfaces/IERC20WithNonce.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.7.5;

import {IERC20} from './IERC20.sol';

interface IERC20WithNonce is IERC20 {
function _nonces(address user) external view returns (uint256);
}
6 changes: 6 additions & 0 deletions contracts/interfaces/IPriceOracle.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.7.5;

interface IPriceOracle {
function getAssetPrice(address asset) external view returns (uint256);
}
34 changes: 34 additions & 0 deletions contracts/interfaces/IStakeUIHelper.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.7.5;
pragma experimental ABIEncoderV2;

interface IStakeUIHelper {
struct AssetUIData {
uint256 stakeTokenTotalSupply;
uint256 stakeCooldownSeconds;
uint256 stakeUnstakeWindow;
uint256 stakeTokenPriceEth;
uint256 rewardTokenPriceEth;
uint256 stakeApy;
uint128 distributionPerSecond;
uint256 distributionEnd;
uint256 stakeTokenUserBalance;
uint256 underlyingTokenUserBalance;
uint256 userCooldown;
uint256 userIncentivesToClaim;
uint256 userPermitNonce;
}

function getStkAaveData(address user) external view returns (AssetUIData memory);

function getStkBptData(address user) external view returns (AssetUIData memory);

function getUserUIData(address user)
external
view
returns (
AssetUIData memory,
AssetUIData memory,
uint256
);
}
80 changes: 80 additions & 0 deletions contracts/misc/ClaimStakingRewardsHelper.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// SPDX-License-Identifier: agpl-3.0
pragma solidity 0.7.5;

import {IClaimStakingRewardsHelper} from '../interfaces/IClaimStakingRewardsHelper.sol';
import {IStakedTokenV3} from '../interfaces/IStakedTokenV3.sol';
import {IERC20} from '../interfaces/IERC20.sol';
import {SafeMath} from '../lib/SafeMath.sol';

/**
* @title ClaimStakingRewardsHelper
* @notice Contract to claim all rewards on the different stake pools
* or claim all and stake Aave token
* @author Aave
**/
contract ClaimStakingRewardsHelper is IClaimStakingRewardsHelper {
using SafeMath for uint256;
address public immutable aaveStakeToken;
address public immutable bptStakeToken;

constructor(
address _aaveStakeToken,
address _bptStakeToken,
address aaveToken
) {
aaveStakeToken = _aaveStakeToken;
bptStakeToken = _bptStakeToken;

IERC20(aaveToken).approve(_aaveStakeToken, type(uint256).max);
}

/**
* @dev Claims all reward for an user, on all the different staked assets.
* @param to Address that will be receiving the rewards
**/
function claimAllRewards(address to) external override returns (uint256) {
uint256 claimedFromAave =
IStakedTokenV3(aaveStakeToken).claimRewardsOnBehalf(msg.sender, to, type(uint256).max);
uint256 claimedFromBPT =
IStakedTokenV3(bptStakeToken).claimRewardsOnBehalf(msg.sender, to, type(uint256).max);
return claimedFromAave.add(claimedFromBPT);
}

/**
* @dev Claims all reward for an user, on all the different staked assets, and stakes this amount on the aave stake pool.
* @param to Address that will be receiving the stk Token representing the staked amount
**/
function claimAllRewardsAndStake(address to) external override {
_claimAndStake(msg.sender, to, aaveStakeToken);
_claimAndStake(msg.sender, to, bptStakeToken);
}

/**
* @dev Claims reward from stakedToken and stakes it into the aave stake pool
* @param to Address that will be receiving the stk Token representing the staked amount
* @param stakeToken Address of the stake token where to claim the rewards
**/
function claimAndStake(address to, address stakeToken) external override {
require(
stakeToken == aaveStakeToken || stakeToken == bptStakeToken,
'Staked Token address must exists'
);
_claimAndStake(msg.sender, to, stakeToken);
}

/**
* @dev Claims reward from stakedToken and stakes it into the aave stake pool
* @param to Address that will be receiving the stk Token representing the staked amount
**/
function _claimAndStake(
address from,
address to,
address stakeToken
) internal {
uint256 rewardsClaimed =
IStakedTokenV3(stakeToken).claimRewardsOnBehalf(from, address(this), type(uint256).max);
if (rewardsClaimed > 0) {
IStakedTokenV3(aaveStakeToken).stake(to, rewardsClaimed);
}
}
}
35 changes: 35 additions & 0 deletions contracts/misc/IStakedToken.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// SPDX-License-Identifier: agpl-3.0
pragma solidity 0.7.5;
pragma experimental ABIEncoderV2;

interface IStakedToken {
struct AssetData {
uint128 emissionPerSecond;
uint128 lastUpdateTimestamp;
uint256 index;
}

function totalSupply() external view returns (uint256);

function COOLDOWN_SECONDS() external view returns (uint256);

function UNSTAKE_WINDOW() external view returns (uint256);

function DISTRIBUTION_END() external view returns (uint256);

function assets(address asset) external view returns (AssetData memory);

function balanceOf(address user) external view returns (uint256);

function getTotalRewardsBalance(address user) external view returns (uint256);

function stakersCooldowns(address user) external view returns (uint256);

function stake(address to, uint256 amount) external;

function redeem(address to, uint256 amount) external;

function cooldown() external;

function claimRewards(address to, uint256 amount) external;
}
121 changes: 121 additions & 0 deletions contracts/misc/StakeUIHelper.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.7.5;
pragma experimental ABIEncoderV2;

import {IStakedToken} from './IStakedToken.sol';
import {IStakeUIHelper} from '../interfaces/IStakeUIHelper.sol';
import {IERC20WithNonce} from '../interfaces/IERC20WithNonce.sol';
import {IERC20} from '../interfaces/IERC20.sol';
import {IPriceOracle} from '../interfaces/IPriceOracle.sol';

interface BPTPriceFeedI {
function latestAnswer() external view returns (uint256);
}

contract StakeUIHelper is IStakeUIHelper {
IPriceOracle public immutable PRICE_ORACLE;
BPTPriceFeedI public immutable BPT_PRICE_FEED;

address public immutable AAVE;
IStakedToken public immutable STAKED_AAVE;

address public immutable BPT;
IStakedToken public immutable STAKED_BPT;

uint256 constant SECONDS_PER_YEAR = 365 * 24 * 60 * 60;
uint256 constant APY_PRECISION = 10000;
address constant MOCK_USD_ADDRESS = 0x10F7Fc1F91Ba351f9C629c5947AD69bD03C05b96;
uint256 internal constant USD_BASE = 1e26;

constructor(
IPriceOracle priceOracle,
BPTPriceFeedI bptPriceFeed,
address aave,
IStakedToken stkAave,
address bpt,
IStakedToken stkBpt
) public {
PRICE_ORACLE = priceOracle;
BPT_PRICE_FEED = bptPriceFeed;

AAVE = aave;
STAKED_AAVE = stkAave;

BPT = bpt;
STAKED_BPT = stkBpt;
}

function _getStakedAssetData(
IStakedToken stakeToken,
address underlyingToken,
address user,
bool isNonceAvailable
) internal view returns (AssetUIData memory) {
AssetUIData memory data;

data.stakeTokenTotalSupply = stakeToken.totalSupply();
data.stakeCooldownSeconds = stakeToken.COOLDOWN_SECONDS();
data.stakeUnstakeWindow = stakeToken.UNSTAKE_WINDOW();
data.rewardTokenPriceEth = PRICE_ORACLE.getAssetPrice(AAVE);
data.distributionEnd = stakeToken.DISTRIBUTION_END();
if (block.timestamp < data.distributionEnd) {
data.distributionPerSecond = stakeToken.assets(address(stakeToken)).emissionPerSecond;
}

if (user != address(0)) {
data.underlyingTokenUserBalance = IERC20(underlyingToken).balanceOf(user);
data.stakeTokenUserBalance = stakeToken.balanceOf(user);
data.userIncentivesToClaim = stakeToken.getTotalRewardsBalance(user);
data.userCooldown = stakeToken.stakersCooldowns(user);
data.userPermitNonce = isNonceAvailable ? IERC20WithNonce(underlyingToken)._nonces(user) : 0;
}
return data;
}

function _calculateApy(uint256 distributionPerSecond, uint256 stakeTokenTotalSupply)
internal
pure
returns (uint256)
{
return (distributionPerSecond * SECONDS_PER_YEAR * APY_PRECISION) / stakeTokenTotalSupply;
}

function getStkAaveData(address user) public view override returns (AssetUIData memory) {
AssetUIData memory data = _getStakedAssetData(STAKED_AAVE, AAVE, user, true);

data.stakeTokenPriceEth = data.rewardTokenPriceEth;
data.stakeApy = _calculateApy(data.distributionPerSecond, data.stakeTokenTotalSupply);
return data;
}

function getStkBptData(address user) public view override returns (AssetUIData memory) {
AssetUIData memory data = _getStakedAssetData(STAKED_BPT, BPT, user, false);

data.stakeTokenPriceEth = address(BPT_PRICE_FEED) != address(0)
? BPT_PRICE_FEED.latestAnswer()
: PRICE_ORACLE.getAssetPrice(BPT);
data.stakeApy = _calculateApy(
data.distributionPerSecond * data.rewardTokenPriceEth,
data.stakeTokenTotalSupply * data.stakeTokenPriceEth
);

return data;
}

function getUserUIData(address user)
external
view
override
returns (
AssetUIData memory,
AssetUIData memory,
uint256
)
{
return (
getStkAaveData(user),
getStkBptData(user),
USD_BASE / PRICE_ORACLE.getAssetPrice(MOCK_USD_ADDRESS)
);
}
}
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ services:
INFURA_KEY: ${INFURA_KEY}
ETHERSCAN_NETWORK: ${ETHERSCAN_NETWORK}
GITLAB_ACCESS_TOKEN: ${GITLAB_ACCESS_TOKEN}
ALCHEMY_KEY: ${ALCHEMY_KEY}
42 changes: 40 additions & 2 deletions helpers/contracts-accessors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { eContractid, tEthereumAddress } from './types';
import { MintableErc20 } from '../types/MintableErc20';
import { StakedAave } from '../types/StakedAave';
import { StakedAaveV2 } from '../types/StakedAaveV2';
import {StakedAaveV3 } from '../types/StakedAaveV3';
import { StakedAaveV3 } from '../types/StakedAaveV3';
import { IcrpFactory } from '../types/IcrpFactory'; // Configurable right pool factory
import { IConfigurableRightsPool } from '../types/IConfigurableRightsPool';
import { IControllerAaveEcosystemReserve } from '../types/IControllerAaveEcosystemReserve';
Expand All @@ -22,6 +22,45 @@ import { DoubleTransferHelper } from '../types/DoubleTransferHelper';
import { zeroAddress } from 'ethereumjs-util';
import { ZERO_ADDRESS } from './constants';
import { Signer } from 'ethers';
import { ClaimStakingRewardsHelper } from '../types';
import { StakeUiHelper } from '../types';

export const deployStakeUIHelper = async (
[priceOracle, bptPriceFeed, aave, stkAave, bpt, stkBpt]: [
tEthereumAddress,
tEthereumAddress,
tEthereumAddress,
tEthereumAddress,
tEthereumAddress,
tEthereumAddress
],
verify?: boolean
) => {
const id = eContractid.StakeUIHelper;
const args: string[] = [priceOracle, bptPriceFeed, aave, stkAave, bpt, stkBpt];
const instance = await deployContract<StakeUiHelper>(id, args);
if (verify) {
await verifyContract(instance.address, args);
}
return instance;
};

export const deployClaimHelper = async (
[aaveStakeTokenAddress, bptStakeTokenAddress, aaveToken]: [
tEthereumAddress,
tEthereumAddress,
tEthereumAddress
],
verify?: boolean
) => {
const id = eContractid.ClaimStakingRewardsHelper;
const args: string[] = [aaveStakeTokenAddress, bptStakeTokenAddress, aaveToken];
const instance = await deployContract<ClaimStakingRewardsHelper>(id, args);
if (verify) {
await verifyContract(instance.address, args);
}
return instance;
};

export const deployStakedAave = async (
[
Expand Down Expand Up @@ -198,7 +237,6 @@ export const deployStakedTokenV3 = async (
return instance;
};


export const deployStakedAaveV3 = async (
[
stakedToken,
Expand Down
2 changes: 2 additions & 0 deletions helpers/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ export enum eContractid {
IBPool = 'IBPool',
IControllerAaveEcosystemReserve = 'IControllerAaveEcosystemReserve',
MockSelfDestruct = 'SelfdestructTransfer',
ClaimStakingRewardsHelper = 'ClaimStakingRewardsHelper',
StakeUIHelper = 'StakeUIHelper',
}

export type tEthereumAddress = string;
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

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

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@
"ropsten:deployment": "npm run hardhat-ropsten -- common-deployment --verify",
"kovan:deployment": "npm run hardhat-kovan -- common-deployment --verify",
"main:deployment": "npm run hardhat-main -- common-deployment --verify",
"kovan:deploy-ClaimHelper": "hardhat --network kovan deploy-ClaimHelper --verify",
"main:deploy-ClaimHelper": "hardhat --network main deploy-OracleAnchor --verify",
"kovan:deploy-StakeUIHelper": "hardhat --network kovan deploy-StakeUIHelper --verify",
"prettier:check": "npx prettier -c 'tasks/**/*.ts' 'contracts/**/*.sol' 'helpers/**/*.ts' 'test/**/*.ts'",
"prettier:write": "prettier --write 'tasks/**/*.ts' 'contracts/**/*.sol' 'helpers/**/*.ts' 'test/**/*.ts'",
"ci:clean": "rm -rf types/ cache/ artifacts/",
Expand Down
Loading