@@ -6,7 +6,7 @@ import { IVault } from "./_interfaces/IVault.sol";
6
6
import { IRolloverVault } from "./_interfaces/IRolloverVault.sol " ;
7
7
import { IERC20Burnable } from "./_interfaces/IERC20Burnable.sol " ;
8
8
import { TokenAmount, RolloverData, SubscriptionParams } from "./_interfaces/CommonTypes.sol " ;
9
- import { UnauthorizedCall, UnauthorizedTransferOut, UnexpectedDecimals, UnexpectedAsset, OutOfBounds, UnacceptableSwap, InsufficientDeployment, DeployedCountOverLimit, InvalidPerc, InsufficientLiquidity } from "./_interfaces/ProtocolErrors.sol " ;
9
+ import { UnauthorizedCall, UnauthorizedTransferOut, UnexpectedDecimals, UnexpectedAsset, OutOfBounds, UnacceptableSwap, InsufficientDeployment, DeployedCountOverLimit, InsufficientLiquidity } from "./_interfaces/ProtocolErrors.sol " ;
10
10
11
11
import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol " ;
12
12
import { PausableUpgradeable } from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol " ;
@@ -84,6 +84,10 @@ contract RolloverVault is
84
84
/// @dev The maximum number of deployed assets that can be held in this vault at any given time.
85
85
uint8 public constant MAX_DEPLOYED_COUNT = 47 ;
86
86
87
+ // Replicating value used here:
88
+ // https://github.com/buttonwood-protocol/tranche/blob/main/contracts/BondController.sol
89
+ uint256 private constant TRANCHE_RATIO_GRANULARITY = 1000 ;
90
+
87
91
/// @dev Immature redemption may result in some dust tranches when balances are not perfectly divisible by the tranche ratio.
88
92
/// Based on current the implementation of `computeRedeemableTrancheAmounts`,
89
93
/// the dust balances which remain after immature redemption will be *at most* {TRANCHE_RATIO_GRANULARITY} or 1000.
@@ -128,16 +132,24 @@ contract RolloverVault is
128
132
/// @return The address of the keeper.
129
133
address public keeper;
130
134
131
- /// @notice The enforced minimum absolute balance of underlying tokens to be held by the vault.
132
- /// @dev On deployment only the delta greater than this balance is deployed.
133
- /// `minUnderlyingBal` is enforced on deployment and swapping operations which reduce the underlying balance.
134
- /// This parameter ensures that the vault's tvl is never too low, which guards against the "share" manipulation attack.
135
- uint256 public minUnderlyingBal;
136
-
137
- /// @notice The enforced minimum percentage of the vault's value to be held as underlying tokens.
138
- /// @dev The percentage minimum is enforced after swaps which reduce the vault's underlying token liquidity.
139
- /// This ensures that the vault has sufficient liquid underlying tokens for upcoming rollovers.
140
- uint256 public minUnderlyingPerc;
135
+ //--------------------------------------------------------------------------
136
+ // The reserved liquidity is the subset of the vault's underlying tokens that it
137
+ // does not deploy for rolling over (or used for swaps) and simply holds.
138
+ // The existence of sufficient reserved liquidity ensures that
139
+ // a) The vault's TVL never goes too low and guards against the "share" manipulation attack.
140
+ // b) Not all of the vault's liquidity is locked up in tranches.
141
+
142
+ /// @notice The absolute amount of underlying tokens, reserved.
143
+ /// @custom:oz-upgrades-renamed-from minUnderlyingBal
144
+ uint256 public reservedUnderlyingBal;
145
+
146
+ /// @notice The percentage of the vault's "neutrally" subscribed TVL, reserved.
147
+ /// @dev A neutral subscription state implies the vault's TVL is exactly enough to
148
+ /// rollover over the entire supply of perp tokens.
149
+ /// NOTE: A neutral subscription ratio of 1.0 is distinct from a deviation ratio (dr) of 1.0.
150
+ /// For more details, refer to the fee policy documentation.
151
+ /// @custom:oz-upgrades-renamed-from minUnderlyingPerc
152
+ uint256 public reservedSubscriptionPerc;
141
153
142
154
//--------------------------------------------------------------------------
143
155
// Modifiers
@@ -190,8 +202,8 @@ contract RolloverVault is
190
202
191
203
// setting initial parameter values
192
204
minDeploymentAmt = 0 ;
193
- minUnderlyingBal = 0 ;
194
- minUnderlyingPerc = ONE / 3 ; // 33%
205
+ reservedUnderlyingBal = 0 ;
206
+ reservedSubscriptionPerc = 0 ;
195
207
196
208
// sync underlying
197
209
_syncAsset (underlying);
@@ -247,19 +259,16 @@ contract RolloverVault is
247
259
minDeploymentAmt = minDeploymentAmt_;
248
260
}
249
261
250
- /// @notice Updates the minimum underlying balance requirement (Absolute number of underlying tokens) .
251
- /// @param minUnderlyingBal_ The new minimum underlying balance.
252
- function updateMinUnderlyingBal (uint256 minUnderlyingBal_ ) external onlyKeeper {
253
- minUnderlyingBal = minUnderlyingBal_ ;
262
+ /// @notice Updates absolute reserved underlying balance.
263
+ /// @param reservedUnderlyingBal_ The new reserved underlying balance.
264
+ function updateReservedUnderlyingBal (uint256 reservedUnderlyingBal_ ) external onlyKeeper {
265
+ reservedUnderlyingBal = reservedUnderlyingBal_ ;
254
266
}
255
267
256
- /// @notice Updates the minimum underlying percentage requirement (Expressed as a percentage).
257
- /// @param minUnderlyingPerc_ The new minimum underlying percentage.
258
- function updateMinUnderlyingPerc (uint256 minUnderlyingPerc_ ) external onlyKeeper {
259
- if (minUnderlyingPerc_ > ONE) {
260
- revert InvalidPerc ();
261
- }
262
- minUnderlyingPerc = minUnderlyingPerc_;
268
+ /// @notice Updates the reserved subscription percentage.
269
+ /// @param reservedSubscriptionPerc_ The new reserved subscription percentage.
270
+ function updateReservedSubscriptionPerc (uint256 reservedSubscriptionPerc_ ) external onlyKeeper {
271
+ reservedSubscriptionPerc = reservedSubscriptionPerc_;
263
272
}
264
273
265
274
//--------------------------------------------------------------------------
@@ -274,20 +283,19 @@ contract RolloverVault is
274
283
275
284
/// @inheritdoc IVault
276
285
/// @dev Its safer to call `recover` before `deploy` so the full available balance can be deployed.
277
- /// The vault holds `minUnderlyingBal` as underlying tokens and deploys the rest.
286
+ /// The vault holds the reserved balance of underlying tokens and deploys the rest.
278
287
/// Reverts if no funds are rolled over or enforced deployment threshold is not reached.
279
288
function deploy () public override nonReentrant whenNotPaused {
280
289
IERC20Upgradeable underlying_ = underlying;
281
290
IPerpetualTranche perp_ = perp;
282
291
283
- // `minUnderlyingBal` worth of underlying liquidity is excluded from the usable balance
284
- uint256 usableBal = underlying_.balanceOf (address (this ));
285
- if (usableBal <= minUnderlyingBal) {
286
- revert InsufficientLiquidity ();
287
- }
288
- usableBal -= minUnderlyingBal;
292
+ // We calculate the usable underlying balance.
293
+ uint256 underlyingBal = underlying_.balanceOf (address (this ));
294
+ uint256 reservedBal = _totalReservedBalance (perp_.getTVL (), perp_.getDepositTrancheRatio ());
295
+ uint256 usableBal = (underlyingBal > reservedBal) ? underlyingBal - reservedBal : 0 ;
289
296
290
297
// We ensure that at-least `minDeploymentAmt` amount of underlying tokens are deployed
298
+ // (i.e used productively for rollovers).
291
299
if (usableBal <= minDeploymentAmt) {
292
300
revert InsufficientDeployment ();
293
301
}
@@ -469,13 +477,12 @@ contract RolloverVault is
469
477
// NOTE: In case this operation mints slightly more perps than that are required for the swap,
470
478
// The vault continues to hold the perp dust until the subsequent `swapPerpsForUnderlying` or manual `recover(perp)`.
471
479
472
- // If vault liquidity has reduced, revert if it reduced too much.
473
- // - Absolute balance is strictly greater than `minUnderlyingBal`.
474
- // - Ratio of the balance to the vault's TVL is strictly greater than `minUnderlyingPerc`.
480
+ // We ensure that the vault's underlying token liquidity
481
+ // remains above the reserved level after swap.
475
482
uint256 underlyingBalPost = underlying_.balanceOf (address (this ));
476
483
if (
477
- underlyingBalPost < underlyingBalPre &&
478
- (underlyingBalPost <= minUnderlyingBal || underlyingBalPost. mulDiv (ONE , s.vaultTVL) <= minUnderlyingPerc )
484
+ ( underlyingBalPost < underlyingBalPre) &&
485
+ (underlyingBalPost <= _totalReservedBalance ((s.perpTVL + underlyingAmtIn) , s.seniorTR) )
479
486
) {
480
487
revert InsufficientLiquidity ();
481
488
}
@@ -973,4 +980,13 @@ contract RolloverVault is
973
980
(uint256 trancheClaim , uint256 trancheSupply ) = tranche.getTrancheCollateralization (collateralToken);
974
981
return trancheClaim.mulDiv (trancheAmt, trancheSupply, MathUpgradeable.Rounding.Up);
975
982
}
983
+
984
+ /// @dev Computes the balance of underlying tokens to NOT be used for any operation.
985
+ function _totalReservedBalance (uint256 perpTVL , uint256 seniorTR ) private view returns (uint256 ) {
986
+ return
987
+ MathUpgradeable.max (
988
+ reservedUnderlyingBal,
989
+ perpTVL.mulDiv (TRANCHE_RATIO_GRANULARITY - seniorTR, seniorTR).mulDiv (reservedSubscriptionPerc, ONE)
990
+ );
991
+ }
976
992
}
0 commit comments