-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathEverlongStrategyKeeper.sol
More file actions
368 lines (331 loc) ยท 16.6 KB
/
Copy pathEverlongStrategyKeeper.sol
File metadata and controls
368 lines (331 loc) ยท 16.6 KB
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
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.24;
import { IHyperdrive } from "hyperdrive/contracts/src/interfaces/IHyperdrive.sol";
import { FixedPointMath } from "hyperdrive/contracts/src/libraries/FixedPointMath.sol";
import { SafeCast } from "hyperdrive/contracts/src/libraries/SafeCast.sol";
import { CommonReportTrigger } from "lib/vault-periphery/lib/tokenized-strategy-periphery/src/ReportTrigger/CommonReportTrigger.sol";
import { Ownable } from "openzeppelin/access/Ownable.sol";
import { IERC20 } from "openzeppelin/token/ERC20/IERC20.sol";
import { SafeERC20 } from "openzeppelin/token/ERC20/utils/SafeERC20.sol";
import { BaseStrategy, ERC20 } from "tokenized-strategy/BaseStrategy.sol";
import { IStrategy } from "tokenized-strategy/interfaces/IStrategy.sol";
import { DebtAllocator } from "vault-periphery/debtAllocators/DebtAllocator.sol";
import { IVault } from "yearn-vaults-v3/interfaces/IVault.sol";
import { Roles } from "yearn-vaults-v3/interfaces/Roles.sol";
import { IEverlongStrategy } from "./interfaces/IEverlongStrategy.sol";
import { IEverlongStrategyKeeper } from "./interfaces/IEverlongStrategyKeeper.sol";
import { IRoleManager } from "./interfaces/IRoleManager.sol";
import { EVERLONG_STRATEGY_KEEPER_KIND, EVERLONG_VERSION, ONE, MAX_BPS } from "./libraries/Constants.sol";
import { EverlongPortfolioLibrary } from "./libraries/EverlongPortfolio.sol";
import { HyperdriveExecutionLibrary } from "./libraries/HyperdriveExecution.sol";
/// @author DELV
/// @title EverlongStrategyKeeper
/// @notice Periphery contract to simplify operations for keepers.
/// @custom:disclaimer The language used in this code is for coding convenience
/// only, and is not intended to, and does not, have any
/// particular legal or regulatory significance.
contract EverlongStrategyKeeper is Ownable {
using FixedPointMath for uint256;
using HyperdriveExecutionLibrary for IHyperdrive;
using EverlongPortfolioLibrary for EverlongPortfolioLibrary.State;
using SafeCast for *;
using SafeERC20 for ERC20;
/// @notice Kind of the EverlongStrategyKeeper.
string public constant kind = EVERLONG_STRATEGY_KEEPER_KIND;
/// @notice Version of the EverlongStrategyKeeper.
string public constant version = EVERLONG_VERSION;
/// @notice Name of the EverlongStrategyKeeper.
string public name;
/// @notice Address of the target RoleManager contract.
/// @dev Helpful for getting periphery contract addresses and enumerating
/// vaults.
address public roleManager;
/// @notice Address of the external `CommonReportTrigger` contract.
/// @dev This contract contains default checks for whether to report+tend.
address public trigger;
/// @notice Initialize the EverlongStrategyKeeper contract.
/// @param _name Name for the keeper contract.
/// @param _roleManager Address for the `RoleManager` contract.
/// @param _trigger Address for the `CommonReportTrigger` contract.
constructor(
string memory _name,
address _roleManager,
address _trigger
) Ownable(msg.sender) {
name = _name;
roleManager = _roleManager;
trigger = _trigger;
}
// โญโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ
// โ Setters โ
// โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ
/// @notice Updates the address of the CommonReportTrigger contract.
/// @param _trigger Address of the CommonReportTrigger contract to set.
function setTrigger(address _trigger) external onlyOwner {
trigger = _trigger;
}
/// @notice Updates the address of the RoleManager contract.
/// @param _roleManager Address of the RoleManager contract to set.
function setRoleManager(address _roleManager) external onlyOwner {
roleManager = _roleManager;
}
// โญโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ
// โ Maintenance โ
// โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ
/// @notice Calls `process_report()` on the vault/strategy combination if
/// needed.
/// @param _vault Address of the vault contract to process the report on.
/// @param _strategy Address of the strategy contract to process the report
/// on.
function processReport(
address _vault,
address _strategy
) external onlyOwner {
// Bail if this contract doesn't have the REPORTING_MANAGER role.
uint256 roleMap = IVault(_vault).roles(address(this));
if (roleMap & Roles.REPORTING_MANAGER != roleMap) {
return;
}
// Check if report should be called on the vault/strategy combination.
(
bool shouldReportVault,
bytes memory vaultCalldataOrReason
) = CommonReportTrigger(trigger).defaultVaultReportTrigger(
_vault,
_strategy
);
// If process_report should be called, call it with the recommended
// parameters.
if (shouldReportVault) {
(bool success, bytes memory err) = _vault.call(
vaultCalldataOrReason
);
if (!success) {
revert IEverlongStrategyKeeper.VaultReportFailed(err);
}
}
}
/// @dev Calls `report()` on the strategy if needed.
/// @param _strategy Address of the strategy contract to report on.
/// @param _config Configuration for the `tend()` function called within
/// `_harvestAndReport()` in the strategy.
function strategyReport(
address _strategy,
IEverlongStrategy.TendConfig memory _config
) external onlyOwner {
// Bail if this contract isn't the keeper.
if (IStrategy(_strategy).keeper() != address(this)) {
return;
}
// Check if report should be called on the strategy.
(
bool shouldReportStrategy,
bytes memory strategyCalldataOrReason
) = CommonReportTrigger(trigger).defaultStrategyReportTrigger(
_strategy
);
// If report should be called, call it with the recommended parameters.
if (shouldReportStrategy) {
IEverlongStrategy(_strategy).setTendConfig(_config);
(bool success, bytes memory err) = _strategy.call(
strategyCalldataOrReason
);
if (!success) {
revert IEverlongStrategyKeeper.StrategyReportFailed(err);
}
}
}
/// @dev Calls `tend()` on the strategy if needed and sets the
/// tend configuration.
/// @param _strategy Address of the strategy to tend.
/// @param _config Configuration for the tend call.
function tend(
address _strategy,
IEverlongStrategy.TendConfig memory _config
) external onlyOwner {
// Bail if this contract isn't the keeper.
if (IStrategy(_strategy).keeper() != address(this)) {
return;
}
// Check if tend should be called.
(bool shouldTend_, bytes memory calldataOrReason) = CommonReportTrigger(
trigger
).strategyTendTrigger(_strategy);
// If tend should be called, call it with the recommended parameters.
if (shouldTend_) {
IEverlongStrategy(_strategy).setTendConfig(_config);
(bool success, bytes memory err) = _strategy.call(calldataOrReason);
if (!success) {
revert IEverlongStrategyKeeper.TendFailed(err);
}
}
}
/// @dev Calls `update_debt()` on the vault if needed.
/// @param _vault Address of the vault to update debt for.
/// @param _strategy Address of the strategy to update debt for.
function update_debt(address _vault, address _strategy) external onlyOwner {
// Get the DebtAllocator contract address.
DebtAllocator debtAllocator = DebtAllocator(
IRoleManager(roleManager).getDebtAllocator(_vault)
);
// Bail if there's no DebtAllocator for the vault or this contract isn't
// the keeper.
if (
address(debtAllocator) == address(0) ||
!debtAllocator.keepers(address(this))
) {
revert("vault update debt failed: improper vault configuration");
}
// If update_debt should be called, call it with the recommended parameters.
(bool shouldUpdateDebt_, bytes memory calldataOrReason) = debtAllocator
.shouldUpdateDebt(_vault, _strategy);
if (shouldUpdateDebt_) {
(bool success, bytes memory err) = address(debtAllocator).call(
calldataOrReason
);
if (!success) {
revert IEverlongStrategyKeeper.UpdateDebtFailed(err);
}
}
}
// โญโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ
// โ Triggers โ
// โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ
/// @notice Returns true if `processReport(..)` should be called on the
/// vault/strategy combination.
/// @param _vault Address of the vault to process the report on.
/// @param _strategy Address of the strategy to process the report on.
/// @return shouldProcessReport_ True if `processReport(..)` should be
/// called, false otherwise.
function shouldProcessReport(
address _vault,
address _strategy
) external view returns (bool shouldProcessReport_) {
// Check if report should be called on the vault/strategy combination.
(shouldProcessReport_, ) = CommonReportTrigger(trigger)
.defaultVaultReportTrigger(_vault, _strategy);
}
/// @notice Returns whether `report(..)` should be called on the strategy.
/// @param _strategy Address of the strategy.
/// @return shouldStrategyReport_ True if `report(..)` should be called,
/// false otherwise.
function shouldStrategyReport(
address _strategy
) external view returns (bool shouldStrategyReport_) {
// Check if report should be called on the strategy.
(shouldStrategyReport_, ) = CommonReportTrigger(trigger)
.defaultStrategyReportTrigger(_strategy);
}
/// @notice Returns whether `tend(..)` should be called on the strategy.
/// @param _strategy Address of the strategy.
/// @return shouldTend_ True if `tend(..)` should be called on the strategy,
/// false otherwise.
function shouldTend(
address _strategy
) external view returns (bool shouldTend_) {
// Check if tend should be called.
(shouldTend_, ) = CommonReportTrigger(trigger).strategyTendTrigger(
_strategy
);
}
/// @notice Returns whether `update_debt(..)` should be called for the
/// vault/strategy combination.
/// @param _vault Address of the vault.
/// @param _strategy Address of the strategy.
/// @return shouldUpdateDebt_ True if `update_debt(..)` should be called
/// on the vault/strategy combination, false
/// otherwise.
function shouldUpdateDebt(
address _vault,
address _strategy
) external view returns (bool shouldUpdateDebt_) {
// Get the DebtAllocator contract address.
DebtAllocator debtAllocator = DebtAllocator(
IRoleManager(roleManager).getDebtAllocator(_vault)
);
// If update_debt should be called, call it with the recommended parameters.
(shouldUpdateDebt_, ) = debtAllocator.shouldUpdateDebt(
_vault,
_strategy
);
}
// โญโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ
// โ IEverlongStrategy.TendConfig Helpers โ
// โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ
/// @notice Calculates an appropriate `TendConfig.minOutput` for the input
/// `_strategy` given the provided `_slippage` tolerance.
/// @param _strategy Strategy to be tended.
/// @param _slippage Maximum acceptable slippage in basis points where a
/// value of 10_000 indicates 100% slippage.
function calculateMinOutput(
address _strategy,
uint256 _slippage
) external view returns (uint256) {
// Obtain the amount of idle currently present in the strategy.
uint256 canSpend = IERC20(IEverlongStrategy(_strategy).asset())
.balanceOf(_strategy);
// Retrieve the strategy's Hyperdrive instance, its PoolConfig, and
// determine whether the strategy will be transacting with its
// base token.
IHyperdrive hyperdrive = IHyperdrive(
IEverlongStrategy(_strategy).hyperdrive()
);
IHyperdrive.PoolConfig memory poolConfig = hyperdrive.getPoolConfig();
bool asBase = IEverlongStrategy(_strategy).asBase();
// If the strategy has matured positions, increase the amount to spend
// by the proceeds of closing these positions.
if (IEverlongStrategy(_strategy).hasMaturedPositions()) {
// Increment the amount that can be spent by the value of each
// matured position in the strategy's portfolio. Since the
// positions are stored from most to least mature, we can exit
// when an immature position is encountered.
uint256 positionCount = IEverlongStrategy(_strategy)
.positionCount();
IEverlongStrategy.EverlongPosition memory position;
for (uint256 i = 0; i < positionCount; i++) {
position = IEverlongStrategy(_strategy).positionAt(i);
if (hyperdrive.isMature(position)) {
canSpend += hyperdrive.previewCloseLong(
asBase,
poolConfig,
position,
""
);
} else {
break;
}
}
}
// Skip calculations and return early if no assets are to be spent.
if (canSpend == 0) {
return 0;
}
// Calculate the amount of bonds that would be received if a long was
// opened with `canSpend` assets.
uint256 expectedOutput = hyperdrive.previewOpenLong(
asBase,
poolConfig,
canSpend.min(
IEverlongStrategy(_strategy).availableDepositLimit(_strategy)
),
""
);
// Decrease the output by the maximum acceptable slippage and return
// the result.
return expectedOutput.mulDivDown(MAX_BPS - _slippage, MAX_BPS);
}
/// @notice Calculates an appropriate `TendConfig.minVaultSharePrice` for
/// the input `_strategy` given the provided `_slippage` tolerance.
/// @param _strategy Strategy to be tended.
/// @param _slippage Maximum acceptable slippage in basis points where a
/// value of 10_000 indicates 100% slippage.
function calculateMinVaultSharePrice(
address _strategy,
uint256 _slippage
) external view returns (uint256) {
return
IHyperdrive(IEverlongStrategy(_strategy).hyperdrive())
.vaultSharePrice()
.mulDivDown(MAX_BPS - _slippage, MAX_BPS);
}
}