From 51d185c45df5dd008884288e6c2a4838eaa69736 Mon Sep 17 00:00:00 2001 From: Schlagonia Date: Sat, 19 Aug 2023 15:23:11 -0600 Subject: [PATCH 1/5] build: instant profit unlocking --- foundry.toml | 2 +- src/TokenizedStrategy.sol | 35 ++- src/test/AccessControl.t.sol | 5 - src/test/ProfitLocking.t.sol | 473 +++++++++++++++++++++++++++++++++++ 4 files changed, 500 insertions(+), 15 deletions(-) diff --git a/foundry.toml b/foundry.toml index 123be77..a725c02 100644 --- a/foundry.toml +++ b/foundry.toml @@ -11,7 +11,7 @@ remappings = [ fs_permissions = [{ access = "read", path = "./"}] [fuzz] -runs = 10_000 +runs = 10_00 max_test_rejects = 1_000_000 [invariant] diff --git a/src/TokenizedStrategy.sol b/src/TokenizedStrategy.sol index 70cd261..cfaeb43 100644 --- a/src/TokenizedStrategy.sol +++ b/src/TokenizedStrategy.sol @@ -941,6 +941,7 @@ contract TokenizedStrategy { uint256 totalFees; uint256 protocolFees; uint256 sharesToLock; + uint256 _profitMaxUnlockTime = S.profitMaxUnlockTime; // Calculate profit/loss. if (newTotalAssets > oldTotalAssets) { // We have a profit. @@ -978,13 +979,15 @@ contract TokenizedStrategy { } } - // we have a net profit - // lock (profit - fees) - unchecked { - sharesToLock = convertToShares(profit - totalFees); + // we have a net profit. Check if we are locking proifit. + if (_profitMaxUnlockTime != 0) { + // lock (profit - fees) + unchecked { + sharesToLock = convertToShares(profit - totalFees); + } + // Mint the shares to lock the strategy. + _mint(address(this), sharesToLock); } - // Mint the shares to lock the strategy. - _mint(address(this), sharesToLock); // Mint fees shares to recipients. if (performanceFeeShares != 0) { @@ -1036,7 +1039,7 @@ contract TokenizedStrategy { // time of the previously locked shares and the profitMaxUnlockTime. uint256 newProfitLockingPeriod = (previouslyLockedTime + sharesToLock * - S.profitMaxUnlockTime) / totalLockedShares; + _profitMaxUnlockTime) / totalLockedShares; // Calculate how many shares unlock per second. S.profitUnlockingRate = @@ -1450,6 +1453,9 @@ contract TokenizedStrategy { * * Denominated in seconds and cannot be greater than 1 year. * + * NOTE: Setting to 0 will cause all currently locked profit + * to be unlocked instantly and should be done with care. + * * `profitMaxUnlockTime` is stored as a uint32 for packing but can * be passed in as uint256 for simplicity. * @@ -1459,9 +1465,20 @@ contract TokenizedStrategy { uint256 _profitMaxUnlockTime ) external onlyManagement { // Must be greater than 0, and less than a year. - require(_profitMaxUnlockTime != 0, "too short"); + //require(_profitMaxUnlockTime != 0, "too short"); require(_profitMaxUnlockTime <= SECONDS_PER_YEAR, "too long"); - _strategyStorage().profitMaxUnlockTime = uint32(_profitMaxUnlockTime); + StrategyData storage S = _strategyStorage(); + + // If we are setting to 0 we need to adjust amounts. + if (_profitMaxUnlockTime == 0) { + // Burn all shares if applicable. + _burn(address(this), S.balances[address(this)]); + // Reset unlocking variables + S.profitUnlockingRate = 0; + S.fullProfitUnlockDate = 0; + } + + S.profitMaxUnlockTime = uint32(_profitMaxUnlockTime); emit UpdateProfitMaxUnlockTime(_profitMaxUnlockTime); } diff --git a/src/test/AccessControl.t.sol b/src/test/AccessControl.t.sol index 39fe277..3f6b1d9 100644 --- a/src/test/AccessControl.t.sol +++ b/src/test/AccessControl.t.sol @@ -203,11 +203,6 @@ contract AccesssControlTest is Setup { vm.expectRevert("too long"); strategy.setProfitMaxUnlockTime(_badAmount); - // Can't be 0 - vm.prank(management); - vm.expectRevert("too short"); - strategy.setProfitMaxUnlockTime(0); - assertEq(strategy.profitMaxUnlockTime(), profitMaxUnlockTime); } diff --git a/src/test/ProfitLocking.t.sol b/src/test/ProfitLocking.t.sol index 2d11ce1..c8da06d 100644 --- a/src/test/ProfitLocking.t.sol +++ b/src/test/ProfitLocking.t.sol @@ -1368,4 +1368,477 @@ contract ProfitLockingTest is Setup { assertEq(strategy.pricePerShare(), wad, "pps reset"); } + + function test_gain_NoFeesNoBuffer_noLocking( + address _address, + uint128 amount, + uint16 _profitFactor + ) public { + uint256 _amount = bound(uint256(amount), minFuzzAmount, maxFuzzAmount); + _profitFactor = uint16(bound(uint256(_profitFactor), 10, MAX_BPS)); + vm.assume( + _address != address(0) && + _address != address(strategy) && + _address != protocolFeeRecipient && + _address != performanceFeeRecipient && + _address != address(yieldSource) + ); + // set all fees to 0 + uint16 protocolFee = 0; + uint16 performanceFee = 0; + setFees(protocolFee, performanceFee); + + // Set max unlocking time to 0. + vm.prank(management); + strategy.setProfitMaxUnlockTime(0); + assertEq(strategy.profitMaxUnlockTime(), 0); + + mintAndDepositIntoStrategy(strategy, _address, _amount); + // Increase time to simulate interest being earned + increaseTimeAndCheckBuffer(strategy, profitMaxUnlockTime, 0); + + uint256 profit = (_amount * _profitFactor) / MAX_BPS; + uint256 expectedPerformanceFee = (profit * performanceFee) / MAX_BPS; + uint256 expectedProtocolFee = (expectedPerformanceFee * protocolFee) / + MAX_BPS; + + createAndCheckProfit( + strategy, + profit, + expectedProtocolFee, + expectedPerformanceFee + ); + + // All profit should have been unlocked instantly. + assertEq(strategy.profitUnlockingRate(), 0, "!rate"); + assertEq(strategy.fullProfitUnlockDate(), 0, "date"); + assertGt(strategy.pricePerShare(), wad, "!pps"); + assertRelApproxEq( + strategy.pricePerShare(), + wad + ((wad * _profitFactor) / MAX_BPS), + MAX_BPS + ); + + checkStrategyTotals( + strategy, + _amount + profit, + _amount + profit, + 0, + _amount + ); + + // Nothing should change + increaseTimeAndCheckBuffer(strategy, profitMaxUnlockTime / 2, 0); + + checkStrategyTotals( + strategy, + _amount + profit, + _amount + profit, + 0, + _amount + ); + + increaseTimeAndCheckBuffer(strategy, profitMaxUnlockTime / 2, 0); + + assertRelApproxEq( + strategy.pricePerShare(), + wad + ((wad * _profitFactor) / MAX_BPS), + MAX_BPS + ); + + checkStrategyTotals( + strategy, + _amount + profit, + _amount + profit, + 0, + _amount + ); + + vm.prank(_address); + strategy.redeem(_amount, _address, _address); + + checkStrategyTotals(strategy, 0, 0, 0, 0); + + assertEq(strategy.pricePerShare(), wad, "pps reset"); + } + + function test_gain_NoFeesNoBuffer_noLocking_withdrawAll( + address _address, + uint128 amount, + uint16 _profitFactor + ) public { + uint256 _amount = bound(uint256(amount), minFuzzAmount, maxFuzzAmount); + _profitFactor = uint16(bound(uint256(_profitFactor), 10, MAX_BPS)); + vm.assume( + _address != address(0) && + _address != address(strategy) && + _address != protocolFeeRecipient && + _address != performanceFeeRecipient && + _address != address(yieldSource) + ); + // set all fees to 0 + uint16 protocolFee = 0; + uint16 performanceFee = 0; + setFees(protocolFee, performanceFee); + + // Set max unlocking time to 0. + vm.prank(management); + strategy.setProfitMaxUnlockTime(0); + assertEq(strategy.profitMaxUnlockTime(), 0); + + mintAndDepositIntoStrategy(strategy, _address, _amount); + // Increase time to simulate interest being earned + increaseTimeAndCheckBuffer(strategy, profitMaxUnlockTime, 0); + + uint256 profit = (_amount * _profitFactor) / MAX_BPS; + uint256 expectedPerformanceFee = (profit * performanceFee) / MAX_BPS; + uint256 expectedProtocolFee = (expectedPerformanceFee * protocolFee) / + MAX_BPS; + + createAndCheckProfit( + strategy, + profit, + expectedProtocolFee, + expectedPerformanceFee + ); + + // All profit should have been unlocked instantly. + assertEq(strategy.profitUnlockingRate(), 0, "!rate"); + assertEq(strategy.fullProfitUnlockDate(), 0, "date"); + assertGt(strategy.pricePerShare(), wad, "!pps"); + assertRelApproxEq( + strategy.pricePerShare(), + wad + ((wad * _profitFactor) / MAX_BPS), + MAX_BPS + ); + + checkStrategyTotals( + strategy, + _amount + profit, + _amount + profit, + 0, + _amount + ); + + // Should be able to withdaw all right away + uint256 beforeBalance = asset.balanceOf(_address); + + vm.prank(_address); + strategy.redeem(_amount, _address, _address); + + assertEq(asset.balanceOf(_address), beforeBalance + _amount + profit); + + checkStrategyTotals(strategy, 0, 0, 0, 0); + + assertEq(strategy.pricePerShare(), wad, "pps reset"); + } + + function test_gainFees_NoBuffer_noLocking( + address _address, + uint128 amount, + uint16 _profitFactor + ) public { + uint256 _amount = bound(uint256(amount), minFuzzAmount, maxFuzzAmount); + _profitFactor = uint16(bound(uint256(_profitFactor), 10, MAX_BPS)); + vm.assume( + _address != address(0) && + _address != address(strategy) && + _address != protocolFeeRecipient && + _address != performanceFeeRecipient && + _address != address(yieldSource) + ); + // set all fees to 0 + uint16 protocolFee = 1_000; + uint16 performanceFee = 1_000; + setFees(protocolFee, performanceFee); + + // Set max unlocking time to 0. + vm.prank(management); + strategy.setProfitMaxUnlockTime(0); + assertEq(strategy.profitMaxUnlockTime(), 0); + + mintAndDepositIntoStrategy(strategy, _address, _amount); + // Increase time to simulate interest being earned + increaseTimeAndCheckBuffer(strategy, profitMaxUnlockTime, 0); + + uint256 profit = (_amount * _profitFactor) / MAX_BPS; + uint256 expectedPerformanceFee = (profit * performanceFee) / MAX_BPS; + uint256 expectedProtocolFee = (expectedPerformanceFee * protocolFee) / + MAX_BPS; + + uint256 totalExpectedFees = expectedPerformanceFee + + expectedProtocolFee; + + createAndCheckProfit( + strategy, + profit, + expectedProtocolFee, + expectedPerformanceFee + ); + + // All profit should have been unlocked instantly. + assertEq(strategy.profitUnlockingRate(), 0, "!rate"); + assertEq(strategy.fullProfitUnlockDate(), 0, "date"); + assertGt(strategy.pricePerShare(), wad, "!pps"); + + checkStrategyTotals( + strategy, + _amount + profit, + _amount + profit, + 0, + _amount + totalExpectedFees + ); + + vm.prank(_address); + strategy.redeem(_amount, _address, _address); + + uint256 expectedAssetsForFees = strategy.convertToAssets( + totalExpectedFees + ); + checkStrategyTotals( + strategy, + expectedAssetsForFees, + expectedAssetsForFees, + 0, + totalExpectedFees + ); + + if (expectedPerformanceFee > 0) { + assertGt(strategy.pricePerShare(), wad, "pps decreased"); + + vm.prank(performanceFeeRecipient); + strategy.redeem( + expectedPerformanceFee, + performanceFeeRecipient, + performanceFeeRecipient + ); + } + + expectedAssetsForFees = strategy.convertToAssets(expectedProtocolFee); + checkStrategyTotals( + strategy, + expectedAssetsForFees, + expectedAssetsForFees, + 0, + expectedProtocolFee + ); + + if (expectedProtocolFee > 0) { + vm.prank(protocolFeeRecipient); + strategy.redeem( + expectedProtocolFee, + protocolFeeRecipient, + protocolFeeRecipient + ); + } + + checkStrategyTotals(strategy, 0, 0, 0, 0); + + assertEq(strategy.pricePerShare(), wad, "pps reset"); + } + + function test_gainBuffer_noFees_noLocking_resets( + address _address, + uint256 _amount, + uint16 _profitFactor + ) public { + _amount = bound(_amount, minFuzzAmount, maxFuzzAmount); + _profitFactor = uint16(bound(uint256(_profitFactor), 10, MAX_BPS)); + vm.assume( + _address != address(0) && + _address != address(strategy) && + _address != protocolFeeRecipient && + _address != performanceFeeRecipient && + _address != address(yieldSource) + ); + // set fees to 0 + uint16 protocolFee = 0; + uint16 performanceFee = 0; + setFees(protocolFee, performanceFee); + + assertEq(strategy.profitUnlockingRate(), 0, "!rate"); + assertEq(strategy.fullProfitUnlockDate(), 0, "date"); + + mintAndDepositIntoStrategy(strategy, _address, _amount); + + // Increase time to simulate interest being earned + increaseTimeAndCheckBuffer(strategy, profitMaxUnlockTime, 0); + + uint256 profit = (_amount * _profitFactor) / MAX_BPS; + + uint256 expectedPerformanceFee = (profit * performanceFee) / MAX_BPS; + uint256 expectedProtocolFee = (expectedPerformanceFee * protocolFee) / + MAX_BPS; + + uint256 totalExpectedFees = expectedPerformanceFee + + expectedProtocolFee; + createAndCheckProfit( + strategy, + profit, + expectedProtocolFee, + expectedPerformanceFee + ); + + assertEq(strategy.pricePerShare(), wad, "!pps"); + + checkStrategyTotals( + strategy, + _amount + profit, + _amount + profit, + 0, + _amount + profit + ); + + increaseTimeAndCheckBuffer( + strategy, + profitMaxUnlockTime / 2, + (profit - totalExpectedFees) / 2 + ); + + checkStrategyTotals( + strategy, + _amount + profit, + _amount + profit, + 0, + _amount + profit - ((profit - totalExpectedFees) / 2) + ); + + // Make sure we have active unlocking + assertGt(strategy.profitUnlockingRate(), 0); + assertGt(strategy.fullProfitUnlockDate(), 0); + assertGt(strategy.balanceOf(address(strategy)), 0); + + // Set max unlocking time to 0. + vm.prank(management); + strategy.setProfitMaxUnlockTime(0); + // Make sure it reset all unlocking rates. + assertEq(strategy.profitMaxUnlockTime(), 0); + assertEq(strategy.profitUnlockingRate(), 0, "!rate"); + assertEq(strategy.fullProfitUnlockDate(), 0, "date"); + assertEq(strategy.balanceOf(address(strategy)), 0); + + assertRelApproxEq( + strategy.pricePerShare(), + wad + ((wad * _profitFactor) / MAX_BPS), + MAX_BPS + ); + + checkStrategyTotals( + strategy, + _amount + profit, + _amount + profit, + 0, + _amount + ); + + uint256 newAmount = _amount + profit; + + createAndCheckProfit( + strategy, + profit, + expectedProtocolFee, + expectedPerformanceFee + ); + + // Should unlock everything right away. + checkStrategyTotals( + strategy, + newAmount + profit, + newAmount + profit, + 0, + _amount + ); + + increaseTimeAndCheckBuffer(strategy, 0, 0); + + vm.prank(_address); + strategy.redeem(newAmount - profit, _address, _address); + + checkStrategyTotals(strategy, 0, 0, 0, 0); + + assertEq(strategy.pricePerShare(), wad, "pps reset"); + } + + function test_loss_NoFeesNoBuffer_noUnlock( + address _address, + uint256 _amount, + uint16 _lossFactor + ) public { + _amount = bound(_amount, minFuzzAmount, maxFuzzAmount); + _lossFactor = uint16(bound(uint256(_lossFactor), 1, 5_000)); + vm.assume( + _address != address(0) && + _address != address(strategy) && + _address != protocolFeeRecipient && + _address != performanceFeeRecipient && + _address != address(yieldSource) + ); + // set all fees to 0 + uint16 protocolFee = 0; + uint16 performanceFee = 0; + setFees(protocolFee, performanceFee); + + // Set max unlocking time to 0. + vm.prank(management); + strategy.setProfitMaxUnlockTime(0); + assertEq(strategy.profitMaxUnlockTime(), 0); + + mintAndDepositIntoStrategy(strategy, _address, _amount); + // Increase time to simulate interest being earned + increaseTimeAndCheckBuffer(strategy, profitMaxUnlockTime, 0); + + uint256 loss = (_amount * _lossFactor) / MAX_BPS; + uint256 expectedProtocolFee = 0; + + createAndCheckLoss(strategy, loss, expectedProtocolFee, true); + + assertEq(strategy.profitUnlockingRate(), 0, "!rate"); + assertEq(strategy.fullProfitUnlockDate(), 0, "date"); + assertRelApproxEq( + strategy.pricePerShare(), + wad - ((wad * _lossFactor) / MAX_BPS), + MAX_BPS / 10 + ); + + checkStrategyTotals( + strategy, + _amount - loss, + _amount - loss, + 0, + _amount + ); + + increaseTimeAndCheckBuffer(strategy, profitMaxUnlockTime / 2, 0); + + checkStrategyTotals( + strategy, + _amount - loss, + _amount - loss, + 0, + _amount + ); + + increaseTimeAndCheckBuffer(strategy, profitMaxUnlockTime / 2, 0); + + assertRelApproxEq( + strategy.pricePerShare(), + wad - ((wad * _lossFactor) / MAX_BPS), + MAX_BPS / 10 + ); + + checkStrategyTotals( + strategy, + _amount - loss, + _amount - loss, + 0, + _amount + ); + + vm.prank(_address); + strategy.redeem(_amount, _address, _address); + + checkStrategyTotals(strategy, 0, 0, 0, 0); + + assertEq(strategy.pricePerShare(), wad, "pps reset"); + } } From 3440c1c4c26e7f76f355302ebe4455655d852383 Mon Sep 17 00:00:00 2001 From: Schlagonia Date: Sat, 19 Aug 2023 15:28:47 -0600 Subject: [PATCH 2/5] test: fix test --- src/test/ProfitLocking.t.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/ProfitLocking.t.sol b/src/test/ProfitLocking.t.sol index c8da06d..b262bce 100644 --- a/src/test/ProfitLocking.t.sol +++ b/src/test/ProfitLocking.t.sol @@ -1565,6 +1565,7 @@ contract ProfitLockingTest is Setup { uint256 expectedPerformanceFee = (profit * performanceFee) / MAX_BPS; uint256 expectedProtocolFee = (expectedPerformanceFee * protocolFee) / MAX_BPS; + expectedPerformanceFee -= expectedProtocolFee; uint256 totalExpectedFees = expectedPerformanceFee + expectedProtocolFee; From e5f6705f800ad5b11979f457d6bb7819801cf726 Mon Sep 17 00:00:00 2001 From: Schlagonia Date: Mon, 21 Aug 2023 12:38:25 -0600 Subject: [PATCH 3/5] chore: comments --- src/TokenizedStrategy.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/TokenizedStrategy.sol b/src/TokenizedStrategy.sol index cfaeb43..e15abdb 100644 --- a/src/TokenizedStrategy.sol +++ b/src/TokenizedStrategy.sol @@ -938,6 +938,7 @@ contract TokenizedStrategy { // Burn unlocked shares. _burnUnlockedShares(); + // Initialize varaibles needed throughout. uint256 totalFees; uint256 protocolFees; uint256 sharesToLock; @@ -1464,8 +1465,7 @@ contract TokenizedStrategy { function setProfitMaxUnlockTime( uint256 _profitMaxUnlockTime ) external onlyManagement { - // Must be greater than 0, and less than a year. - //require(_profitMaxUnlockTime != 0, "too short"); + // Must be less than a year. require(_profitMaxUnlockTime <= SECONDS_PER_YEAR, "too long"); StrategyData storage S = _strategyStorage(); From ab81d8de35cf3c00cc97393b313073830874cf59 Mon Sep 17 00:00:00 2001 From: Schlagonia Date: Tue, 22 Aug 2023 09:34:10 -0600 Subject: [PATCH 4/5] test: issued fees --- src/test/ProfitLocking.t.sol | 124 ++++++++++++++++++++++++++++++++++- 1 file changed, 123 insertions(+), 1 deletion(-) diff --git a/src/test/ProfitLocking.t.sol b/src/test/ProfitLocking.t.sol index b262bce..cd19880 100644 --- a/src/test/ProfitLocking.t.sol +++ b/src/test/ProfitLocking.t.sol @@ -130,6 +130,19 @@ contract ProfitLockingTest is Setup { assertEq(strategy.pricePerShare(), wad, "!pps"); + assertApproxEq( + strategy.convertToAssets( + strategy.balanceOf(performanceFeeRecipient) + ), + expectedPerformanceFee, + 100 + ); + assertApproxEq( + strategy.convertToAssets(strategy.balanceOf(protocolFeeRecipient)), + expectedProtocolFee, + 100 + ); + checkStrategyTotals( strategy, _amount + profit, @@ -232,6 +245,19 @@ contract ProfitLockingTest is Setup { assertEq(strategy.pricePerShare(), wad, "!pps"); + assertApproxEq( + strategy.convertToAssets( + strategy.balanceOf(performanceFeeRecipient) + ), + expectedPerformanceFee, + 100 + ); + assertApproxEq( + strategy.convertToAssets(strategy.balanceOf(protocolFeeRecipient)), + expectedProtocolFee, + 100 + ); + checkStrategyTotals( strategy, _amount + profit, @@ -336,6 +362,19 @@ contract ProfitLockingTest is Setup { assertEq(strategy.pricePerShare(), wad, "!pps"); + assertApproxEq( + strategy.convertToAssets( + strategy.balanceOf(performanceFeeRecipient) + ), + expectedPerformanceFee, + 100 + ); + assertApproxEq( + strategy.convertToAssets(strategy.balanceOf(protocolFeeRecipient)), + expectedProtocolFee, + 100 + ); + checkStrategyTotals( strategy, _amount + profit, @@ -562,6 +601,19 @@ contract ProfitLockingTest is Setup { assertEq(strategy.pricePerShare(), wad, "!pps"); + assertApproxEq( + strategy.convertToAssets( + strategy.balanceOf(performanceFeeRecipient) + ), + expectedPerformanceFee, + 100 + ); + assertApproxEq( + strategy.convertToAssets(strategy.balanceOf(protocolFeeRecipient)), + expectedProtocolFee, + 100 + ); + checkStrategyTotals( strategy, _amount + profit, @@ -691,6 +743,19 @@ contract ProfitLockingTest is Setup { assertEq(strategy.pricePerShare(), wad, "!pps"); + assertApproxEq( + strategy.convertToAssets( + strategy.balanceOf(performanceFeeRecipient) + ), + expectedPerformanceFee, + 100 + ); + assertApproxEq( + strategy.convertToAssets(strategy.balanceOf(protocolFeeRecipient)), + expectedProtocolFee, + 100 + ); + checkStrategyTotals( strategy, _amount + profit, @@ -818,6 +883,19 @@ contract ProfitLockingTest is Setup { assertEq(strategy.pricePerShare(), wad, "!pps"); + assertApproxEq( + strategy.convertToAssets( + strategy.balanceOf(performanceFeeRecipient) + ), + expectedPerformanceFee, + 100 + ); + assertApproxEq( + strategy.convertToAssets(strategy.balanceOf(protocolFeeRecipient)), + expectedProtocolFee, + 100 + ); + checkStrategyTotals( strategy, _amount + profit, @@ -1031,6 +1109,12 @@ contract ProfitLockingTest is Setup { MAX_BPS / 10 ); + assertApproxEq( + strategy.convertToAssets(strategy.balanceOf(protocolFeeRecipient)), + expectedProtocolFee, + 100 + ); + checkStrategyTotals( strategy, _amount - loss, @@ -1160,7 +1244,6 @@ contract ProfitLockingTest is Setup { increaseTimeAndCheckBuffer(strategy, profitMaxUnlockTime / 2, 0); - console.log("Current bal ", strategy.balanceOf(address(strategy))); checkStrategyTotals( strategy, newAmount - loss, @@ -1218,6 +1301,19 @@ contract ProfitLockingTest is Setup { assertEq(strategy.pricePerShare(), wad, "!pps"); + assertApproxEq( + strategy.convertToAssets( + strategy.balanceOf(performanceFeeRecipient) + ), + expectedPerformanceFee, + 100 + ); + assertApproxEq( + strategy.convertToAssets(strategy.balanceOf(protocolFeeRecipient)), + expectedProtocolFee, + 100 + ); + checkStrategyTotals( strategy, _amount + loss * 2, @@ -1323,6 +1419,19 @@ contract ProfitLockingTest is Setup { assertEq(strategy.pricePerShare(), wad, "!pps"); + assertApproxEq( + strategy.convertToAssets( + strategy.balanceOf(performanceFeeRecipient) + ), + expectedPerformanceFee, + 100 + ); + assertApproxEq( + strategy.convertToAssets(strategy.balanceOf(protocolFeeRecipient)), + expectedProtocolFee, + 100 + ); + checkStrategyTotals( strategy, _amount + profit, @@ -1582,6 +1691,19 @@ contract ProfitLockingTest is Setup { assertEq(strategy.fullProfitUnlockDate(), 0, "date"); assertGt(strategy.pricePerShare(), wad, "!pps"); + assertApproxEq( + strategy.convertToAssets( + strategy.balanceOf(performanceFeeRecipient) + ), + expectedPerformanceFee, + 100 + ); + assertApproxEq( + strategy.convertToAssets(strategy.balanceOf(protocolFeeRecipient)), + expectedProtocolFee, + 100 + ); + checkStrategyTotals( strategy, _amount + profit, From 402e6223ee9c5b3a490d7199ae4a825800c9fd92 Mon Sep 17 00:00:00 2001 From: Schlagonia Date: Wed, 23 Aug 2023 13:22:35 -0600 Subject: [PATCH 5/5] fix: test --- foundry.toml | 2 +- src/TokenizedStrategy.sol | 4 ++-- src/test/ProfitLocking.t.sol | 25 ++++++------------------- 3 files changed, 9 insertions(+), 22 deletions(-) diff --git a/foundry.toml b/foundry.toml index a725c02..123be77 100644 --- a/foundry.toml +++ b/foundry.toml @@ -11,7 +11,7 @@ remappings = [ fs_permissions = [{ access = "read", path = "./"}] [fuzz] -runs = 10_00 +runs = 10_000 max_test_rejects = 1_000_000 [invariant] diff --git a/src/TokenizedStrategy.sol b/src/TokenizedStrategy.sol index e15abdb..88ab4a5 100644 --- a/src/TokenizedStrategy.sol +++ b/src/TokenizedStrategy.sol @@ -1009,8 +1009,8 @@ contract TokenizedStrategy { // We will try and burn shares from any pending profit still unlocking // to offset the loss to prevent any PPS decline post report. uint256 sharesToBurn = Math.min( - convertToShares(loss), - S.balances[address(this)] + S.balances[address(this)], + convertToShares(loss) ); // Check if there is anything to burn. diff --git a/src/test/ProfitLocking.t.sol b/src/test/ProfitLocking.t.sol index cd19880..e61d88f 100644 --- a/src/test/ProfitLocking.t.sol +++ b/src/test/ProfitLocking.t.sol @@ -1679,31 +1679,18 @@ contract ProfitLockingTest is Setup { uint256 totalExpectedFees = expectedPerformanceFee + expectedProtocolFee; - createAndCheckProfit( - strategy, - profit, - expectedProtocolFee, - expectedPerformanceFee - ); + asset.mint(address(strategy), profit); + + vm.prank(keeper); + (uint256 _profit, ) = strategy.report(); + + assertEq(profit, _profit, "profit reported wrong"); // All profit should have been unlocked instantly. assertEq(strategy.profitUnlockingRate(), 0, "!rate"); assertEq(strategy.fullProfitUnlockDate(), 0, "date"); assertGt(strategy.pricePerShare(), wad, "!pps"); - assertApproxEq( - strategy.convertToAssets( - strategy.balanceOf(performanceFeeRecipient) - ), - expectedPerformanceFee, - 100 - ); - assertApproxEq( - strategy.convertToAssets(strategy.balanceOf(protocolFeeRecipient)), - expectedProtocolFee, - 100 - ); - checkStrategyTotals( strategy, _amount + profit,