@@ -6,7 +6,7 @@ import { IVault } from "./_interfaces/IVault.sol";
66import { IRolloverVault } from "./_interfaces/IRolloverVault.sol " ;
77import { IERC20Burnable } from "./_interfaces/IERC20Burnable.sol " ;
88import { 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 " ;
1010
1111import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol " ;
1212import { PausableUpgradeable } from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol " ;
@@ -84,6 +84,10 @@ contract RolloverVault is
8484 /// @dev The maximum number of deployed assets that can be held in this vault at any given time.
8585 uint8 public constant MAX_DEPLOYED_COUNT = 47 ;
8686
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+
8791 /// @dev Immature redemption may result in some dust tranches when balances are not perfectly divisible by the tranche ratio.
8892 /// Based on current the implementation of `computeRedeemableTrancheAmounts`,
8993 /// the dust balances which remain after immature redemption will be *at most* {TRANCHE_RATIO_GRANULARITY} or 1000.
@@ -128,16 +132,24 @@ contract RolloverVault is
128132 /// @return The address of the keeper.
129133 address public keeper;
130134
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;
141153
142154 //--------------------------------------------------------------------------
143155 // Modifiers
@@ -190,8 +202,8 @@ contract RolloverVault is
190202
191203 // setting initial parameter values
192204 minDeploymentAmt = 0 ;
193- minUnderlyingBal = 0 ;
194- minUnderlyingPerc = ONE / 3 ; // 33%
205+ reservedUnderlyingBal = 0 ;
206+ reservedSubscriptionPerc = 0 ;
195207
196208 // sync underlying
197209 _syncAsset (underlying);
@@ -247,19 +259,16 @@ contract RolloverVault is
247259 minDeploymentAmt = minDeploymentAmt_;
248260 }
249261
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_ ;
254266 }
255267
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_;
263272 }
264273
265274 //--------------------------------------------------------------------------
@@ -274,20 +283,19 @@ contract RolloverVault is
274283
275284 /// @inheritdoc IVault
276285 /// @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.
278287 /// Reverts if no funds are rolled over or enforced deployment threshold is not reached.
279288 function deploy () public override nonReentrant whenNotPaused {
280289 IERC20Upgradeable underlying_ = underlying;
281290 IPerpetualTranche perp_ = perp;
282291
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 ;
289296
290297 // We ensure that at-least `minDeploymentAmt` amount of underlying tokens are deployed
298+ // (i.e used productively for rollovers).
291299 if (usableBal <= minDeploymentAmt) {
292300 revert InsufficientDeployment ();
293301 }
@@ -469,13 +477,12 @@ contract RolloverVault is
469477 // NOTE: In case this operation mints slightly more perps than that are required for the swap,
470478 // The vault continues to hold the perp dust until the subsequent `swapPerpsForUnderlying` or manual `recover(perp)`.
471479
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.
475482 uint256 underlyingBalPost = underlying_.balanceOf (address (this ));
476483 if (
477- underlyingBalPost < underlyingBalPre &&
478- (underlyingBalPost <= minUnderlyingBal || underlyingBalPost. mulDiv (ONE , s.vaultTVL) <= minUnderlyingPerc )
484+ ( underlyingBalPost < underlyingBalPre) &&
485+ (underlyingBalPost <= _totalReservedBalance ((s.perpTVL + underlyingAmtIn) , s.seniorTR) )
479486 ) {
480487 revert InsufficientLiquidity ();
481488 }
@@ -973,4 +980,13 @@ contract RolloverVault is
973980 (uint256 trancheClaim , uint256 trancheSupply ) = tranche.getTrancheCollateralization (collateralToken);
974981 return trancheClaim.mulDiv (trancheAmt, trancheSupply, MathUpgradeable.Rounding.Up);
975982 }
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+ }
976992}
0 commit comments