1
1
// SPDX-License-Identifier: GPL-3.0-only
2
- pragma solidity 0.8.13 ;
2
+ pragma solidity 0.8.19 ;
3
3
4
4
import "solmate/mixins/ERC4626.sol " ;
5
5
import "openzeppelin-contracts/contracts/access/Ownable2Step.sol " ;
6
6
import {Gauge} from "./Gauge.sol " ;
7
7
import {IStrategy} from "../interfaces/IStrategy.sol " ;
8
- import {RescueFundsLib} from "./RescueFundsLib.sol " ;
8
+ import "./RescueFundsLib.sol " ;
9
9
import "openzeppelin-contracts/contracts/utils/math/Math.sol " ;
10
+ import "openzeppelin-contracts/contracts/security/ReentrancyGuard.sol " ;
10
11
11
- // add report external function (called from cron)
12
- // call report from withdraw and deposit too (with timestamp check, settable by admin)
12
+
13
+ // add rebalance external function (called from cron)
14
+ // call rebalance from withdraw and deposit too (with timestamp check, settable by admin)
13
15
// pausable vault
14
16
// redeem all from strategy and detach
15
17
// reentrancy guard
16
18
19
+
20
+
21
+
17
22
contract YVault is Gauge , Ownable2Step , ERC4626 {
18
23
using SafeTransferLib for ERC20 ;
19
24
ERC20 public immutable token__;
20
25
21
26
uint256 public totalIdle; // Amount of tokens that are in the vault
22
27
uint256 public totalDebt; // Amount of tokens that strategy have borrowed
23
-
24
- uint256 public totalProfit; // Amount of tokens that strategy have earned
25
- uint256 public totalLoss; // Amount of tokens that strategy have lost
26
28
uint256 public debtRatio; // Debt ratio for the Vault (in BPS, <= 10k)
29
+ uint128 public lastRebalanceTimestamp; // Timstamp of last rebalance
30
+ uint128 public rebalanceingDelay; // Delay between rebalances
27
31
address public strategy; // address of the strategy contract
32
+ bool public emergencyShutdown; // if true, no funds can be invested in the strategy
33
+
28
34
uint256 public constant MAX_BPS = 10_000 ;
29
35
struct UpdateLimitParams {
30
36
bool isLock;
@@ -36,9 +42,9 @@ contract YVault is Gauge, Ownable2Step, ERC4626 {
36
42
error ConnectorUnavailable ();
37
43
error ZeroAmount ();
38
44
error DebtRatioTooHigh ();
39
- error ZeroAddress ();
40
45
error InvestingAboveThreshold ();
41
46
error NotEnoughAssets ();
47
+ error VaultShutdown ();
42
48
43
49
event LimitParamsUpdated (UpdateLimitParams[] updates );
44
50
event TokensDeposited (address depositor , uint256 depositAmount );
@@ -68,20 +74,19 @@ contract YVault is Gauge, Ownable2Step, ERC4626 {
68
74
address receiver ,
69
75
uint256 unlockedAmount
70
76
);
71
- event WithdrawFromStrategy (uint256 withdrawn , uint256 loss );
72
-
73
- event StrategyReported (
74
- address strategy ,
75
- uint256 profit ,
76
- uint256 loss ,
77
- uint256 debtPayment ,
78
- uint256 totalProfit ,
79
- uint256 totalLoss ,
77
+ event WithdrawFromStrategy (uint256 withdrawn );
78
+
79
+ event Rebalanced (
80
+ uint256 totalIdle ,
80
81
uint256 totalDebt ,
81
82
uint256 credit ,
82
- uint256 debtRatio
83
+ uint256 debtOutstanding
83
84
);
84
85
86
+ modifier notShutdown () {
87
+ if (emergencyShutdown) revert VaultShutdown ();
88
+ _;
89
+ }
85
90
constructor (
86
91
address token_ ,
87
92
string memory name_ ,
@@ -100,6 +105,14 @@ contract YVault is Gauge, Ownable2Step, ERC4626 {
100
105
strategy = strategy_;
101
106
}
102
107
108
+ function setRebalanceingDelay (address rebalanceingDelay_ ) external onlyOwner {
109
+ rebalanceingDelay = rebalanceingDelay_;
110
+ }
111
+
112
+ function updateEmergencyShutdownState (bool shutdownState_ ) external onlyOwner {
113
+ emergencyShutdown = shutdownState_;
114
+ }
115
+
103
116
/// @notice Returns the total quantity of all assets under control of this
104
117
/// Vault, whether they're loaned out to a Strategy, or currently held in
105
118
/// the Vault.
@@ -117,144 +130,80 @@ contract YVault is Gauge, Ownable2Step, ERC4626 {
117
130
function deposit (
118
131
uint256 assets_ ,
119
132
address receiver_
120
- ) public override returns (uint256 ) {
133
+ ) public override nonReentrant notShutdown returns (uint256 ) {
121
134
if (receiver_ == address (0 )) revert ZeroAddress ();
122
135
totalIdle += assets_;
136
+ _checkDelayAndRebalance ();
123
137
return super .deposit (assets_, receiver_);
124
138
}
125
139
126
140
function withdraw (
127
141
uint256 assets_ ,
128
142
address receiver_ ,
129
143
address owner_
130
- ) public override returns (uint256 ) {
144
+ ) external override nonReentrant notShutdown returns (uint256 ) {
131
145
if (receiver_ == address (0 )) revert ZeroAddress ();
132
146
if (assets_ > totalIdle) revert NotEnoughAssets ();
133
147
134
148
totalIdle -= assets_;
149
+ _checkDelayAndRebalance ();
135
150
return super .withdraw (assets_, receiver_, owner_);
136
151
}
137
152
138
153
function withdrawFromStrategy (
139
154
uint256 assets_
140
155
) external onlyOwner returns (uint256 ) {
156
+ _witndrawFromStrategy (assets_);
157
+ }
158
+
159
+ function _withdrawFromStrategy (
160
+ uint256 assets_
161
+ ) internal returns (uint256 ) {
141
162
uint256 preBalance = token__.balanceOf (address (this ));
142
- uint256 loss = IStrategy (strategy).withdraw (assets_);
163
+ IStrategy (strategy).withdraw (assets_);
143
164
uint256 withdrawn = token__.balanceOf (address (this )) - preBalance;
144
165
totalIdle += withdrawn;
145
166
totalDebt -= withdrawn;
146
- if (loss > 0 ) _reportLoss (loss);
147
- emit WithdrawFromStrategy (withdrawn, loss);
167
+ emit WithdrawFromStrategy (withdrawn);
148
168
return withdrawn;
149
169
}
150
170
171
+
151
172
function maxAvailableShares () public view returns (uint256 ) {
152
173
return convertToShares (_totalAssets ());
153
174
}
154
175
155
- function report (
156
- uint256 profit_ ,
157
- uint256 loss_ ,
158
- uint256 debtPayment_
159
- ) external returns (uint256 ) {
160
- // Only approved strategies can call this function
161
- require (msg .sender == strategy, "Not a strategy " );
176
+ function rebalance () external notShutdown returns (uint256 ) {
177
+ _rebalance ();
178
+ }
162
179
163
- // No lying about total available to withdraw
164
- require (
165
- token__.balanceOf (msg .sender ) >= profit_ + debtPayment_,
166
- "Insufficient balance for reporting "
167
- );
180
+ function _checkDelayAndRebalance () internal returns (uint256 ) {
168
181
169
- // We have a loss to report, do it before the rest of the calculations
170
- if (loss_ > 0 ) _reportLoss (loss_);
182
+ uint128 timeElapsed = uint128 (block .timestamp ) - lastRebalanceTimestamp;
183
+ if (timeElapsed >= rebalanceingDelay) {
184
+ return _rebalance ();
185
+ }
186
+ }
171
187
172
- // Returns are always "realized profits"
173
- totalProfit += profit_;
188
+ function _rebalance () internal returns (uint256 ) {
174
189
175
190
// Compute the line of credit the Vault is able to offer the Strategy (if any)
176
191
uint256 credit = _creditAvailable ();
192
+ uint256 pendingDebt = _debtOutstanding ();
177
193
178
- // Outstanding debt the Strategy wants to take back from the Vault (if any)
179
- // debtOutstanding <= StrategyParams.totalDebt
180
- uint256 debt = _debtOutstanding ();
181
- uint256 debtPayment = Math.min (debtPayment_, debt);
182
-
183
- if (debtPayment > 0 ) {
184
- totalDebt -= debtPayment;
185
- debt -= debtPayment;
186
- }
187
-
188
- // Update the actual debt based on the full credit we are extending to the Strategy
189
- // or the returns if we are taking funds back
190
- // credit + strategies[msg.sender].totalDebt is always < debtLimit
191
- // At least one of credit or debt is always 0 (both can be 0)
192
- if (credit > 0 ) totalDebt += credit;
193
-
194
- // Give/take balance to Strategy, based on the difference between the reported profits
195
- // (if any), the debt payment (if any), the credit increase we are offering (if any),
196
- // and the debt needed to be paid off (if any)
197
- // This is just used to adjust the balance of tokens between the Strategy and
198
- // the Vault based on the Strategy's debt limit (as well as the Vault's).
199
- uint256 totalAvailable = profit_ + debtPayment;
200
-
201
- if (totalAvailable < credit) {
194
+ if (credit> 0 ) {
202
195
// Credit surplus, give to Strategy
203
- totalIdle -= credit - totalAvailable;
204
- token__.safeTransfer (msg .sender , credit - totalAvailable);
205
- } else if (totalAvailable > credit) {
206
- // Credit deficit, take from Strategy
207
- totalIdle += totalAvailable - credit;
208
- token__.safeTransferFrom (
209
- msg .sender ,
210
- address (this ),
211
- totalAvailable - credit
212
- );
213
- }
214
- // else, don't do anything because it is balanced
215
-
216
- // Profit is locked and gradually released per block
217
- // compute current locked profit and replace with sum of current and new
218
- // uint256 lockedProfitBeforeLoss = _calculateLockedProfit() + profit - totalFees;
219
-
220
- // if (lockedProfitBeforeLoss > loss) {
221
- // lockedProfit = lockedProfitBeforeLoss - loss;
222
- // } else {
223
- // lockedProfit = 0;
224
- // }
225
-
226
- // Update reporting time
227
- // strategies[msg.sender].lastReport = block.timestamp;
228
- // lastReport = block.timestamp;
229
-
230
- emit StrategyReported (
231
- msg .sender ,
232
- profit_,
233
- loss_,
234
- debtPayment,
235
- totalProfit,
236
- totalLoss,
237
- totalDebt,
238
- credit,
239
- debtRatio
240
- );
196
+ totalIdle -= credit;
197
+ totalDebt += credit;
198
+ token__.safeTransfer (strategy, credit);
199
+ IStrategy (strategy).invest ();
241
200
242
- if (debtRatio == 0 ) {
243
- // Take every last penny the Strategy has (Emergency Exit/revokeStrategy)
244
- // This is different than debt in order to extract *all* of the returns
245
- return IStrategy (msg .sender ).estimatedTotalAssets ();
246
- } else {
247
- // Otherwise, just return what we have as debt outstanding
248
- return debt;
201
+ } else if (pendingDebt > 0 ) {
202
+ // Credit deficit, take from Strategy
203
+ _withdrawFromStrategy (pendingDebt);
249
204
}
250
- }
251
205
252
- function _reportLoss (uint256 loss ) internal {
253
- // Loss can only be up to the amount of debt issued to strategy
254
- require (totalDebt >= loss, "Loss exceeds total debt " );
255
- // Adjust strategy's parameters by the loss
256
- totalLoss += loss;
257
- totalDebt -= loss;
206
+ emit Rebalanced (totalIdle, totalDebt, credit, debtOutstanding);
258
207
}
259
208
260
209
function _creditAvailable () internal view returns (uint256 ) {
0 commit comments