@@ -360,7 +360,58 @@ contract MultistrategyLockedVault is MultistrategyVault, IMultistrategyLockedVau
360360 }
361361
362362 /**
363- * @notice Override withdrawal functions to handle custodied shares
363+ * @notice Withdraws assets with default parameters (maxLoss = 10000, default queue)
364+ * @dev Overload to match Vyper's default parameters behavior
365+ * Enforces custody withdrawal rules - requires active rage quit with cooldown passed
366+ * @param assets Amount of assets to withdraw
367+ * @param receiver Address to receive the withdrawn assets
368+ * @param owner Address whose shares will be burned
369+ * @return shares Amount of shares actually burned from owner
370+ * @custom:security Requires active custody and cooldown period passed
371+ */
372+ function withdraw (
373+ uint256 assets ,
374+ address receiver ,
375+ address owner
376+ ) external override (MultistrategyVault, IMultistrategyVault) nonReentrant returns (uint256 ) {
377+ uint256 shares = _convertToShares (assets, Rounding.ROUND_UP);
378+ _processCustodyWithdrawal (owner, shares);
379+ _redeem (msg .sender , receiver, owner, assets, shares, 0 , new address [](0 ));
380+ return shares;
381+ }
382+
383+ /**
384+ * @notice Withdraws assets from the vault with custody enforcement
385+ * @dev ERC4626-extended withdraw function with custody checks
386+ *
387+ * CUSTODY REQUIREMENTS:
388+ * - Owner must have initiated rage quit (active custody)
389+ * - Cooldown period must have passed
390+ * - Withdrawal amount cannot exceed custodied shares
391+ * - Multiple partial withdrawals allowed from same custody
392+ *
393+ * CONVERSION:
394+ * - Uses ROUND_UP when calculating shares (favors vault)
395+ * - shares = (assets * totalSupply + totalAssets - 1) / totalAssets
396+ *
397+ * BEHAVIOR:
398+ * - Validates custody status before processing withdrawal
399+ * - Updates custody by reducing locked shares
400+ * - Clears custody when all locked shares withdrawn
401+ * - Follows standard withdrawal flow after custody checks
402+ *
403+ * LOSS HANDLING:
404+ * - maxLoss_ = 0: Revert if any loss
405+ * - maxLoss_ = 10000: Accept any loss (100%)
406+ * - Users receive proportional share of unrealized losses
407+ *
408+ * @param assets Amount of assets to withdraw
409+ * @param receiver Address to receive the withdrawn assets
410+ * @param owner Address whose shares will be burned
411+ * @param maxLoss Maximum acceptable loss in basis points (0-10000)
412+ * @param strategiesArray Optional custom withdrawal queue (empty = use default)
413+ * @return shares Amount of shares actually burned from owner
414+ * @custom:security Reentrancy protected, requires active custody
364415 */
365416 function withdraw (
366417 uint256 assets ,
@@ -376,7 +427,58 @@ contract MultistrategyLockedVault is MultistrategyVault, IMultistrategyLockedVau
376427 }
377428
378429 /**
379- * @notice Override redeem function to handle custodied shares
430+ * @notice Redeems shares with default parameters (maxLoss = 10000, default queue)
431+ * @dev Overload to match Vyper's default parameters behavior
432+ * Enforces custody withdrawal rules - requires active rage quit with cooldown passed
433+ * @param shares Exact amount of shares to burn
434+ * @param receiver Address to receive the withdrawn assets
435+ * @param owner Address whose shares will be burned
436+ * @return assets Amount of assets actually withdrawn and sent to receiver
437+ * @custom:security Requires active custody and cooldown period passed
438+ */
439+ function redeem (
440+ uint256 shares ,
441+ address receiver ,
442+ address owner
443+ ) external override (MultistrategyVault, IMultistrategyVault) nonReentrant returns (uint256 ) {
444+ _processCustodyWithdrawal (owner, shares);
445+ uint256 assets = _convertToAssets (shares, Rounding.ROUND_DOWN);
446+ return _redeem (msg .sender , receiver, owner, assets, shares, 10_000 , new address [](0 ));
447+ }
448+
449+ /**
450+ * @notice Redeems exact amount of shares for assets with custody enforcement
451+ * @dev ERC4626-extended redeem function with custody checks
452+ *
453+ * CUSTODY REQUIREMENTS:
454+ * - Owner must have initiated rage quit (active custody)
455+ * - Cooldown period must have passed
456+ * - Redemption amount cannot exceed custodied shares
457+ * - Multiple partial redemptions allowed from same custody
458+ *
459+ * CONVERSION:
460+ * - Uses ROUND_DOWN when calculating assets (favors vault)
461+ * - assets = (shares * totalAssets) / totalSupply
462+ *
463+ * BEHAVIOR:
464+ * - Validates custody status before processing redemption
465+ * - Burns exact shares amount from owner
466+ * - Updates custody by reducing locked shares
467+ * - Clears custody when all locked shares redeemed
468+ * - May return less assets than expected if losses occur
469+ *
470+ * DIFFERENCE FROM WITHDRAW:
471+ * - withdraw(): User specifies assets, function calculates shares
472+ * - redeem(): User specifies shares, function calculates assets
473+ * - redeem() may return less assets than preview if losses occur
474+ *
475+ * @param shares Exact amount of shares to burn
476+ * @param receiver Address to receive the withdrawn assets
477+ * @param owner Address whose shares will be burned
478+ * @param maxLoss Maximum acceptable loss in basis points (0-10000)
479+ * @param strategiesArray Optional custom withdrawal queue (empty = use default)
480+ * @return assets Amount of assets actually withdrawn and sent to receiver
481+ * @custom:security Reentrancy protected, requires active custody
380482 */
381483 function redeem (
382484 uint256 shares ,
@@ -466,7 +568,7 @@ contract MultistrategyLockedVault is MultistrategyVault, IMultistrategyLockedVau
466568 */
467569 function _transfer (address sender_ , address receiver_ , uint256 amount_ ) internal override {
468570 // Check if sender has locked shares that would prevent this transfer
469- CustodyInfo memory custody = custodyInfo[sender_];
571+ CustodyInfo storage custody = custodyInfo[sender_];
470572
471573 if (custody.lockedShares > 0 ) {
472574 uint256 senderBalance = balanceOf (sender_);
@@ -495,9 +597,9 @@ contract MultistrategyLockedVault is MultistrategyVault, IMultistrategyLockedVau
495597 function maxWithdraw (
496598 address owner_ ,
497599 uint256 maxLoss_ ,
498- address [] calldata strategiesArray_
499- ) external view override (MultistrategyVault, IMultistrategyVault) returns (uint256 ) {
500- CustodyInfo memory custody = custodyInfo[owner_];
600+ address [] memory strategiesArray_
601+ ) public view override (MultistrategyVault, IMultistrategyVault) returns (uint256 ) {
602+ CustodyInfo storage custody = custodyInfo[owner_];
501603 if (block .timestamp < custody.unlockTime) {
502604 return 0 ;
503605 }
@@ -512,6 +614,35 @@ contract MultistrategyLockedVault is MultistrategyVault, IMultistrategyLockedVau
512614 return Math.min (parentMax, custodyAssets);
513615 }
514616
617+ /**
618+ * @notice Returns maximum assets that owner can withdraw with custom loss tolerance
619+ * @dev Uses default queue for withdrawal strategies
620+ * @param owner_ Address that owns the shares
621+ * @param maxLoss_ Maximum acceptable loss in basis points (0-10000)
622+ * @return max Maximum withdrawable assets (constrained by custody)
623+ */
624+
625+ function maxWithdraw (
626+ address owner_ ,
627+ uint256 maxLoss_
628+ ) public view override (MultistrategyVault, IMultistrategyVault) returns (uint256 ) {
629+ return maxWithdraw (owner_, maxLoss_, new address [](0 ));
630+ }
631+
632+ /**
633+ * @notice Returns maximum assets that owner can withdraw with default parameters
634+ * @dev Overload to match Vyper's default parameters behavior (maxLoss = 0, default queue)
635+ * Enforces custody constraints - returns 0 if cooldown period not passed
636+ * @param owner_ Address that owns the shares
637+ * @return max Maximum withdrawable assets (constrained by custody)
638+ */
639+
640+ function maxWithdraw (
641+ address owner_
642+ ) external view override (MultistrategyVault, IMultistrategyVault) returns (uint256 ) {
643+ return maxWithdraw (owner_, 0 , new address [](0 ));
644+ }
645+
515646 /**
516647 * @notice Get the maximum amount of shares that can be redeemed by an owner
517648 * @param owner_ Address owning shares to check redemption limits for
@@ -525,9 +656,9 @@ contract MultistrategyLockedVault is MultistrategyVault, IMultistrategyLockedVau
525656 function maxRedeem (
526657 address owner_ ,
527658 uint256 maxLoss_ ,
528- address [] calldata strategiesArray_
529- ) external view override (MultistrategyVault, IMultistrategyVault) returns (uint256 ) {
530- CustodyInfo memory custody = custodyInfo[owner_];
659+ address [] memory strategiesArray_
660+ ) public view override (MultistrategyVault, IMultistrategyVault) returns (uint256 ) {
661+ CustodyInfo storage custody = custodyInfo[owner_];
531662
532663 if (block .timestamp < custody.unlockTime) {
533664 return 0 ;
@@ -547,29 +678,29 @@ contract MultistrategyLockedVault is MultistrategyVault, IMultistrategyLockedVau
547678 }
548679
549680 /**
550- * @notice Get the amount of shares that can be transferred by a user
551- * @param user Address to check transferable shares for
552- * @return Amount of shares available for transfer (not locked in custody)
553- * @dev Returns total balance minus shares currently locked in custody
681+ * @notice Returns maximum shares that owner can redeem with default parameters
682+ * @dev Overload to match Vyper's default parameters behavior (maxLoss = MAX_BPS, default queue)
683+ * Enforces custody constraints - returns 0 if cooldown period not passed
684+ * @param owner_ Address that owns the shares
685+ * @param maxLoss_ Maximum acceptable loss in basis points (0-10000)
686+ * @return max Maximum redeemable shares (constrained by custody)
554687 */
555- function getTransferableShares (address user ) external view returns (uint256 ) {
556- uint256 totalShares = balanceOf (user);
557- uint256 lockedShares = custodyInfo[user].lockedShares;
558- return totalShares - lockedShares;
688+
689+ function maxRedeem (
690+ address owner_ ,
691+ uint256 maxLoss_
692+ ) public view override (MultistrategyVault, IMultistrategyVault) returns (uint256 ) {
693+ return maxRedeem (owner_, maxLoss_, new address [](0 ));
559694 }
560695
561696 /**
562- * @notice Get the amount of shares available for rage quit initiation
563- * @param user Address to check rage quitable shares for
564- * @return Amount of shares available for initiating rage quit
565- * @dev Returns 0 if user already has active custody, otherwise returns full balance
697+ * @notice Returns maximum shares that owner can redeem with default parameters
698+ * @dev Overload to match Vyper's default parameters behavior (maxLoss = MAX_BPS, default queue)
699+ * Enforces custody constraints - returns 0 if cooldown period not passed
700+ * @param owner_ Address that owns the shares
701+ * @return max Maximum redeemable shares (constrained by custody)
566702 */
567- function getRageQuitableShares (address user ) external view returns (uint256 ) {
568- // If user already has active custody, they cannot initiate new rage quit
569- if (custodyInfo[user].lockedShares > 0 ) {
570- return 0 ;
571- }
572- // Otherwise, they can rage quit all their shares
573- return balanceOf (user);
703+ function maxRedeem (address owner_ ) public view override (MultistrategyVault, IMultistrategyVault) returns (uint256 ) {
704+ return maxRedeem (owner_, MAX_BPS, new address [](0 ));
574705 }
575706}
0 commit comments