-
Notifications
You must be signed in to change notification settings - Fork 31
/
Copy pathReaperStrategyGranarySupplyOnly.sol
237 lines (208 loc) · 8.23 KB
/
ReaperStrategyGranarySupplyOnly.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
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
// SPDX-License-Identifier: BUSL1.1
pragma solidity ^0.8.0;
import "./abstract/ReaperBaseStrategyv4.sol";
import "./interfaces/IAToken.sol";
import "./interfaces/IAaveProtocolDataProvider.sol";
import "./interfaces/ILendingPool.sol";
import "./interfaces/ILendingPoolAddressesProvider.sol";
import "./interfaces/IRewardsController.sol";
import "./libraries/ReaperMathUtils.sol";
import "./mixins/VeloSolidMixin.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/math/MathUpgradeable.sol";
/**
* @dev This strategy will deposit a token on Granary to maximize yield
*/
contract ReaperStrategyGranarySupplyOnly is ReaperBaseStrategyv4, VeloSolidMixin {
using ReaperMathUtils for uint256;
using SafeERC20Upgradeable for IERC20Upgradeable;
// 3rd-party contract addresses
address public constant VELO_ROUTER = 0xa132DAB612dB5cB9fC9Ac426A0Cc215A3423F9c9;
ILendingPoolAddressesProvider public constant ADDRESSES_PROVIDER =
ILendingPoolAddressesProvider(0xdDE5dC81e40799750B92079723Da2acAF9e1C6D6);
IAaveProtocolDataProvider public constant DATA_PROVIDER =
IAaveProtocolDataProvider(0x9546F673eF71Ff666ae66d01Fd6E7C6Dae5a9995);
IRewardsController public constant REWARDER = IRewardsController(0x6A0406B8103Ec68EE9A713A073C7bD587c5e04aD);
// this strategy's configurable tokens
IAToken public gWant;
// Misc constants
uint16 private constant LENDER_REFERRAL_CODE_NONE = 0;
/**
* @dev Tokens Used:
* {rewardClaimingTokens} - Array containing gWant, used for claiming rewards
*/
address[] public rewardClaimingTokens;
/**
* We break down the harvest logic into the following operations:
* 1. Claiming rewards
* 2. A series of swaps as required
* 3. Creating more of the strategy's underlying token, if necessary.
*
* #1 and #3 are specific to each protocol.
* #2 however is mostly the same across all strats. So to make things more generic, we
* will execute #2 by iterating through a series of pre-defined "steps".
*
* This array holds all the swapping operations in sequence.
* {ADMIN} role or higher will be able to set this array.
*/
address[2][] public steps;
/**
* @dev Initializes the strategy. Sets parameters, saves routes, and gives allowances.
* @notice see documentation for each variable above its respective declaration.
*/
function initialize(
address _vault,
address[] memory _strategists,
address[] memory _multisigRoles,
IAToken _gWant
) public initializer {
gWant = _gWant;
want = _gWant.UNDERLYING_ASSET_ADDRESS();
__ReaperBaseStrategy_init(_vault, want, _strategists, _multisigRoles);
rewardClaimingTokens = [address(_gWant)];
}
function _adjustPosition(uint256 _debt) internal override {
if (emergencyExit) {
return;
}
uint256 wantBalance = balanceOfWant();
if (wantBalance > _debt) {
uint256 toReinvest = wantBalance - _debt;
_deposit(toReinvest);
}
}
function _liquidatePosition(uint256 _amountNeeded)
internal
override
returns (uint256 liquidatedAmount, uint256 loss)
{
uint256 wantBal = balanceOfWant();
if (wantBal < _amountNeeded) {
_withdraw(_amountNeeded - wantBal);
liquidatedAmount = balanceOfWant();
} else {
liquidatedAmount = _amountNeeded;
}
if (_amountNeeded > liquidatedAmount) {
loss = _amountNeeded - liquidatedAmount;
}
}
function _liquidateAllPositions() internal override returns (uint256 amountFreed) {
_withdrawUnderlying(balanceOfPool());
return balanceOfWant();
}
/**
* @dev Core function of the strat, in charge of collecting and swapping rewards
* to produce more want.
* @notice Assumes the deposit will take care of resupplying excess want.
*/
function _harvestCore(uint256 _debt) internal override returns (int256 roi, uint256 repayment) {
_claimRewards();
uint256 numSteps = steps.length;
for (uint256 i = 0; i < numSteps; i = i.uncheckedInc()) {
address[2] storage step = steps[i];
IERC20Upgradeable startToken = IERC20Upgradeable(step[0]);
uint256 amount = startToken.balanceOf(address(this));
if (amount == 0) {
continue;
}
_swapVelo(step[0], step[1], amount, VELO_ROUTER);
}
uint256 allocated = IVault(vault).strategies(address(this)).allocated;
uint256 totalAssets = balanceOf();
uint256 toFree = _debt;
if (totalAssets > allocated) {
uint256 profit = totalAssets - allocated;
toFree += profit;
roi = int256(profit);
} else if (totalAssets < allocated) {
roi = -int256(allocated - totalAssets);
}
(uint256 amountFreed, uint256 loss) = _liquidatePosition(toFree);
repayment = MathUpgradeable.min(_debt, amountFreed);
roi -= int256(loss);
}
/**
* Only {STRATEGIST} or higher roles may update the swap path for a token.
*/
function updateVeloSwapPath(
address _tokenIn,
address _tokenOut,
address[] calldata _path
) external override {
_atLeastRole(STRATEGIST);
_updateVeloSwapPath(_tokenIn, _tokenOut, _path);
}
/**
* Only {ADMIN} or higher roles may set the array
* of steps executed as part of harvest.
*/
function setHarvestSteps(address[2][] calldata _newSteps) external {
_atLeastRole(ADMIN);
delete steps;
uint256 numSteps = _newSteps.length;
for (uint256 i = 0; i < numSteps; i = i.uncheckedInc()) {
address[2] memory step = _newSteps[i];
require(step[0] != address(0));
require(step[1] != address(0));
steps.push(step);
}
}
/**
* @dev Function that puts the funds to work.
*/
function _deposit(uint256 toReinvest) internal {
if (toReinvest != 0) {
address lendingPoolAddress = ADDRESSES_PROVIDER.getLendingPool();
IERC20Upgradeable(want).safeIncreaseAllowance(lendingPoolAddress, toReinvest);
ILendingPool(lendingPoolAddress).deposit(want, toReinvest, address(this), LENDER_REFERRAL_CODE_NONE);
}
}
/**
* @dev Withdraws funds from external contracts and brings them back to this contract.
*/
function _withdraw(uint256 _amount) internal {
if (_amount != 0) {
_withdrawUnderlying(_amount);
}
}
/**
* @dev Attempts to Withdraw {_withdrawAmount} from pool. Withdraws max amount that can be
* safely withdrawn if {_withdrawAmount} is too high.
*/
function _withdrawUnderlying(uint256 _withdrawAmount) internal {
uint256 withdrawable = balanceOfPool();
_withdrawAmount = MathUpgradeable.min(_withdrawAmount, withdrawable);
ILendingPool(ADDRESSES_PROVIDER.getLendingPool()).withdraw(address(want), _withdrawAmount, address(this));
}
/**
* @dev Claim rewards for supply.
*/
function _claimRewards() internal {
IRewardsController(REWARDER).claimAllRewardsToSelf(rewardClaimingTokens);
}
/**
* @dev Attempts to safely withdraw {_amount} from the pool.
*/
function authorizedWithdrawUnderlying(uint256 _amount) external {
_atLeastRole(STRATEGIST);
_withdrawUnderlying(_amount);
}
/**
* @dev Function to calculate the total {want} held by the strat.
* It takes into account both the funds in hand, plus the funds in the lendingPool.
*/
function balanceOf() public view override returns (uint256) {
return balanceOfPool() + balanceOfWant();
}
function balanceOfWant() public view returns (uint256) {
return IERC20Upgradeable(want).balanceOf(address(this));
}
function balanceOfPool() public view returns (uint256) {
(uint256 supply, , , , , , , , ) = IAaveProtocolDataProvider(DATA_PROVIDER).getUserReserveData(
address(want),
address(this)
);
return supply;
}
}