diff --git a/test/fuzz/.gitkeep b/test/fuzz/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/test/fuzz/Accumulate.t.sol b/test/fuzz/Accumulate.t.sol new file mode 100644 index 0000000..a69e727 --- /dev/null +++ b/test/fuzz/Accumulate.t.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: Unlicensed +pragma solidity 0.8.26; + +import { UtilsLib } from "morpho/libraries/UtilsLib.sol"; +import "../ERC4626StrategyTest.t.sol"; + +contract AccumulateFuzzTest is ERC4626StrategyTest { + using UtilsLib for uint256; + + function testFuzz_accumulate_Normal( + uint256 depositAmount, + uint256[5] memory timeOffsets, + uint256[5] memory balances + ) public { + depositAmount = bound(depositAmount, 1e18, 1e21); + deal(asset, alice, depositAmount); + + vm.startPrank(alice); + IERC20(asset).approve(address(strategy), depositAmount); + strategy.deposit(depositAmount, alice); + vm.stopPrank(); + + uint256 integratorShares = 0; + uint256 developerShares = 0; + + for (uint256 i = 0; i < 5; i++) { + timeOffsets[i] = bound(timeOffsets[i], 1, 365 days); + vm.warp(block.timestamp + timeOffsets[i]); + + balances[i] = bound(balances[i], 1, 1e22); + vm.mockCall(strategyAsset, abi.encodeWithSelector(IERC20.balanceOf.selector), abi.encode(balances[i])); + uint256 totalAssets = strategy.totalAssets(); + uint256 lastTotalAssets = strategy.lastTotalAssets(); + + vm.mockCall(strategyAsset, abi.encodeWithSelector(IERC20.balanceOf.selector), abi.encode(balances[i])); + strategy.accumulate(); + + uint256 feeShare = strategy.convertToShares( + ((totalAssets.zeroFloorSub(lastTotalAssets)) * strategy.performanceFee()) / strategy.BPS() + ); + uint256 developerFeeShare = (feeShare * strategy.developerFee()) / strategy.BPS(); + + integratorShares += feeShare - developerFeeShare; + developerShares += developerFeeShare; + assertEq(strategy.lastTotalAssets(), totalAssets); + assertEq(strategy.balanceOf(strategy.integratorFeeRecipient()), integratorShares); + assertEq(strategy.balanceOf(strategy.developerFeeRecipient()), developerShares); + } + } +} diff --git a/test/fuzz/Deposit.t.sol b/test/fuzz/Deposit.t.sol new file mode 100644 index 0000000..36fc75e --- /dev/null +++ b/test/fuzz/Deposit.t.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Unlicensed +pragma solidity 0.8.26; + +import "../ERC4626StrategyTest.t.sol"; + +contract DepositFuzzTest is ERC4626StrategyTest { + function testFuzz_Deposit_Normal(uint256[5] memory amounts) public { + for (uint256 i = 0; i < 5; i++) { + amounts[i] = bound(amounts[i], 1e18, 1e21); + deal(asset, alice, amounts[i]); + + uint256 previousBalance = strategy.balanceOf(alice); + uint256 previousStrategyBalance = ERC4626(strategyAsset).balanceOf(address(strategy)); + + vm.startPrank(alice); + IERC20(asset).approve(address(strategy), amounts[i]); + uint256 previewedDeposit = strategy.previewDeposit(amounts[i]); + uint256 deposited = strategy.deposit(amounts[i], alice); + vm.stopPrank(); + + assertEq(previewedDeposit, deposited); + assertEq( + ERC4626(strategyAsset).balanceOf(address(strategy)), + previousStrategyBalance + ERC4626(strategyAsset).convertToShares(amounts[i]) + ); + assertEq(IERC20(asset).balanceOf(alice), 0); + assertEq(IERC20(asset).balanceOf(address(strategy)), 0); + assertEq(strategy.balanceOf(alice), previousBalance + previewedDeposit); + assertEq(strategy.totalSupply(), previousBalance + previewedDeposit); + } + } +} diff --git a/test/fuzz/Mint.t.sol b/test/fuzz/Mint.t.sol new file mode 100644 index 0000000..1b8ed2f --- /dev/null +++ b/test/fuzz/Mint.t.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: Unlicensed +pragma solidity 0.8.26; + +import "../ERC4626StrategyTest.t.sol"; + +contract MintFuzzTest is ERC4626StrategyTest { + function testFuzz_Mint_Normal(uint256[5] memory amounts) public { + for (uint256 i = 0; i < 5; i++) { + amounts[i] = bound(amounts[i], 1e18, 1e21); + deal(asset, alice, amounts[i]); + + uint256 previousBalance = strategy.balanceOf(alice); + uint256 previousStrategyBalance = ERC4626(strategyAsset).balanceOf(address(strategy)); + + vm.startPrank(alice); + IERC20(asset).approve(address(strategy), amounts[i]); + + uint256 shares = strategy.convertToShares(amounts[i]); + uint256 previewedMint = strategy.previewMint(shares); + uint256 assetsMinted = strategy.mint(shares, alice); + vm.stopPrank(); + + assertEq( + ERC4626(strategyAsset).balanceOf(address(strategy)), + previousStrategyBalance + ERC4626(strategyAsset).convertToShares(amounts[i]) + ); + assertEq(IERC20(asset).balanceOf(alice), 0); + assertEq(IERC20(asset).balanceOf(address(strategy)), 0); + assertEq(strategy.balanceOf(alice), previousBalance + shares); + assertEq(strategy.totalSupply(), previousBalance + shares); + assertEq(assetsMinted, previewedMint); + } + } +} diff --git a/test/fuzz/Redeem.t.sol b/test/fuzz/Redeem.t.sol new file mode 100644 index 0000000..cfe819a --- /dev/null +++ b/test/fuzz/Redeem.t.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: Unlicensed +pragma solidity 0.8.26; + +import "../ERC4626StrategyTest.t.sol"; + +contract RedeemFuzzTest is ERC4626StrategyTest { + function testFuzz_Redeem_Normal(uint256[5] memory amounts) public { + uint256 totalAmounts; + for (uint256 i = 0; i < 5; i++) { + amounts[i] = bound(amounts[i], 1e18, 1e21); + totalAmounts += amounts[i]; + } + deal(asset, alice, totalAmounts); + + vm.startPrank(alice); + IERC20(asset).approve(address(strategy), totalAmounts); + strategy.deposit(totalAmounts, alice); + vm.stopPrank(); + + for (uint256 i = 0; i < 5; i++) { + uint256 previousBalance = strategy.balanceOf(alice); + + uint256 totalAssets = strategy.totalAssets(); + uint256 lastTotalAssets = strategy.lastTotalAssets(); + + uint256 previousAssetBalance = IERC20(asset).balanceOf(alice); + + vm.startPrank(alice); + uint256 previewedRedeem = strategy.previewRedeem(amounts[i]); + uint256 redeemed = strategy.redeem(amounts[i], alice, alice); + vm.stopPrank(); + + assertEq(previewedRedeem, redeemed); + assertEq(IERC20(asset).balanceOf(alice), previousAssetBalance + previewedRedeem); + assertEq(strategy.balanceOf(alice), previousBalance - amounts[i]); + } + } +} diff --git a/test/fuzz/Swap.t.sol b/test/fuzz/Swap.t.sol new file mode 100644 index 0000000..5e28f8b --- /dev/null +++ b/test/fuzz/Swap.t.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: Unlicensed +pragma solidity 0.8.26; + +import { UtilsLib } from "morpho/libraries/UtilsLib.sol"; +import { MockRouter } from "../mock/MockRouter.sol"; +import "../ERC4626StrategyTest.t.sol"; + +contract SwapFuzzTest is ERC4626StrategyTest { + using UtilsLib for uint256; + + MockRouter router; + + function setUp() public override { + super.setUp(); + + router = new MockRouter(); + vm.startPrank(developer); + strategy.setSwapRouter(address(router)); + strategy.setTokenTransferAddress(address(router)); + vm.stopPrank(); + } + + function testFuzz_swap_normal( + uint256[5] memory amountIns, + uint256[5] memory amountOuts, + uint256[5] memory timeOffsets + ) public { + uint256 lastLockedProfit; + for (uint256 i = 0; i < 5; ++i) { + amountIns[i] = bound(amountIns[i], 1e18, 1e21); + amountOuts[i] = bound(amountOuts[i], 1e18, 1e21); + timeOffsets[i] = bound(timeOffsets[i], 1 days, 3 weeks); + + deal(USDC, address(strategy), amountIns[i]); + deal(asset, address(router), amountOuts[i]); + + address[] memory tokens = new address[](1); + tokens[0] = USDC; + uint256[] memory amounts = new uint256[](1); + amounts[0] = amountIns[i]; + bytes[] memory data = new bytes[](1); + data[0] = abi.encodeWithSelector(MockRouter.swap.selector, amountIns[i], USDC, amountOuts[i], asset); + + uint256 strategyBalance = ERC4626(strategyAsset).balanceOf(address(strategy)); + uint256 lockedProfit = strategy.lockedProfit(); + + vm.prank(keeper); + strategy.swap(tokens, data, amounts); + + assertEq(IERC20(USDC).allowance(address(strategy), address(router)), 0); + assertEq( + ERC4626(strategyAsset).balanceOf(address(strategy)), + strategyBalance + ERC4626(strategyAsset).convertToShares(amountOuts[i]) + ); + assertEq(IERC20(asset).balanceOf(address(strategy)), 0); + + assertEq(strategy.vestingProfit(), lockedProfit + amountOuts[i]); + assertEq(strategy.lastUpdate(), block.timestamp); + assertEq( + strategy.lockedProfit(), + strategy.vestingProfit() + (lastLockedProfit * timeOffsets[i]) / strategy.vestingPeriod() + ); + assertEq( + strategy.totalAssets(), + ERC4626(strategyAsset) + .convertToAssets(ERC4626(strategyAsset).balanceOf(address(strategy))) + .zeroFloorSub(strategy.lockedProfit()) + ); + + uint256 lastLockedProfit = strategy.lockedProfit(); + + vm.warp(block.timestamp + timeOffsets[i]); + } + } +} diff --git a/test/fuzz/Withdraw.t.sol b/test/fuzz/Withdraw.t.sol new file mode 100644 index 0000000..92f0208 --- /dev/null +++ b/test/fuzz/Withdraw.t.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: Unlicensed +pragma solidity 0.8.26; + +import { UtilsLib } from "morpho/libraries/UtilsLib.sol"; +import "../ERC4626StrategyTest.t.sol"; + +contract WithdrawFuzzTest is ERC4626StrategyTest { + using UtilsLib for uint256; + + function testFuzz_Withdraw_Normal(uint256[5] memory amounts) public { + uint256 totalAmounts; + for (uint256 i = 0; i < 5; i++) { + amounts[i] = bound(amounts[i], 1e18, 1e21); + totalAmounts += amounts[i]; + } + deal(asset, alice, totalAmounts); + + vm.startPrank(alice); + IERC20(asset).approve(address(strategy), totalAmounts); + strategy.deposit(totalAmounts, alice); + vm.stopPrank(); + + for (uint256 i = 0; i < 5; i++) { + uint256 previousBalance = strategy.balanceOf(alice); + uint256 previousAssetBalance = IERC20(asset).balanceOf(alice); + + uint256 strategyAssets = ERC4626(strategyAsset).convertToAssets( + IERC20(strategyAsset).balanceOf(address(strategy)) + ); + vm.startPrank(alice); + if (amounts[i] >= strategyAssets) { + amounts[i] = strategyAssets; + } + uint256 previewedWithdraw = strategy.previewWithdraw(amounts[i]); + uint256 withdrawed = strategy.withdraw(amounts[i], alice, alice); + vm.stopPrank(); + + assertEq(previewedWithdraw, withdrawed); + assertEq(IERC20(asset).balanceOf(alice), previousAssetBalance + amounts[i]); + + uint256 assetsHeld = ERC4626(strategyAsset).convertToShares( + totalAmounts - previousAssetBalance - amounts[i] + ); + assertLe(IERC20(strategyAsset).balanceOf(address(strategy)), assetsHeld); + assertGe(IERC20(strategyAsset).balanceOf(address(strategy)), assetsHeld.zeroFloorSub(5)); + assertEq(strategy.balanceOf(alice), previousBalance - previewedWithdraw); + } + } +}