diff --git a/src/TokenizedStrategy.sol b/src/TokenizedStrategy.sol index c79822c..b913901 100644 --- a/src/TokenizedStrategy.sol +++ b/src/TokenizedStrategy.sol @@ -780,6 +780,17 @@ contract TokenizedStrategy { return _maxWithdraw(_strategyStorage(), owner); } + /** + * @notice Accepts a `maxLoss` variable in order to match the multi + * strategy vaults ABI. + */ + function maxWithdraw( + address owner, + uint256 /*maxLoss*/ + ) external view returns (uint256) { + return _maxWithdraw(_strategyStorage(), owner); + } + /** * @notice Total number of strategy shares that can be * redeemed from the strategy by `owner`, where `owner` @@ -792,6 +803,17 @@ contract TokenizedStrategy { return _maxRedeem(_strategyStorage(), owner); } + /** + * @notice Accepts a `maxLoss` variable in order to match the multi + * strategy vaults ABI. + */ + function maxRedeem( + address owner, + uint256 /*maxLoss*/ + ) external view returns (uint256) { + return _maxRedeem(_strategyStorage(), owner); + } + /*////////////////////////////////////////////////////////////// INTERNAL 4626 VIEW METHODS //////////////////////////////////////////////////////////////*/ diff --git a/src/interfaces/ITokenizedStrategy.sol b/src/interfaces/ITokenizedStrategy.sol index 8e10d22..d68dd78 100644 --- a/src/interfaces/ITokenizedStrategy.sol +++ b/src/interfaces/ITokenizedStrategy.sol @@ -72,6 +72,16 @@ interface ITokenizedStrategy is IERC4626, IERC20Permit { uint256 maxLoss ) external returns (uint256); + function maxWithdraw( + address owner, + uint256 /*maxLoss*/ + ) external view returns (uint256); + + function maxRedeem( + address owner, + uint256 /*maxLoss*/ + ) external view returns (uint256); + /*////////////////////////////////////////////////////////////// MODIFIER HELPERS //////////////////////////////////////////////////////////////*/ diff --git a/src/test/CustomImplementation.t.sol b/src/test/CustomImplementation.t.sol index 95acdc6..ac36be1 100644 --- a/src/test/CustomImplementation.t.sol +++ b/src/test/CustomImplementation.t.sol @@ -100,6 +100,103 @@ contract CustomImplementationsTest is Setup { ); } + function test_customWithdrawLimit_maxLossVariable( + address _address, + uint256 _amount, + uint16 _profitFactor + ) public { + _amount = bound(_amount, minFuzzAmount, maxFuzzAmount); + _profitFactor = uint16(bound(uint256(_profitFactor), 10, MAX_BPS)); + + uint256 profit = (_amount * _profitFactor) / MAX_BPS; + + strategy = IMockStrategy(setUpIlliquidStrategy()); + + vm.assume( + _address != address(0) && + _address != address(strategy) && + _address != address(yieldSource) + ); + + setFees(0, 0); + + mintAndDepositIntoStrategy(strategy, _address, _amount); + + uint256 idle = asset.balanceOf(address(strategy)); + assertGt(idle, 0); + + // Assure we have a withdraw limit + assertEq(strategy.availableWithdrawLimit(_address), idle); + assertGt(strategy.totalAssets(), idle); + + // Make sure max withdraw and redeem return the correct amounts + assertEq(strategy.maxWithdraw(_address, 9), idle); + assertEq( + strategy.maxRedeem(_address, 0), + strategy.convertToShares(idle) + ); + assertLe( + strategy.convertToAssets(strategy.maxRedeem(_address)), + strategy.availableWithdrawLimit(_address) + ); + + vm.expectRevert("ERC4626: redeem more than max"); + vm.prank(_address); + strategy.redeem(_amount, _address, _address); + + vm.expectRevert("ERC4626: withdraw more than max"); + vm.prank(_address); + strategy.withdraw(_amount, _address, _address); + + createAndCheckProfit(strategy, profit, 0, 0); + + increaseTimeAndCheckBuffer(strategy, 5 days, profit / 2); + + idle = asset.balanceOf(address(strategy)); + assertGt(idle, 0); + + // Assure we have a withdraw limit + assertEq(strategy.availableWithdrawLimit(_address), idle); + assertGt(strategy.totalAssets(), idle); + + // Make sure max withdraw and redeem return the correct amounts + assertEq(strategy.maxWithdraw(_address, 69), idle); + assertEq( + strategy.maxRedeem(_address, 2 ** 256 - 1), + strategy.convertToShares(idle) + ); + assertLe( + strategy.convertToAssets(strategy.maxRedeem(_address)), + strategy.availableWithdrawLimit(_address) + ); + + vm.expectRevert("ERC4626: redeem more than max"); + vm.prank(_address); + strategy.redeem(_amount, _address, _address); + + vm.expectRevert("ERC4626: withdraw more than max"); + vm.prank(_address); + strategy.withdraw(_amount, _address, _address); + + uint256 before = asset.balanceOf(_address); + uint256 redeem = strategy.maxRedeem(_address, _amount); + uint256 conversion = strategy.convertToAssets(_amount); + + vm.prank(_address); + strategy.redeem(redeem, _address, _address, 0); + + // We need to give 2 wei rounding buffer + assertApproxEq(strategy.convertToAssets(_amount), conversion, 2); + assertApproxEq(asset.balanceOf(_address) - before, idle, 2); + assertApproxEq(strategy.availableWithdrawLimit(_address), 0, 2); + assertApproxEq(strategy.maxWithdraw(_address, 99), 0, 2); + assertApproxEq(strategy.maxRedeem(_address, 0), 0, 2); + assertLe( + strategy.convertToAssets(strategy.maxRedeem(_address, _amount)), + strategy.availableWithdrawLimit(_address) + ); + } + function test_customDepositLimit( address _allowed, address _notAllowed,