diff --git a/pkg/interfaces/contracts/vault/ISwapFeePercentageBounds.sol b/pkg/interfaces/contracts/vault/ISwapFeePercentageBounds.sol index 06eb3dfaa..9be5f2a0b 100644 --- a/pkg/interfaces/contracts/vault/ISwapFeePercentageBounds.sol +++ b/pkg/interfaces/contracts/vault/ISwapFeePercentageBounds.sol @@ -9,8 +9,12 @@ pragma solidity ^0.8.24; * * A minimum swap fee might be necessary to ensure mathematical soundness (e.g., Weighted Pools, which use the power * function in the invariant). A maximum swap fee is general protection for users. With no limits at the Vault level, - * a pool could specify a 100% swap fee, effectively disabling trading. Though there are some use cases, such as - * LVR/MEV strategies, where a 100% fee makes sense. + * a pool could specify a near 100% swap fee, effectively disabling trading. Though there are some use cases, such as + * LVR/MEV strategies, where a very high fee makes sense. + * + * Note that the Vault does ensure that dynamic and aggregate fees are less than 100% to prevent attempting to allocate + * more fees than were collected by the operation. The true `MAX_FEE_PERCENTAGE` is defined in VaultTypes.sol, and is + * the highest value below 100% that satisfies the precision requirements. */ interface ISwapFeePercentageBounds { /// @return minimumSwapFeePercentage The minimum swap fee percentage for a pool diff --git a/pkg/interfaces/contracts/vault/VaultTypes.sol b/pkg/interfaces/contracts/vault/VaultTypes.sol index 235f27fb6..ce9dd87db 100644 --- a/pkg/interfaces/contracts/vault/VaultTypes.sol +++ b/pkg/interfaces/contracts/vault/VaultTypes.sol @@ -414,5 +414,7 @@ struct BufferWrapOrUnwrapParams { // between 0% and 100% (step 0.00001%). Protocol and pool creator fees are set in the `ProtocolFeeController`, and // ensure both constituent and aggregate fees do not exceed this precision. uint256 constant FEE_BITLENGTH = 24; -uint256 constant MAX_FEE_PERCENTAGE = 1e18; // 100% uint256 constant FEE_SCALING_FACTOR = 1e11; +// Used to ensure the safety of fee-related math (e.g., pools or hooks don't set it greater than 100%). +// This is the highest value that meets the precision requirements and is < 100%. +uint256 constant MAX_FEE_PERCENTAGE = (1e18 / FEE_SCALING_FACTOR - 1) * FEE_SCALING_FACTOR; diff --git a/pkg/vault/contracts/ProtocolFeeController.sol b/pkg/vault/contracts/ProtocolFeeController.sol index 2830dcc2f..14f11c058 100644 --- a/pkg/vault/contracts/ProtocolFeeController.sol +++ b/pkg/vault/contracts/ProtocolFeeController.sol @@ -6,8 +6,8 @@ import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.s import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { FEE_SCALING_FACTOR, MAX_FEE_PERCENTAGE } from "@balancer-labs/v3-interfaces/contracts/vault/VaultTypes.sol"; import { IProtocolFeeController } from "@balancer-labs/v3-interfaces/contracts/vault/IProtocolFeeController.sol"; -import { FEE_SCALING_FACTOR } from "@balancer-labs/v3-interfaces/contracts/vault/VaultTypes.sol"; import { IVaultErrors } from "@balancer-labs/v3-interfaces/contracts/vault/IVaultErrors.sol"; import { IVault } from "@balancer-labs/v3-interfaces/contracts/vault/IVault.sol"; @@ -140,7 +140,7 @@ contract ProtocolFeeController is } modifier withValidPoolCreatorFee(uint256 newPoolCreatorFeePercentage) { - if (newPoolCreatorFeePercentage > FixedPoint.ONE) { + if (newPoolCreatorFeePercentage > MAX_FEE_PERCENTAGE) { revert PoolCreatorFeePercentageTooHigh(); } _; diff --git a/pkg/vault/contracts/Vault.sol b/pkg/vault/contracts/Vault.sol index 1618aa0df..1810c791c 100644 --- a/pkg/vault/contracts/Vault.sol +++ b/pkg/vault/contracts/Vault.sol @@ -406,8 +406,10 @@ contract Vault is IVaultMain, VaultCommon, Proxy { } } else { // To ensure symmetry with EXACT_IN, the swap fee used by ExactOut is - // `amountCalculated * fee% / (100% - fee%)`. Add it to the calculated amountIn. Round up to avoid losses - // during precision loss. + // `amountCalculated * fee% / (100% - fee%)`. Add it to the calculated amountIn. Round up to avoid losing + // value due to precision loss. Note that if the `swapFeePercentage` were 100% here, this would revert with + // division by zero. We protect against this by ensuring in PoolConfigLib and HooksConfigLib that all swap + // fees (static, dynamic, pool creator, and aggregate) are less than 100%. locals.totalSwapFeeAmountScaled18 = amountCalculatedScaled18.mulDivUp( swapState.swapFeePercentage, swapState.swapFeePercentage.complement() diff --git a/pkg/vault/contracts/lib/HooksConfigLib.sol b/pkg/vault/contracts/lib/HooksConfigLib.sol index 6c0e0ac24..459c5ae87 100644 --- a/pkg/vault/contracts/lib/HooksConfigLib.sol +++ b/pkg/vault/contracts/lib/HooksConfigLib.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.24; -import { IHooks } from "@balancer-labs/v3-interfaces/contracts/vault/IHooks.sol"; import { IVaultErrors } from "@balancer-labs/v3-interfaces/contracts/vault/IVaultErrors.sol"; +import { IHooks } from "@balancer-labs/v3-interfaces/contracts/vault/IHooks.sol"; import "@balancer-labs/v3-interfaces/contracts/vault/VaultTypes.sol"; import { WordCodec } from "@balancer-labs/v3-solidity-utils/contracts/helpers/WordCodec.sol"; @@ -189,7 +189,9 @@ library HooksConfigLib { revert IVaultErrors.DynamicSwapFeeHookFailed(); } - if (swapFeePercentage > FixedPoint.ONE) { + // A 100% fee is not supported. In the ExactOut case, the Vault divides by the complement of the swap fee. + // The minimum precision constraint provides an additional buffer. + if (swapFeePercentage > MAX_FEE_PERCENTAGE) { revert IVaultErrors.PercentageAboveMax(); } diff --git a/pkg/vault/contracts/lib/PoolConfigLib.sol b/pkg/vault/contracts/lib/PoolConfigLib.sol index 5294755e1..6233b3770 100644 --- a/pkg/vault/contracts/lib/PoolConfigLib.sol +++ b/pkg/vault/contracts/lib/PoolConfigLib.sol @@ -165,6 +165,8 @@ library PoolConfigLib { } function setStaticSwapFeePercentage(PoolConfigBits config, uint256 value) internal pure returns (PoolConfigBits) { + // A 100% fee is not supported. In the ExactOut case, the Vault divides by the complement of the swap fee. + // The minimum precision constraint provides an additional buffer. if (value > MAX_FEE_PERCENTAGE) { revert IVaultErrors.PercentageAboveMax(); } diff --git a/pkg/vault/test/.contract-sizes/VaultAdmin b/pkg/vault/test/.contract-sizes/VaultAdmin index a084f0615..dd5b040e2 100644 --- a/pkg/vault/test/.contract-sizes/VaultAdmin +++ b/pkg/vault/test/.contract-sizes/VaultAdmin @@ -1,2 +1,2 @@ -Bytecode 13.985 -InitCode 15.064 \ No newline at end of file +Bytecode 14.056 +InitCode 15.135 \ No newline at end of file diff --git a/pkg/vault/test/.contract-sizes/VaultExtension b/pkg/vault/test/.contract-sizes/VaultExtension index befe6f0f8..c54bab51f 100644 --- a/pkg/vault/test/.contract-sizes/VaultExtension +++ b/pkg/vault/test/.contract-sizes/VaultExtension @@ -1,2 +1,2 @@ -Bytecode 19.778 -InitCode 20.931 \ No newline at end of file +Bytecode 19.693 +InitCode 20.846 \ No newline at end of file diff --git a/pkg/vault/test/foundry/DynamicFeePoolTest.t.sol b/pkg/vault/test/foundry/DynamicFeePoolTest.t.sol index 797b56fda..555cbac5e 100644 --- a/pkg/vault/test/foundry/DynamicFeePoolTest.t.sol +++ b/pkg/vault/test/foundry/DynamicFeePoolTest.t.sol @@ -165,18 +165,8 @@ contract DynamicFeePoolTest is BaseVaultTest { router.swapSingleTokenExactOut(pool, dai, usdc, defaultAmount, MAX_UINT256, MAX_UINT256, false, bytes("")); } - function testSwapTooSmallAmountCalculated() public { - // Near 100% swap fee will result in near 0 amount calculated. - PoolHooksMock(poolHooksContract).setDynamicSwapFeePercentage(FixedPoint.ONE - 1); - PoolHooksMock(poolHooksContract).setSpecialSender(bob); - - vm.prank(alice); - vm.expectRevert(IVaultErrors.TradeAmountTooSmall.selector); - router.swapSingleTokenExactIn(pool, dai, usdc, defaultAmount, 0, MAX_UINT256, false, bytes("")); - } - function testSwapCallsComputeFeeWithSender() public { - // Set a 100% fee, and bob as 0 swap fee sender. + // Set a near 100% fee, and bob as 0 swap fee sender. PoolHooksMock(poolHooksContract).setDynamicSwapFeePercentage(99e16); PoolHooksMock(poolHooksContract).setSpecialSender(bob); @@ -186,7 +176,7 @@ contract DynamicFeePoolTest is BaseVaultTest { router.swapSingleTokenExactIn(pool, dai, usdc, defaultAmount, 0, MAX_UINT256, false, bytes("")); uint256 aliceBalanceAfter = usdc.balanceOf(alice); - // 100% fee; should get nothing. + // Near 100% fee; should get nothing. assertEq(aliceBalanceAfter - aliceBalanceBefore, defaultAmount.mulDown(1e16), "Wrong alice balance (high fee)"); // Now set Alice as the special 0-fee sender. @@ -249,11 +239,7 @@ contract DynamicFeePoolTest is BaseVaultTest { } function testSwapChargesFees__Fuzz(uint256 dynamicSwapFeePercentage) public { - dynamicSwapFeePercentage = bound( - dynamicSwapFeePercentage, - 0, - FixedPoint.ONE - PRODUCTION_MIN_TRADE_AMOUNT.divDown(defaultAmount) - ); + dynamicSwapFeePercentage = bound(dynamicSwapFeePercentage, 0, MAX_FEE_PERCENTAGE); PoolHooksMock(poolHooksContract).setDynamicSwapFeePercentage(dynamicSwapFeePercentage); vm.prank(alice); diff --git a/pkg/vault/test/foundry/E2eBatchSwap.t.sol b/pkg/vault/test/foundry/E2eBatchSwap.t.sol index 77e6f533b..b81eb58cc 100644 --- a/pkg/vault/test/foundry/E2eBatchSwap.t.sol +++ b/pkg/vault/test/foundry/E2eBatchSwap.t.sol @@ -7,14 +7,14 @@ import "forge-std/Test.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IAuthentication } from "@balancer-labs/v3-interfaces/contracts/solidity-utils/helpers/IAuthentication.sol"; -import { IBasePool } from "@balancer-labs/v3-interfaces/contracts/vault/IBasePool.sol"; -import { IBatchRouter } from "@balancer-labs/v3-interfaces/contracts/vault/IBatchRouter.sol"; import { IProtocolFeeController } from "@balancer-labs/v3-interfaces/contracts/vault/IProtocolFeeController.sol"; -import { Rounding } from "@balancer-labs/v3-interfaces/contracts/vault/VaultTypes.sol"; +import { Rounding, MAX_FEE_PERCENTAGE } from "@balancer-labs/v3-interfaces/contracts/vault/VaultTypes.sol"; +import { IBatchRouter } from "@balancer-labs/v3-interfaces/contracts/vault/IBatchRouter.sol"; +import { IBasePool } from "@balancer-labs/v3-interfaces/contracts/vault/IBasePool.sol"; -import { ArrayHelpers } from "@balancer-labs/v3-solidity-utils/contracts/test/ArrayHelpers.sol"; import { CastingHelpers } from "@balancer-labs/v3-solidity-utils/contracts/helpers/CastingHelpers.sol"; import { ERC20TestToken } from "@balancer-labs/v3-solidity-utils/contracts/test/ERC20TestToken.sol"; +import { ArrayHelpers } from "@balancer-labs/v3-solidity-utils/contracts/test/ArrayHelpers.sol"; import { FixedPoint } from "@balancer-labs/v3-solidity-utils/contracts/math/FixedPoint.sol"; import { PoolMock } from "../../contracts/test/PoolMock.sol"; @@ -82,10 +82,10 @@ contract E2eBatchSwapTest is BaseVaultTest { vm.stopPrank(); vm.startPrank(poolCreator); - // Set pool creator fee to 100%, so protocol + creator fees equals the total charged fees. - feeController.setPoolCreatorSwapFeePercentage(poolA, FixedPoint.ONE); - feeController.setPoolCreatorSwapFeePercentage(poolB, FixedPoint.ONE); - feeController.setPoolCreatorSwapFeePercentage(poolC, FixedPoint.ONE); + // Set pool creator fee as close as possible to 100%, so protocol + creator fees ~ the total charged fees. + feeController.setPoolCreatorSwapFeePercentage(poolA, MAX_FEE_PERCENTAGE); + feeController.setPoolCreatorSwapFeePercentage(poolB, MAX_FEE_PERCENTAGE); + feeController.setPoolCreatorSwapFeePercentage(poolC, MAX_FEE_PERCENTAGE); vm.stopPrank(); tokensToTrack = [address(tokenA), address(tokenB), address(tokenC), address(tokenD)].toMemoryArray().asIERC20(); diff --git a/pkg/vault/test/foundry/E2eErc4626Swaps.t.sol b/pkg/vault/test/foundry/E2eErc4626Swaps.t.sol index d048b1647..072b69408 100644 --- a/pkg/vault/test/foundry/E2eErc4626Swaps.t.sol +++ b/pkg/vault/test/foundry/E2eErc4626Swaps.t.sol @@ -63,8 +63,8 @@ contract E2eErc4626SwapsTest is BaseERC4626BufferTest { ); vm.prank(poolCreator); - // Set pool creator fee to 100%, so protocol + creator fees equals the total charged fees. - feeController.setPoolCreatorSwapFeePercentage(pool, FixedPoint.ONE); + // Set pool creator fee close to 100%, so protocol + creator fees ~ the total charged fees. + feeController.setPoolCreatorSwapFeePercentage(pool, MAX_FEE_PERCENTAGE); minPoolSwapFeePercentage = IBasePool(pool).getMinimumSwapFeePercentage(); maxPoolSwapFeePercentage = IBasePool(pool).getMaximumSwapFeePercentage(); diff --git a/pkg/vault/test/foundry/E2eSwap.t.sol b/pkg/vault/test/foundry/E2eSwap.t.sol index 05a96741e..a3cce39c5 100644 --- a/pkg/vault/test/foundry/E2eSwap.t.sol +++ b/pkg/vault/test/foundry/E2eSwap.t.sol @@ -83,8 +83,8 @@ contract E2eSwapTest is BaseVaultTest { ); vm.prank(poolCreator); - // Set pool creator fee to 100%, so protocol + creator fees equals the total charged fees. - feeController.setPoolCreatorSwapFeePercentage(pool, FixedPoint.ONE); + // Set pool creator fee near 100%, so protocol + creator fees ~ the total charged fees. + feeController.setPoolCreatorSwapFeePercentage(pool, MAX_FEE_PERCENTAGE); minPoolSwapFeePercentage = IBasePool(pool).getMinimumSwapFeePercentage(); maxPoolSwapFeePercentage = IBasePool(pool).getMaximumSwapFeePercentage(); diff --git a/pkg/vault/test/foundry/ProtocolFeeController.t.sol b/pkg/vault/test/foundry/ProtocolFeeController.t.sol index 3d7b9a698..8080160a3 100644 --- a/pkg/vault/test/foundry/ProtocolFeeController.t.sol +++ b/pkg/vault/test/foundry/ProtocolFeeController.t.sol @@ -8,12 +8,12 @@ import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IAuthentication } from "@balancer-labs/v3-interfaces/contracts/solidity-utils/helpers/IAuthentication.sol"; import { IProtocolFeeController } from "@balancer-labs/v3-interfaces/contracts/vault/IProtocolFeeController.sol"; -import { PoolConfig, FEE_SCALING_FACTOR } from "@balancer-labs/v3-interfaces/contracts/vault/VaultTypes.sol"; import { IVaultErrors } from "@balancer-labs/v3-interfaces/contracts/vault/IVaultErrors.sol"; import { IVaultEvents } from "@balancer-labs/v3-interfaces/contracts/vault/IVaultEvents.sol"; import { IVaultAdmin } from "@balancer-labs/v3-interfaces/contracts/vault/IVaultAdmin.sol"; import { IPoolInfo } from "@balancer-labs/v3-interfaces/contracts/pool-utils/IPoolInfo.sol"; import { IVault } from "@balancer-labs/v3-interfaces/contracts/vault/IVault.sol"; +import "@balancer-labs/v3-interfaces/contracts/vault/VaultTypes.sol"; import { FixedPoint } from "@balancer-labs/v3-solidity-utils/contracts/math/FixedPoint.sol"; @@ -601,18 +601,18 @@ contract ProtocolFeeControllerTest is BaseVaultTest { feeController.withdrawPoolCreatorFees(pool, alice); } - function test100PercentPoolCreatorFee() public { + function testMaxPoolCreatorFee() public { // Protocol fees will be zero. pool = createPool(); // Set 100% pool creator fees vm.startPrank(lp); - feeController.setPoolCreatorSwapFeePercentage(pool, FixedPoint.ONE); + feeController.setPoolCreatorSwapFeePercentage(pool, MAX_FEE_PERCENTAGE); // Check initial conditions: aggregate swap fee percentage should be 100%. (uint256 aggregateSwapFeePercentage, uint256 aggregateYieldFeePercentage) = IPoolInfo(pool) .getAggregateFeePercentages(); - assertEq(aggregateSwapFeePercentage, FixedPoint.ONE, "Aggregate swap fee != 100%"); + assertEq(aggregateSwapFeePercentage, MAX_FEE_PERCENTAGE, "Aggregate swap fee != max (~100%)"); assertEq(aggregateYieldFeePercentage, 0, "Aggregate swap fee is not zero"); vault.manualSetAggregateSwapFeeAmount(pool, dai, PROTOCOL_SWAP_FEE_AMOUNT); @@ -985,7 +985,7 @@ contract ProtocolFeeControllerTest is BaseVaultTest { uint256 poolCreatorFeePercentage ) public { protocolSwapFeePercentage = bound(protocolSwapFeePercentage, FEE_SCALING_FACTOR, MAX_PROTOCOL_SWAP_FEE_PCT); - poolCreatorFeePercentage = bound(poolCreatorFeePercentage, FEE_SCALING_FACTOR, FixedPoint.ONE); + poolCreatorFeePercentage = bound(poolCreatorFeePercentage, FEE_SCALING_FACTOR, MAX_FEE_PERCENTAGE); // Ensure valid precision of each component. protocolSwapFeePercentage = (protocolSwapFeePercentage / FEE_SCALING_FACTOR) * FEE_SCALING_FACTOR; @@ -1051,7 +1051,7 @@ contract ProtocolFeeControllerTest is BaseVaultTest { uint256 poolCreatorFeePercentage ) public { protocolYieldFeePercentage = bound(protocolYieldFeePercentage, FEE_SCALING_FACTOR, MAX_PROTOCOL_YIELD_FEE_PCT); - poolCreatorFeePercentage = bound(poolCreatorFeePercentage, FEE_SCALING_FACTOR, FixedPoint.ONE); + poolCreatorFeePercentage = bound(poolCreatorFeePercentage, FEE_SCALING_FACTOR, MAX_FEE_PERCENTAGE); // Ensure valid precision of each component. protocolYieldFeePercentage = (protocolYieldFeePercentage / FEE_SCALING_FACTOR) * FEE_SCALING_FACTOR; diff --git a/pkg/vault/test/foundry/Registration.t.sol b/pkg/vault/test/foundry/Registration.t.sol index 220668821..75eb7f5ee 100644 --- a/pkg/vault/test/foundry/Registration.t.sol +++ b/pkg/vault/test/foundry/Registration.t.sol @@ -4,22 +4,22 @@ pragma solidity ^0.8.24; import "forge-std/Test.sol"; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { IHooks } from "@balancer-labs/v3-interfaces/contracts/vault/IHooks.sol"; -import { IVault } from "@balancer-labs/v3-interfaces/contracts/vault/IVault.sol"; import { IVaultErrors } from "@balancer-labs/v3-interfaces/contracts/vault/IVaultErrors.sol"; import { IVaultEvents } from "@balancer-labs/v3-interfaces/contracts/vault/IVaultEvents.sol"; +import { IHooks } from "@balancer-labs/v3-interfaces/contracts/vault/IHooks.sol"; +import { IVault } from "@balancer-labs/v3-interfaces/contracts/vault/IVault.sol"; import "@balancer-labs/v3-interfaces/contracts/vault/VaultTypes.sol"; import { CastingHelpers } from "@balancer-labs/v3-solidity-utils/contracts/helpers/CastingHelpers.sol"; -import { ArrayHelpers } from "@balancer-labs/v3-solidity-utils/contracts/test/ArrayHelpers.sol"; import { InputHelpers } from "@balancer-labs/v3-solidity-utils/contracts/helpers/InputHelpers.sol"; +import { ArrayHelpers } from "@balancer-labs/v3-solidity-utils/contracts/test/ArrayHelpers.sol"; import { FixedPoint } from "@balancer-labs/v3-solidity-utils/contracts/math/FixedPoint.sol"; -import { PoolMock } from "../../contracts/test/PoolMock.sol"; import { HooksConfigLib } from "../../contracts/lib/HooksConfigLib.sol"; +import { PoolMock } from "../../contracts/test/PoolMock.sol"; import { BaseVaultTest } from "./utils/BaseVaultTest.sol"; contract RegistrationTest is BaseVaultTest { @@ -121,7 +121,7 @@ contract RegistrationTest is BaseVaultTest { } function testRegisterSetSwapFeePercentage__Fuzz(uint256 swapFeePercentage) public { - swapFeePercentage = bound(swapFeePercentage, 0, FixedPoint.ONE); + swapFeePercentage = bound(swapFeePercentage, 0, MAX_FEE_PERCENTAGE); PoolRoleAccounts memory roleAccounts; TokenConfig[] memory tokenConfig = vault.buildTokenConfig(standardPoolTokens); LiquidityManagement memory liquidityManagement; diff --git a/pkg/vault/test/foundry/YieldFees.t.sol b/pkg/vault/test/foundry/YieldFees.t.sol index 7736d82f3..d806174f8 100644 --- a/pkg/vault/test/foundry/YieldFees.t.sol +++ b/pkg/vault/test/foundry/YieldFees.t.sol @@ -232,9 +232,9 @@ contract YieldFeesTest is BaseVaultTest { } function testYieldFeesOnSwap() public { - // Protocol yield fee 20%, and pool creator yield fees 100%. + // Protocol yield fee 20%, and pool creator yield fees near 100%. uint256 protocolYieldFeePercentage = 20e16; - uint256 poolCreatorFeePercentage = 100e16; + uint256 poolCreatorFeePercentage = 99e16; uint256 aggregateYieldFeePercentage = feeController.computeAggregateFeePercentage( protocolYieldFeePercentage, diff --git a/pkg/vault/test/foundry/unit/HooksConfigLibHelpers.t.sol b/pkg/vault/test/foundry/unit/HooksConfigLibHelpers.t.sol index 986d556fe..ab57410e2 100644 --- a/pkg/vault/test/foundry/unit/HooksConfigLibHelpers.t.sol +++ b/pkg/vault/test/foundry/unit/HooksConfigLibHelpers.t.sol @@ -4,15 +4,16 @@ pragma solidity ^0.8.24; import "forge-std/Test.sol"; +import { IVaultErrors } from "@balancer-labs/v3-interfaces/contracts/vault/IVaultErrors.sol"; import { IBasePool } from "@balancer-labs/v3-interfaces/contracts/vault/IBasePool.sol"; import { IHooks } from "@balancer-labs/v3-interfaces/contracts/vault/IHooks.sol"; -import { IVaultErrors } from "@balancer-labs/v3-interfaces/contracts/vault/IVaultErrors.sol"; import "@balancer-labs/v3-interfaces/contracts/vault/VaultTypes.sol"; +import { HooksConfigLibMock } from "@balancer-labs/v3-vault/contracts/test/HooksConfigLibMock.sol"; +import { WordCodec } from "@balancer-labs/v3-solidity-utils/contracts/helpers/WordCodec.sol"; import { PoolConfigConst } from "@balancer-labs/v3-vault/contracts/lib/PoolConfigConst.sol"; +import { FixedPoint } from "@balancer-labs/v3-solidity-utils/contracts/math/FixedPoint.sol"; import { HooksConfigLib } from "@balancer-labs/v3-vault/contracts/lib/HooksConfigLib.sol"; -import { WordCodec } from "@balancer-labs/v3-solidity-utils/contracts/helpers/WordCodec.sol"; -import { HooksConfigLibMock } from "@balancer-labs/v3-vault/contracts/test/HooksConfigLibMock.sol"; import { VaultContractsDeployer } from "../utils/VaultContractsDeployer.sol"; @@ -32,9 +33,9 @@ contract HooksConfigLibHelpersTest is VaultContractsDeployer { // callComputeDynamicSwapFeeHook function testCallComputeDynamicSwapFee() public { uint256 swapFeePercentage = MAX_FEE_PERCENTAGE; - PoolSwapParams memory swapParams; - uint256 staticSwapFeePercentage = swapFeePercentage - 1; + + uint256 staticSwapFeePercentage = swapFeePercentage; vm.mockCall( hooksContract, abi.encodeCall(IHooks.onComputeDynamicSwapFeePercentage, (swapParams, pool, staticSwapFeePercentage)), @@ -51,6 +52,19 @@ contract HooksConfigLibHelpersTest is VaultContractsDeployer { assertEq(value, swapFeePercentage, "swap fee percentage mismatch"); } + function testCallComputeDynamicSwapFeeAboveMax() public { + PoolSwapParams memory swapParams; + + vm.mockCall( + hooksContract, + abi.encodeCall(IHooks.onComputeDynamicSwapFeePercentage, (swapParams, pool, MAX_FEE_PERCENTAGE)), + abi.encode(true, MAX_FEE_PERCENTAGE + 1) + ); + + vm.expectRevert(abi.encodeWithSelector(IVaultErrors.PercentageAboveMax.selector)); + hooksConfigLibMock.callComputeDynamicSwapFeeHook(swapParams, pool, MAX_FEE_PERCENTAGE, IHooks(hooksContract)); + } + function testCallComputeDynamicSwapFeeRevertIfCallIsNotSuccess() public { uint256 swapFeePercentage = MAX_FEE_PERCENTAGE; diff --git a/pkg/vault/test/foundry/unit/VaultUnit.t.sol b/pkg/vault/test/foundry/unit/VaultUnit.t.sol index c9a9c6d10..088260864 100644 --- a/pkg/vault/test/foundry/unit/VaultUnit.t.sol +++ b/pkg/vault/test/foundry/unit/VaultUnit.t.sol @@ -4,14 +4,14 @@ pragma solidity ^0.8.24; import "forge-std/Test.sol"; +import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; -import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; import { IRateProvider } from "@balancer-labs/v3-interfaces/contracts/solidity-utils/helpers/IRateProvider.sol"; import { IVaultErrors } from "@balancer-labs/v3-interfaces/contracts/vault/IVaultErrors.sol"; -import { IBasePool } from "@balancer-labs/v3-interfaces/contracts/vault/IBasePool.sol"; import { IVaultMock } from "@balancer-labs/v3-interfaces/contracts/test/IVaultMock.sol"; +import { IBasePool } from "@balancer-labs/v3-interfaces/contracts/vault/IBasePool.sol"; import "@balancer-labs/v3-interfaces/contracts/vault/VaultTypes.sol"; import { CastingHelpers } from "@balancer-labs/v3-solidity-utils/contracts/helpers/CastingHelpers.sol"; @@ -20,8 +20,8 @@ import { ArrayHelpers } from "@balancer-labs/v3-solidity-utils/contracts/test/Ar import { FixedPoint } from "@balancer-labs/v3-solidity-utils/contracts/math/FixedPoint.sol"; import { BaseTest } from "@balancer-labs/v3-solidity-utils/test/foundry/utils/BaseTest.sol"; -import { PoolConfigLib } from "../../../contracts/lib/PoolConfigLib.sol"; import { VaultContractsDeployer } from "../../../test/foundry/utils/VaultContractsDeployer.sol"; +import { PoolConfigLib } from "../../../contracts/lib/PoolConfigLib.sol"; contract VaultUnitTest is BaseTest, VaultContractsDeployer { using ArrayHelpers for *; @@ -258,7 +258,7 @@ contract VaultUnitTest is BaseTest, VaultContractsDeployer { } function testFeeConstants() public pure { - assertLt(MAX_FEE_PERCENTAGE / FEE_SCALING_FACTOR, 2 ** FEE_BITLENGTH, "Fee constants are not consistent"); + assertLt(FixedPoint.ONE / FEE_SCALING_FACTOR, 2 ** FEE_BITLENGTH, "Fee constants are not consistent"); } function testMinimumTradeAmountWithZero() public view {