-
Notifications
You must be signed in to change notification settings - Fork 322
/
Copy pathBaseStrategy.sol
421 lines (348 loc) · 17.5 KB
/
BaseStrategy.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
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.7.0;
pragma experimental ABIEncoderV2;
import "@openzeppelinV3/contracts/token/ERC20/IERC20.sol";
import "@openzeppelinV3/contracts/math/SafeMath.sol";
struct StrategyParams {
uint256 performanceFee;
uint256 activation;
uint256 debtLimit;
uint256 rateLimit;
uint256 lastReport;
uint256 totalDebt;
uint256 totalReturns;
}
interface VaultAPI is IERC20 {
function apiVersion() external view returns (string memory);
function token() external view returns (address);
function strategies(address _strategy) external view returns (StrategyParams memory);
/*
* View how much the Vault would increase this strategy's borrow limit,
* based on it's present performance (since its last report). Can be used to
* determine expectedReturn in your strategy.
*/
function creditAvailable() external view returns (uint256);
/*
* View how much the Vault would like to pull back from the Strategy,
* based on it's present performance (since its last report). Can be used to
* determine expectedReturn in your strategy.
*/
function debtOutstanding() external view returns (uint256);
/*
* View how much the Vault expect this strategy to return at the current block,
* based on it's present performance (since its last report). Can be used to
* determine expectedReturn in your strategy.
*/
function expectedReturn() external view returns (uint256);
/*
* This is the main contact point where the strategy interacts with the Vault.
* It is critical that this call is handled as intended by the Strategy.
* Therefore, this function will be called by BaseStrategy to make sure the
* integration is correct.
*/
function report(uint256 _harvest) external returns (uint256);
/*
* This function is used in the scenario where there is a newer strategy that
* would hold the same positions as this one, and those positions are easily
* transferrable to the newer strategy. These positions must be able to be
* transferred at the moment this call is made, if any prep is required to
* execute a full transfer in one transaction, that must be accounted for
* separately from this call.
*/
function migrateStrategy(address _newStrategy) external;
/*
* This function should only be used in the scenario where the strategy is
* being retired but no migration of the positions are possible, or in the
* extreme scenario that the Strategy needs to be put into "Emergency Exit"
* mode in order for it to exit as quickly as possible. The latter scenario
* could be for any reason that is considered "critical" that the Strategy
* exits it's position as fast as possible, such as a sudden change in market
* conditions leading to losses, or an imminent failure in an external
* dependency.
*/
function revokeStrategy() external;
/*
* View the governance address of the Vault to assert privileged functions
* can only be called by governance. The Strategy serves the Vault, so it
* is subject to governance defined by the Vault.
*
*/
function governance() external view returns (address);
}
/*
* This interface is here for the keeper bot to use
*/
interface StrategyAPI {
function apiVersion() external pure returns (string memory);
function name() external pure returns (string memory);
function vault() external view returns (address);
function keeper() external view returns (address);
function tendTrigger(uint256 callCost) external view returns (bool);
function tend() external;
function harvestTrigger(uint256 callCost) external view returns (bool);
function harvest() external;
event Harvested(uint256 profit);
}
/*
* BaseStrategy implements all of the required functionality to interoperate closely
* with the core protocol. This contract should be inherited and the abstract methods
* implemented to adapt the strategy to the particular needs it has to create a return.
*/
abstract contract BaseStrategy {
using SafeMath for uint256;
// Version of this contract's StrategyAPI (must match Vault)
function apiVersion() public pure returns (string memory) {
return "0.1.3";
}
// Name of this contract's Strategy (Must override!)
// NOTE: You can use this field to manage the "version" of this strategy
// e.g. `StrategySomethingOrOtherV1`. It's up to you!
function name() external virtual pure returns (string memory);
VaultAPI public vault;
address public strategist;
address public rewards;
address public keeper;
IERC20 public want;
// So indexers can keep track of this
event Harvested(uint256 profit);
// The minimum number of blocks between harvest calls
// NOTE: Override this value with your own, or set dynamically below
uint256 public minReportDelay = 6300; // ~ once a day
// The minimum multiple that `callCost` must be above the credit/profit to be "justifiable"
// NOTE: Override this value with your own, or set dynamically below
uint256 public profitFactor = 100;
// Use this to adjust the threshold at which running a debt causes a harvest trigger
uint256 public debtThreshold = 0;
// Adjust this using `setReserve(...)` to keep some of the position in reserve in the strategy,
// to accomodate larger variations needed to sustain the strategy's core positon(s)
uint256 private reserve = 0;
function getReserve() internal view returns (uint256) {
return reserve;
}
function setReserve(uint256 _reserve) internal {
if (_reserve != reserve) reserve = _reserve;
}
bool public emergencyExit;
constructor(address _vault) public {
vault = VaultAPI(_vault);
want = IERC20(vault.token());
want.approve(_vault, uint256(-1)); // Give Vault unlimited access (might save gas)
strategist = msg.sender;
rewards = msg.sender;
keeper = msg.sender;
}
function setStrategist(address _strategist) external {
require(msg.sender == strategist || msg.sender == governance(), "!authorized");
strategist = _strategist;
}
function setKeeper(address _keeper) external {
require(msg.sender == strategist || msg.sender == governance(), "!authorized");
keeper = _keeper;
}
function setRewards(address _rewards) external {
require(msg.sender == strategist, "!authorized");
rewards = _rewards;
}
function setMinReportDelay(uint256 _delay) external {
require(msg.sender == strategist || msg.sender == governance(), "!authorized");
minReportDelay = _delay;
}
function setProfitFactor(uint256 _profitFactor) external {
require(msg.sender == strategist || msg.sender == governance(), "!authorized");
profitFactor = _profitFactor;
}
function setDebtThreshold(uint256 _debtThreshold) external {
require(msg.sender == strategist || msg.sender == governance(), "!authorized");
debtThreshold = _debtThreshold;
}
/*
* Resolve governance address from Vault contract, used to make
* assertions on protected functions in the Strategy
*/
function governance() internal view returns (address) {
return vault.governance();
}
/*
* Provide an accurate estimate for the total amount of assets (principle + return)
* that this strategy is currently managing, denominated in terms of `want` tokens.
* This total should be "realizable" e.g. the total value that could *actually* be
* obtained from this strategy if it were to divest it's entire position based on
* current on-chain conditions.
*
* NOTE: care must be taken in using this function, since it relies on external
* systems, which could be manipulated by the attacker to give an inflated
* (or reduced) value produced by this function, based on current on-chain
* conditions (e.g. this function is possible to influence through flashloan
* attacks, oracle manipulations, or other DeFi attack mechanisms).
*
* NOTE: It is up to governance to use this function to correctly order this strategy
* relative to its peers in the withdrawal queue to minimize losses for the Vault
* based on sudden withdrawals. This value should be higher than the total debt of
* the strategy and higher than it's expected value to be "safe".
*/
function estimatedTotalAssets() public virtual view returns (uint256);
/*
* Perform any strategy unwinding or other calls necessary to capture
* the "free return" this strategy has generated since the last time it's
* core position(s) were adusted. Examples include unwrapping extra rewards.
* This call is only used during "normal operation" of a Strategy, and should
* be optimized to minimize losses as much as possible. It is okay to report
* "no returns", however this will affect the credit limit extended to the
* strategy and reduce it's overall position if lower than expected returns
* are sustained for long periods of time.
*/
function prepareReturn(uint256 _debtOutstanding) internal virtual returns (uint256 _profit);
/*
* Perform any adjustments to the core position(s) of this strategy given
* what change the Vault made in the "investable capital" available to the
* strategy. Note that all "free capital" in the strategy after the report
* was made is available for reinvestment. Also note that this number could
* be 0, and you should handle that scenario accordingly.
*/
function adjustPosition(uint256 _debtOutstanding) internal virtual;
/*
* Make as much capital as possible "free" for the Vault to take. Some slippage
* is allowed, since when this method is called the strategist is no longer receiving
* their performance fee. The goal is for the strategy to divest as quickly as possible
* while not suffering exorbitant losses. This function is used during emergency exit
* instead of `prepareReturn()`
*/
function exitPosition() internal virtual;
/*
* Vault calls this function after shares are created during `Vault.report()`.
* You can customize this function to any share distribution mechanism you want.
*/
function distributeRewards(uint256 _shares) external virtual {
// Send 100% of newly-minted shares to the rewards address.
vault.transfer(rewards, _shares);
}
/*
* Provide a signal to the keeper that `tend()` should be called. The keeper will provide
* the estimated gas cost that they would pay to call `tend()`, and this function should
* use that estimate to make a determination if calling it is "worth it" for the keeper.
* This is not the only consideration into issuing this trigger, for example if the position
* would be negatively affected if `tend()` is not called shortly, then this can return `true`
* even if the keeper might be "at a loss" (keepers are always reimbursed by Yearn)
*
* NOTE: `callCost` must be priced in terms of `want`
*
* NOTE: this call and `harvestTrigger` should never return `true` at the same time.
*/
function tendTrigger(uint256 callCost) public virtual view returns (bool) {
// We usually don't need tend, but if there are positions that need active maintainence,
// overriding this function is how you would signal for that
return false;
}
function tend() external {
if (keeper != address(0)) {
require(msg.sender == keeper || msg.sender == strategist || msg.sender == governance(), "!authorized");
}
// Don't take profits with this call, but adjust for better gains
adjustPosition(vault.debtOutstanding());
}
/*
* Provide a signal to the keeper that `harvest()` should be called. The keeper will provide
* the estimated gas cost that they would pay to call `harvest()`, and this function should
* use that estimate to make a determination if calling it is "worth it" for the keeper.
* This is not the only consideration into issuing this trigger, for example if the position
* would be negatively affected if `harvest()` is not called shortly, then this can return `true`
* even if the keeper might be "at a loss" (keepers are always reimbursed by Yearn)
*
* NOTE: `callCost` must be priced in terms of `want`
*
* NOTE: this call and `tendTrigger` should never return `true` at the same time.
*/
function harvestTrigger(uint256 callCost) public virtual view returns (bool) {
StrategyParams memory params = vault.strategies(address(this));
// Should not trigger if strategy is not activated
if (params.activation == 0) return false;
// Should trigger if hadn't been called in a while
if (block.number.sub(params.lastReport) >= minReportDelay) return true;
// If some amount is owed, pay it back
// NOTE: Since debt is adjusted in step-wise fashion, it is appropiate to always trigger here,
// because the resulting change should be large (might not always be the case)
uint256 outstanding = vault.debtOutstanding();
if (outstanding > 0) return true;
// Check for profits and losses
uint256 total = estimatedTotalAssets();
// Trigger if we have a loss to report
if (total.add(debtThreshold) < params.totalDebt) return true;
uint256 profit = 0;
if (total > params.totalDebt) profit = total.sub(params.totalDebt); // We've earned a profit!
// Otherwise, only trigger if it "makes sense" economically (gas cost is <N% of value moved)
uint256 credit = vault.creditAvailable();
return (profitFactor * callCost < credit.add(profit));
}
function harvest() external {
if (keeper != address(0)) {
require(msg.sender == keeper || msg.sender == strategist || msg.sender == governance(), "!authorized");
}
uint256 profit = 0;
if (emergencyExit) {
exitPosition(); // Free up as much capital as possible
// NOTE: Don't take performance fee in this scenario
} else {
profit = prepareReturn(vault.debtOutstanding()); // Free up returns for Vault to pull
}
if (reserve > want.balanceOf(address(this))) reserve = want.balanceOf(address(this));
// Allow Vault to take up to the "harvested" balance of this contract, which is
// the amount it has earned since the last time it reported to the Vault
uint256 outstanding = vault.report(want.balanceOf(address(this)).sub(reserve));
// Check if free returns are left, and re-invest them
adjustPosition(outstanding);
emit Harvested(profit);
}
/*
* Liquidate as many assets as possible to `want`, irregardless of slippage,
* up to `_amountNeeded`. Any excess should be re-invested here as well.
*/
function liquidatePosition(uint256 _amountNeeded) internal virtual returns (uint256 _amountFreed);
function withdraw(uint256 _amountNeeded) external {
require(msg.sender == address(vault), "!vault");
// Liquidate as much as possible to `want`, up to `_amount`
uint256 amountFreed = liquidatePosition(_amountNeeded);
// Send it directly back (NOTE: Using `msg.sender` saves some gas here)
want.transfer(msg.sender, amountFreed);
// Adjust reserve to what we have after the freed amount is sent to the Vault
reserve = want.balanceOf(address(this));
}
/*
* Do anything necesseary to prepare this strategy for migration, such
* as transfering any reserve or LP tokens, CDPs, or other tokens or stores of value.
*/
function prepareMigration(address _newStrategy) internal virtual;
function migrate(address _newStrategy) external {
require(msg.sender == address(vault) || msg.sender == governance());
require(BaseStrategy(_newStrategy).vault() == vault);
prepareMigration(_newStrategy);
want.transfer(_newStrategy, want.balanceOf(address(this)));
}
function setEmergencyExit() external {
require(msg.sender == strategist || msg.sender == governance(), "!authorized");
emergencyExit = true;
exitPosition();
vault.revokeStrategy();
if (reserve > want.balanceOf(address(this))) reserve = want.balanceOf(address(this));
}
// Override this to add all tokens/tokenized positions this contract manages
// on a *persistant* basis (e.g. not just for swapping back to want ephemerally)
// NOTE: Do *not* include `want`, already included in `sweep` below
//
// Example:
//
// function protectedTokens() internal override view returns (address[] memory) {
// address[] memory protected = new address[](3);
// protected[0] = tokenA;
// protected[1] = tokenB;
// protected[2] = tokenC;
// return protected;
// }
function protectedTokens() internal virtual view returns (address[] memory);
function sweep(address _token) external {
require(msg.sender == governance(), "!authorized");
require(_token != address(want), "!want");
address[] memory _protectedTokens = protectedTokens();
for (uint256 i; i < _protectedTokens.length; i++) require(_token != _protectedTokens[i], "!protected");
IERC20(_token).transfer(governance(), IERC20(_token).balanceOf(address(this)));
}
}