-
Notifications
You must be signed in to change notification settings - Fork 0
/
IdleYearnVaultStrategy.sol
181 lines (160 loc) · 6.79 KB
/
IdleYearnVaultStrategy.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.7;
import "./interfaces/IIdleCDOStrategy.sol";
import "./interfaces/IIdleToken.sol";
import "./interfaces/IYearnVault.sol";
import "./interfaces/IERC20Detailed.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
import "hardhat/console.sol";
/// @author Idle Labs Inc.
/// @title IdleYearnVaultStrategy
/// @notice IIdleCDOStrategy to deploy funds in Idle Finance
/// @dev This contract should not have any funds at the end of each tx.
/// The contract is upgradable, to add storage slots, add them after the last `###### End of storage VXX`
contract IdleYearnVaultStrategy is Initializable, OwnableUpgradeable, ReentrancyGuardUpgradeable, IIdleCDOStrategy {
using SafeERC20Upgradeable for IERC20Detailed;
/// ###### Storage V1
/// @notice one idleToken (all idleTokens have 18 decimals)
uint256 public constant ONE_TOKEN = 10**18;
/// @notice address of the strategy used, in this case idleToken address
address public override strategyToken;
/// @notice underlying token address (eg DAI)
address public override token;
/// @notice one underlying token
uint256 public override oneToken;
/// @notice decimals of the underlying asset
uint256 public override tokenDecimals;
/// @notice underlying ERC20 token contract
IERC20Detailed public underlyingToken;
/// @notice yearnVault contract
IYearnVault public yearnVault;
address public whitelistedCDO;
/// ###### End of storage V1
// Used to prevent initialization of the implementation contract
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
token = address(1);
}
// ###################
// Initializer
// ###################
/// @notice can only be called once
/// @dev Initialize the upgradable contract
/// @param _strategyToken address of the strategy token
/// @param _owner owner address
function initialize(address _strategyToken, address _owner) public initializer {
require(token == address(0), 'Initialized');
// Initialize contracts
OwnableUpgradeable.__Ownable_init();
ReentrancyGuardUpgradeable.__ReentrancyGuard_init();
// Set basic parameters
strategyToken = _strategyToken;
token = IYearnVault(_strategyToken).token();
tokenDecimals = IERC20Detailed(token).decimals();
oneToken = 10**(tokenDecimals);
yearnVault = IYearnVault(_strategyToken);
underlyingToken = IERC20Detailed(token);
underlyingToken.safeApprove(_strategyToken, type(uint256).max);
// transfer ownership
transferOwnership(_owner);
}
// ###################
// Public methods
// ###################
/// @dev msg.sender should approve this contract first to spend `_amount` of `token`
/// @param _amount amount of `token` to deposit
/// @return minted strategyTokens minted
function deposit(uint256 _amount) external override returns (uint256 minted) {
if (_amount > 0) {
IYearnVault _yearnVault = yearnVault;
/// get `tokens` from msg.sender
underlyingToken.safeTransferFrom(msg.sender, address(this), _amount);
/// deposit those in Idle
minted = _yearnVault.deposit(_amount);
/// transfer idleTokens to msg.sender
_yearnVault.transfer(msg.sender, minted);
}
}
/// @dev msg.sender should approve this contract first to spend `_amount` of `strategyToken`
/// @param _amount amount of strategyTokens to redeem
/// @return amount of underlyings redeemed
function redeem(uint256 _amount) external override returns(uint256) {
return _redeem(_amount);
}
function redeemRewards() external virtual override returns (uint256[] memory _balances) {
// No Implementation
}
function redeemUnderlying(uint256 _amount) external virtual override returns(uint256){
return _redeem(_amount * ONE_TOKEN / price());
}
// ###################
// Internal
// ###################
/// @dev msg.sender should approve this contract first to spend `_amount` of `strategyToken`
/// @param _amount amount of strategyTokens to redeem
/// @return redeemed amount of underlyings redeemed
function _redeem(uint256 _amount) internal returns(uint256 redeemed) {
if (_amount > 0) {
IYearnVault _yearnVault = yearnVault;
// get idleTokens from the user
_yearnVault.transferFrom(msg.sender, address(this), _amount);
// redeem underlyings from Idle
redeemed = _yearnVault.withdraw(_amount);
// transfer underlyings to msg.sender
underlyingToken.safeTransfer(msg.sender, redeemed);
}
}
// ###################
// Views
// ###################
/// @return net price in underlyings of 1 strategyToken
function price() public override view returns(uint256) {
return yearnVault.pricePerShare();
}
/// @return apr net apr (fees should already be excluded)
function getApr() external override virtual view returns(uint256 apr){
uint256 index;
uint256 toralExpectedPercent;
uint256 expectedReturn;
uint256 strategyTvl;
IYearnVault _yearnVault = yearnVault;
address strategy = _yearnVault.withdrawalQueue(index);
while(strategy != address(0)) {
expectedReturn = _yearnVault.expectedReturn(strategy);
strategyTvl = _yearnVault.creditAvailable(strategy);
toralExpectedPercent += ((expectedReturn * 1e20) / strategyTvl); // 1e20 = 100 * 1e18
index += 1;
strategy = _yearnVault.withdrawalQueue(index);
}
apr = toralExpectedPercent / index;
apr -= apr * _yearnVault.performanceFee() / 10000; // 10000 = 100%
apr -= apr * _yearnVault.managementFee() / 10000; // 10000 = 100%
}
/// @return tokens array of reward token addresses
function getRewardTokens() external virtual override view returns(address[] memory tokens){
// No Implementation
}
// ###################
// Protected
// ###################
/// @notice Allow the CDO to pull stkAAVE rewards
/// @return _bal amount of stkAAVE transferred
function pullStkAAVE() external virtual override returns(uint256 _bal){
// No Implementation
}
/// @notice This contract should not have funds at the end of each tx (except for stkAAVE), this method is just for leftovers
/// @dev Emergency method
/// @param _token address of the token to transfer
/// @param value amount of `_token` to transfer
/// @param _to receiver address
function transferToken(address _token, uint256 value, address _to) external onlyOwner nonReentrant {
IERC20Detailed(_token).safeTransfer(_to, value);
}
/// @notice allow to update address whitelisted to pull stkAAVE rewards
function setWhitelistedCDO(address _cdo) external onlyOwner {
require(_cdo != address(0), "IS_0");
whitelistedCDO = _cdo;
}
}