From e29f3fcf5420afd72d00190775c3aada99c95e38 Mon Sep 17 00:00:00 2001 From: 0xchin Date: Fri, 6 Dec 2024 21:19:57 -0300 Subject: [PATCH] test(fuzz): add fuzz testing --- foundry.toml | 2 +- test/unit/FeeManagement.t.sol | 119 ++++++++++++++++++++++------------ test/unit/Payment.t.sol | 82 +++++++++++------------ test/unit/Withdrawal.t.sol | 71 ++++++++++++++------ 4 files changed, 170 insertions(+), 104 deletions(-) diff --git a/foundry.toml b/foundry.toml index 690e22e..2216cd7 100644 --- a/foundry.toml +++ b/foundry.toml @@ -25,7 +25,7 @@ out = 'out-via-ir' src = 'src/interfaces/' [fuzz] -runs = 10 +runs = 1000 [rpc_endpoints] mainnet = "${MAINNET_RPC}" diff --git a/test/unit/FeeManagement.t.sol b/test/unit/FeeManagement.t.sol index 411caf2..3e2bdb0 100644 --- a/test/unit/FeeManagement.t.sol +++ b/test/unit/FeeManagement.t.sol @@ -12,8 +12,10 @@ contract UnitFeeManagement is UnitBase { SETTING GENERAL FEE //////////////////////////////////////////////////////////////*/ - function test_setFeeSuccess() public { - uint256 newFee = 0.02 ether; // 2% + function test_setFeeSuccess( + uint256 newFee + ) public { + vm.assume(newFee <= grateful.MAX_FEE()); // Set the fee as the owner vm.prank(owner); @@ -25,17 +27,20 @@ contract UnitFeeManagement is UnitBase { assertEq(grateful.fee(), newFee, "Fee was not updated correctly"); } - function test_revertIfSetFeeNotOwner() public { - uint256 newFee = 0.02 ether; // 2% + function test_revertIfSetFeeNotOwner(address nonOwner, uint256 newFee) public { + vm.assume(nonOwner != owner); + vm.assume(newFee <= grateful.MAX_FEE()); // Attempt to set the fee as a non-owner and expect a revert - vm.prank(user); + vm.prank(nonOwner); vm.expectRevert(); grateful.setFee(newFee); } - function test_revertIfSetFeeTooHigh() public { - uint256 invalidFee = 1.1 ether; // 110%, exceeds MAX_FEE + function test_revertIfSetFeeTooHigh( + uint256 invalidFee + ) public { + vm.assume(invalidFee > grateful.MAX_FEE()); // Attempt to set an invalid fee and expect a revert vm.prank(owner); @@ -47,8 +52,10 @@ contract UnitFeeManagement is UnitBase { SETTING PERFORMANCE FEE //////////////////////////////////////////////////////////////*/ - function test_setPerformanceFeeSuccess() public { - uint256 newPerformanceFee = 0.1 ether; // 10% + function test_setPerformanceFeeSuccess( + uint256 newPerformanceFee + ) public { + vm.assume(newPerformanceFee <= grateful.MAX_PERFORMANCE_FEE()); // Set the performance fee as the owner vm.prank(owner); @@ -60,17 +67,20 @@ contract UnitFeeManagement is UnitBase { assertEq(grateful.performanceFee(), newPerformanceFee, "Performance fee was not updated correctly"); } - function test_revertIfSetPerformanceFeeNotOwner() public { - uint256 newPerformanceFee = 0.1 ether; // 10% + function test_revertIfSetPerformanceFeeNotOwner(address nonOwner, uint256 newPerformanceFee) public { + vm.assume(nonOwner != owner); + vm.assume(newPerformanceFee <= grateful.MAX_PERFORMANCE_FEE()); // Attempt to set the performance fee as a non-owner and expect a revert - vm.prank(user); + vm.prank(nonOwner); vm.expectRevert(); grateful.setPerformanceFee(newPerformanceFee); } - function test_revertIfSetPerformanceFeeTooHigh() public { - uint256 invalidPerformanceFee = 0.6 ether; // 60%, exceeds MAX_PERFORMANCE_FEE + function test_revertIfSetPerformanceFeeTooHigh( + uint256 invalidPerformanceFee + ) public { + vm.assume(invalidPerformanceFee > grateful.MAX_PERFORMANCE_FEE()); // Attempt to set an invalid performance fee and expect a revert vm.prank(owner); @@ -82,8 +92,10 @@ contract UnitFeeManagement is UnitBase { SETTING CUSTOM FEE FOR MERCHANT //////////////////////////////////////////////////////////////*/ - function test_setCustomFeeSuccess() public { - uint256 customFee = 0.03 ether; // 3% + function test_setCustomFeeSuccess( + uint256 customFee + ) public { + vm.assume(customFee <= grateful.MAX_FEE()); // Set a custom fee for the merchant as the owner vm.prank(owner); @@ -97,17 +109,20 @@ contract UnitFeeManagement is UnitBase { assertEq(fee, customFee, "Custom fee value is incorrect"); } - function test_revertIfSetCustomFeeNotOwner() public { - uint256 customFee = 0.03 ether; // 3% + function test_revertIfSetCustomFeeNotOwner(address nonOwner, uint256 customFee) public { + vm.assume(nonOwner != owner); + vm.assume(customFee <= grateful.MAX_FEE()); // Attempt to set a custom fee as a non-owner and expect a revert - vm.prank(user); + vm.prank(nonOwner); vm.expectRevert(); grateful.setCustomFee(customFee, merchant); } - function test_revertIfSetCustomFeeInvalidMerchant() public { - uint256 customFee = 0.03 ether; // 3% + function test_revertIfSetCustomFeeInvalidMerchant( + uint256 customFee + ) public { + vm.assume(customFee <= grateful.MAX_FEE()); // Attempt to set a custom fee with an invalid merchant address and expect a revert vm.prank(owner); @@ -115,8 +130,10 @@ contract UnitFeeManagement is UnitBase { grateful.setCustomFee(customFee, address(0)); } - function test_revertIfSetCustomFeeTooHigh() public { - uint256 invalidCustomFee = 1.1 ether; // 110%, exceeds MAX_FEE + function test_revertIfSetCustomFeeTooHigh( + uint256 invalidCustomFee + ) public { + vm.assume(invalidCustomFee > grateful.MAX_FEE()); // Attempt to set an invalid custom fee and expect a revert vm.prank(owner); @@ -128,8 +145,10 @@ contract UnitFeeManagement is UnitBase { UNSETTING CUSTOM FEE //////////////////////////////////////////////////////////////*/ - function test_unsetCustomFeeSuccess() public { - uint256 customFee = 0.03 ether; // 3% + function test_unsetCustomFeeSuccess( + uint256 customFee + ) public { + vm.assume(customFee <= grateful.MAX_FEE()); // Arrange: Set a custom fee first vm.prank(owner); @@ -147,9 +166,13 @@ contract UnitFeeManagement is UnitBase { assertEq(fee, 0, "Custom fee value should be zero after unset"); } - function test_revertIfUnsetCustomFeeNotOwner() public { + function test_revertIfUnsetCustomFeeNotOwner( + address nonOwner + ) public { + vm.assume(nonOwner != owner); + // Attempt to unset a custom fee as a non-owner and expect a revert - vm.prank(user); + vm.prank(nonOwner); vm.expectRevert(); grateful.unsetCustomFee(merchant); } @@ -165,8 +188,13 @@ contract UnitFeeManagement is UnitBase { APPLY FEE FUNCTION //////////////////////////////////////////////////////////////*/ - function test_applyFeeWithGeneralFee() public { - uint256 amount = 0 ether; // Example amount + function test_applyFeeWithGeneralFee( + uint256 amount + ) public { + vm.assume(amount > 0); + // Ensure amount * fee won't overflow when divided by 1e18 + vm.assume(amount <= type(uint256).max / grateful.fee()); + uint256 expectedFee = (amount * grateful.fee()) / 1e18; uint256 expectedAmountAfterFee = amount - expectedFee; @@ -177,9 +205,12 @@ contract UnitFeeManagement is UnitBase { assertEq(amountAfterFee, expectedAmountAfterFee, "Amount after fee is incorrect"); } - function test_applyFeeWithCustomFee() public { - uint256 amount = 0 ether; // Example amount - uint256 customFee = 0.02 ether; // 2% + function test_applyFeeWithCustomFee(uint256 amount, uint256 customFee) public { + vm.assume(amount > 0); + vm.assume(customFee <= grateful.MAX_FEE()); + if (customFee > 0) { + vm.assume(amount <= type(uint256).max / customFee); + } // Arrange: Set a custom fee for the merchant vm.prank(owner); @@ -199,8 +230,13 @@ contract UnitFeeManagement is UnitBase { CALCULATE PERFORMANCE FEE //////////////////////////////////////////////////////////////*/ - function test_calculatePerformanceFee() public { - uint256 profit = 500 ether; // Example profit + function test_calculatePerformanceFee( + uint256 profit + ) public { + vm.assume(profit > 0); + // Ensure profit * performanceFee won't overflow when divided by 1e18 + vm.assume(profit <= type(uint256).max / grateful.performanceFee()); + uint256 expectedPerformanceFee = (profit * grateful.performanceFee()) / 1e18; // Calculate performance fee @@ -214,27 +250,28 @@ contract UnitFeeManagement is UnitBase { ACCESS CONTROL TESTS //////////////////////////////////////////////////////////////*/ - function test_revertIfNonOwnerCallsFeeFunctions() public { - uint256 newFee = 0.02 ether; - uint256 newPerformanceFee = 0.1 ether; + function test_revertIfNonOwnerCallsFeeFunctions(address nonOwner, uint256 newFee, uint256 newPerformanceFee) public { + vm.assume(nonOwner != owner); + vm.assume(newFee <= grateful.MAX_FEE()); + vm.assume(newPerformanceFee <= grateful.MAX_PERFORMANCE_FEE()); // Attempt to call setFee as non-owner - vm.prank(user); + vm.prank(nonOwner); vm.expectRevert(); grateful.setFee(newFee); // Attempt to call setPerformanceFee as non-owner - vm.prank(user); + vm.prank(nonOwner); vm.expectRevert(); grateful.setPerformanceFee(newPerformanceFee); // Attempt to call setCustomFee as non-owner - vm.prank(user); + vm.prank(nonOwner); vm.expectRevert(); grateful.setCustomFee(newFee, merchant); // Attempt to call unsetCustomFee as non-owner - vm.prank(user); + vm.prank(nonOwner); vm.expectRevert(); grateful.unsetCustomFee(merchant); } diff --git a/test/unit/Payment.t.sol b/test/unit/Payment.t.sol index 9f11ae9..c387645 100644 --- a/test/unit/Payment.t.sol +++ b/test/unit/Payment.t.sol @@ -9,9 +9,9 @@ import {Grateful, IGrateful} from "contracts/Grateful.sol"; import {ERC20Mock} from "test/mocks/ERC20Mock.sol"; contract UnitPayment is UnitBase { - function test_paySuccessWithoutYield() public { - uint256 amount = 1000 ether; - uint256 paymentId = 1; + function test_paySuccessWithoutYield(uint256 amount, uint256 paymentId) public { + vm.assume(amount > 0); + vm.assume(amount <= type(uint256).max / grateful.fee()); vm.prank(user); token.mint(user, amount); @@ -30,9 +30,9 @@ contract UnitPayment is UnitBase { assertEq(token.balanceOf(merchant), expectedMerchantAmount); } - function test_paySuccessWithYield() public { - uint256 amount = 1000 ether; - uint256 paymentId = 1; + function test_paySuccessWithYield(uint256 amount, uint256 paymentId) public { + vm.assume(amount > 0); + vm.assume(amount <= type(uint256).max / grateful.fee()); vm.prank(user); token.mint(user, amount); @@ -57,12 +57,10 @@ contract UnitPayment is UnitBase { assertEq(merchantDeposit, amountAfterFee); } - function test_revertIfPayWithNonWhitelistedToken() public { + function test_revertIfPayWithNonWhitelistedToken(uint256 amount, uint256 paymentId) public { + vm.assume(amount > 0); ERC20Mock nonWhitelistedToken = new ERC20Mock(); - uint256 amount = 1000 ether; - uint256 paymentId = 1; - vm.prank(user); nonWhitelistedToken.mint(user, amount); vm.prank(user); @@ -73,9 +71,10 @@ contract UnitPayment is UnitBase { grateful.pay(merchant, address(nonWhitelistedToken), amount, paymentId, false); } - function test_revertIfPayWithZeroAmount() public { + function test_revertIfPayWithZeroAmount( + uint256 paymentId + ) public { uint256 amount = 0; - uint256 paymentId = 1; vm.prank(user); token.approve(address(grateful), amount); @@ -85,9 +84,8 @@ contract UnitPayment is UnitBase { grateful.pay(merchant, address(token), amount, paymentId, false); } - function test_revertIfPayWithInvalidMerchant() public { - uint256 amount = 1000 ether; - uint256 paymentId = 1; + function test_revertIfPayWithInvalidMerchant(uint256 amount, uint256 paymentId) public { + vm.assume(amount > 0); vm.prank(user); token.approve(address(grateful), amount); @@ -97,9 +95,8 @@ contract UnitPayment is UnitBase { grateful.pay(address(0), address(token), amount, paymentId, false); } - function test_revertIfPayWithInsufficientAllowance() public { - uint256 amount = 1000 ether; - uint256 paymentId = 1; + function test_revertIfPayWithInsufficientAllowance(uint256 amount, uint256 paymentId) public { + vm.assume(amount > 0); vm.prank(user); token.mint(user, amount); @@ -109,9 +106,8 @@ contract UnitPayment is UnitBase { grateful.pay(merchant, address(token), amount, paymentId, false); } - function test_revertIfPayWithInsufficientBalance() public { - uint256 amount = 1000 ether; - uint256 paymentId = 1; + function test_revertIfPayWithInsufficientBalance(uint256 amount, uint256 paymentId) public { + vm.assume(amount > 0); vm.prank(user); token.approve(address(grateful), amount); @@ -121,22 +117,21 @@ contract UnitPayment is UnitBase { grateful.pay(merchant, address(token), amount, paymentId, false); } - function test_revertIfPayWithInvalidTokenAddress() public { - uint256 amount = 1000 ether; - uint256 paymentId = 1; + function test_revertIfPayWithInvalidTokenAddress(uint256 amount, uint256 paymentId) public { + vm.assume(amount > 0); vm.prank(user); vm.expectRevert(IGrateful.Grateful_TokenNotWhitelisted.selector); grateful.pay(merchant, address(0), amount, paymentId, false); } - function test_payWithoutVaultYieldFundsTrue() public { + function test_payWithoutVaultYieldFundsTrue(uint256 amount, uint256 paymentId) public { + vm.assume(amount > 0); + vm.assume(amount <= type(uint256).max / grateful.fee()); + vm.prank(owner); grateful.removeVault(address(token)); - uint256 amount = 1000 ether; - uint256 paymentId = 1; - vm.prank(user); token.mint(user, amount); vm.prank(user); @@ -160,18 +155,16 @@ contract UnitPayment is UnitBase { assertEq(merchantDeposit, 0); } - function test_revertIfPayWithZeroAddressToken() public { - uint256 amount = 1000 ether; - uint256 paymentId = 1; + function test_revertIfPayWithZeroAddressToken(uint256 amount, uint256 paymentId) public { + vm.assume(amount > 0); vm.prank(user); vm.expectRevert(IGrateful.Grateful_TokenNotWhitelisted.selector); grateful.pay(merchant, address(0), amount, paymentId, false); } - function test_revertIfPayToZeroAddressMerchant() public { - uint256 amount = 1000 ether; - uint256 paymentId = 1; + function test_revertIfPayToZeroAddressMerchant(uint256 amount, uint256 paymentId) public { + vm.assume(amount > 0); vm.prank(user); token.mint(user, amount); @@ -183,14 +176,17 @@ contract UnitPayment is UnitBase { grateful.pay(address(0), address(token), amount, paymentId, false); } - function test_payWithCustomFee() public { - uint256 customFee = 0.02 ether; // 2% + function test_payWithCustomFee(uint256 amount, uint256 paymentId, uint256 customFee) public { + vm.assume(amount > 0); + vm.assume(customFee <= grateful.MAX_FEE()); + + if (customFee > 0) { + vm.assume(amount <= type(uint256).max / customFee); + } + vm.prank(owner); grateful.setCustomFee(customFee, merchant); - uint256 amount = 1000 ether; - uint256 paymentId = 1; - vm.prank(user); token.mint(user, amount); vm.prank(user); @@ -206,13 +202,13 @@ contract UnitPayment is UnitBase { assertEq(token.balanceOf(merchant), expectedMerchantAmount); } - function test_payWithVaultNotSetAndYieldFundsTrue() public { + function test_payWithVaultNotSetAndYieldFundsTrue(uint256 amount, uint256 paymentId) public { + vm.assume(amount > 0); + vm.assume(amount <= type(uint256).max / grateful.fee()); + vm.prank(owner); grateful.removeVault(address(token)); - uint256 amount = 1000 ether; - uint256 paymentId = 1; - vm.prank(user); token.mint(user, amount); vm.prank(user); diff --git a/test/unit/Withdrawal.t.sol b/test/unit/Withdrawal.t.sol index 62f7b87..1ed213e 100644 --- a/test/unit/Withdrawal.t.sol +++ b/test/unit/Withdrawal.t.sol @@ -14,8 +14,12 @@ import {ERC20Mock} from "test/mocks/ERC20Mock.sol"; import {IPool, IRewardsController} from "yield-daddy/aave-v3/AaveV3ERC4626.sol"; contract UnitWithdrawal is UnitBase { - function test_withdrawFullSuccess() public { - uint256 amount = 1000 ether; + function test_withdrawFullSuccess( + uint128 amount + ) public { + vm.assume(amount > 1e8); + vm.assume(amount <= 10 ether); + vm.assume(amount <= type(uint256).max / grateful.fee()); uint256 paymentId = 1; vm.prank(user); @@ -41,10 +45,15 @@ contract UnitWithdrawal is UnitBase { assertEq(finalMerchantBalance, initialDeposit); } - function test_withdrawPartialSuccess() public { - uint256 amount = 10 ether; + function test_withdrawPartialSuccess(uint128 amount, uint128 withdrawAmount) public { + vm.assume(amount > 0); + vm.assume(amount <= 10 ether); + vm.assume(amount <= type(uint256).max / grateful.fee()); + vm.assume(withdrawAmount > 0); + vm.assume(withdrawAmount <= grateful.applyFee(merchant, amount)); + vm.assume(withdrawAmount >= 100_000); // Ensure withdrawAmount is large enough for meaningful tolerance + uint256 paymentId = 1; - uint256 withdrawAmount = 5 ether; uint256 tolerance = withdrawAmount / 10_000; // 0.01% precision loss tolerance vm.prank(user); @@ -73,10 +82,15 @@ contract UnitWithdrawal is UnitBase { assertApproxEqAbs(finalDeposit, initialDeposit - withdrawAmount, tolerance); } - function test_withdrawMultipleFullSuccess() public { + function test_withdrawMultipleFullSuccess( + uint128 amount + ) public { + vm.assume(amount > 0); + vm.assume(amount <= 10 ether); + vm.assume(amount <= type(uint256).max / grateful.fee()); + (address token2, AaveV3Vault vault2) = _deployNewTokenAndVault(); - uint256 amount = 1000 ether; uint256 paymentId1 = 1; uint256 paymentId2 = 2; @@ -112,10 +126,16 @@ contract UnitWithdrawal is UnitBase { assertEq(finalMerchantBalanceToken2, expectedMerchantBalanceToken2); } - function test_withdrawMultiplePartialSuccess() public { + function test_withdrawMultiplePartialSuccess(uint128 amount, uint128 withdrawAmount) public { + vm.assume(amount > 0); + vm.assume(amount <= 10 ether); + vm.assume(amount <= type(uint256).max / grateful.fee()); + vm.assume(withdrawAmount > 0); + vm.assume(withdrawAmount <= grateful.applyFee(merchant, amount)); + vm.assume(withdrawAmount >= 100_000); // Ensure withdrawAmount is large enough for meaningful tolerance + (address token2, AaveV3Vault vault2) = _deployNewTokenAndVault(); - uint256 amount = 10 ether; uint256 paymentId1 = 1; uint256 paymentId2 = 2; @@ -134,8 +154,8 @@ contract UnitWithdrawal is UnitBase { tokens[1] = token2; uint256[] memory assets = new uint256[](2); - assets[0] = 5 ether; - assets[1] = 5 ether; + assets[0] = withdrawAmount; + assets[1] = withdrawAmount; vm.prank(merchant); vm.expectEmit(true, true, true, true); @@ -147,7 +167,7 @@ contract UnitWithdrawal is UnitBase { uint256 finalMerchantBalanceToken1 = token.balanceOf(merchant); uint256 finalMerchantBalanceToken2 = ERC20Mock(token2).balanceOf(merchant); - uint256 tolerance = (assets[0] * 1) / 10_000; // 0.01% precision loss tolerance + uint256 tolerance = (withdrawAmount * 1) / 10_000; // 0.01% precision loss tolerance assertApproxEqAbs(finalMerchantBalanceToken1, assets[0], tolerance); assertApproxEqAbs(finalMerchantBalanceToken2, assets[1], tolerance); @@ -176,8 +196,12 @@ contract UnitWithdrawal is UnitBase { grateful.withdraw(address(0)); } - function test_revertIfWithdrawInvalidAmount() public { - uint256 amount = 1000 ether; + function test_revertIfWithdrawInvalidAmount( + uint128 amount + ) public { + vm.assume(amount > 0); + vm.assume(amount <= 10 ether); + vm.assume(amount <= type(uint256).max / grateful.fee()); uint256 paymentId = 1; vm.prank(user); @@ -192,8 +216,12 @@ contract UnitWithdrawal is UnitBase { grateful.withdraw(address(token), 0); } - function test_revertIfWithdrawExceedsShares() public { - uint256 amount = 1000 ether; + function test_revertIfWithdrawExceedsShares( + uint128 amount + ) public { + vm.assume(amount > 0); + vm.assume(amount <= 10 ether); + vm.assume(amount <= type(uint256).max / grateful.fee()); uint256 paymentId = 1; vm.prank(user); @@ -205,16 +233,21 @@ contract UnitWithdrawal is UnitBase { vm.prank(merchant); vm.expectRevert(); - grateful.withdraw(address(token), 2000 ether); + grateful.withdraw(address(token), amount * 2); } - function test_revertIfWithdrawMultipleMismatchedArrays() public { + function test_revertIfWithdrawMultipleMismatchedArrays( + uint128 amount + ) public { + vm.assume(amount > 0); + vm.assume(amount <= 10 ether); + address[] memory tokens = new address[](2); tokens[0] = address(token); tokens[1] = address(token); uint256[] memory assets = new uint256[](1); - assets[0] = 1000 ether; + assets[0] = amount; vm.prank(merchant); vm.expectRevert(IGrateful.Grateful_MismatchedArrays.selector);